I have wrote a code for Runge-Kutta 4th order, which works perfectly fine for a system of differential equations:
import numpy as np
import matplotlib.pyplot as plt
import numba
import time
start_time = time.clock()
#numba.jit()
def V(u,t):
x1,dx1, x2, dx2=u
ddx1=-w**2 * x1 -b * dx1
ddx2=-(w+0.5)**2 * x2 -(b+0.1) * dx2
return np.array([dx1,ddx1,dx2,ddx2])
#numba.jit()
def rk4(f, u0, t0, tf , n):
t = np.linspace(t0, tf, n+1)
u = np.array((n+1)*[u0])
h = t[1]-t[0]
for i in range(n):
k1 = h * f(u[i], t[i])
k2 = h * f(u[i] + 0.5 * k1, t[i] + 0.5*h)
k3 = h * f(u[i] + 0.5 * k2, t[i] + 0.5*h)
k4 = h * f(u[i] + k3, t[i] + h)
u[i+1] = u[i] + (k1 + 2*(k2 + k3) + k4) / 6
return u, t
u, t = rk4(V,np.array([0,0.2,0,0.3]) ,0,100, 20000)
print("Execution time:",time.clock() - start_time, "seconds")
x1,dx1,x2,dx2 = u.T
plt.plot(x1,x2)
plt.xlabel('X1')
plt.ylabel('X2')
plt.show()
The above code, returns the desired result:
And thanks to Numba JIT, this code works really fast. However, this method doesn't use adaptive step size and hence, it is not very suitable for a system of stiff differential equations. Runge Kutta Fehlberg method, solves this problem by using a straight forward algorithm. Based on the algorithm (https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta%E2%80%93Fehlberg_method) I wrote this code which works for only one differential equation :
import numpy as np
def rkf( f, a, b, x0, tol, hmax, hmin ):
a2 = 2.500000000000000e-01 # 1/4
a3 = 3.750000000000000e-01 # 3/8
a4 = 9.230769230769231e-01 # 12/13
a5 = 1.000000000000000e+00 # 1
a6 = 5.000000000000000e-01 # 1/2
b21 = 2.500000000000000e-01 # 1/4
b31 = 9.375000000000000e-02 # 3/32
b32 = 2.812500000000000e-01 # 9/32
b41 = 8.793809740555303e-01 # 1932/2197
b42 = -3.277196176604461e+00 # -7200/2197
b43 = 3.320892125625853e+00 # 7296/2197
b51 = 2.032407407407407e+00 # 439/216
b52 = -8.000000000000000e+00 # -8
b53 = 7.173489278752436e+00 # 3680/513
b54 = -2.058966861598441e-01 # -845/4104
b61 = -2.962962962962963e-01 # -8/27
b62 = 2.000000000000000e+00 # 2
b63 = -1.381676413255361e+00 # -3544/2565
b64 = 4.529727095516569e-01 # 1859/4104
b65 = -2.750000000000000e-01 # -11/40
r1 = 2.777777777777778e-03 # 1/360
r3 = -2.994152046783626e-02 # -128/4275
r4 = -2.919989367357789e-02 # -2197/75240
r5 = 2.000000000000000e-02 # 1/50
r6 = 3.636363636363636e-02 # 2/55
c1 = 1.157407407407407e-01 # 25/216
c3 = 5.489278752436647e-01 # 1408/2565
c4 = 5.353313840155945e-01 # 2197/4104
c5 = -2.000000000000000e-01 # -1/5
t = a
x = np.array(x0)
h = hmax
T = np.array( [t] )
X = np.array( [x] )
while t < b:
if t + h > b:
h = b - t
k1 = h * f( x, t )
k2 = h * f( x + b21 * k1, t + a2 * h )
k3 = h * f( x + b31 * k1 + b32 * k2, t + a3 * h )
k4 = h * f( x + b41 * k1 + b42 * k2 + b43 * k3, t + a4 * h )
k5 = h * f( x + b51 * k1 + b52 * k2 + b53 * k3 + b54 * k4, t + a5 * h )
k6 = h * f( x + b61 * k1 + b62 * k2 + b63 * k3 + b64 * k4 + b65 * k5, \
t + a6 * h )
r = abs( r1 * k1 + r3 * k3 + r4 * k4 + r5 * k5 + r6 * k6 ) / h
if len( np.shape( r ) ) > 0:
r = max( r )
if r <= tol:
t = t + h
x = x + c1 * k1 + c3 * k3 + c4 * k4 + c5 * k5
T = np.append( T, t )
X = np.append( X, [x], 0 )
h = h * min( max( 0.84 * ( tol / r )**0.25, 0.1 ), 4.0 )
if h > hmax:
h = hmax
elif h < hmin:
raise RuntimeError("Error: Could not converge to the required tolerance %e with minimum stepsize %e." % (tol,hmin))
break
return ( T, X )
but I'm struggling to convert it to a function like the first code, where I can input a system of differential equations. The most confusing part for me, is how can I vectorize everything in the second code without messing things up. In other words, I cannot reproduce the first result using the RKF algorithm. Can anyone point me in the right direction?
I'm not really sure where your problem lies. Setting the not given parameters to w=1; b=0.1 and calling, without changing anything
T, X = rkf( f=V, a=0, b=100, x0=[0,0.2,0,0.3], tol=1e-6, hmax=1e1, hmin=1e-16 )
gives the phase plot
The step sizes grow as the system slows down as
which is the expected behavior for an unfiltered step size controller.
Related
I have this function for solve_ivp, which solves a series of differential equations.
For every time step, the variable position [Pos] should be updated depending on a probability of it jumping. However, solve_ivp is not remembering the previous variable.
def jump_fun(t, Y, P, flux = 670):
global Pos_list
k1, k2 , k3, k4, k5, k6 , k , kay, q , K1, c5_tot, c6_tot, phi, Nc, V, qs, Cx, Ks, F, Cs0, flux, Pos, Pos_list = P
c1, c2, c3, c4, c5 ,c6, c4ex1, c4ex2, Cs1, Cs2= Y.reshape([10,-1])
#print(c1, c2, c3, c4, c5 ,c6, c4ex, Cs, Pos)
print(f'Pos: {Pos}')
# function heterogeneous_environment
prob = heterogeneous_environment(Nc, flux, V, 10, 10)
print(f'prob: {prob}')
#These arrays keep track of where the cells are
R1 = np.where(Pos == 1, 1, 0 )
R2 = np.where(Pos == 2, 1, 0 )
# We need to know the external concentrations of each reactor
rs1 = (Cx*qs*Cs1/ (Ks+Cs1))
dCs1 = F/V + flux*Cs2/V - flux*Cs1/V - rs1 # Position of cell is reactor 1, C1 = Cs
rs2 = (Cx*qs*Cs2/ (Ks+Cs2))
dCs2 = flux*Cs1/V - flux*Cs2/V - rs2 # Position of cell is reactor 2, C2 = Cs
j0 = rs1*R1 + rs2*R2
jm1 = kay * (c4 - c4ex1)*R1
jm2 = kay * (c4 - c4ex2)*R2
dc1= j0 - (k1 * c1 * c6 * (1/( 1 + ((c6/K1 )**q))))
dc2 = 2* k1 * c1 * c6 * (1/( 1 + ((c6/K1 )**q))) - k2 * c2 * (c5_tot - c5) - k6 *c2 *c5
dc3 = k2 * c2 * (c5_tot - c5) - k3 * c3* (c6_tot - c6)
dc4 = k3 * c3* (c6_tot - c6) - k4 * c4 *c5 - (jm1*R1 + jm2*R2)
dc5= k2 * c2 * (c5_tot - c5) - k4 * c4 *c5 - k6 *c2 *c5
dc6 = -2 * k1 * c1 * c6 * (1/( 1 + ((c6/K1 )**q))) + 2 * k3 * c3* (c6_tot - c6) - k5 * c6
cell1 = np.count_nonzero(R1 == 1)
cell2 = np.count_nonzero(R2 == 1)
dc4ex1= - k * c4ex1 + (phi / cell1) * np.sum(jm1) - (flux*c4ex1)/V + (flux*c4ex2)/V
dc4ex2= - k * c4ex2 + (phi / cell2) * np.sum(jm2) + (flux*c4ex1)/V - (flux*c4ex2)/V
#print('1')
#print(Pos)
#print('2')
Pos = Pos + prob*R1
Pos = Pos - prob*R2
#print(Pos)
Pos_list.append(Pos)
#iterate through the Pos vector, if Pos =1 do the balances for reactor 1 and then if the probability says to change
#reactor, change the position of the cell. Same for POS=2
#UPDATE THE MASS BALANCE FOR EACH REACTOR
return dc1, dc2, dc3, dc4, dc5 , dc6, dc4ex1, dc4ex2, dCs1, dCs2
I tried passing Pos as a return of the function, but it also didn't seem to work. Please help.
I want to simulate the laser power along a silica fiber. The ODEs and the used parameters can be found in the paper linked below. Note that I converted all units to SI units in my code. The authors of this paper named the shooting method as a way to numerically solve this equations.
The paper I am referring to can be found here: https://www.osapublishing.org/oe/fulltext.cfm?uri=oe-21-17-20090&id=260516
I tried it in python but I did not manage to implement the root-finding. I used scypi.integrate_ivp to integrate the ODEs with a first guess. If I use a small value (~500m) at fiberlength I get a result but at a higher value in the range of km the solver does not manage to finish without an error. Can somebody please tell me how I can implement the shooting method here and also how I get the same solution as in the paper?
My code:
import matplotlib.pyplot as plt
import numpy as np
from scipy.integrate import odeint
from scipy.integrate import solve_ivp
from scipy.integrate import solve_bvp
from scipy.optimize import fsolve
#%% ODEs und Parameter
#constants
alpha = 10**(-0.25/10)/1000 #fiber loss [1/m]
a0= alpha
a1= alpha
a2= alpha
a3= alpha
g = 0.53e-3 #RamanGain [1/(W*m)]
g1= g
g2= g*0.8
g3= g*0.6
epsilon0 = 1e-4*1e-3 #Rayleigh-scattering [1/m] REFERENZ: Paper: Third order random lasing via Raman gain and Rayleigh backscattering
epsilon1 = 5e-5*1e-3
epsilon2 = 2e-5*1e-3
epsilon3 = 1e-5*1e-3
wl_start = 1365e-9
RamanShift= 13.2e12
f0=3e8/wl_start
f1= f0 - RamanShift
f2= f1 - RamanShift
f3= f2 - RamanShift
#R_L1 = 0.99 # Reflexionskoeffizienten (Fasereingang FBG)
#R_L2 = 5e-3 # Reflexionskoeffizienten (Fasereingang)
#R_R1 = 4e-5 # Reflexionskoeffizienten (Faserausgang)
#R_R2 = 4e-5 # Reflexionskoeffizienten (Faserausgang)
R=0.6 # reflectivity
# G[i] = 4*h*f[i]*df[i]*(1+1/(np.exp((h*(f[i-1]-f[i]))/(K*T))-1))
h=6.63e-34 #Placksches Wirkungsquantum [Js]
K=1.38e-23 #Bolzmannkonstante [J/K]
T=300 #Temperature [K]
#df1= 0.18e12 #Bandbreite [Hz] REFERENZ: Paper: Third order random lasing via Raman gain and Rayleigh backscattering
#df2= 0.25e12
#df3= 0.25e12
#bandwidth
df1= 0.18e12
df2= 0.25e12
df3= 0.25e12
Gamma1= 4*h*f1*df1*(1+(1/(np.exp(h*(f0-f1)/(K*T))-1)))
Gamma2= 4*h*f2*df2*(1+(1/(np.exp(h*(f1-f2)/(K*T))-1)))
Gamma3= 4*h*f3*df3*(1+(1/(np.exp(h*(f2-f3)/(K*T))-1)))
#start conditions
Pin = 2.7 #W
fiberlength = 90000 #m
points = fiberlength*100
P0 = [Pin, 0, 0, 0, 0, 0, 0, 0 ]
def odes2 (z, P):
Pump_forward = P[0]
Pump_backward = P[1]
Stokes1_forward = P[2]
Stokes1_backward = P[3]
Stokes2_forward = P[4]
Stokes2_backward = P[5]
Stokes3_forward = P[6]
Stokes3_backward = P[7]
dPump_forwarddz = - a0 * Pump_forward - g1 * f0/f1 * Pump_forward * (Stokes1_forward + Stokes1_backward + Gamma1) + epsilon0 * Pump_backward
dPump_backwarddz = + a0 * Pump_backward + g1 * f0/f1 * Pump_backward * (Stokes1_forward + Stokes1_backward + Gamma1) - epsilon0 * Pump_forward
dStokes1_forwarddz = - a1 * Stokes1_forward - g2 * f1/f2 * Stokes1_forward * (Stokes2_forward + Stokes2_backward + Gamma2) + g1 * (Stokes1_forward + 0.5 * Gamma1) * (Pump_forward + Pump_backward) + epsilon1 * Stokes1_backward
dStokes1_backwardz = + a1 * Stokes1_backward + g2 * f1/f2 * Stokes1_backward * (Stokes2_forward + Stokes2_backward + Gamma2) - g1 * (Stokes1_backward + 0.5 * Gamma1) * (Pump_forward + Pump_backward) - epsilon1 * Stokes1_forward
dStokes2_forwarddz = - a2 * Stokes2_forward - g3 * f2/f3 * Stokes2_forward * (Stokes3_forward + Stokes3_backward + Gamma3) + g2 * (Stokes2_forward + 0.5 * Gamma2) * (Stokes1_forward + Stokes1_backward) + epsilon2 * Stokes2_backward
dStokes2_backwardz = + a2 * Stokes2_backward + g3 * f2/f3 * Stokes2_backward * (Stokes3_forward + Stokes3_backward + Gamma3) - g2 * (Stokes2_backward + 0.5 * Gamma2) * (Stokes1_forward + Stokes1_backward) - epsilon2 * Stokes2_forward
dStokes3_forwarddz = - a3 * Stokes3_forward + g3 * (Stokes3_forward + 0.5 * Gamma3) * (Stokes2_forward + Stokes2_backward) + epsilon3 * Stokes3_backward
dStokes3_backwardz = + a3 * Stokes3_backward - g3 * (Stokes3_backward + 0.5 * Gamma3) * (Stokes2_forward + Stokes2_backward) - epsilon3 * Stokes3_forward
return [dPump_forwarddz, dPump_backwarddz, dStokes1_forwarddz, dStokes1_backwardz, dStokes2_forwarddz, dStokes2_backwardz, dStokes3_forwarddz, dStokes3_backwardz]
sol = solve_ivp(odes2, (0, fiberlength), P0, t_eval=np.linspace(0, fiberlength, points))
Pump_f, Pump_b, Stokes1_f, Stokes1_b, Stokes2_f, Stokes2_b, Stokes3_f, Stokes3_b = sol.y
x=sol.t
plt.figure(1)
plt.title('backwards')
plt.plot(x, Pump_b, label='pump')
plt.plot(x, Stokes1_b, label='stokes#1')
plt.plot(x, Stokes2_b, label='stokes#2')
plt.plot(x, Stokes3_b, label='stokes#3')
plt.legend(loc=1, fontsize='xx-small')
plt.grid(True)
plt.figure(2)
plt.title('forwards')
plt.plot(x, Pump_f, label='pump')
plt.plot(x, Stokes1_f, label='stokes#1')
plt.plot(x, Stokes2_f, label='stokes#2')
plt.plot(x, Stokes3_f, label='stokes#3')
plt.legend(loc=1, fontsize='xx-small')
plt.grid(True)
I also tried the solve_bvp but it wasn't successfull.
I'd like to use an implementation of RK4 I found online for something, but I'm having a bit of difficulty wrapping my head around the implementations I have found online.
For example:
def rk4(f, x0, y0, x1, n):
vx = [0] * (n + 1)
vy = [0] * (n + 1)
h = (x1 - x0) / float(n)
vx[0] = x = x0
vy[0] = y = y0
for i in range(1, n + 1):
k1 = h * f(x, y)
k2 = h * f(x + 0.5 * h, y + 0.5 * k1)
k3 = h * f(x + 0.5 * h, y + 0.5 * k2)
k4 = h * f(x + h, y + k3)
vx[i] = x = x0 + i * h
vy[i] = y = y + (k1 + k2 + k2 + k3 + k3 + k4) / 6
return vx, vy
Could someone please help me understand what exactly the parameters are? If possible, I'd like a more general explanation, but, if being more specific makes it easier to explain, I'm going to be using it specifically for an ideal spring system.
You are asking for the parameters here:
def rk4(f, x0, y0, x1, n):
...
return vx, vy
f is the ODE function, declared as def f(x,y) for the differential equation y'(x)=f(x,y(x)),
(x0,y0) is the initial point and value,
x1 is the end of the integration interval [x0,x1]
n is the number of sub-intervals or integration steps
vx,vx are the computed sample points, vy[k] approximates y(vx[k]).
You can not use this for the spring system, as that code only works for a scalar v. You would need to change it to work with numpy for vector operations.
def rk4(func, x0, y0, x1, n):
y0 = np.array(y0)
f = lambda x,y: np.array(func(x,y))
vx = [0] * (n + 1)
vy = np.zeros( (n + 1,)+y0.shape)
h = (x1 - x0) / float(n)
vx[0] = x = x0
vy[0] = y = y0[:]
for i in range(1, n + 1):
k1 = h * f(x, y)
k2 = h * f(x + 0.5 * h, y + 0.5 * k1)
k3 = h * f(x + 0.5 * h, y + 0.5 * k2)
k4 = h * f(x + h, y + k3)
vx[i] = x = x0 + i * h
vy[i] = y = y + (k1 + 2*(k2 + k3) + k4) / 6
return vx, vy
I am trying to implement the runge-kutta method to solve a Lotka-Volterra systtem, but the code (bellow) is not working properly. I followed the recomendations that I found in other topics of the StackOverflow, but the results do not converge with the builtin Runge-Kutta method, like rk4 method available in Pylab, for example. Someone could help me?
import matplotlib.pyplot as plt
import numpy as np
from pylab import *
def meurk4( f, x0, t ):
n = len( t )
x = np.array( [ x0 ] * n )
for i in range( n - 1 ):
h = t[i+1] - t[i]
k1 = h * f( x[i], t[i] )
k2 = h * f( x[i] + 0.5 * h * k1, t[i] + 0.5 * h )
k3 = h * f( x[i] + 0.5 * h * k2, t[i] + 0.5 * h )
k4 = h * f( x[i] + h * k3, t[i] + h)
x[i+1] = x[i] + ( k1 + 2 * ( k2 + k3 ) + k4 ) * 6**-1
return x
def model(state,t):
x,y = state
a = 0.8
b = 0.02
c = 0.2
d = 0.004
k = 600
return np.array([ x*(a*(1-x*k**-1)-b*y) , -y*(c - d*x) ]) # corresponds to [dx/dt, dy/dt]
# initial conditions for the system
x0 = 500
y0 = 200
# vector of time
t = np.linspace( 0, 50, 100 )
result = meurk4( model, [x0,y0], t )
print result
plt.plot(t,result)
plt.xlabel('Time')
plt.ylabel('Population Size')
plt.legend(('x (prey)','y (predator)'))
plt.title('Lotka-Volterra Model')
plt.show()
I just updated the code following the comments. So, the function meurk4:
def meurk4( f, x0, t ):
n = len( t )
x = np.array( [ x0 ] * n )
for i in range( n - 1 ):
h = t[i+1] - t[i]
k1 = h * f( x[i], t[i] )
k2 = h * f( x[i] + 0.5 * h * k1, t[i] + 0.5 * h )
k3 = h * f( x[i] + 0.5 * h * k2, t[i] + 0.5 * h )
k4 = h * f( x[i] + h * k3, t[i] + h)
x[i+1] = x[i] + ( k1 + 2 * ( k2 + k3 ) + k4 ) * 6**-1
return x
Becomes now (corrected):
def meurk4( f, x0, t ):
n = len( t )
x = np.array( [ x0 ] * n )
for i in range( n - 1 ):
h = t[i+1] - t[i]
k1 = f( x[i], t[i] )
k2 = f( x[i] + 0.5 * h * k1, t[i] + 0.5 * h )
k3 = f( x[i] + 0.5 * h * k2, t[i] + 0.5 * h )
k4 = f( x[i] + h * k3, t[i] + h)
x[i+1] = x[i] + ( k1 + 2 * ( k2 + k3 ) + k4 ) * (h/6)
return x
Nevertheless, the results is the following:
While the buitin method rk4 (from Pylab) results the following:
So, certainly my code still is not correct, as its results are not the same of the builtin rk4 method. Please, someone can help me?
You are doing a very typical error,see for instance How to pass a hard coded differential equation through Runge-Kutta 4 or here Error in RK4 algorithm in Python
It is either
k2 = f( x+0.5*h*k1, t+0.5*h )
...
x[i+1]=x[i]+(k1+2*(k2+k3)+k4)*(h/6)
or
k2 = h*f( x+0.5*k1, t+0.5*h )
and so on, with x[i+1] as it was, but not both variants at the same time.
Update: A more insidious error is the inferred type of the initial values and in consequence of the array of x vectors. By the original definition, both are integers, and thus
x = np.array( [ x0 ] * n )
creates a list of integer vectors. Thus the update step
x[i+1] = x[i] + ( k1 + 2 * ( k2 + k3 ) + k4 ) * (h/6)
will always round to integer. And since there is a phase where both values fall below 1, the integration stabilizes at zero. Thus modify to
# initial conditions for the system
x0 = 500.0
y0 = 200.0
to avoid that problem.
This question already exists:
python code for multiple ode
Closed 9 years ago.
The following is the code for multiple solvers so far.
The system for this problem is here, our system
However, when I execute it in Python, it shows me the following error:
Traceback (most recent call last):
File "G:\math3511\assignment\assignment5\qu2", line 59, in
X = AdamsBashforth4(equation, init, t)
File "G:\math3511\assignment\assignment5\qu2", line 32, in AdamsBashforth4
k2 = h * f( x[i] + 0.5 * k1, t[i] + 0.5 * h )
TypeError: can't multiply sequence by non-int of type 'float'
the code:
import numpy
from numpy import array, exp, linspace
def AdamsBashforth4( f, x0, t ):
"""
Fourth-order Adams-Bashforth method::
u[n+1] = u[n] + dt/24.*(55.*f(u[n], t[n]) - 59*f(u[n-1], t[n-1]) +
37*f(u[n-2], t[n-2]) - 9*f(u[n-3], t[n-3]))
for constant time step dt.
RK2 is used as default solver for first steps.
"""
n = len( t )
x = numpy.array( [ x0 ] * n )
# Start up with 4th order Runge-Kutta (single-step method). The extra
# code involving f0, f1, f2, and f3 helps us get ready for the multi-step
# method to follow in order to minimize the number of function evaluations
# needed.
f1 = f2 = f3 = 0
for i in xrange( min( 3, n - 1 ) ):
h = t[i+1] - t[i]
f0 = f( x[i], t[i] )
k1 = h * f0
k2 = h * f( x[i] + 0.5 * k1, t[i] + 0.5 * h )
k3 = h * f( x[i] + 0.5 * k2, t[i] + 0.5 * h )
k4 = h * f( x[i] + k3, t[i+1] )
x[i+1] = x[i] + ( k1 + 2.0 * ( k2 + k3 ) + k4 ) / 6.0
f1, f2, f3 = ( f0, f1, f2 )
for i in xrange( n - 1 ):
h = t[i+1] - t[i]
f0 = f( x[i], t[i] )
k1 = h * f0
k2 = h * f( x[i] + 0.5 * k1, t[i] + 0.5 * h )
k3 = h * f( x[i] + 0.5 * k2, t[i] + 0.5 * h )
k4 = h * f( x[i] + k3, t[i+1] )
x[i+1] = x[i] + h * ( 9.0 * fw + 19.0 * f0 - 5.0 * f1 + f2 ) / 24.0
f1, f2, f3 = ( f0, f1, f2 )
return x
def equation(X, t):
x, y = X
return [ x+y-exp(t), x+y+2*exp(t) ]
init = [ -1.0, -1.0 ]
t = linspace(0, 4, 50)
X = AdamsBashforth4(equation, init, t)
It seems that f is returning a sequence (list, tuple, or like) and there's no operation to multiply all items in a sequence by a value.
Dirk is right, looking at your algorithm, your equation should return (even if it small) a numpy array, as you can scalar-multiply a numpy array. So keeping your code but embedding your return in equation with an array. Like this:
np.array([1,2]) # a numpy array containing [1,2]