Related
How can I solve this MATLAB ode problem using python
This is the IVP with the BCs:
F'''+FF''-F'^2+1=0
F(0)=F'(0)=0, F'(oo)=1
The current matlab code will generate the following plot
and it is identical to the textbook solution:
The problem is that I need to recode the same problem using python
% stagnation flow
clc; close all; clear all; clf;
tol=1e-3;
x=1; % f''(0)
dx=0.1;
Xf=3;
tspan=(0:dx:Xf);
Nt=Xf/dx+1;
for i=1:10000
iter=i;
x=x+0.0001;
F = #(t,y)[-y(1)*y(3)-1+y(2)^2;y(1);y(2)];
yo=[x;0;0];
[t,y]= ode45(F,tspan,yo);
y2=(y(Nt,2));
% x=x/(y2^(3/2)) % f''())=f''(0)/[f'(inf)^(3/2)]
if abs(y(Nt,2)-1.0)<tol, break, end
end
y2=(y(Nt,2));
% x=x/(y2^(3/2)) % f''())=f''(0)/[f'(inf)^(3/2)]
figure(1)
plot(t,y(:,1),t,y(:,2),t,y(:,3))
legend('fpp','fp','f');
xlabel('η=y(B/v)^2');
ylabel('f,fp,fpp');
title('Numerical solutions for stagnation flow')
fprintf ('η \t\t fp \n')
fprintf ('%.2f \t\t%.6f \n',t,y(:,2))
I have tried to code the same problem using python but I couldn't find any tutorial about this matter.
If the task was just to solve the mathematical problem, one could say you already "did it wrong" in Matlab (in the sense of using a too expensive method). As you want to solve a boundary value problem, you should use the dedicated BVP solver bvp4c, see the Matlab documentation on how to.
Even if the task was to implement a single-shooting approach, the root-finding procedure should be upgraded to some method with a convergence order, even bisection should be faster than the linear search. The secant method with the unknown second derivate starting at 1, 1.1 seems also to work well.
In python there is also a BVP solver that works well if it works.
import numpy as np
from scipy.integrate import solve_bvp
import matplotlib.pyplot as plt
x0,xf = 0,3
F = lambda t,y: [-y[0]*y[2]-1+y[1]**2,y[0],y[1]]
bc = lambda y0,yf: [ y0[1], y0[2], yf[1]-1]
res = solve_bvp(F,bc,[x0,xf], [[0,0],[1,1],[0,xf-1]])
print(f"y''(0)={res.y[0,0]}")
x = np.linspace(x0,xf,150)
plt.plot(x,res.sol(x).T)
plt.plot(res.x,res.y.T,'x', ms=2)
plt.legend(["y''", "y'", "y"])
plt.grid(); plt.show()
resulting in the plot
This initial guess also still works with xf=20, but fails for xf=30, this probably needs a more detailed initial guess with a short initial curve and a long linear part.
For larger xf the following intialization seems to work well
x = [0., 1.]
while x[-1] < xf: x.append(x[-1]*1.5)
xf = x[-1]
x = np.asarray(x); x[0]=0
y0 = x-x0-1; y0[0]=0
y1 = 0*x+1; y1[0]=0
y2 = 0*x; y2[0]=1
res = solve_bvp(F,bc,x, [y2,y1,y0], tol=1e-8)
Try this to make your matlab code run faster under the same logic of linear search with ODE45. I agree it's too expensive.
clc; close all; clear all; clf;
flow = #(t, F) [-F(1)*F(3)-1+F(2)^2; F(1); F(2)];
tol = 1e-3;
x = 1;
y2 = 0;
while abs(y2-1) >= tol
[t, y] = ode45(flow, [0,3], [x;0;0]);
x += 0.0001;
y2 = y(end, 2);
end
plot(t, y)
Here is an implementation in python
import numpy as np
from scipy.integrate import solve_ivp
import matplotlib.pyplot as plt
def flow(t, F):
return [-F[0]*F[2]-1+F[1]**2, F[0], F[1]]
tol = 1E-3
x = 1
y2 = 0
while np.abs(y2-1) >= tol:
sol = solve_ivp(flow, [0,3], [x,0,0])
x += 0.0001
y2 = sol.y[1, -1]
plt.plot(sol.t, sol.y.T)
plt.legend([r"$F^{\prime \prime} \propto \tau $",r"$F^\prime \propto u$", r"$F \propto \Psi$"])
plt.axis([0, 3, 0, 2])
plt.xlabel(r'$\eta = y \sqrt{\frac{B}{v}}$')
And here is an implementation where the response is proportional to the error, which runs faster.
clc; close all; clear all; clf;
flow = #(t, F) [-F(1)*F(3)-1+F(2)^2; F(1); F(2)];
tol = 1e-3;
x = 1;
error = 1;
while abs(error) >= tol
[t, y] = ode45(flow, [0,3], [x;0;0]);
y2 = y(end, 2);
error = y2 - 1;
x -= 0.1*error;
end
plot(t, y)
import numpy as np
from scipy.integrate import solve_ivp
import matplotlib.pyplot as plt
def flow(t, F):
return [-F[0]*F[2]-1+F[1]**2, F[0], F[1]]
tol = 1E-3
x = 1
error = 1
while np.abs(error) >= tol:
sol = solve_ivp(flow, [0,3], [x,0,0])
y2 = sol.y[1, -1]
error = y2 - 1
x -= 0.1 * error
plt.plot(sol.t, sol.y.T)
plt.legend([r"$F^{\prime \prime} \propto \tau $",r"$F^\prime \propto u$", r"$F \propto \Psi$"])
plt.axis([0, 3, 0, 2])
plt.xlabel(r'$\eta = y \sqrt{\frac{B}{v}}$')
I am experimenting with RK45/RK23 solver from python scipy module. Using it to solve simple ODEs and it is not giving me the correct results. When I manually code for Runge Kutta 4th order it works perfectly so does the odeint solver in the module but RK23/RK45 does not. If someone could aid me in figuring out the issue it would be help full. I have so far implemented only simple ODEs
dydt = -K*(y^2)
Code:
import numpy as np
from scipy.integrate import solve_ivp,RK45,odeint
import matplotlib.pyplot as plt
# function model fun(y,t)
def model(y,t):
k = 0.3
dydt = -k*(y**2)
return dydt
# intial condition
y0 = np.array([0.5])
y = np.zeros(100)
t = np.linspace(0,20)
t_span = [0,20]
#RK45 implementation
yr = solve_ivp(fun=model,t_span=t_span,y0=y,t_eval=t,method=RK45)
##odeint solver
yy = odeint(func=model,y0=y0,t=t)
##manual implementation
t1 = 0
h = 0.05
y = np.zeros(21)
y[0]=y0;
i=0
k=0.3
##Runge Kutta 4th order implementation
while (t1<1.):
m1 = -k*(y[i]**2)
y1 = y[i]+ m1*h/2
m2 = -k*(y1**2)
y2 = y1 + m2*h/2
m3 = -k*(y2**2)
y3 = y2 + m3*h/2
m4 = -k*(y3**2)
i=i+1
y[i] = y[i-1] + (m1 + 2*m2 + 2*m3 + m4)/6
t1 = t1 + h
#plotting
t2 = np.linspace(0,20,num=21)
plt.plot(t2,y,'r-',label='RK4')
plt.plot(t,yy,'b--',label='odeint')
#plt.plot(t2,yr.y[0],'g:',label='RK45')
plt.xlabel('time')
plt.ylabel('y(t)')
plt.legend()
plt.show()
Output: (without showing RK45 result)
enter image description here
Output: (with just RK45 plot displayed)
enter image description here
I cant find out where am I making a mistake
Okay I have figured out the solution. RK45 requires function definition to be like fun(t,y) and odeint requires it to be func(y,t) due to which giving them same function will result in different results.
I am solving a set of coupled ODEs using solve_bvp in python. I have solved the equations for the boundary conditions that U = 0 and B = 0 on the boundaries, however I am trying to solve them such that U' = 0 and B = 0 on the boundary. My problem is that I keep encountering a singular jacobian in the return message - my guess is that the initial guess is diverging, however I have tried a range of initial guesses and still no solution. Is there a more systematic way of figuring this out? My code is below:
import numpy as np
from scipy import integrate
from scipy.integrate import solve_bvp
import matplotlib.pyplot as plt
%matplotlib inline
x = np.linspace(-0.5, 0.5, 1000)
y = np.ones((4, x.size))
y[0] = U_0*np.tanh(Q*x)
U_0 = 0.1
Q = 30
## Then we calculate the total energy
def FullSolver(x,y,Ha, Rm):
def fun(x, y):
U, dU, B, dB = y;
d2U = -2*U_0*Q**2*(1/np.cosh(Q*x))**2*np.tanh(Q*x)-((Ha**2)/(Rm))*dB;
d2B = -Rm*dU;
return dU, d2U, dB, d2B
def bc(ya, yb):
return ya[1], yb[1], ya[2]-0, yb[2]+0
# sol will give us the solutions which are accessible if we need them
sol = solve_bvp(fun, bc, x, y, tol=1e-12)
return(sol.x, sol.yp[0], sol.y[2], sol.message)
FullSolver(x, y, 0.000005, 0.00009)
I'm transitioning my code from using scipy's odeint to scipy's solve_ivp. When using odeint I would use a while loop as follows:
while solver.successful() :
solver.integrate(t_final, step=True)
# do other operations
This method allowed me to store values that depended on the solutions after each timestep.
I'm now switching to using solve_ivp but not sure how to accomplish this functionality with the solve_ivp solver. Has anyone accomplished this functionality with solve_ivp?
Thanks!
I think i know what your trying to ask. I had a program that used solve_ivp to integrate between each time step individually, then used the values to calculate the values for the next iteration. (i.e. heat transfer coefficients, transport coefficients etc.) I used two nested for loops. The inner for loop calculates or completes the operations you need to at each step. Then save each value in a list or array and then the inner loop should terminate. The outer loop should only be used to feed time values and possibly reload necessary constants.
For example:
for i in range(start_value, end_value, time_step):
start_time = i
end_time = i + time_step
# load initial values and used most recent values
for j in range(0, 1, 1):
answer = solve_ivp(function,(start_time,end_time), [initial_values])
# Save new values at the end of a list storing all calculated values
Say you have a system such as
d(Y1)/dt = a1*Y2 + Y1
d(Y2)/dt = a2*Y1 + Y2
and you want to solve it from t = 0, 10. With a 0.1 time step. Where a1 and a2 are values calculated or determined elsewhere. This code would work.
from scipy.integrate import solve_ivp
import sympy as sp
import numpy as np
import math
import matplotlib.pyplot as plt
def a1(n):
return 1E-10*math.exp(n)
def a2(n):
return 2E-10*math.exp(n)
def rhs(t,y, *args):
a1, a2 = args
return [a1*y[1] + y[0],a2*y[0] + y[1]]
Y1 = [0.02]
Y2 = [0.01]
A1 = []
A2 = []
endtime = 10
time_step = 0.1
times = np.linspace(0,endtime, int(endtime/time_step)+1)
tsymb = sp.symbols('t')
ysymb = sp.symbols('y')
for i in range(0,endtime,1):
for j in range(0,int(1/time_step),1):
tstart = i + j*time_step
tend = i + j*time_step + time_step
A1.append(a1(tstart/100))
A2.append(a2(tstart/100))
Y0 = [Y1[-1],Y2[-1]]
args = [A1[-1],A2[-1]]
answer = solve_ivp(lambda tsymb, ysymb : rhs(tsymb,ysymb, *args), (tstart,tend), Y0)
Y1.append(answer.y[0][-1])
Y2.append(answer.y[1][-1])
fig = plt.figure()
plt1 = plt.plot(times,Y1, label = "Y1")
plt2 = plt.plot(times,Y2, label = "Y2")
plt.xlabel('Time')
plt.ylabel('Y Values')
plt.legend()
plt.grid()
plt.show()
The goal is to plot two identical dynamical systems that are coupled.
We have:
X = [x0,x1,x2]
U = [u0,u1,u2]
And
Xdot = f(X) + alpha*(U-X)
Udot = f(U) + alpha*(X-U)
So I wish to plot the solution to this grand system on one set of axes (i.e in xyz for example) and eventually change the coupling strength to investigate the behaviour.
import matplotlib.pyplot as plt
from scipy.integrate import odeint
from mpl_toolkits.mplot3d import Axes3D
def couple(s,t,a=0.2,beta=0.2,gamma=5.7,alpha=0.03):
[x,u] = s
[u0,u1,u2] = u
[x0,x1,x2] = x
xdot = np.zeros(3)
xdot[0] = -x1-x2
xdot[1] = x0+a*x1
xdot[2] = beta + x2*(x0-gamma)
udot = np.zeros(3)
udot[0] = -u1-u2
udot[1] = u0+a*u1
udot[2] = beta + u2*(u0-gamma)
sdot = np.zeros(2)
sdot[0] = xdot + alpha*(u-x)
sdot[1] = udot + alpha*(x-u)
return sdot
s_init = [0.1,0.1]
t_init=0; t_final = 300; t_step = 0.01
tpoints = np.arange(t_init,t_final,t_step)
a=0.2; beta=0.2; gamma=5.7; alpha=0.03
y = odeint(couple, s_init, tpoints,args=(a,beta,gamma,alpha), hmax = 0.01)
I imagine that something is wrong with s_init since it should be TWO initial condition vectors but when I try that I get that "odeint: y0 should be one-dimensional." On the other hand when I try s_init to be a 6-vector I get "too many values to unpack (expected two)." With the current setup, I am getting the error
File "C:/Users/Python Scripts/dynsys2019work.py", line 88, in couple
[u0,u1,u2] = u
TypeError: cannot unpack non-iterable numpy.float64 object
Cheers
*Edit: Please note this is basically my first time attempting this kind of thing and will be happy to receive further documentation and references.
The ode definition takes in and returns a 1D vector in scipy odeint, and I think some of your confusion is that you actually have 1 system of ODEs with 6 variables. You have just mentally apportioned it into 2 separate ODEs that are coupled.
You can do it like this:
import matplotlib.pyplot as plt
from scipy.integrate import odeint
import numpy as np
def couple(s,t,a=0.2,beta=0.2,gamma=5.7,alpha=0.03):
x0, x1, x2, u0, u1, u2 = s
xdot = np.zeros(3)
xdot[0] = -x1-x2
xdot[1] = x0+a*x1
xdot[2] = beta + x2*(x0-gamma)
udot = np.zeros(3)
udot[0] = -u1-u2
udot[1] = u0+a*u1
udot[2] = beta + u2*(u0-gamma)
return np.ravel([xdot, udot])
s_init = [0.1,0.1, 0.1, 0.1, 0.1, 0.1]
t_init=0; t_final = 300; t_step = 0.01
tpoints = np.arange(t_init,t_final,t_step)
a=0.2; beta=0.2; gamma=5.7; alpha=0.03
y = odeint(couple, s_init, tpoints,args=(a,beta,gamma,alpha), hmax = 0.01)
plt.plot(tpoints,y[:,0])