I've a system of ODEs which I so far solved via solve_ivp.
scipy.integrate.solve_ivp(fun=model, t_span=(0.0, t_end), y0=[s0])
My problem is, that I want to solve the ODEs in a running simulation, where new values are constantly injected into the simulation and results are displayed. The simulation may run for several hours. My approach was to call solve_ivp repeatedly, roughly as follows (this allows me to show intermediate results and fetch new data, which is not shown here):
t = 0.0
s = s0
while t < t_end:
result = scipy.integrate.solve_ivp(fun=model, t_span=(t, t + t_step), y0=[s])
s = result.y[0][-1]
t += t_step
I wrote a few test cases, where I solved the ODEs analytically and for reasons I do not fully understand yet, repeatedly calling solve_ivp is always closer to the analytical solution (without manually tweaking the different parameters of solve_ivp). My question is more along the lines: If there is something wrong in this approach, or if there is some functionality in scipy or another package which may be better suited to my needs?
A poorly documented option is to use the integrators that underly solve_ivp directly. For example, RK45. When an external event changes something, you would need to restart the integration as most (all?) of these integrators use multiple prior steps to construct the value for the next time point. If you change values or the derivative function mid-integration, you will introduce subtle hard to understand bugs.
Here is an example that changes the derivative function based on an external event to the integrator. In this case, it is simply based on the clock time. Note that this example will give different results on different machines depending on speed of execution.
import datetime
import matplotlib.pyplot as plt
import numpy as np
from scipy import integrate
def fun1(t, y):
return -y
def fun2(t, y):
return 2 * y
def use_fun2():
return datetime.datetime.utcnow().time().microsecond >= 500000
time_bound = 20.
# max_step used to make sure this runs slow enough
# try changing this to see the difference
max_step = 0.0001
y0 = [5]
fun=fun1
rk45 = integrate.RK45(fun, 0, y0, time_bound, max_step=max_step)
t = []
y = []
while rk45.status == "running" and rk45.y[0] < 200:
if use_fun2():
if fun is fun1:
fun = fun2
rk45 = integrate.RK45(fun, rk45.t, rk45.y, time_bound, max_step=max_step)
else:
if fun is fun2:
fun = fun1
rk45 = integrate.RK45(fun, rk45.t, rk45.y, time_bound, max_step=max_step)
t.append(rk45.t)
y.append(rk45.y)
rk45.step()
import matplotlib.pyplot as plt
plt.plot(t, y)
plt.show()
Related
I am trying to solve a set of ODEs using SciPy. The task I am given asks me to solve the differential equation for 500 time steps. How can I achieve this using SciPy?
So far, I have tried using scipy.integrate.solve_ivp, which gives me a correct solution, but I cannot control the number of time steps that it runs for. The t_span argument lets me configure what the initial and final values of t are, but I'm not actually interested in that -- I am only interested in how many times I integrate. (For example, when I run my equations with t_span = (0, 500) the solver integrates 907 times.)
Below is a simplified example of my code:
from scipy.integrate import solve_ivp
def simple_diff(t, z) :
x, y = z
return [1 - 2*x*y, 2*x - y]
t_range = (0, 500)
xy_init = [0, 0]
sol = solve_ivp(simple_diff, t_range, xy_init)
I am also fine with using something other than SciPy, but solutions with SciPy are preferable.
You can use the t_eval argument to solve_ivp to evaluate at particular time points:
import numpy as np
t_eval = np.arange(501)
sol = solve_ivp(simple_diff, t_range, xy_init, t_eval=t_eval)
However, note that this will not cause the solver to limit the number of integration steps - that is determined by error metrics.
If you absolutely must evaluate the function exactly 500 times to obtain 500 integration steps, you are describing Euler integration, which will be less accurate than the algorithm that solve_ivp uses.
Looking at the solutions to your equation, it feels like you probably want to integrate only up to t=5.
Here's what the result looks like when integrating with the above settings:
And here's the result for
t_eval = np.linspace(0, 5)
t_range = (0, 5)
sol = solve_ivp(simple_diff, t_range, xy_init, t_eval=t_eval)
I am currently working with fitting decline curves to real-world production data. I have had good luck with creating a hyperbolic and using curve_fit from scipy.optimize. The current function I use:
def hyp_func(x,qi,b,di):
return qi*(1.0-b*di*x)**(-1.0/b)
What I would like to do now, is at a certain rate of decline, transition to an exponential function. How would i go about this and still be able to use in curve_fit (I think below works)? I am trying the code below, is this the way to do it? or is there a better way?
def hyp_func2(x,qi,b,di):
dlim = -0.003
hy = qi*(1.0-b*di*x)**(-1.0/b)
hdy = di/(1.0-b*di*x)
ex = x[hdy>dlim]
qlim = qi*(dlim/di)**(1/b)
xlim = ((qi/qlim)**b-1)/(b*-di)
ey = qlim*np.exp(dlim*(ex-xlim))
y = np.concatenate((hy[hdy<dlim],ey))
return y
hy is the hyperbolic equation
hdy is the hy derivative
ex is the part of x after derivative hits dlim
ey is the exponential equation
I am still working out the equations, I am not getting a continuous function.
edit: data here, and updated equations
Sorry to be the bearer of bad news, but if I understand what you are trying to do, I think it is very difficult to have scipy.optimize.curve_fit, or any of the other methods from scipy.optimize do what you are hoping to do.
Most fitting algorithms are designed to work with continuous variables, and usually (and curve_fit for sure) start off by making very small changes in parameter values to find the right direction and step size to take to improve the result.
But what you're looking for is a discrete variable as the breakpoint between one functional form (roughly, "power law") to another ("exponential") The algorithm won't normally make a large enough change in your di parameter to make a difference for which value is used as that breakpoint, and may decide that di does not affect the fit (your model used di in other ways too, so you might get lucky and di might have an affect on the fit.
Assuming that qi>0 the slope is actually positive, so I do not get the choice of -0.003. Moreover I think the derivative is wrong.
You can calculate exactly the value where the lope reaches a critical value.
Now, from my experience you have two choices. If you define a piecewise function yourself, you usually run into trouble with function calls using numpy arrays. I typically use scipy.optimize.leastsq with a self-defined residual function. A second option is a continuous transition between the two functions. You can make that as sharp as you want, as value and slope already fit, by definition.
The two solutions look as follows
import matplotlib.pyplot as plt
import numpy as np
def hy(x,b,qi,di):
return qi*(1.0-b*di*x)**(-1.0/b)
def abshy(x,b,qi,di):#same as hy but defined for all x
return qi*abs(1.0-b*di*x)**(-1.0/b)
def dhy(x,b,qi,di):#derivative of hy
return qi*di*(1.0-b*di*x)**(-(b+1.0)/b)
def get_x_from_slope(s,b,qi,di):#self explaining
return (1.0-(s/(qi*di))**(-b/(b+1.0)))/(b*di)
def exh(x,xlim,qlim,dlim):#exponential part (actually no free parameters)
return qlim*np.exp(dlim*(x-xlim))
def trans(x,b,qi,di, s0):#piecewise function
x0=get_x_from_slope(s0,b,qi,di)
if x<x0:
out= hy(x,b,qi,di)
else:
H0=hy(x0,b,qi,di)
out=exh(x,x0,H0,s0/H0)
return out
def no_if_trans(x,b,qi,di, s0,sharpness=10):#continuous transition between the two functions
x0=get_x_from_slope(s0,b,qi,di)
H0=hy(x0,b,qi,di)
weight=0.5*(1+np.tanh(sharpness*(x-x0)))
return weight*exh(x,x0,H0,s0/H0)+(1.0-weight)*abshy(x,b,qi,di)
xList=np.linspace(0,5.5,90)
hyList=np.fromiter(( hy(x,2.2,1.2,.1) for x in xList ) ,np.float)
t1List=np.fromiter(( trans(x,2.2,1.2,.1,3.59) for x in xList ) ,np.float)
nt1List=np.fromiter(( no_if_trans(x,2.2,1.2,.1,3.59) for x in xList ) ,np.float)
fig1=plt.figure(1)
ax=fig1.add_subplot(1,1,1)
ax.plot(xList,hyList)
ax.plot(xList,t1List,linestyle='--')
ax.plot(xList,nt1List,linestyle=':')
ax.set_ylim([1,10])
ax.set_yscale('log')
plt.show()
There is almost no differences in the two solutions, but your options for using scipy fitting functions are slightly different. The second solution should easily work with curve_fit
I'm currently making the switch from MATLAB to Python for a project that involves solving differential equations.
In MATLAB if the t array that's passed only contains two elements, the solver outputs all the intermediate steps of the simulation. However, in Python you just get the start and end point. To get time points in between you have to explicitly specify the time points you want.
from scipy import integrate as sp_int
import numpy as np
def odeFun(t,y):
k = np.ones((2))
dy_dt = np.zeros(y.shape)
dy_dt[0]= k[1]*y[1]-k[0]*y[0]
dy_dt[1]=-dy_dt[0]
return(dy_dt)
t = np.linspace(0,10,1000)
yOut = sp_int.odeint(odeFun,[1,0],t)
I've also looked into the following method:
solver = sp_int.ode(odefun).set_integrator('vode', method='bdf')
solver.set_initial_value([1,0],0)
dt = 0.01
solver.integrate(solver.t+dt)
However, it still requires an explicit dt. From reading around I understand that Python's solvers (e.g. 'vode') calculates intermediate steps for the dt requested, and then interpolates that time point and outputs it. What I'd like though is to get all these intermediate steps directly without the interpolation. This is because they represent the minimum number of points required to fully describe the time series within the integration tolerances.
Is there an option available to do that?
I'm working in Python 3.
scipy.integrate.odeint
odeint has an option full_output that allows you to obtain a dictionary with information on the integration, including tcur which is:
vector with the value of t reached for each time step. (will always be at least as large as the input times).
(Note the second sentence: The actual steps are always as fine as your desired output. If you want use the minimum number of necessary step, you must ask for a coarse sampling.)
Now, this does not give you the values, but we can obtain those by integrating a second time using these very steps:
from scipy.integrate import odeint
import numpy as np
def f(y,t):
return np.array([y[1]-y[0],y[0]-y[1]])
start,end = 0,10 # time range we want to integrate
y0 = [1,0] # initial conditions
# Function to add the initial time and the target time if needed:
def ensure_start_and_end(times):
times = np.insert(times,0,start)
if times[-1] < end:
times = np.append(times,end)
return times
# First run to establish the steps
first_times = np.linspace(start,end,100)
first_run = odeint(f,y0,first_times,full_output=True)
first_steps = np.unique(first_run[1]["tcur"])
# Second run to obtain the results at the steps
second_times = ensure_start_and_end(first_steps)
second_run = odeint(f,y0,second_times,full_output=True,h0=second_times[0])
second_steps = np.unique(second_run[1]["tcur"])
# ensuring that the second run actually uses (almost) the same steps.
np.testing.assert_allclose(first_steps,second_steps,rtol=1e-5)
# Your desired output
actual_steps = np.vstack((second_times, second_run[0].T)).T
scipy.integrate.ode
Having some experience with this module, I am not aware of any way to obtain the step size without digging deeply into the internals.
Here is the thing.
I am trying to use fsolve function in Python to find the root of a cubic function. This cubic function has a parameter, deltaW. What I do is change this parameter deltaW from -50 to 50, and find the root of the cubic function at the same time. Below is my script:
from scipy.optimize import fsolve
import matplotlib.pyplot as plt
import numpy as np
import pylab
g = 5.61
gamma = 6.45
kappa = 6.45
J = 6.45
rs = 1.0 #There are just parameters
m = 5.0*10**(-11)
wm = 2*3.14*23.4
X = []
X1 = []
def func(x): #Define the cubic function I need to solve
A = 1j*g**2*(kappa + 1j*deltaW)*x*x/(m*wm**2)
B = J**2 + (1j*deltaW - gamma)*(1j*deltaW + kappa)
C = A + B
D = abs(C)*x - J*np.sqrt(2*kappa)*rs
return D
for deltaW in np.linspace(-50, 50, 1000):
x0 = fsolve(func, 0.0001)
X.append(x0)
deltaW = np.linspace(-50, 50, 1000)
plt.plot(deltaW, X)
plt.show()
When I run this script, I get these two messages:
RuntimeWarning: The iteration is not making good progress, as measured by the
improvement from the last five Jacobian evaluations.
warnings.warn(msg, RuntimeWarning)
/usr/lib/python2.7/dist-packages/scipy/optimize/minpack.py:152: RuntimeWarning: The iteration is not making good progress, as measured by the
improvement from the last ten iterations.
warnings.warn(msg, RuntimeWarning)
I am sorry I do not have enough reputation to put the plot of this script here. My question is why do I get this message and why do my plot look so weird in the left part.
Is it because of my code is wrong?
As in almost all cases of finding roots, a good initial guess is imperative. Sometimes the best initial guess is, in fact, known to be wrong. That is the case here. The behavior of your script, which shows unexpected 'spikes' in the answer, can be looked at more deeply by both plotting up the function, and plotting up the found roots around those spikes (hey, you've got a Python console - this is really easy).
What you find is that the solution returned by the solver is jumping around, even though the function really doesn't look that different. The problem is that your initial guess of 0.0001 lies close to a tiny minimum of the function, and the solver can't figure out how to get out of there. Setting the initial guess to 1.0 (way far away, but on a nice, easy descending portion of the function that will head directly to the root), results instead in:
So, three things:
1. solvers need loving care and attention - they are rarely automagic.
Sometimes the 'right' initial guess can be well away from what you know is the right answer, but in such a way that the solver has an easy time of it.
the interactive Python console lets you look quickly at what is going on. Use the power of it!
I am numerically solving a differential equation that depends on parameters. I am not really interested on the solutions but on their behaviour depending on the value of the parameters. Since I want a very precise description I must use a very fine array of parameters' values resulting in a lot of ODE solving processes. So I want to know if it would be possible to "parallelize" such a program. The idea is that maybe each processor of my computer can solve the ODE for a distinct pair of parameters. A kind of example is the following:
import matplotlib.pyplot as plt
from scipy.integrate import ode
import numpy as np
# - ODE - #
def sys(t,x,p1,p2): #p1 and p2 are the parameters
dx=np.zeros(2)
dx[0] = x[1]
dx[1] = (p1+p2*cos(t))*x[0]
return dx
t0=0; tEnd=10; dt=0.01
r = ode(sys).set_integrator('dopri5', nsteps=10,max_step=dt)
Y=[];S=[];T=[]
ic=[.1,0]
# - parameters range - #
P1=np.linspace(0,1,100)
P2=np.linspace(0,1,100)
# -------------------- #
for p1 in P1:
for p2 in P2:
r.set_initial_value(ic, t0).set_f_params(p1,p2)
flag='No'
while r.successful() and r.t +dt < tEnd:
r.integrate(r.t+dt)
Y.append(r.y)
T.append(r.t)
#-This is what we want to know.
if r.y[0]>2*ic[0]:
flag='Yes'
break
if flag=='Yes':
plt.scatter(p1,p2,s=1, c='k', marker='.')
# ------------------------------------ #
plt.show()
Note that each for loop is independent so: Is it possible to make those for loops in a parallel way? So I would imagine that it is possible that each of my 8 processors do one double for loop at a time and then probably make the computations roughly 8 times faster? Or at least faster?
I think it is easiest to use multiprocessing, just implement inner loops as a stand-alone function and run result = Pool(8).map(solver, P1). To scale on multiple computers I'd recommend Apache Spark.
Edit: Note that you cannot call plotting methods within the method itself, you should return raw numbers to the caller and do plotting after the .map calls has finished.