How to implement a system of stochastic ODEs (SDEs) in python? - python

I have a system of ODEs in which I am trying to include an 'error' term, so that it becomes a system of stochastic ODEs.
For solving a system of ODEs in python I normally use scipy's odeint.
An example derived from the Scipy Cookbook, involving the famous Zombie apocalypse:
# zombie apocalypse modeling
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint
plt.rcParams['figure.figsize'] = 10, 8
P = 0 # birth rate
d = 0.0001 # natural death percent (per day)
B = 0.0095 # transmission percent (per day)
G = 0.0001 # resurect percent (per day)
A = 0.0001 # destroy percent (per day)
# solve the system dy/dt = f(y, t)
def f(y, t):
Si = y[0]
Zi = y[1]
Ri = y[2]
# the model equations (see Munz et al. 2009)
f0 = P - B*Si*Zi - d*Si
f1 = B*Si*Zi + G*Ri - A*Si*Zi
f2 = d*Si + A*Si*Zi - G*Ri
return [f0, f1, f2]
# initial conditions
S0 = 500. # initial population
Z0 = 0 # initial zombie population
R0 = 0 # initial death population
y0 = [S0, Z0, R0] # initial condition vector
t = np.linspace(0, 5., 1000) # time grid
# solve the DEs
soln = odeint(f, y0, t)
S = soln[:, 0]
Z = soln[:, 1]
R = soln[:, 2]
# plot results
plt.figure()
plt.plot(t, S, label='Living')
plt.plot(t, Z, label='Zombies')
plt.xlabel('Days from outbreak')
plt.ylabel('Population')
plt.title('Zombie Apocalypse - No Init. Dead Pop.; No New Births.')
plt.legend(loc=0)
plt.show()
Is it possible to use odeint to solve a system of stochastic ODEs?
For example if I would like to include an error term/random walk in the birth rate (P) of the equations?
My idea was to use an extra equation in the system to be able to define a random walk (randomly sampled death rate (using random.normalvariate()) and to solve the system like this:
f0 = P - B*Si*Zi - f3*Si
f1 = B*Si*Zi + G*Ri - A*Si*Zi
f2 = f3*Si + A*Si*Zi - G*Ri
f3 = random.normalvariate(mu, sigma)
return [f0, f1, f2]
Is this the right way to solve a system of SDEs? Or do I have to use a different solver for stochastic ODEs?

With help the system of ODEs was rewriten into an system of SDEs in which the birth rate was a stochastic process.
It was a great suggestion to use SDEint package.
# Zombie apocalypse SDE model
import matplotlib.pyplot as plt
import numpy as np
import sdeint
P, d, B, G, A = 0.0001, 0.0001, 0.0095, 0.0001, 0.0001
tspan = np.linspace(0, 5., 1000)
y0 = np.array([500., 0., 0., P])
def f(y, t):
Si = y[0]
Zi = y[1]
Ri = y[2]
f0 = y[3] - B * Si * Zi - d * Si
f1 = B * Si * Zi + G * Ri - A * Si * Zi
f2 = d * Si + A * Si * Zi - G * Ri
f3 = 0
return np.array([f0, f1, f2, f3])
def GG(y, t):
return np.diag([0, 0, 0, 100])
result = sdeint.itoint(f, GG, y0, tspan)
plt.plot(result)
plt.show()

Related

Odeint problem in model: lsoda-- repeated occurrences of illegal input && lsoda-- at start of problem, too much accuracy

I need help to solve a ODE system using odeint or any workaround that works. I`m trying to model a ODE system descriving the beheaviour of a cilinder on a mechanichal arm, using Lagrangian mechanics I got the following 2nd order ODE system:
$$\ddot{x} = x*\dot{\phi}^2 - g*(sin(\phi) +cos(\phi))$$
$$\ddot{\phi} = \frac{1}{2*\dot{x}*m_c}*(\frac{k*i}{x}-\frac{m_b*L*\dot{\phi}}{2}-g*cos(\phi)*(m_c*x-m_b*L/2))$$
Then I transformed it to a 4x4 1st order ODE system y linearized 2 equations aroun initial condition, to make it more "solveable".
In python I`wrote the folllowing:
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
# source: https://apmonitor.com/pdc/index.php/Main/SolveDifferentialEquations
g, mc, mb, k, L = (9.8, 0.5, 1.3, 2, 1.2)
def model(z, t, i, z0):
global g, mc, mb, k, L
#ODE variables
x1 = z[0]
y1 = z[1]
x2 = z[2]
y2 = z[3]
#ODE initial conditions
zx1 = z0[0]
zy1 = z0[1]
zx2 = z0[2]
zy2 = z0[3]
#Diferential for linealization around I.C.
dx1 = z[0]-z0[0]
dy1 = z[1]-z0[1]
dx2 = z[2]-z0[2]
dy2 = z[3]-z0[3]
def w1(zx1, zy1, zx2, zy2):
global g, mc, mb, k, L
return zx2*zy1**2-g*(np.sin(zy2)+np.cos(zy2))
def w2(zx1, zy1, zx2, zy2):
global g, mc, mb, k, L
return 1/(2*zx1*mc)*((k*i)/(zx2)-mb*zy2*L/2-g*np.cos(zy2*(mc*zx2-mb*L/2)))
dx2dt = x1
#dx1dt = x2*y1**2-g*(np.sin(y2)+np.cos(y2))
dx1dt = w1(zx1, zy1, zx2, zy2) + zy1**2*dx2+2*zx2 * \
zy1*dy1-g*(np.cos(zy2)-np.sin(zy2))*dy2
dy2dt = y1
#dy1dt = 1/(2*x1*mc)*((k*i)/(x2)-mb*y2*L/2-g*np.cos(y2*(mc*x2-mb*L/2)))
dy1dt = w2(zx1, zy1, zx2, zy2) - 1/(4*zx1**2*mc) * \
((k*i)/(zx2)-mb*zy2*L/2-g*np.cos(zy2*(mc*zx2-mb*L/2)))*dx1
+(k*i*np.log(abs(zx2))/(2*zx1*mc)-g*np.cos(zy2)*mc)*dx2-(mb*L) / \
(4*zx1*mc)*dy1+g*np.sin(zy2)*(mc*zx2-mb*L/2)*(2*zx1*mc)*dy2
dzdt = [dx1dt, dy1dt, dx2dt, dy2dt]
return dzdt
# initial condition
z0 = [0.01, 0.01, 0.8, np.pi/8]
# number of time points
n = 30
# time points
t = np.linspace(0, 1, n)
# step input
u = np.zeros(n)
# change to 2.0 at time = 5.0
u[:] = 1
# store solution
x1 = np.zeros(n)
y1 = np.zeros(n)
x2 = np.zeros(n)
y2 = np.zeros(n)
# record initial conditions
x1[0] = z0[0]
y1[0] = z0[1]
x2[0] = z0[2]
y2[0] = z0[3]
# solve ODE
for i in range(1, n):
# span for next time step
tspan = [t[i-1], t[i]]
# solve for next step
z = odeint(model, z0, tspan, args=((u[i], z0)) )
# store solution for plotting
if -np.pi/4 <= z[1][3] <= np.pi/4: # Angulo +/- Pi/4
u[i] = u[i-1] + 1
else:
u[i] = u[i-1]
if 0 <= z[1][2] <= L: # Se mantiene en el brazo
print("Se saliΓ³")
x1[i] = z[1][0]
y1[i] = z[1][1]
x1[i] = z[1][2]
y1[i] = z[1][3]
# Siguientes CI
z0 = z[1]
# plot results
plt.plot(t, u, 'g:', label='u(t)')
plt.plot(t, x2, 'b-', label='x(t)')
plt.plot(t, y2, 'r--', label='phi(t)')
plt.ylabel('Cilindro(x,phi)')
plt.xlabel('Tiempo(s)')
plt.legend(loc='best')
plt.show()
Then I`ve got the following error, telling me than too much accuracy is needed to solve my system:
lsoda-- at start of problem, too much accuracy
requested for precision of machine.. see tolsf (=r1)
in above message, r1 = NaN
lsoda-- repeated occurrences of illegal input
lsoda-- at start of problem, too much accuracy
requested for precision of machine.. see tolsf (=r1)
in above message, r1 = NaN
dy1dt = w2(zx1, zy1, zx2, zy2) - 1/(4*zx1**2*mc) * \
lsoda-- warning..internal t (=r1) and h (=r2) are
such that in the machine, t + h = t on the next step
(h = step size). solver will continue anyway
in above, r1 = 0.3448275862069D+00 r2 = 0.1555030227910D-28

Adjusting shape of a data array to perform optimization in SciPy

I have a code which performs optimization to infer a parameter:
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
from scipy.optimize import root
from scipy.optimize import minimize
import pandas as pd
d = {'Week': [1, 2,3,4,5,6,7,8,9,10,11], 'incidence': [206.1705794,2813.420201,11827.9453,30497.58655,10757.66954,7071.878779,3046.752723,1314.222882,765.9763902,201.3800578,109.8982006]}
df = pd.DataFrame(data=d)
def peak_infections(beta, df):
# Weeks for which the ODE system will be solved
weeks = df.Week.to_numpy()
# Total population, N.
N = 100000
# Initial number of infected and recovered individuals, I0 and R0.
I0, R0 = 10, 0
# Everyone else, S0, is susceptible to infection initially.
S0 = N - I0 - R0
J0 = I0
# Contact rate, beta, and mean recovery rate, gamma, (in 1/days).
#reproductive no. R zero is beta/gamma
gamma = 1/7 * 7 #rate should be in weeks now
# A grid of time points
t = np.linspace(0, weeks[-1], weeks[-1] + 1)
# The SIR model differential equations.
def deriv(y, t, N, beta, gamma):
S, I, R, J = y
dS = ((-beta * S * I) / N)
dI = ((beta * S * I) / N) - (gamma * I)
dR = (gamma * I)
dJ = ((beta * S * I) / N)
return dS, dI, dR, dJ
# Initial conditions are S0, I0, R0
# Integrate the SIR equations over the time grid, t.
solve = odeint(deriv, (S0, I0, R0, J0), t, args=(N, beta, gamma))
S, I, R, J = solve.T
return I/N
def residual(x, df):
# Total population, N.
N = 100000
incidence = df.incidence.to_numpy()/N
return np.sum((peak_infections(x, df)[1:] - incidence) ** 2)
x0 = 0.5
res = minimize(residual, x0, args=(df), method="Nelder-Mead").x
print(res)
However, it is not giving the correct values, so instead of taking weeks as 1,2,3... in the line d = {'Week': [1, 2,3,4,5,6,7,8,9,10,11], 'incidence': [206.1705794,2813.420201,11827.9453,30497.58655,10757.66954,7071.878779,3046.752723,1314.222882,765.9763902,201.3800578,109.8982006]} I'd like to use days instead so Python has clearer information to work with. I'd like to slice the linspace of days as weekly intervals. However, I'm having some shape alignment issues:
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
from scipy.optimize import root
from scipy.optimize import minimize
import pandas as pd
time = np.linspace(0, 77, 77 + 1)
d = {'Week': [time[7],time[14],time[21],time[28],time[35],time[42],time[49],time[56],time[63],time[70],time[77]], 'incidence': [206.1705794,2813.420201,11827.9453,30497.58655,10757.66954,7071.878779,3046.752723,1314.222882,765.9763902,201.3800578,109.8982006]}
#d = {'Week': [1, 2,3,4,5,6,7,8,9,10,11], 'incidence': [206.1705794,2813.420201,11827.9453,30497.58655,10757.66954,7071.878779,3046.752723,1314.222882,765.9763902,201.3800578,109.8982006]}
df = pd.DataFrame(data=d)
def peak_infections(beta, df):
# Weeks for which the ODE system will be solved
weeks = df.Week.to_numpy()
# Total population, N.
N = 100000
# Initial number of infected and recovered individuals, I0 and R0.
I0, R0 = 10, 0
# Everyone else, S0, is susceptible to infection initially.
S0 = N - I0 - R0
J0 = I0
# Contact rate, beta, and mean recovery rate, gamma, (in 1/days).
#reproductive no. R zero is beta/gamma
gamma = 1/7 * 7 #rate should be in weeks now
# A grid of time points
t = np.linspace(0, 77, 77 + 1)
# The SIR model differential equations.
def deriv(y, t, N, beta, gamma):
S, I, R, J = y
dS = ((-beta * S * I) / N)
dI = ((beta * S * I) / N) - (gamma * I)
dR = (gamma * I)
dJ = ((beta * S * I) / N)
return dS, dI, dR, dJ
# Initial conditions are S0, I0, R0
# Integrate the SIR equations over the time grid, t.
solve = odeint(deriv, (S0, I0, R0, J0), t, args=(N, beta, gamma))
S, I, R, J = solve.T
return I/N
def residual(x, df):
# Total population, N.
N = 100000
incidence = df.incidence.to_numpy()/N
return np.sum((peak_infections(x, df)[1:] - incidence) ** 2)
x0 = 0.5
res = minimize(residual, x0, args=(df), method="Nelder-Mead").x
print(res)
The approach I tried here was recreating the dataframe by slicing time which is 77 days, so 11 weeks. It still returns that the shape error, 77 against 11 elements occurs within my function residual in the line return np.sum((peak_infections(x, df)[1:] - incidence) ** 2). Where is my approach going wrong?
-----------EDIT----------
updated code
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
from scipy.optimize import minimize
import pandas as pd
t = np.arange(7,84,7)
d = {'Week': t, 'incidence': [206.1705794,2813.420201,11827.9453,30497.58655,10757.66954,7071.878779,3046.752723,1314.222882,765.9763902,201.3800578,109.8982006]}
#d = {'Week': [time[7],time[14],time[21],time[28],time[35],time[42],time[49],time[56],time[63],time[70],time[77]], 'incidence': [206.1705794,2813.420201,11827.9453,30497.58655,10757.66954,7071.878779,3046.752723,1314.222882,765.9763902,201.3800578,109.8982006]}
#d = {'Week': [1, 2,3,4,5,6,7,8,9,10,11], 'incidence': [206.1705794,2813.420201,11827.9453,30497.58655,10757.66954,7071.878779,3046.752723,1314.222882,765.9763902,201.3800578,109.8982006]}
df = pd.DataFrame(data=d)
def peak_infections(beta, df):
# Weeks for which the ODE system will be solved
weeks = df.Week.to_numpy()
# Total population, N.
N = 100000
# Initial number of infected and recovered individuals, I0 and R0.
I0, R0 = 10, 0
# Everyone else, S0, is susceptible to infection initially.
S0 = N - I0 - R0
J0 = I0
# Contact rate, beta, and mean recovery rate, gamma, (in 1/days).
#reproductive no. R zero is beta/gamma
gamma = 1/7 * 7 #rate should be in weeks now
# A grid of time points
t = np.linspace(0, 77, 77 + 1)
# The SIR model differential equations.
def deriv(y, t, N, beta, gamma):
S, I, R, J = y
dS = ((-beta * S * I) / N)
dI = ((beta * S * I) / N) - (gamma * I)
dR = (gamma * I)
dJ = ((beta * S * I) / N)
return dS, dI, dR, dJ
# Initial conditions are S0, I0, R0
# Integrate the SIR equations over the time grid, t.
solve = odeint(deriv, (S0, I0, R0, J0), t, args=(N, beta, gamma))
S, I, R, J = solve.T
return I/N
def residual(x, df):
# Total population, N.
N = 100000
incidence = df.incidence.to_numpy()/N
return np.sum((peak_infections(x, df) - incidence) ** 2)
x0 = 0.5
res = minimize(residual, x0, args=(df), method="Nelder-Mead").x
print(res)
Your problem occurs at line 52, where you are getting 77 values by solving peak_infections(x, df)[1:] and you have 11 values of incidence, as you have mentioned.
This arises because you are solving your ode at t (line 29) which has 78 values. To avoid this, generate a time vector with 7 values in your peak_infections function as follows:
t = np.linspace(0, 77, 77 + 1)
t = [t[7],t[14],t[21],t[28],t[35],t[42],t[49],t[56],t[63],t[70],t[77]]
or a completely new one as:
t = np.arange(7,84,7)
and change your residual function (don't slice peak_infections(x, df)[1:]) as follows:
def residual(x, df):
# Total population, N.
N = 100000
incidence = df.incidence.to_numpy()/N
return np.sum((peak_infections(x, df) - incidence) ** 2)
this will solve your problem as now you are comparing NumPy arrays with shapes (7,) and (7,) which will not produce an error.

Couple Differential Equations using Python

I am trying to solve a system of geodesics orbital equations using python. They are coupled ordinary equations. I've tried different approaches, but they all yielded me a wrong shape (the shape should be some periodic function when plotting r and phi). Any idea on how to do this?
Here are my constants
G = 4.30091252525 * (pow(10, -3)) #Gravitational constant in (parsec*km^2)/(Ms*sec^2)
c = 0.0020053761 #speed of light , AU/sec
M = 170000 #mass of the central body, in solar masses
m = 10 #mass of the orbiting body, in solar masses
rs = 2 * G * M / pow(c, 2) #Schwarzschild radius
Lz= 0.000024 #Angular momemntum
h = Lz / m #Just the constant in equation
E= 1.715488e-007 #energy
And initial conditions are:
Y(0) = rs
Phi(0) = math.pi
Orbital equations
The way I tried to do it:
def rhs(t, u):
Y, phi = u
dY = np.sqrt((E**2 / (m**2 * c**2) - (1 - rs / Y) * (c**2 + h**2 / Y**2)))
dphi = L / Y**2
return [dY, dphi]
Y0 = np.array([rs,math.pi])
sol = solve_ivp(rhs, [1, 1000], Y0, method='Radau', dense_output=True)
It seems like you are looking at the spacial coordinates in an invariant plane of the geodesic equations of an object moving in Schwarzschild gravity.
One can use many different methods, which preserve as much of the underlying geometric structure of the model as possible, like symplectic geometric integrators or perturbation theory. As Lutz Lehmann pointed out in the comments, the default method for 'solve_ivp' uses as default the Dormand-Prince (4)5 stepper that utilizes the extrapolation mode, that is, the order 5 step, with the step size selection driven by the error estimate of the order 4 step.
Warning: your initial condition for Y equals Schwarzschild's radius, so these equations may fail or require special treatment (especially the time component of the equations, which you have not included here!) It may be that you have to switch to different coordinates, that remove the singularity at the even horizon. Moreover, the solutions may not be periodic curves, but quasi-periodic, so they may not close up nicely.
For a quick and dirty treatment, but possibly a fairly accurate one, I would differentiate the first equation
(dr / dtau)^2 = (E2_mc2 - c2) + (2*GM)/r - (h^2)/(r^2) + (r_schw*h^2)/(r^3)
with respect to the proper time tau, then cancel out the first derivative dr / dtau with respect to r on both sides, and end up with an equation with second derivative for the radius r on the left. Then turn this second derivative equation into a pair of first derivative equations for r and its rate of change v, i.e
dphi / dtau = h / (r^2)
dr / dtau = v
dv / dtau = - GM / (r^2) + h^2 / (r^3) - 3*r_schw*(h^2) / (2*r^4)
and calculate from the original equation for r and its first derivative dr / dtau an initial value for the rate of change v = dr / dtau, i.e. I would solve for v the equations with r=r0:
(v0)^2 = (E2_mc2 - c2) + (2*GM)/r0 - (h^2)/(r0^2) + (r_schw*h^2)/(r0^3)
Maybe some kind of python code like this may work:
import math
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import solve_ivp
#from ode_helpers import state_plotter
# u = [phi, Y, V, t] or if time is excluded
# u = [phi, Y, V]
def f(tau, u, param):
E2_mc2, c2, GM, h, r_schw = param
Y = u[1]
f_phi = h / (Y**2)
f_Y = u[2] # this is the dr / dt auxiliary equation
f_V = - GM / (Y**2) + h**2 / (Y**3) - 3*r_schw*(h**2) / (2*Y**4)
#f_time = (E2_mc2 * Y) / (Y - r_schw) # this is the equation of the time coordinate
return [f_phi, f_Y, f_V] # or [f_phi, f_Y, f_V, f_time]
# from the initial value for r = Y0 and given energy E,
# calculate the initial rate of change dr / dtau = V0
def ivp(Y0, param, sign):
E2_mc2, c2, GM, h, r_schw = param
V0 = math.sqrt((E2_mc2 - c2) + (2*GM)/Y0 - (h**2)/(Y0**2) + (r_schw*h**2)/(Y0**3))
return sign*V0
G = 4.30091252525 * (pow(10, -3)) #Gravitational constant in (parsec*km^2)/(Ms*sec^2)
c = 0.0020053761 #speed of light , AU/sec
M = 170000 #mass of the central body, in solar masses
m = 10 #mass of the orbiting body, in solar masses
Lz= 0.000024 #Angular momemntum
h = Lz / m #Just the constant in equation
E= 1.715488e-007 #energy
c2 = c**2
E2_mc2 = (E**2) / (c2*m**2)
GM = G*M
r_schw = 2*GM / c2
param = [E2_mc2, c2, GM, h, r_schw]
Y0 = r_schw
sign = 1 # or -1
V0 = ivp(Y0, param, sign)
tau_span = np.linspace(1, 1000, num=1000)
u0 = [math.pi, Y0, V0]
sol = solve_ivp(lambda tau, u: f(tau, u, param), [1, 1000], u0, t_eval=tau_span)
Double check the equations, mistakes and inaccuracies are possible.

Solving set of ODEs with Scipy

I am trying to develop an algorithm (use scipy.integrate.odeint()) that predicts the changing concentration of cells, substrate and product (i.e., 𝑋, 𝑆, 𝑃) over time until the system reaches steady- state (~100 or 200 hours). The initial concentration of cells in the bioreactor is 0.1 𝑔/𝐿 and there is no glucose or product in the reactor initially. I want to test the algorithm for a range of different flow rates, 𝑄, between 0.01 𝐿/β„Ž and 0.25 𝐿/β„Ž and analyze the impact of the flow rate on product production (i.e., 𝑄 β‹… 𝑃 in 𝑔/β„Ž). Eventually, I would like to generate a plot that shows product production rate (y-axis) versus flow rate, 𝑄, on the x-axis. My goal is to estimate the flow rate that results in the maximum (or critical) production rate. This is my code so far:
from scipy.integrate import odeint
import numpy as np
# Constants
u_max = 0.65
K_s = 0.14
K_1 = 0.48
V = 2
X_in = 0
S_in = 4
Y_s = 0.38
Y_p = 0.2
# Variables
# Q - Flow Rate (L/h), value between 0.01 and 0.25 that produces best Q * P
# X - Cell Concentration (g/L)
# S - The glucose concentration (g/L)
# P - Product Concentration (g/L)
# Equations
def func_dX_dt(X, t, S):
u = (u_max) / (1 + (K_s / S))
dX_dt = (((Q * S_in) - (Q * S)) / V) + (u * X)
return dX_dt
def func_dS_dt(S, t, X):
u = (u_max) / (1 + (K_s / S))
dS_dt = (((Q * S_in) - (Q * S)) / V) - (u * (X / Y_s))
return dS_dt
def func_dP_dt(P, t, X, S):
u = (u_max) / (1 + (K_s / S))
dP_dt = ((-Q * P) / V) - (u * (X / Y_p))
return dP_dt
t = np.linspace(0, 200, 200)
# Q placeholder
Q = 0.01
# Attempt to solve the Ordinary differential equations
sol_dX_dt = odeint(func_dX_dt, 0.1, t, args=(S,))
sol_dS_dt = odeint(func_dS_dt, 0.1, t, args=(X,))
sol_dP_dt = odeint(func_dP_dt, 0.1, t, args=(X,S))
In the programs current state there does not seem to be be a way to generate the steady state value for P. I attempted to make this modification to get the value of X.
sol_dX_dt = odeint(func_dX_dt, 0.1, t, args=(odeint(func_dS_dt, 0.1, t, args=(X,)),))
It produces the error:
NameError: name 'X' is not defined
At this point I am not sure how to move forward.
(Edit 1: Added Original Equations)
First Equation
Second Equation and Third Equation
You do not have to apply the functions to each part but return a tuple of the derivatives as I show below:
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
Q = 0.01
V = 2
Ys = 0.38
Sin = 4
Yp = 0.2
Xin = 0
umax = 0.65
Ks = 0.14
K1 = 0.48
def mu(S, umax, Ks, K1):
return umax/((1+Ks/S)*(1+S/K1))
def dxdt(x, t, *args):
X, S, P = x
Q, V, Xin, Ys, Sin, Yp, umax, Ks, K1 = args
m = mu(S, umax, Ks, K1)
dXdt = (Q*Xin - Q*X)/V + m*X
dSdt = (Q*Sin - Q*S)/V - m*X/Ys
dPdt = -Q*P/V - m*X/Yp
return dXdt, dSdt, dPdt
t = np.linspace(0, 200, 200)
X0 = 0.1
S0 = 0.1
P0 = 0.1
x0 = X0, S0, P0
sol = odeint(dxdt, x0, t, args=(Q, V, Xin, Ys, Sin, Yp, umax, Ks, K1))
plt.plot(t, sol[:, 0], 'r', label='X(t)')
plt.plot(t, sol[:, 1], 'g', label='S(t)')
plt.plot(t, sol[:, 2], 'b', label='P(t)')
plt.legend(loc='best')
plt.xlabel('t')
plt.grid()
plt.show()
Output:

Implement pseudo-spectral method with RK4 in Python

I am using pseudo-spectral method to solve for KdV equation u_t + u*u_x + u_xxx = 0. After simplification by Fourier Transform, I got two equations with two variables:
uhat = vhat * exp(-i*k^3*t)
d(vhat)/dt =-0.5i * k * exp(-i*k^3*t)*F[u^2]
where F represents Fourier Transform, uhat = F[u], vhat = F[v]
I'd like to use RK4 to solve for u by Γ¨ ifft(uhat)Γ¨ eventually. Now I got two variables uhat and vhat, I have 2 ideas for solving uhat and vhat:
To treat them as a system of ODEs to implement RK4.
Treat the equation 2 above as the to-be solved ODE to solve chat by
RK4, and calculate uhat = vhat * exp(-i*k^2*delta_t) after each
time step delta_t.
I got problem to implement both ideas. Here are the codes for the 2nd idea above.
import numpy as np
import math
from matplotlib import pyplot as plt
from matplotlib import animation
#----- Numerical integration of ODE via fixed-step classical Runge-Kutta -----
def RK4Step(yprime,t,y,dt):
k1 = yprime(t , y )
k2 = yprime(t+0.5*dt, y+0.5*k1*dt)
k3 = yprime(t+0.5*dt, y+0.5*k2*dt)
k4 = yprime(t+ dt, y+ k3*dt)
return y + (k1+2*k2+2*k3+k4)*dt/6.
def RK4(yprime,times,y0):
y = np.empty(times.shape+y0.shape,dtype=y0.dtype)
y[0,:] = y0 # enter initial conditions in y
steps = 4
for i in range(times.size-1):
dt = (times[i+1]-times[i])/steps
y[i+1,:] = y[i,:]
for k in range(steps):
y[i+1,:] = RK4Step(yprime, times[i]+k*dt, y[i+1,:], dt)
y[i+1,:] = y[i+1,:] * np.exp(delta_t * 1j * kx**3)
return y
#====================================================================
#----- Parameters for PDE -----
L = 100
n = 512
delta_t = 0.1
tmax = 20
c1 = 1.5 # amplitude of 1st wave
c2 = 0.5 # amplitude of 2nd wave
#----- Constructing the grid and kernel functions
x2 = np.linspace(-L/2,L/2, n+1)
x = x2[:n] # periodic B.C. #0 = #n
kx1 = np.linspace(0,n/2-1,n/2)
kx2 = np.linspace(1,n/2, n/2)
kx2 = -1*kx2[::-1]
kx = (2.* np.pi/L)*np.concatenate((kx1,kx2)); kx[0] = 1e-6
#----- Initial Condition -----
z1 = np.sqrt(c1)/2. * (x-0.1*L)
z2 = np.sqrt(c2)/2. * (x-0.4*L)
soliton = 6*(0.5 * c1 / (np.cosh(z1))**2 + 0.5 * c2/ (np.cosh(z2))**2)
uhat0 = np.fft.fft(soliton)
vhat0 = uhat0
#----- Define ODE -----
def wprime(t,vhat):
g = -0.5 * 1j* kx * np.exp(-1j * kx**3 * t)
return g * np.fft.fft(np.real(np.fft.ifft(np.exp(1j*kx**3*t)*vhat)**2))
#====================================================================
#----- Compute the numerical solution -----
TimeStart = 0.
TimeEnd = tmax+delta_t
TimeSpan = np.arange(TimeStart, TimeEnd, delta_t)
w_sol = RK4(wprime,TimeSpan, vhat0)
#----- Animate the numerical solution -----
fig = plt.figure()
ims = []
for i in TimeSpan:
w = np.real(np.fft.ifft(w_sol[i,:]))
im = plt.plot(x,w)
ims.append([im])
ani = animation.ArtistAnimation(fig, ims, interval=100, blit=False)
plt.show()
The RK4 part was from #LutzL.
In general it looks good. Better use a FuncAnimation, reduces the memory footprint and works:
#----- Animate the numerical solution -----
fig = plt.figure()
ax = plt.axes(ylim=(-5,5))
line, = ax.plot(x, soliton, '-')
NT = TimeSpan.size;
def animate(i):
i = i % (NT + 10)
if i<NT:
w = np.real(np.fft.ifft(w_sol[i,:]))
line.set_ydata(w)
return line,
anim = animation.FuncAnimation(fig, animate, interval=50, blit=True)
plt.show()
However, the first graph remains standing, I do not know why.

Categories