Solving differential equations numerically - python

I tried solving a very simple equation f = t**2 numerically. I coded a for-loop, so as to use f for the first time step and then use the solution of every loop through as the inital function for the next loop.
I am not sure if my approach to solve it numerically is correct and for some reason my loop only works twice (one through the if- then the else-statement) and then just gives zeros.
Any help very much appreciatet. Thanks!!!
## IMPORT PACKAGES
import numpy as np
import math
import sympy as sym
import matplotlib.pyplot as plt
## Loop to solve numerically
for i in range(1,4,1):
if i == 1:
f_old = t**2
print(f_old)
else:
f_old = sym.diff(f_old, t).evalf(subs={t: i})
f_new = f_old + dt * (-0.5 * f_old)
f_old = f_new
print(f_old)

Scipy.integrate package has a function called odeint that is used for solving differential equations
Here are some resources
Link 1
Link 2
y = odeint(model, y0, t)
model: Function name that returns derivative values at requested y and t values as dydt = model(y,t)
y0: Initial conditions of the differential states
t: Time points at which the solution should be reported. Additional internal points are often calculated to maintain accuracy of the solution but are not reported.
Example that plots the results as well :
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
# function that returns dy/dt
def model(y,t):
k = 0.3
dydt = -k * y
return dydt
# initial condition
y0 = 5
# time points
t = np.linspace(0,20)
# solve ODE
y = odeint(model,y0,t)
# plot results
plt.plot(t,y)
plt.xlabel('time')
plt.ylabel('y(t)')
plt.show()

Related

How to put initial condition of ODE at a specific time point using odeint in Python?

How to put initial condition of ODE at a specific time point using odeint in Python?
So I have y(0) = 5 as initial condition,
following code works::
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
# function that returns dy/dt
def model(y,t):
k = 0.3
dydt = -k * y
return dydt
# initial condition
y0 = 5
# time points
t = np.linspace(0,20)
# solve ODE
y = odeint(model,y0,t)
# plot results
plt.plot(t,y)
plt.xlabel('time')
plt.ylabel('y(t)')
plt.show()
I wanna see the graph in both negative and positive time line.
So I change t = np.linspace(0,20) to t = np.linspace(-5,20), but then the initial condition is taken as y(-5) = 5.
How to solve this?
I do not think you can, according to the docs
But you can solve for positive and negative t's separately and then stich them together. Replace the relevant lines with
tp = np.linspace(0,20)
tm = np.linspace(0,-5)
# solve ODE
yp = odeint(model,y0,tp)
ym = odeint(model,y0,tm)
# stich together; note we flip the time direction with [::-1] construct
t = np.concatenate([tm[::-1],tp])
y = np.concatenate([ym[::-1],yp])
this produces

Non linear ODE solution by odeint

I have to solve the following differential equation:
or
Without the F_1 term the code is straight forward. But I fail to solve it with the F_1 term included though I know the solution should look like a damped harmonic oscillation.
from scipy.integrate import odeint
import numpy as np
import matplotlib.pyplot as plt
def harmonic_motion(X,t,k,m,tau):
x,v=X
F=-(k/m)*x
F_1=tau/v*(np.gradient(x,t)**2) # This line doesn't work.
dx_dt=v
dv_dt=F-F_1
dX_dt=[dx_dt,dv_dt]
return dX_dt
m=1
k=1
tau=0.1
X0=-3
V0=0
t = np.linspace(0, 15, 250)
sol = odeint(harmonic_motion, [X0,V0], t,args=(k,m,tau))
x=sol[:,0]
v=sol[:,1]
plt.plot(t,x,label='x')
plt.plot(t,v,label='v')
plt.legend()
plt.xlabel('t (s)')
plt.ylabel('x,v (m,m/s)')
plt.show()
The correct version for a spring with air friction is
m*x'' + tau*abs(x')*x' + k*x = 0
The first order system for this is
def harmonic_motion(X,t,k,m,tau):
x,v = X
return v, -(tau*abs(v)*v + k*x)/m
The phase plot of this should now give a nice spiral to the origin.

Python - Using a Kronecker Delta with ODEINT

I'm trying to plot the output from an ODE using a Kronecker delta function which should only become 'active' at a specific time = t1.
This should give a sawtooth like response where the initial value decays down exponentially until t=t1 where it rises again instantly before decaying down once again.
However, when I plot this it looks like the solver is seeing the Kronecker delta function as zero for all time t. Is there anyway to do this in Python?
from scipy import KroneckerDelta
import scipy.integrate as sp
import matplotlib.pyplot as plt
import numpy as np
def dy_dt(y,t):
dy_dt = 500*KroneckerDelta(t,t1) - 2y
return dy_dt
t1 = 4
y0 = 500
t = np.arrange(0,10,0.1)
y = sp.odeint(dy_dt,y0,t)
plt.plot(t,y)
In the case of a simple Kronecker delta using time, you can run the ode in pieces like so:
from scipy.integrate import odeint
import matplotlib.pyplot as plt
import numpy as np
def dy_dt(y,t):
return -2*y
t_delta = 4
tend = 10
y0 = [500]
t1 = np.linspace(0,t_delta,50)
y1 = odeint(dy_dt,y0,t1)
y0 = y1[-1] + 500 # execute Kronecker delta
t2 = np.linspace(t_delta,tend,50)
y2 = odeint(dy_dt,y0,t2)
t = np.append(t1, t2)
y = np.append(y1, y2)
plt.plot(t,y)
Another option for complicated situations is to the events functionality of solve_ivp.
I think the problem could be internal rounding errors, because 0.1 cannot be represented exactly as a python float. I would try
import math
def dy_dt(y,t):
if math.isclose(t, t1):
return 500 - 2*y
else:
return -2y
Also the documentation of odeint suggests using the args parameter instead of global variables to give your derivative function access to additional arguments and replacing np.arange by np.linspace:
import scipy.integrate as sp
import matplotlib.pyplot as plt
import numpy as np
import math
def dy_dt(y, t, t1):
if math.isclose(t, t1):
return 500 - 2*y
else:
return -2*y
t1 = 4
y0 = 500
t = np.linspace(0, 10, num=101)
y = sp.odeint(dy_dt, y0, t, args=(t1,))
plt.plot(t, y)
I did not test the code so tell me if there is anything wrong with it.
EDIT:
When testing my code I took a look at the t values for which dy_dt is evaluated. I noticed that odeint does not only use the t values that where specified, but alters them slightly:
...
3.6636447422787928
3.743098503914526
3.822552265550259
3.902006027185992
3.991829287543431
4.08165254790087
4.171475808258308
...
Now using my method, we get
math.isclose(3.991829287543431, 4) # False
because the default tolerance is set to a relative error of at most 10^(-9), so the odeint function "misses" the bump of the derivative at 4. Luckily, we can fix that by specifying a higher error threshold:
def dy_dt(y, t, t1):
if math.isclose(t, t1, abs_tol=0.01):
return 500 - 2*y
else:
return -2*y
Now dy_dt is very high for all values between 3.99 and 4.01. It is possible to make this range smaller if the num argument of linspace is increased.
TL;DR
Your problem is not a problem of python but a problem of numerically solving an differential equation: You need to alter your derivative for an interval of sufficient length, otherwise the solver will likely miss the interesting spot. A kronecker delta does not work with numeric approaches to solving ODEs.

How to integrate coupled differential equations?

I've got a system of equations that I've been tryin to get Python to solve and plot but the plot is not coming out right.
This is my code:
from scipy.integrate import odeint
import numpy as np
import matplotlib.pyplot as plt
#function that returns dx/dt and dy/dt
def func(z,t):
for r in range(-10,10):
beta=2
gamma=0.8
c = z[0]
tau = z[1]
dcdt = r*c+c**2-c**3-beta*c*tau**2
dtaudt = -gamma*tau+0.5*beta*c*tau
return [dcdt,dtaudt]
#inital conditions
z0 = [2,0]
#time points
t = np.linspace(0,24,100)
#solve ODE
z = odeint(func,z0,t)
#seperating answers out
c = z[:,0]
tau = z[:,1]
print(z)
#plot results
plt.plot(t,c,'r-')
plt.plot(t,tau,'b--')
plt.legend(['c(t)','tau(t)'])
plt.show()
Let me explain. I am studying doubly diffusive convection. I din't want any assumptions to be made on the value of r, but beta and gamma are positive. So I thougt to assign values to them but not to r.
This is the plot I get and from understanding the problem, that the graph is not right. The tau plot should efinitely not be stuck on 0 and the c plot should be doing more. I am relitively new to Python and am taking courses but really want to understand what I've done wrong, so help in a simple language would be appreciated.
I see 2 problems in your function that you should check.
for r in range(-10,10):
Here you are doing a for loop just reevaluating dcdt and dtaudt. As a result, the output value is the same as just evaluating r=9 (last value in the loop)
dtaudt = -gamma*tau+0.5*beta*c*tau
Here you have dtaudt = tau*(beta*c/2. -gamma). Your choice tau[0]=0 implies that tau will remain 0.
Try this:
from scipy.integrate import odeint
import numpy as np
import matplotlib.pyplot as plt
r = 1
beta=2
gamma=0.8
#function that returns dx/dt and dy/dt
def func(z,t):
c = z[0]
tau = z[1]
dcdt = r*c+c**2-c**3-beta*c*tau**2
dtaudt = -gamma*tau+0.5*beta*c*tau
print(dtaudt)
return [dcdt,dtaudt]
#inital conditions
z0 = [2,0.2] #tau[0] =!0.0
#time points
t = np.linspace(0,24,100)
#solve ODE
z = odeint(func,z0,t)
#seperating answers out
c = z[:,0]
tau = z[:,1]
#plot results
plt.plot(t,c,'r-')
plt.plot(t,tau,'b--')
plt.legend(['c(t)','tau(t)'])
plt.show()

What am I doing wrong in this Dopri5 implementation

I am totally new to python, and try to integrate following ode:
$\dot{x} = -2x-y^2$
$\dot{y} = -y-x^2
This results in an array with everything 0 though
What am I doing wrong? It is mostly copied code, and with another, not coupled ode it worked fine.
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import ode
def fun(t, z):
"""
Right hand side of the differential equations
dx/dt = -omega * y
dy/dt = omega * x
"""
x, y = z
f = [-2*x-y**2, -y-x**2]
return f
# Create an `ode` instance to solve the system of differential
# equations defined by `fun`, and set the solver method to 'dop853'.
solver = ode(fun)
solver.set_integrator('dopri5')
# Set the initial value z(0) = z0.
t0 = 0.0
z0 = [0, 0]
solver.set_initial_value(z0, t0)
# Create the array `t` of time values at which to compute
# the solution, and create an array to hold the solution.
# Put the initial value in the solution array.
t1 = 2.5
N = 75
t = np.linspace(t0, t1, N)
sol = np.empty((N, 2))
sol[0] = z0
# Repeatedly call the `integrate` method to advance the
# solution to time t[k], and save the solution in sol[k].
k = 1
while solver.successful() and solver.t < t1:
solver.integrate(t[k])
sol[k] = solver.y
k += 1
# Plot the solution...
plt.plot(t, sol[:,0], label='x')
plt.plot(t, sol[:,1], label='y')
plt.xlabel('t')
plt.grid(True)
plt.legend()
plt.show()
Your initial state (z0) is [0,0]. The time derivative (fun) for this initial state is also [0,0]. Hence, for this initial condition, [0,0] is the correct solution for all times.
If you change your initial condition to some other value, you should observe more interesting result.

Categories