Changing order of DE in python (scipy.integrate.odeint) - python

I am facing a problem with differential equation (python):
This is my DE:
m*x"(t) + d*x'(t) + k*x(t) = y(t)
Where y(t) = Y * sin(w*t)
Data:
m=3
d=79
k=200000
w=152
Y=0.05
t=np.linspace(t_0,t_1, n) # not that important.
I have to get numpy.array of x(t) using scipy.integrate.odeint and i am having really big issues transforming my DE of 2. order to DE of 1. order.
What i am looking for is func in scipy.integrate.odeint
https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.odeint.html
based on my equations. And primarily and need to find np.array of x(t), then x'(t) and x''(t).

The usual procedure is to set v=x' so that then
x' = v
v' = ( y(t) - d*v - k*x) / m
or
def derivs(u,t):
x,v = u
return [ v, ( y(t) - d*v - k*x) / m ]
def y(t): return Y*np.sin(w*t)
t_0, t_1, n = 0, 1, 501
t=np.linspace(t_0,t_1, n)
x0, v0 = 0.0, 0.0
u0 = [ x0, v0 ]
u = odeint(derivs, u0, t)
plt.subplot(2,1,1); plt.title("x(t)"); plt.plot(t, u[:,0])
plt.subplot(2,1,2); plt.title("x'(t)"); plt.plot(t, u[:,1])
plt.show()
with the result

Related

CFD simulation (with multiple for loops and matrix operations) is very slow to run. Looking to replace with faster numpy functions (or alternative)

As mentioned above, the function below works, however its very slow. I am very interested in using faster/optimised numpy (or other) vectorized alternatives. I have not posted the entire script here due to it being too large.
My specific question is - are there suitable numpy (or other) functions that I can use to 1) reduce run time and 2) reduce code volume of this function, specifically the for loop?
Edit: mass, temp, U and dpdh are functions that carry out simple algebraic calculations and return constants
def my_system(t, y, n, hIn, min, mAlumina, cpAlumina, sa, V):
dydt = np.zeros(3 * n) #setting up zeros array for solution (solving for [H0,Ts0,m0,H1,Ts1,m1,H2,Ts2,m2,..Hn,Tsn,mn])
# y = [h_0, Ts_0, m_0, ... h_n, Ts_n, m_n]
# y[0] = hin
# y[1] = Ts0
# y[2] = minL
i=0
## Using thermo
T = temp(y[i],P) #initial T
m = mass(y[i],P) #initial m
#initial values
dydt[i] = (min * (hIn - y[i]) + (U(hIn,P,min) * sa * (y[i + 1] - T))) / m # dH/dt (eq. 2)
dydt[i + 1] = -(U(hIn,P,min) * sa * (y[i + 1] - T)) / (mAlumina * cpAlumina) # dTs/dt from eq.3
dmdt = dydt[i] * dpdh(y[i], P) * V # dm/dt (holdup variation) eq. 4b
dydt[i + 2] = min - dmdt # mass flow out (eq.4a)
for i in range(3, 3 * n, 3): #starting at index 3, and incrementing by 3 because we are solving for 'triplets' [h,Ts,m] in each loop
## Using thermo
T = temp(y[i],P)
m = mass(y[i],P)
# [h, TS, mdot]
dydt[i] = (dydt[i-1] * (y[i - 3] - y[i]) + (U(y[i-3], P, dydt[i-1]) * sa * (y[i + 1] - T))) / m # dH/dt (eq.2), dydt[i-1] is the mass of the previous tank
dydt[i + 1] = -(U(y[i-3], P, dydt[i-1]) * sa * (y[i + 1] - T)) / (mAlumina * cpAlumina) # dTs/dt eq. (3)
dmdt = dydt[i] * dpdh(y[i], P) * V # Equation 4b
dydt[i + 2] = dydt[i-1] - dmdt # Equation 4a
return dydt
The functions mass, temp, U, and dpdh used inside the my_system function all take numbers as input, perform some simple algebraic operation and return a number (no need to optimise these I am just providing them for further context)
def temp(H,P):
# returns temperature given enthalpy (after processing function)
T = flasher.flash(H=H, P=P, zs=zs, retry=True).T
return T
def mass(H, P):
# returns mass holdup in mol
m = flasher.flash(H=H, P=P, zs=zs, retry=True).rho()*V
return m
def dpdh(H, P):
res = flasher.flash(H=H, P=P, zs=zs, retry=True)
if res.phase_count == 1:
if res.phase == 'L':
drho_dTf = res.liquid0.drho_dT()
else:
drho_dTf = res.gas.drho_dT()
else:
drho_dTf = res.bulk._equilibrium_derivative(of='rho', wrt='T', const='P')
dpdh = drho_dTf/res.dH_dT_P()
return dpdh
def U(H,P,m):
# Given T, P, m
air = Mixture(['nitrogen', 'oxygen'], Vfgs=[0.79, 0.21], H=H, P=P)
mu = air.mu*1000/mWAir #mol/m.s
cp = air.Cpm #J/mol.K
kg = air.k #W/m.K
g0 = m/areaBed #mol/m2.s
a = sa*n/vTotal #m^2/m^3 #QUESTIONABLE
psi = 1
beta = 10
pr = (mu*cp)/kg
re = (6*g0)/(a*mu*psi)
hfs = ((2.19*(re**1/3)) + (0.78*(re**0.619)))*(pr**1/3)*(kg)/diameterParticle
h = 1/((1/hfs) + ((diameterParticle/beta)/kAlumina))
return h
Reference Image:
enter image description here
For improving the speed, you can see Numba, which is useable if you use NumPy a lot but not every code can be used with Numba. Apart from that, the formulation of the equation system is confusing. You are solving 3 equations and adding the result to a single dydt list by 3 elements each. You can simply create three lists, solve each equation and add them to their respective list. For this, you need to re-write my_system as:
import numpy as np
def my_system(t, RHS, hIn, Ts0, minL, mAlumina, cpAlumina, sa, V):
# get initial boundary condition values
y1 = RHS[0]
y2 = RHS[1]
y3 = RHS[2]
## Using thermo
T = # calculate T
m = # calculate m
# [h, TS, mdot] solve dy1dt for h, dy2dt for TS and dy3dt for mdot
dy1dt = # dH/dt (eq.2), y1 corresponds to initial or previous value of dy1dt
dy2dt = # dTs/dt eq. (3), y2 corresponds to initial or previous value of dy2dt
dmdt = # Equation 4b
dy3dt = # Equation 4a, y3 corresponds to initial or previous value of dy3dt
# Left-hand side of ODE
LHS = np.zeros([3,])
LHS[0] = dy1dt
LHS[1] = dy2dt
LHS[2] = dy3dt
return LHS
In this function, you can pass RHS as a list with initial values ([dy1dt, dy2dt, dy3dt]) which will be unpacked as y1, y2, and y3 respectively and use them for respective differential equations. The solved equations (next values) will be saved to dy1dt, dy2dt, and dy3dt which will be returned as a list LHS.
Now you can solve this using scipy.integrate.odeint. Therefore, you can leave the for loop structure and solve the equations by using this method as follows:
hIn = #some val
Ts0 = #some val
minL = #some val
mAlumina = #some vaL
cpAlumina = #some val
sa = #some val
V = #some val
P = #some val
## Using thermo
T = temp(hIn,P) #initial T
m = mass(hIn,P) #initial m
#initial values
y01 = # calculate dH/dt (eq. 2)
y02 = # calculate dTs/dt from eq.3
dmdt = # calculate dm/dt (holdup variation) eq. 4b
y03 = # calculatemass flow out (eq.4a)
n = # time till where you want to solve the equation system
y0 = [y01, y02, y03]
step_size = 1
t = np.linspace(0, n, int(n/step_size)) # use that start time to which initial values corresponds
res = odeint(my_sytem, y0, t, args=(hIn, Ts0, minL, mAlumina, cpAlumina, sa, V,), tfirst=True)
print(res[:,0]) # print results for dH/dt
print(res[:,1]) # print results for dTs/dt
print(res[:,2]) # print results for Equation 4a
Here, I have passed all the initial values as y0 and chosen a step size of 1 which you can change as per your need.

Different behaviour between MATLAB fmincon and scipy optimize minimize

I'm translating some code from MATLAB to python. This code simulate the behaviour of a model and I want to estimate parameters from it. The problem is that results obtained with python and with MATLAB are very different. I've tought it was related to the difference between the MATLAB's
fmincon and the python scipy.optimize.minimize function, but according to this tutorial that I've found on youtube (https://www.youtube.com/watch?v=SwogAa1719M) the results are almost the same,so problems must be in my code but I can't find them.
I report a minimum example of my code
def cost_function(parameters, measured, t, x0, total_active):
Tp = simulate(parameters, t, x0, total_active)
measured = np.squeeze(measured)
Tp = Tp[:,2]
return (np.sum(np.power((np.divide(np.diff(measured), measured[1:])-np.divide(np.diff(Tp),Tp[1:])),2)))
def SIR_model(x, t, params, total_active):
S0, _, R0 = x
v, tau, I0 = params
dSdt = - v * S0 * I0 / total_active(t)
dIdt = v * S0 * I0 / total_active(t) - g * I0 - tau * I0
dCdt = tau * I0
return [dSdt, dIdt, dCdt]
def simulate(p, t, x0, total_active):
T = np.zeros((len(t), 3))
T[0, :] = x0
for i in range(len(t) - 1):
ts = [t[i], t[i + 1]]
y = odeint(SIR_model, x0, ts, args=(p, total_active))
x0 = y[-1]
T[i + 1, :] = x0
return T
def identify_model(data, initial_guess, t, N, total_active):
# Set initial condition of the model
x0 = []
Q0 = data[0]
x0.append(N - initial_guess['It0'] - Q0)
x0.append(initial_guess['It0'])
x0.append(Q0)
It0 = initial_guess['It0']
v = initial_guess['v']
tau = initial_guess['tau']
lim_sup = [v * 10, tau * 1.5, It0 * 1.3]
lim_inf = [v * 0, tau * 0.9, It0 * 0.7]
bounds = Bounds(lim_inf, lim_sup)
options = {"maxiter": 1000,"ftol": 1e-08}
return minimize(cost_function, np.asarray([initial_guess['v'], initial_guess['tau'],initial_guess['It0']]),args=(data, t, x0, total_active), bounds=bounds,options=options, tol=1e-08,method="SLSQP")['x']
data=[275.5,317.,457.33333333,646.,888.66666667,1236.66666667,1619.33333333,2077.33333333,2542.33333333]
times = [i for i in range(len(data))]
total_active_data=[59999725.,59999684.33333334,59999558.66666666,59999385.33333334,59999158.33333333,59998823.,59998474.66666666,59998053.33333333,59997652.66666666]
total_active = interp1d([i for i in range(len(total_active_data))], total_active_data, fill_value="extrapolate")
initial_guess = {"v": 0.97, "tau": 0.066, "It0": 100}
print(identify_model(data,initial_guess,times,60e6,total_active))
This snippet gives, as result, [0.97099097,0.099,130.].
The (I hope so) equivalent MATLAB code is:
function [pars] = Identify_Model(data,initial_guess,lim_inf,lim_sup,times,N,total_active)
scalefac=100;
%Initialize the initial guess for each parameter
v=initial_guess.v;
It0=initial_guess.It0;
tau=initial_guess.tau;
g=0.07;
lim_inf(3)=lim_inf(3)/scalefac;
lim_sup(3)=lim_sup(3)/scalefac;
%Identify the model parameters
options=optimset('MaxIter',100,'TolFun',1e-6,'TolX',1e-6);
parmin=fmincon(#(pars) error_SIR(data,[pars(1),pars(2),g,scalefac*pars(3)],times,N,total_active),[v,tau,It0],[],[],[],[],lim_inf,lim_sup,[],options);
pars=num2cell([parmin(1:2),scalefac*parmin(3)]);
pars=[pars(1),pars(2),g,pars(3)];
end
function [costo]=error_SIR(data,pars,tempi,N,totale_attivi)
%Assign the parameters
pars=num2cell(pars);
[v,tau,g,I0]=pars{:};
%Simulate the model
Q0=data(1,1);
S0=N-I0-Q0;
[~,y]=ode45(#(t,x) SIR(t,x,v,tau,g,totale_attivi),tempi,[S0;I0;Q0]);
if length(tempi)==2
y=[y(1,:);y(end,:)];
end
%Identify on the normalized data (Data/mean data)
costo=sum(((diff(sum(data,1))./sum(data(:,2:end),1))-(diff(sum(y(:,3),2))./sum(y(2:end,3),2))').^2);
end
function y=SIR(t,x,v,tau,g,total_active)
y=zeros(3,1);
y(1)=-v*x(1)*x(2)/total_active(t); % S
y(2)=v*x(1)*x(2)/total_active(t)-(tau+g)*x(2); % I
y(3)=tau*x(2); % C
end
total_active_data=[59999725.,59999684.333,59999558.666,59999385.333,59999158.33,59998823.,59998474.66,59998053.333,59997652.66666666]
total_active = #(t) interp1(1:9,total_active_data,t);
initial_guess.It0=100;
initial_guess.v=0.97;
initial_guess.g=0.07;
initial_guess.tau=0.066;
g=initial_guess.g;
It0=initial_guess.It0;
v=initial_guess.v;
tau=initial_guess.tau;
N=60e6
%Define the constrints for the identification
lim_sup=[v*10, tau*1.5, It0*1.3];
lim_inf=[v*0, tau*0.9, It0*0.7];
data=[275.5,317.,457.33333333,646.,888.66666667,1236.66666667,1619.33333333,2077.33333333,2542.33333333]
times=1:9;
%identify the model parameters
pars=Identify_Model(data,initial_guess,lim_inf,lim_sup,times,N,total_active)
And the result is {[0.643004812025865]} {[0.0989999761533351]} {[0.07]} {[129.9999687237]} (don't consider the value 0.07, it's fixed). I thought that the problem could linked to the fact that I want to minimize a non-convex function, and maybe fmincon is more powerful than the scipy.optimize.minimize function?

python COVID-19 SIRD model ODEs symfit not working

I want to fit some real data (of Italy) with SIRD odes using symfit but i cannot understand why my code is not working. I gave as initial value of susceptible a fixed value around 10^4 but when i plot the result they start to zero and go down
here the code:
n_susceptible = 4.13 * 10**4
infection_rate, recovery_rate, death_rate= parameters('infection_rate, recovery_rate, death_rate')
S, I, R, De, t= variables('S, I, R, De, t')
model_dict = {
D(S, t): -infection_rate * I * S ,
D(I, t): infection_rate* S * I- (recovery_rate + death_rate) * I,
D(R, t): recovery_rate * I,
D(De, t): death_rate * I
}
I0 = I_italy[20]
S0 = n_susceptible - I0
R0 = R_italy[20]
t0 = days[20]
D0 = D_italy[20]
ode_model = ODEModel(model_dict, initial={t : t0, De : D0, I : I0, R : R0 ,S : S0})
fit = Fit(ode_model ,De=D_italy,I=I_italy, R=R_italy, S=None,t=days)
fit_result = fit.execute()

Solving a boundary value problem DE in python

I am trying to solve the following set of DE's:
dx' = cos(a)
dy' = sin(a)
dF' = - b * x * cos(a) + sin(a)
da' = (b * x * sin(a) + cos(a)) / F
with the conditions:
x(0) = y(0) = x(1) = 0
y(1) = 0.6
F(0) = 0.38
a(0) = -0.5
I tried following a similar problem, but I just can't get it to work. Is it possible, that my F(0) and a(0) are completely off, I am not even sure about them.
import numpy as np
from scipy.integrate import solve_bvp
import matplotlib.pyplot as plt
beta = 5
def fun(x, y):
x, dx, y, dy, F, dF, a, da, = y;
dxds=np.cos(a)
dyds=np.sin(a)
dFds=-beta * x * np.cos(a) + np.sin(a)
dads=(beta * x * np.sin(a) + np.cos(a) ) / F
return dx, dxds, dy, dyds, dF, dFds, da, dads
def bc(ya, yb):
return ya[0], yb[0], ya[2], yb[2] + 0.6, ya[4] + 1, yb[4] + 1, ya[6], yb[6]
x = np.linspace(0, 0.5, 10)
y = np.zeros((8, x.size))
y[4] = 0.38
y[6] = 2.5
res = solve_bvp(fun, bc, x, y)
print(res.message)
x_plot = np.linspace(0, 0.5, 200)
plt.plot(x_plot, res.sol(x_plot)[0])
I think that you have foremost a physics problem, translating the physical situation into an ODE system.
x(s) and y(s) are the coordinates of the rope where s is the length along the rope. Consequently, (x'(s),y'(s)) is a unit vector that is uniquely characterized by its angle a(s), giving
x'(s) = cos(a(s))
y'(s) = sin(a(s))
To get the shape, one now has to consider the mechanics. The assumption seems to be that the rope rotates without spiraling around the rotation axis, staying in one plane. Additionally, from the equilibrium of forces you also get that the other two equations are indeed first order, not second order equations. So your state only has 4 components and the ODE system function thus has to be
def fun(s, u):
x, y, F, a = u;
dxds=np.cos(a)
dyds=np.sin(a)
dFds=-beta * x * np.cos(a) + np.sin(a)
dads=(beta * x * np.sin(a) + np.cos(a) ) / F
return dxds, dyds, dFds, dads
Now there are only 4 boundary condition slots available, which are the coordinates of the start and end of the rope.
def bc(ua, ub):
return ua[0], ub[0], ua[1], ub[1] - 0.6
Additionally, the interval length for s is also the rope length, so a value of 0.5 is impossible for the given coordinates on the pole, try 1.0. There is some experimentation needed to get an initial guess that does not lead to a singular Jacobian in the BVP solver. In the end I get the solution in the x-y plane
with the components

fmin_ncg not returning an optimized result

I am trying to use fmin_ncg for minimizing my cost function. But, the results that I get back are not minimized. I get the same result I would get without advanced optimization. I know for a fact that it can further be minimized.
PS. I am trying to code assignment 2 of the Coursera's ML course.
My cost fn:
def costFn(theta, X, y, m, lam):
h = sigmoid(X.dot(theta))
theta0 = theta
J = 1 / m * np.sum((-(y * np.log(h))) - ((1-y) * np.log(1-h))) + (lam/(2*m) * theta0.T.dot(theta0))
return J.flatten()
X would look something like this:
[[ 1.00000000e+00 5.12670000e-02 6.99560000e-01 ..., 6.29470940e-04
8.58939846e-03 1.17205992e-01]
[ 1.00000000e+00 -9.27420000e-02 6.84940000e-01 ..., 1.89305413e-03
-1.39810280e-02 1.03255971e-01]
[ 1.00000000e+00 -2.13710000e-01 6.92250000e-01 ..., 1.04882142e-02
-3.39734512e-02 1.10046893e-01]
...,
[ 1.00000000e+00 -4.84450000e-01 9.99270000e-01 ..., 2.34007252e-01
-4.82684337e-01 9.95627986e-01]
....
Y is a bunch of 0s and 1s
[[1]
[1]
[1]
[1]
...
[0]
[0]]
X.shape = (118, 28)
y.shape = (118, 1)
My grad function:
def grad(theta, X, y, m, lam):
h = sigmoid(X.dot(theta))
theta0 = initial_theta
gg = 1.0 / m * ((X.T.dot(h-y)) + (lam * theta0))
return gg.flatten()
Using just my costFn and grad, I get the following:
Cost at initial theta (zeros): 0.69314718056
With fmin_ncg:
xopt = fmin_ncg(costFn, fprime=grad, x0=initial_theta, args=(X, y, m, lam), maxiter=400, disp=True, full_output=True )
I get:
Optimization terminated successfully.
Current function value: 0.693147
Iterations: 1
Function evaluations: 2
Gradient evaluations: 4
Hessian evaluations: 0
Using octave, my J after advanced optimization should be:
0.52900
What am I doing wrong?
EDIT:
I got my optimization to work:
y1 = y.flatten()
Result = op.minimize(fun = costFn,
x0 = initial_theta,
args = (X, y1, m, lam),
method = 'CG',
options={'disp': True})
I get the costFn to be 0.52900, which is what I expected.
But the values of 'theta' are a bit off that the accuracy is only 42%. It's supposed to be 83%.
The values of theta I got:
[ 1.14227089 0.60130664 1.16707559 -1.87187892 -0.91534354 -1.26956697
0.12663015 -0.36875537 -0.34522652 -0.17363325 -1.42401493 -0.04872243
-0.60650726 -0.269242 -1.1631064 -0.24319088 -0.20711764 -0.04333854
-0.28026111 -0.28693582 -0.46918892 -1.03640373 0.02909611 -0.29266766
0.01725324 -0.32899144 -0.13795701 -0.93215664]
The actual values:
[1.273005 0.624876 1.177376 -2.020142 -0.912616 -1.429907 0.125668 -0.368551
-0.360033 -0.171068 -1.460894 -0.052499 -0.618889 -0.273745 -1.192301
-0.240993 -0.207934 -0.047224 -0.278327 -0.296602 -0.453957 -1.045511
0.026463 -0.294330 0.014381 -0.328703 -0.143796 -0.924883]
First of all your gradient is invalid
def grad(theta, X, y, m, lam):
h = sigmoid(X.dot(initial_theta))
theta0 = initial_theta
gg = 1 / m * ((X.T.dot(h-y)) + (lam * theta0))
return gg.flatten()
this function never uses theta, you put initial_theta instead, which is incorrect.
Similar error in the cost
def costFn(theta, X, y, m, lam):
h = sigmoid(X.dot(initial_theta))
theta0 = theta
J = 1 / m * np.sum((-(y * np.log(h))) - ((1-y) * np.log(1-h))) + (lam/(2*m) * theta0.T.dot(theta0))
return J.flatten()
you have some odd mix of theta and initial_theta, which also does not make sense, there should be only theta inside. As a side note - there should be no need for flattening, your cost function should be a scalar, thus if you have to flatten - something is wrong in your computations.
Also worth checking - what is your m? If it is an integer, and you are using python 2.X, then 1 / m equals zero, since it is integer division. You should do 1.0 / m instead. (in both functions)

Categories

Resources