Numerical Python: Solving a BVP with a boolean condition? - python

I am not sure about the best way to ask this question, but I am trying to find the long-term state of an ODE system with an arbitrary extra constraint that needs to be fulfilled.
Ex:
import numpy as np
from scipy.integrate import solve_ivp
import matplotlib.pyplot as plt
def f(t, X, a, b):
return a*X + b
N = 5
X0 = np.random.randn(N)
t_range = [0, 1]
a = 3
b = 4
sol = solve_ivp(f, t_range, X0, args=(a, b))
for i in range(N):
plt.plot(sol['t'], sol['y'][i, :])
In the code above, f is the right-hand-side of my ODE model. N is the dimension of my state vector X, t_range is the interval I want to view the values of X over, X0 is my initial condition, and a and b are just arbitrary model parameters.
The code written produces the following chart:
However, let's say I didn't want to evaluate X over some fixed t_range. Let's say I wanted to keep solving the ODE until some condition was satisfied:
def bc(X) -> bool:
""" True means we stop solving. """
return (X < 0).any()
In other words, I want to do something like this:
X = X0
while not bc(X):
X = new value from ODE solver
Is there an easy way to do this in scipy? If not, is there a straightforward way to implement this?
Please let me know if I can clarify anything better.

#Lutz Lehmann answered my question in the comments.
If I wanted the solver to stop when one entry in X became < 0:
event = lambda t, X, a, b : min(X) # account for when args are passed to event func
event.terminal # tells it to stop
sol = solve_ivp(f, t_range, X0, args=(a, b), events = event)

Related

Double Direct Integration

I am trying to solve the set of coupled boundary value problems such that;
U'' +aB'+ b*(cosh(lambda z))^{-2}tanh(lambda*z) = 0,
B'' + c*U' = 0,
T'' = (gamma^{-1} - 1)*(d*(U')^2 + e*(B')^2)
subject to the boundary conditions U(+/- 1/2) = +/-U_0*tanh(lambda/2), B(+/- 1/2) = 0 and T(-1/2) = 1, T(1/2) = 4. I have decomposed this set of equations into a set of first order differential equations, and used the derivative array such that [U, U', B, B', T, T']. But bvp solve is returning the error that I have a single Jacobian. When I remove the last two equations, I get a solution for U and B and that works fine. However, I am unsure why adding the other two equations results in this issue.
import numpy as np
from scipy.integrate import solve_bvp
import matplotlib.pyplot as plt
%matplotlib inline
alpha = 1E-7
zeta = 8E-3
C_k = 0.01
sigma = 0.005
Q = 30
U_0 = 0.1
gamma = 5/3
theta = 3
def fun(x, y):
return y[1], -2*U_0*Q**2*(1/np.cosh(Q*x))**2*np.tanh(Q*x)-((alpha)/(C_k*sigma))*y[3], y[3],\
-(1/(C_k*zeta))*y[1], y[4], (1/gamma - 1)*(sigma*(y[1])**2 + zeta*alpha*(y[3])**2)
def bc(ya, yb):
return ya[0]+U_0*np.tanh(Q*0.5), yb[0]-U_0*np.tanh(Q*0.5), ya[2]-0, yb[2]-0, ya[4] - 1, yb[4] - 4
x = np.linspace(-0.5, 0.5, 500)
y = np.zeros((6, x.size))
sol = solve_bvp(fun, bc, x, y)
print(sol)
However, the error that I am getting is that 'setting an array with sequence'. The first function and boundary conditions solves two coupled equations, then I use these results to evaluate the equation I have given. I have tried writing all of my equations in one function, however this seems to be returning trivial solutions i.e an array full of zeros.
Any help would be appreciated.
When the expressions become larger it is often more helpful to keep the computations human readable instead of compact.
def fun(x, y):
U, dU, B, dB, T, dT = y;
d2U = -2*U_0*Q**2*(1/np.cosh(Q*x))**2*np.tanh(Q*x)-((alpha)/(C_k*sigma))*dB;
d2B = -(1/(C_k*zeta))*dU;
d2T = (1/gamma - 1)*(sigma*dU**2 + zeta*alpha*dB**2);
return dU, d2U, dB, d2B, dT, d2T
This avoids missing an index error as there are no indices in this computation, all has names close to the original formulas.
Then the solution components (using initialization with only 5 points, resulting in a refinement with 65 points) plots as

Is there a way to easily integrate a set of differential equations over a full grid of points?

The problem is that I would like to be able to integrate the differential equations starting for each point of the grid at once instead of having to loop over the scipy integrator for each coordinate. (I'm sure there's an easy way)
As background for the code I'm trying to solve the trajectories of a Couette flux alternating the direction of the velocity each certain period, that is a well known dynamical system that produces chaos. I don't think the rest of the code really matters as the part of the integration with scipy and my usage of the meshgrid function of numpy.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation, writers
from scipy.integrate import solve_ivp
start_T = 100
L = 1
V = 1
total_run_time = 10*3
grid_points = 10
T_list = np.arange(start_T, 1, -1)
x = np.linspace(0, L, grid_points)
y = np.linspace(0, L, grid_points)
X, Y = np.meshgrid(x, y)
condition = True
totals = np.zeros((start_T, total_run_time, 2))
alphas = np.zeros(start_T)
i = 0
for T in T_list:
alphas[i] = L / (V * T)
solution = np.array([X, Y])
for steps in range(int(total_run_time/T)):
t = steps*T
if condition:
def eq(t, x):
return V * np.sin(2 * np.pi * x[1] / L), 0.0
condition = False
else:
def eq(t, x):
return 0.0, V * np.sin(2 * np.pi * x[1] / L)
condition = True
time_steps = np.arange(t, t + T)
xt = solve_ivp(eq, time_steps, solution)
solution = np.array([xt.y[0], xt.y[1]])
totals[i][t: t + T][0] = solution[0]
totals[i][t: t + T][1] = solution[1]
i += 1
np.save('alphas.npy', alphas)
np.save('totals.npy', totals)
The error given is :
ValueError: y0 must be 1-dimensional.
And it comes from the 'solve_ivp' function of scipy because it doesn't accept the format of the numpy function meshgrid. I know I could run some loops and get over it but I'm assuming there must be a 'good' way to do it using numpy and scipy. I accept advice for the rest of the code too.
Yes, you can do that, in several variants. The question remains if it is advisable.
To implement a generally usable ODE integrator, it needs to be abstracted from the models. Most implementations do that by having the state space a flat-array vector space, some allow a vector space engine to be passed as parameter, so that structured vector spaces can be used. The scipy integrators are not of this type.
So you need to translate the states to flat vectors for the integrator, and back to the structured state for the model.
def encode(X,Y): return np.concatenate([X.flatten(),Y.flatten()])
def decode(U): return U.reshape([2,grid_points,grid_points])
Then you can implement the ODE function as
def eq(t,U):
X,Y = decode(U)
Vec = V * np.sin(2 * np.pi * x[1] / L)
if int(t/T)%2==0:
return encode(Vec, np.zeros(Vec.shape))
else:
return encode(np.zeros(Vec.shape), Vec)
with initial value
U0 = encode(X,Y)
Then this can be directly integrated over the whole time span.
Why this might be not such a good idea: Thinking of each grid point and its trajectory separately, each trajectory has its own sequence of adapted time steps for the given error level. In integrating all simultaneously, the adapted step size is the minimum over all trajectories at the given time. Thus while the individual trajectories might have only short intervals with very small step sizes amid long intervals with sparse time steps, these can overlap in the ensemble to result in very small step sizes everywhere.
If you go beyond the testing stage, switch to a more compiled solver implementation, odeint is a Fortran code with wrappers, so half a solution. JITcode translates to C code and links with the compiled solver behind odeint. Leaving python you get sundials, the diffeq module of julia-lang, or boost::odeint.
TL;DR
I don't think you can "integrate the differential equations starting for each point of the grid at once".
MWE
Please try to provide a MWE to reproduce your problem, like you said : "I don't think the rest of the code really matters", and it makes it harder for people to understand your problem.
Understanding how to talk to the solver
Before answering your question, there are several things that seem to be misunderstood :
by defining time_steps = np.arange(t, t + T) and then calling solve_ivp(eq, time_steps, solution) : the second argument of solve_ivp is the time span you want the solution for, ie, the "start" and "stop" time as a 2-uple. Here your time_steps is 30-long (for the first loop), so I would probably replace it by (t, t+T). Look for t_span in the doc.
from what I understand, it seems like you want to control each iteration of the numerical resolution : that's not how solve_ivp works. More over, I think you want to switch the function "eq" at each iteration. Since you have to pass the "the right hand side" of the equation, you need to wrap this behavior inside a function. It would not work (see right after) but in terms of concept something like this:
def RHS(t, x):
# unwrap your variables, condition is like an additional variable of your problem,
# with a very simple differential equation
x0, x1, condition = x
# compute new results for x0 and x1
if condition:
x0_out, x1_out = V * np.sin(2 * np.pi * x[1] / L), 0.0
else:
x0_out, x1_out = 0.0, V * np.sin(2 * np.pi * x[1] / L)
# compute new result for condition
condition_out = not(condition)
return [x0_out, x1_out, condition_out]
This would not work because the evolution of condition doesn't satisfy some mathematical properties of derivation/continuity. So condition is like a boolean switch that parametrizes the model, we can use global to control the state of this boolean :
condition = True
def RHS_eq(t, y):
global condition
x0, x1 = y
# compute new results for x0 and x1
if condition:
x0_out, x1_out = V * np.sin(2 * np.pi * x1 / L), 0.0
else:
x0_out, x1_out = 0.0, V * np.sin(2 * np.pi * x1 / L)
# update condition
condition = 0 if condition==1 else 1
return [x0_out, x1_out]
finaly, and this is the ValueError you mentionned in your post : you define solution = np.array([X, Y]) which actually is initial condition and supposed to be "y0: array_like, shape (n,)" where n is the number of variable of the problem (in the case of [x0_out, x1_out] that would be 2)
A MWE for a single initial condition
All that being said, lets start with a simple MWE for a single starting point (0.5,0.5), so we have a clear view of how to use the solver :
import numpy as np
from scipy.integrate import solve_ivp
import matplotlib.pyplot as plt
# initial conditions for x0, x1, and condition
initial = [0.5, 0.5]
condition = True
# time span
t_span = (0, 100)
# constants
V = 1
L = 1
# define the "model", ie the set of equations of t
def RHS_eq(t, y):
global condition
x0, x1 = y
# compute new results for x0 and x1
if condition:
x0_out, x1_out = V * np.sin(2 * np.pi * x1 / L), 0.0
else:
x0_out, x1_out = 0.0, V * np.sin(2 * np.pi * x1 / L)
# update condition
condition = 0 if condition==1 else 1
return [x0_out, x1_out]
solution = solve_ivp(RHS_eq, # Right Hand Side of the equation(s)
t_span, # time span, a 2-uple
initial, # initial conditions
)
fig, ax = plt.subplots()
ax.plot(solution.t,
solution.y[0],
label="x0")
ax.plot(solution.t,
solution.y[1],
label="x1")
ax.legend()
Final answer
Now, what we want is to do the exact same thing but for various initial conditions, and from what I understand, we can't : again, quoting the doc
y0 : array_like, shape (n,) : Initial state. . The solver's initial condition only allows one starting point vector.
So to answer the initial question : I don't think you can "integrate the differential equations starting for each point of the grid at once".

Finding unknowns in function from known output using Scipy/Python

I have the following python function required inputs a and b.
def interimfunc(x,y,z):
#this is a dummy function - not a part of the actual question but included for completeness.
#the actual function involves some statistical treatment - but that is not a problem here.
sol = x*y+z
return sol
def finalfunc(a, b):
interimsol1 = interimfunc(0.4,a,b)
interimsol2 = interimfunc(0.8,a,b)
finalsol = interimsol1/interimsol2
return finalsol
if finalsol is a known value.
How do I find out the unknowns "a" and "b" by solving non-linear system of equations??
===
I got 4 down-votes after posting this. I am a mechanical engineer and learning computer science. I did try researching on the internet but need to refine my search - hence I asked a question to experts here.
===
In addition to above equations - there is one more information:
interimsol2 = interimfunc(0.8,a,b)
where interimsol2 = 10 #i.e. known value
How do we inclde this new information in our unknown finding?
===
Actual problem as requested below by #SergeyIvanov
def func(mu, sigma):
tenpercent = st.norm.ppf(2, mu, sigma)
ninetypercent = st.norm.ppf(2, mu, sigma)
rfs = tenpercent/ninetypercent
return rfs
I think you should solve a system of nonlinear equations. This code should solve your problem in case of two equations for two known solutions (of course you can extend it):
from scipy.optimize import fsolve
known_values = [3,5]
def interimfunc(x,y,z):
sol = x*y+z
return sol
def finalfunc(a, b):
interimsol1 = interimfunc(0.4,a,b)
interimsol2 = interimfunc(0.8,a,b)
finalsol = interimsol1/interimsol2
return finalsol
def equations(p):
a, b = p
return (finalfunc(a,b) - known_values[0], # finalfunc(a,b) == solution1
finalfunc(a,b) - known_values[1]) # finalfunc(a,b) == solution2
a, b = fsolve(equations, (1, 1))# solution
print(a,b)
# -6192.07497308 5779.26987919
print(equations((a, b)))
# (1.0000003476651482, -0.99999965233485177) <-- bad convergence beacause there is no free paremeter in finalfunc.
But it works only with equal known_values, that is pointless (solution will be a random combination of a and b). The problem is that you should have something to distinguish two equations finalfunc (e.g. additional parameter), because you can get diffrent solutions only with different arguments. So finally you should have something like this:
from scipy.optimize import fsolve
def interimfunc(x,y,z):
sol = x*y+z
return sol
def finalfunc(a, b, c ):
interimsol1 = interimfunc(0.4,a,b) + c
interimsol2 = interimfunc(0.8,a,b) + c
finalsol = interimsol1/interimsol2
return finalsol
known_values = [0.8260869565217391,0.8333333333333334]
def equations(p):
a, b = p
return (finalfunc(a,b,0) - known_values[0], # finalfunc(a,b,c) == solution1
finalfunc(a,b,1) - known_values[1]) # finalfunc(a,b,c) == solution2
a, b = fsolve(equations, (1, 1))# solution
print(a,b)
print(equations((a, b)))
# 10.0 15.0 <-- correct values
# (4.4408920985006262e-16, 2.2204460492503131e-16) <-- good convergence
For last example:
from scipy.optimize import fsolve
import scipy.stats as st
def equations(p):
mu, sigma = p
tenpercent = st.norm.ppf(2, mu, sigma)
ninetypercent = st.norm.ppf(2, mu, sigma)
return (ninetypercent - 500,
tenpercent / ninetypercent - 1.0)
mu, sigma = fsolve(equations,x0=(100, 10))# solution
print("mu, sigma:",mu, sigma)
print(equations((mu, sigma)))
The problem here is that ppf can generate nan and ruin an optimization process. So guess values should be proposed very carefully.

Solving an ordinary differential equation on a fixed grid (preferably in python)

I have a differential equation of the form
dy(x)/dx = f(y,x)
that I would like to solve for y.
I have an array xs containing all of the values of x for which I need ys.
For only those values of x, I can evaluate f(y,x) for any y.
How can I solve for ys, preferably in python?
MWE
import numpy as np
# these are the only x values that are legal
xs = np.array([0.15, 0.383, 0.99, 1.0001])
# some made up function --- I don't actually have an analytic form like this
def f(y, x):
if not np.any(np.isclose(x, xs)):
return np.nan
return np.sin(y + x**2)
# now I want to know which array of ys satisfies dy(x)/dx = f(y,x)
Assuming you can use something simple like Forward Euler...
Numerical solutions will rely on approximate solutions at previous times. So if you want a solution at t = 1 it is likely you will need the approximate solution at t<1.
My advice is to figure out what step size will allow you to hit the times you need, and then find the approximate solution on an interval containing those times.
import numpy as np
#from your example, smallest step size required to hit all would be 0.0001.
a = 0 #start point
b = 1.5 #possible end point
h = 0.0001
N = float(b-a)/h
y = np.zeros(n)
t = np.linspace(a,b,n)
y[0] = 0.1 #initial condition here
for i in range(1,n):
y[i] = y[i-1] + h*f(t[i-1],y[i-1])
Alternatively, you could use an adaptive step method (which I am not prepared to explain right now) to take larger steps between the times you need.
Or, you could find an approximate solution over an interval using a coarser mesh and interpolate the solution.
Any of these should work.
I think you should first solve ODE on a regular grid, and then interpolate solution on your fixed grid. The approximate code for your problem
import numpy as np
from scipy.integrate import odeint
from scipy import interpolate
xs = np.array([0.15, 0.383, 0.99, 1.0001])
# dy/dx = f(x,y)
def dy_dx(y, x):
return np.sin(y + x ** 2)
y0 = 0.0 # init condition
x = np.linspace(0, 10, 200)# here you can control an accuracy
sol = odeint(dy_dx, y0, x)
f = interpolate.interp1d(x, np.ravel(sol))
ys = f(xs)
But dy_dx(y, x) should always return something reasonable (not np.none).
Here is the drawing for this case

Why is odeint time evolving these two differently in python?

I have two programs, one that can take in N coupled ODEs and one that uses 2 coupled ODEs. In the case that I input the 2 same ODEs into both codes, with the same time span, I get different answers. I know the correct answer, so I can deduce that my N many program is wrong.
Here is the code for the 2 equation dedicated one:
# solve the coupled system dy/dt = f(y, t)
def f(y, t):
"""Returns the collections of first-order
coupled differential equations"""
#v11i = y[0]
#v22i = y[1]
#v12i = y[2]
print y[0]
# the model equations
f0 = dHs(tRel,vij)[0].subs(v12,y[2])
f1 = dHs(tRel,vij)[3].subs(v12,y[2])
f2 = dHs(tRel,vij)[1].expand().subs([(v11,y[0]),(v22,y[1]),(v12,y[2])])
return [f0, f1, f2]
# Initial conditions for graphing
v110 = 6
v220 = 6
v120 = 4
y0 = [v110, v220, v120] # initial condition vector
sMesh = np.linspace(0, 1, 10e3) # time grid
# Solve the DE's
soln = odeint(f, y0, sMesh)
and here is the N equation dedicated one:
def f(y, t):
"""Returns the derivative of H_s with initial
values plugged in"""
# the model equations
print y[0]
for i in range (0,len(dh)):
for j in range (0,len(y)):
dh[i] = dh[i].subs(v[j],y[j])
dhArray = []
for i in range(0,len(dh)):
dhArray.append(dh[i])
return dhArray
sMesh = np.linspace(0, 1, 10e3) # time grid
dh = dHsFunction(t, V_s).expand()
soln = odeint(f, v0, sMesh)
where dHs(tRel,vij) = dHsFunction(t,V_s) i.e. the exact same ODEs. Similarly y0 and v0 are the exact same. But when I print y[0] in the N many case, I get an output of:
6.0
5.99999765602
5.99999531204
5.97655553477
5.95311575749
5.92967598021
5.69527820744
5.46088043467
5.2264826619
2.88250493418
0.53852720647
-1.80545052124
-25.2452277984
-48.6850050755
-72.1247823527
-306.522555124
as opposed to the 2 dedicated case of:
6.0
5.99999765602
5.99999765602
5.99999531205
5.99999531205
5.98848712729
5.98848712125
5.97702879748
5.97702878476
5.96562028875
5.96562027486
5.91961750442
5.91961733611
5.93039037809
5.93039029335
5.89564277275
5.89564273736
5.86137647436
5.86137638807
5.82758984835
etc.
where the second result is the correct one and graphs the proper graphs.
Please let me know if more code is needed or anything else. Thanks.
Your second version for f modifies the value of the global variable dh.
On the first call, you substitute in values in it, and these same values are then used in all subsequent calls.
Avoid that by using e.g. dh_tmp = list(dh) inside the function.

Categories

Resources