Is there a way to speed up odeint in python - python

currently I am trying to think about how to speedup odeint for a simulation I am running. Actually its only a primitive second order ode with a friction term and a discontinuous force. The model I am using to describe the dynamics is defined in a seperate function. Trying to solve the ode results in an error or extremely high computation times (talking about days).
Here is my code mostly hardcoded:
import numpy as np
import pandas as pd
from scipy.integrate import odeint
import matplotlib.pyplot as plt
def create_ref(tspan):
if tspan>2 and tspan<8:
output = np.sin(tspan)
elif tspan>=20:
output = np.sin(tspan*10)
else:
output = 0.5
return output
def model(state,t):
def Fs(x):
pT = 0
pB = 150
p0 = 200
pA = 50
kein = 0.25
kaus = 0.25
if x<0:
pA = pT
Fsres = -kaus*x*pA-kein*x*(p0-pB)
else:
pB = pT
Fsres = -kaus*x*pB-kein*x*(p0-pA)
return Fsres
x,dx = state
refnow = np.interp(t,xref.index.values,xref.values.squeeze())
refprev = np.interp(t-dt,xref.index.values,xref.values.squeeze())
drefnow = (refnow-refprev)/dt
x_meas = x
dx_meas = dx
errnow = refnow-x_meas
errprev = refprev-(x_meas-dx_meas*dt)
intrefnow = dt*(errnow-errprev)
kp = 10
kd = 0.1
ki = 100
sigma = kp*(refnow-x_meas)+kd*(drefnow-dx_meas)+ki*intrefnow
tr0 = 5
FricRed = (1.5-0.5*np.tanh(0.1*(t-tr0)))
kpu = 300
fr = 0.1
m = 0.01
d = 0.01
k = 10
u = float(kpu*np.sqrt(np.abs(sigma))*np.sign(sigma))
ddx = 1/m*(Fs(x)+FricRed*fr*np.sign(dx)-d*dx-k*x + u )
return [dx,ddx]
dt = 1e-3
tspan = np.arange(start=0, stop=50, step=dt)
steplim = tspan[-1]*0.1
reffunc = np.vectorize(create_ref)
xrefvals = reffunc(tspan)
xref = pd.DataFrame(data=xrefvals,index=tspan,columns=['xref'])
x0 = [-0.5,0]
simresult = odeint(model, x0, tspan)
plt.figure(num=1)
plt.plot(tspan,simresult[:,0],label='ispos')
plt.plot(tspan,xref['xref'].values,label='despos')
plt.legend()
plt.show()
I change the code according to Pranav Hosangadi s comments. Thanks for the hints. I didn't know that and learned something new and didn't expect dictionaries to have such a high impact on computation time. But now its much faster.

Related

FTCS Solution of the Wave Equation - Issues with Vpython

I am attempting to make an animation of the motion of the piano string
using the facilities provided by the vpython package. There are
various ways you could do this, but my goal is to do this with using
the curve object within the vpython package. Below is my code for
solution of the initial problem of solving the complete sets of
simultaneous 1st-order equation. Thanks in advance, I am really
uncertain as to where to start with the vpython animation.
# Key Module and Function Import(s):
import numpy as np
import math as m
import pylab as py
import matplotlib
from time import time
import scipy
# Variable(s) and Constant(s):
L = 1.0 # Length on string in m
C = 1.0 # velocity of the hammer strike in ms^-1
d = 0.1 # Hammer distance from 0 to point of impact with string
N = 100 # Number of divisions in grid
sigma = 0.3 # sigma value in meters
a = L/N # Grid spacing
v = 100.0 # Initial velocity of wave on the string
h = 1e-6 # Time-step
epsilon = h/1000
# Computation(s):
def initialpsi(x):
return (C*x*(L-x)/(L**2))*m.exp((-(x-d)**2)/(2*sigma**2)) # Definition of the function
phibeg = 0.0 # Beginning - fixed point
phimiddle = 0.0 # Initial x
phiend = 0.0 # End fixed point
psibeg = 0.0 # Initial v at beg
psiend = 0.0 # Initial v at end
t2 = 2e-3 # string at 2ms
t50 = 50e-3 # string at 50ms
t100 = 100e-3 # string at 100ms
tend = t100 + epsilon
# Creation of empty array(s)
phi = np.empty(N+1,float)
phi[0] = phibeg
phi[N] = phiend
phi[1:N] = phimiddle
phip = np.empty(N+1,float)
phip[0] = phibeg
phip[N] = phiend
psi = np.empty(N+1,float)
psi[0] = psibeg
psi[N] = psiend
for i in range(1,N):
psi[i] = initialpsi(i*a)
psip = np.empty(N+1,float)
psip[0] = psibeg
psip[N] = psiend
# Main loop
t = 0.0
D = h*v**2 / (a*a)
timestart = time()
while t<tend:
# Calculation the new values of T
for i in range(1,N):
phip[i] = phi[i] + h*psi[i]
psip[i] = psi[i] + D*(phi[i+1]+phi[i-1]-2*phi[i])
phip[1:N] = phi[1:N] + h*psi[1:N]
psip[1:N] = psi[1:N] + D*(phi[0:N-1] + phi[2:N+1] -2*phi[1:N])
phi= np.copy(phip)
psi= np.copy(psip)
#phi,phip = phip,phi
#psi,psip = psip,psi
t += h
# Plot creation in step(s)
if abs(t-t2)<epsilon:
t2array = np.copy(phi)
py.plot(phi, label = "2 ms")
if abs(t-t50)<epsilon:
t50array = np.copy(phi)
py.plot(phi, label = "50 ms")
if abs(t-t100)<epsilon:
t100array = np.copy(phi)
py.plot(phi, label = "100 ms")
See the curve documentation at
https://www.glowscript.org/docs/VPythonDocs/curve.html
Use the "modify" method to change the individual points along the curve object, inside a loop that contains a rate statement:
https://www.glowscript.org/docs/VPythonDocs/rate.html

Having problems with scipy.integrate.solve_ivp

I am trying to use scipy.integrate.solve_ivp to calculate the solutions to newton's gravitation equation for my n body simulation, however I am confused how the function is passed into solve_ivp. I have the following code:
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import solve_ivp
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
G = 6.67408e-11
m_sun = 1988500e24
m_jupiter = 1898.13e24
m_earth = 5.97219e24
au = 149597870.700e3
v_factor = 1731460
year = 31557600.e0
init_s = np.array([-6.534087946884256E-03*au, 6.100454846284101E-03*au, 1.019968145073305E-04*au, -6.938967653087248E-06*v_factor, -5.599052606952444E-06*v_factor, 2.173251724105919E-07*v_factor])
init_j = np.array([2.932487231769548E+00*au, -4.163444383137574E+00*au, -4.833604407653648E-02*au, 6.076788230491844E-03*v_factor, 4.702729516645153E-03*v_factor, -1.554436340872727E-04*v_factor])
variables_s = init_s
variables_j = init_j
N = 2
tStart = 0e0
t_End = 25*year
Nt = 2000
dt = t_End/Nt
temp_end = dt
t=tStart
domain = (t, temp_end)
planetsinit = np.vstack((init_s, init_j))
planetspos = planetsinit[:,0:3]
mass = np.vstack((1988500e24, 1898.13e24))
def weird_division(n, d):
return n / d if d else 0
variables_save = np.zeros((N,6,Nt))
variables_save[:,:,0] = planetsinit
pos_s = planetspos[0]
pos_j = planetspos[1]
while t < t_End:
t_index = int(weird_division(t, dt))
for index in range(len(planetspos)):
for otherindex in range(len(planetspos)):
if index != otherindex:
x1_p1, x2_p1, x3_p1 = planetsinit[index, 0:3]
x1_p2, x2_p2, x3_p2 = planetsinit[otherindex, 0:3]
m = mass[otherindex]
def f_grav(t, y):
x1_p1, x2_p1, x3_p1, v1_p1, v2_p1, v3_p1 = y
x1_diff = x1_p1 - x1_p2
x2_diff = x2_p1 - x2_p2
x3_diff = x3_p1 - x3_p2
dydt = [v1_p1,
v2_p1,
v3_p1,
-(x1_diff)*G*m/((x1_diff)**2+(x2_diff)**2+(x3_diff)**2)**(3/2),
-(x2_diff)*G*m/((x1_diff)**2+(x2_diff)**2+(x3_diff)**2)**(3/2),
-(x3_diff)*G*m/((x1_diff)**2+(x2_diff)**2+(x3_diff)**2)**(3/2)]
return dydt
solution = solve_ivp(fun=f_grav, t_span=domain, y0=planetsinit[index])
planetsinit[index] = solution['y'][0:6, -1]
variables_save[index,:,t_index] = solution['y'][0:6, -1]
planetspos[index] = planetsinit[index][0:3]
t += dt
temp_end += dt
domain = (t,temp_end)
pos_s = variables_save[0,0:3,:]
pos_j = variables_save[1,0:3,:]
plt.plot(variables_save[0,0:3,:][0], variables_save[0,0:3,:][1])
plt.plot(variables_save[1,0:3,:][0], variables_save[1,0:3,:][1])
The code above works very nicely and produces a stable orbit. However when I calculate the acceleration outside the function and feed that through into the f_grav function, something goes wrong and produces an orbit which is no longer stable. However I am perplexed as I don't know why the data is different as to be it seems like that I have passed through the exactly same inputs. Which leads me to think that maybe its the way the the function f_grav is interpolated by the solve_ivp integrator? To calculate the acceleration outside all I do is change the following code in the loop to:
x1_p1, x2_p1, x3_p1 = planetsinit[index, 0:3]
x1_p2, x2_p2, x3_p2 = planetsinit[otherindex, 0:3]
m = mass[otherindex]
x1_diff = x1_p1 - x1_p2
x2_diff = x2_p1 - x2_p2
x3_diff = x3_p1 - x3_p2
ax = -(x1_diff)*G*m/((x1_diff)**2+(x2_diff)**2+(x3_diff)**2)**(3/2)
ay = -(x2_diff)*G*m/((x1_diff)**2+(x2_diff)**2+(x3_diff)**2)**(3/2)
az = -(x3_diff)*G*m/((x1_diff)**2+(x2_diff)**2+(x3_diff)**2)**(3/2)
def f_grav(t, y):
x1_p1, x2_p1, x3_p1, v1_p1, v2_p1, v3_p1 = y
dydt = [v1_p1,
v2_p1,
v3_p1,
ax,
ay,
az]
return dydt
solution = solve_ivp(fun=f_grav, t_span=domain, y0=planetsinit[index])
planetsinit[index] = solution['y'][0:6, -1]
variables_save[index,:,t_index] = solution['y'][0:6, -1]
planetspos[index] = planetsinit[index][0:3]
As I said I don't know why different orbits are produces which are shown below and any hints as to why or how to solve it would me much appreciated. To clarify why I can't use the working code as it is, as when more bodies are involved I aim to sum the accelerations contribution of all the other planets which isn't possible this way where the acceleration is calculated in the function itself.
Sorry for the large coding chunks but I did feel it was appropriate as then it could be run and the problem itself is clearer.
Both have the same time period, dt, however the orbit on the left is stable and the one on the right is not

Extremely slow calculations

I am trying to write an equation that does the following things:
1) Integrates an equation
2) Stores that equation for later use
3) Numerically integrate the first and evaluate the 2nd equation on 100 different intervals, increasing by a fixed amount each time
import math
from sympy import *
import kvalues
import time
import random
import pandas as pd
import matplotlib.pyplot as plt
The first task is very simple, I completed it like so:
def integration_gas(number,Fa_0,Fb_0,Fc_0,v_0,a,b,c,d,e):
Ca_0 = Fa_0/v_0
Cb_0 = Fb_0/v_0
Cc_0 = Fc_0/v_0
Ft_0 = Fb_0 + Fa_0 + Fc_0
theta1 = Cb_0/Ca_0
stoic1 = b/a
theta2 = Cc_0/Ca_0
stoic2 = c/a
stoic3 = d/a
stoic4 = e/a
Cd = stoic3*x
Ce = stoic4*x
sigma = e+d-c-b-1
epsilon = (Fa_0/Ft_0)*sigma
Ca_eq = Ca_0*((1-x)/(1+epsilon*x))
Cb_eq = Ca_0*((1*theta1-stoic1*x)/(1+epsilon*x))
Cc_eq = Ca_0*((1*theta2-stoic2*x)/(1+epsilon*x))
ra = 1*(Ca_eq**a)*(Cb_eq**b)*(Cc_eq**c)*final_k[number-1]
equation = Fa_0/ra
int1 = Integral(equation,x)
pprint(int1)
evaluate = int1.doit()
pprint(evaluate)
return equation
This part of the code works perfectly fine, so on to the 2nd part.
def Ra_gas(number,Fa_0,Fb_0,Fc_0,v_0,a,b,c,d,e):
Ca_0 = Fa_0/v_0
Cb_0 = Fb_0/v_0
Cc_0 = Fc_0/v_0
Ft_0 = Fb_0 + Fa_0 + Fc_0
theta1 = Cb_0/Ca_0
stoic1 = b/a
theta2 = Cc_0/Ca_0
stoic2 = c/a
sigma = e+d-c-b-1
epsilon = (Fa_0/Ft_0)*sigma
Ca_eq = Ca_0*((1-x)/(1+epsilon*x))
Cb_eq = Ca_0*((1*theta1-stoic1*x)/(1+epsilon*x))
Cc_eq = Ca_0*((1*theta2-stoic2*x)/(1+epsilon*x))
ra = 1*(Ca_eq**a)*(Cb_eq**b)*(Cc_eq**c)*final_k[number-1]
pprint(ra)
return ra
This part of the code also works perfectly fine. So for the last part I have the following code:
Number = 4
FA0 = 10
FB0 = 25
FC0 = 5
V0 = 2
A = 1
B = 2
C = 0.5
D = 1
E = 1
Ra = []
volume = []
Xff = []
eq1 = integration_gas(Number,FA0,FB0,FC0,V0,A,B,C,D,E)
Ra1 = Ra_gas(Number,FA0,FB0,FC0,V0,A,B,C,D,E)
#print(Ra1)
Xf = 0.01
# Calculates the reaction rate and volume for every interval of conversion
while Xf <=1:
int2 = Integral(eq1,(x,0,Xf))
volume.append(int2.doit())
f = lambdify(x,Ra1,"math")
f(Xf)
Ra.append(f(Xf))
Xff.append(Xf)
Xf += 0.01
I then take the results and plot them. Everything i've written works perfectly fine for some situations and is completed in around 10~15 seconds. However, in situations like this one in particular, i've been running this code for 5+ hours with no solutions. How can I optimize this code?
Take a look at sympy, it can symbolically integrate your original equation and you then can evaluate it via numpy. For "real" maths Python is a bit slow, the scipy Stack (numpy, matplotlib, sympy...) is WAY faster.
Though 5+ hours is a bit long, are you sure that it actually executes?
EDIT: A simple thing to try
Sorry, Just now noticed you're lambdifying, you might want to include your imports so people see what you're using.
At the beginning:
import numpy as np
Let's go through this piece of your Code:
Xf = 0.01
while Xf <=1:
int2 = Integral(eq1,(x,0,Xf))
volume.append(int2.doit())
f = lambdify(x,Ra1,"math") #you're lambdifying each iteration that takes time
f(Xf) # no assignment here, unless you're doing something in place this line does nothing
Ra.append(f(Xf))
Xff.append(Xf)
Xf += 0.01
With something along the lines of this:
Xf = np.arange(0.01, 1.01, 0.01) #vector with values from 0.01 to 1 in steps of 0.01
f = np.vectorize(lambdify(x,Ra1,"math")) # you anonymous function but able to take np vectors/np arrays
Ra = f(Xf)
#Xff would be Xf

How to evaluate a numpy array inside an mpmath fuction?

I get an error when I try to use a numpy array inside mpmath function, this example fails when it reaches the line:
C = (f*L/D) + 2*mp.log(P1/P2)
Where P1 is an array.
With the error:
cannot create mpf from array([**P1_array**])
I'm aware of this and this treads, which are related. But I cant get my code to work. Can someone help me to correct this mistake?
import numpy as np
import mpmath as mp
mp.mp.dps = 20
# State equation --> pV = nZRT
P1 = np.linspace(101325,10*101325,100)
P2 = 101325
T = 300
D = 0.0095
A = mp.power(D,2)*mp.pi/4
L = 300
R = 8.31446
f = 0.05
Z1 = 0.9992
Z2 = 0.9999
Zm = 0.5*(Z1+Z2)
C = (f*L/D) + 2*mp.log(P1/P2)
w2 = (mp.power(P1,2)-mp.power(P2,2))*mp.power(A,2)/(Zm*R*T*C)
w = mp.power(w2,0.5)
You'll need to broadcast the function you want (log and power here) on your numpy array using np.frompyfunc:
import numpy as np
import mpmath as mp
mp.mp.dps = 20
# State equation --> pV = nZRT
P1 = np.linspace(101325,10*101325,100)
P2 = 101325
T = 300
D = 0.0095
A = mp.power(D,2)*mp.pi/4
L = 300
R = 8.31446
f = 0.05
Z1 = 0.9992
Z2 = 0.9999
Zm = 0.5*(Z1+Z2)
log_array = np.frompyfunc(mp.log, 1, 1) #to evaluate mpmath log function on a numpy array
pow_array = np.frompyfunc(mp.power, 2, 1) #to evaluate mpmath power function on a numpy array
C = (f*L/D) + 2*log_array(P1/P2)
w2 = (pow_array(P1,2)-pow_array(P2,2))*pow_array(A,2)/(Zm*R*T*C)
w = pow_array(w2,0.5)

Odeint, shooting method and boundary conditions in Python

I have been working with odeint and boundary conditions. Bassically what I am trying to do is to solve the differential equations given in this figure 1
where in my code R=R, ph = Phi, al = alpha, a = a, m = m, l = l and om = omega. The initial conditions that I am trying to implement are R(0)=O(r^l); Phi(0)=O(r^{l-1}) if l/=0 and Phi(0)=O(r) if l=0; a(0) = 1 and a(inf)=1/alpha(inf) (additionally I need that R(inf)=0). I tried to applied the shooting method in order to find initial conditions for alpha that best matches with my boundary conditions. I also need to find the omega that best matches the boundary conditions for R at infinity. The code that I wrote is the following:
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint
import time
start = time.clock()
def system_DE(IC,p,r):
l = p[0]
m = p[1]
om = p[2]
R = IC[0]
ph = IC[1]
a = IC[2]
al = IC[3]
dR_dr = ph
da_dr = a*((2*l+1)*r/2*(om**2*a**2*R**2/al**2+ph**2+l*(l+1)*a**2*R**2/r**2+m**2*a**2*R**2)-(a**2-1)/(2*r))
dal_dr = al*(da_dr/a-l*(l+1)*(2*l+1)*a**2*R**2/r-(2*l+1)*m**2*a**2*r*R**2+(a**2-1)/r)
dph_dr = -2*ph/r-dal_dr*ph/al+da_dr*ph/a-om**2*a**2*R/al**2+l*(l+1)*a**2*R/r**2+m**2*a**2*R
return [dR_dr,da_dr,dal_dr,dph_dr]
def init(u,p,r):
if p==0:
return np.array([1,r,1,u])
else:
return np.array([r**l,l*r**(l-1),1,u])
l = 0
m = 1
ep = 0.3
n_om = 10
omega = np.linspace(m-ep,m+ep,n_om)
r = np.linspace(0.0001, 100, 1000)
niter = 100
u = 0
tol = 0.1
ustep = 0.01
p = np.zeros(3)
p[0] = l
p[1] = m
for j in range(len(omega)):
p[2] = omega[j]
for i in range(niter):
u += ustep
Y = odeint(system_DE(init(u,p[0],r[0]),p,r), init(u,p[0],r[0]), r)
print Y[-1,2]
print Y[-1,3]
if abs(Y[len(Y)-1,2]-1/Y[len(Y)-1,3]) < tol:
print(i,'times iterations')
print("a'(inf)) = ", Y[len(Y)-1,2])
print('y"(0) =',u)
break
if abs(Y[len(Y)-1,0]) < tol:
print(j,'times iterations in omega')
print("R'(inf)) = ", Y[len(Y)-1,0])
break
However, when I run it I am obtaining: error: The function and its Jacobian must be callable functions.
Could some one help me to understand what my mistake is?
Regards,
Luis Padilla.
To start with, the first argument to odeint is your derivative function system_DE. Just pass its name, no parentheses or arguments. Odeint with call it internally and supply arguments.
I fixed my code and now it is giving me some results. However, when I run it I am obtaining some warnings that I don't know how to solve it. Could some one help me to solve it? Basically my code is this:
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint
import time
def system_DE(IC,r,l,m,om):
R = IC[0]
ph = IC[1]
a = IC[2]
al = IC[3]
dR_dr = ph
da_dr = a*((2*l+1)*r/2*(om**2*a**2*R**2/al**2+ph**2+l*(l+1)*a**2*R**2/r**2+m**2*a**2*R**2)-(a**2-1)/(2*r))
dal_dr = al*(da_dr/a-l*(l+1)*(2*l+1)*a**2*R**2/r-(2*l+1)*m**2*a**2*r*R**2+(a**2-1)/r)
dph_dr = -2*ph/r-dal_dr*ph/al+da_dr*ph/a-om**2*a**2*R/al**2+l*(l+1)*a**2*R/r**2+m**2*a**2*R
return [dR_dr,dph_dr,da_dr,dal_dr]
def init(u,p,r):
if p==0:
return np.array([1.,r,1.,u])
else:
return np.array([r**p,l*r**(p-1),1,u])
l = 0.
m = 1.
ep = 0.2
n_om = 30
omega = np.linspace(m-ep,m+ep,n_om)
r = np.linspace(0.001, 100, 1000)
niter = 1000
tol = 0.01
ustep = 0.01
for j in range(len(omega)):
print('trying with $omega =$',omega[j])
p = (l,m,omega[j])
u = 0.001
for i in range(niter):
u += ustep
ini = init(u,p[0],r[0])
Y = odeint(system_DE, ini,r,p,mxstep=500000)
if abs(Y[len(Y)-1,2]-1/Y[len(Y)-1,3]) < tol:
break
if abs(Y[len(Y)-1,0]) < tol and abs(Y[len(Y)-1,2]-1/Y[len(Y)-1,3]) < tol:
print(j,'times iterations in omega')
print(i,'times iterations')
print("R'(inf)) = ", Y[len(Y)-1,0])
print("alpha(0)) = ", Y[0,3])
print("\omega",omega[j])
break
plt.subplot(2,1,1)
plt.plot(r,Y[:,0],'r',label = '$R$')
plt.plot(r,Y[:,1],'b',label = '$d R /dr$')
plt.xlim([0,10])
plt.legend()
plt.subplot(2,1,2)
plt.plot(r,Y[:,2],'r',label = 'a')
plt.plot(r,Y[:,3],'b', label = '$alpha$')
plt.xlim([0,10])
plt.legend()
plt.show()
But when I run it I am obtaining this:
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.1243782486482D+01 r2 = 0.8727680448722D-16
How could I fix the problem?
Regards,
Luis Padilla.

Categories