Lane-emden solutions using RK4 (hard-coded) - python

I'm working on finding an plotting solutions to the Lane-Emden equation for values n=[0,6], in intervals of 1/2. I'm new to Python, and can't seem to figure out how to use RK4 to make this work. Please help!
Current progress.
TypeError: unsupported operand type(s) for Pow: 'int' and 'list' on line 37 in main.py
The error just appeared after I added in the equations defined as r2, r3, r4 and k2, k3, k4.
import numpy as np
import matplotlib.pyplot as plt
n = [0,1,2,3,4,5,6,7,8,9,10,11,12,13]
theta0 = 1
phi0 = 0
step = 0.01
xi0 = 0
xi_max = 100
theta = theta0
phi = phi0
xi = xi0 + step
Theta = [[],[],[],[],[],[],[],[],[],[],[],[],[],[]]
Phi = [[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]]
Xi = [[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]]
for i in n:
Theta[i].append(theta)
Phi[i].append(phi)
Xi[i].append(xi)
def dTheta_dXi(phi,xi): #r1
return -phi/xi**2
def r2(phi,xi):
return dTheta_dXi(phi+step,xi+step*dTheta_dXi(phi,xi))
def r3(phi,xi):
return dTheta_dXi(phi+step,xi+step*r2(phi,xi))
def r4(phi,xi):
return dTheta_dXi(phi+step,xi+step*r3(phi,xi))
def dPhi_dXi(theta,xi,n): #k1
return theta**(n)*xi**2
def k2(theta,xi,n):
return dPhi_dXi(theta+step,xi+step*dPhi_dXi(theta,xi,n),n)
def k3(theta,xi,n):
return dPhi_dXi(theta+step,xi+step*k2(theta,xi,n),n)
def k4(theta,xi,n):
return dPhi_dXi(theta+step,xi+step*k3(theta,xi,n),n)
for i in n:
while xi < xi_max:
if theta < 0:
break
dTheta = (step/6)*(dTheta_dXi(phi,xi)+2*r2(phi,xi)+2*r3(phi,xi)+r4(phi,xi))
dPhi = (step/6)*(dPhi_dXi(theta,xi,i/2.)+2*k2(theta,xi,n)+2*k3(theta,xi,n)+k4(theta,xi,n))
theta += dTheta
phi += dPhi
xi += step
Theta[i].append(theta)
Phi[i].append(phi)
Xi[i].append(xi)
print i/2., round(xi,4), round(dTheta_dXi(phi,xi),4), round(xi/3./dTheta_dXi(phi,xi),4), round(1./(4*np.pi*(i/2.+1))/dTheta_dXi(phi,xi)**2,4)
theta = theta0
phi = phi0
xi = xi0 + step

Using RK4 for coupled systems
If one understands a first-order system as a vector-valued system working with vector states, then the scalar version of RK4
k1 = f(x,y)
k2 = f(x+0.5*h, y+0.5*h*k1)
k3 = f(x+0.5*h, y+0.5*h*k2)
k4 = f(x+h, y+h*k3)
x,y = x+h, y+h/6*(k1+2*k2+2*k3+k4)
can also directly be used for the vector case. Sometimes it appears educational to implement this component-wise. While in mathematical texts it is preferred to use one-letter variable names, possibly with sub- or superscripts, the variables in the code of programs usually are multi-lettered. So instead of r2 and k2 it would be more descriptive to use k2_Theta and k2_Phi.
Then it becomes rather intuitive that the state used to evaluate the k3 components has the arguments theta+0.5*step*k2_Theta and phi+0.5*step*k2_Phi.
k2_Xi etc. is always 1 for the independent variable, so the value for the 3rd stage is simply xi+0.5*step.
Implementation details RK4
The values of k1 etc. are fixed inside the step and result from the evaluation of the derivatives function. It makes absolutely no sense to declare them as functions themselves. That is, the RK4 step specialized to this situation becomes just
def RK4_update(theta, phi, xi, step, n):
k1_Theta = dTheta_dXi(phi, xi)
k1_Phi = dPhi_dXi(theta, xi, n)
k2_Theta = dTheta_dXi(phi+0.5*step*k1_Phi, xi+0.5*step)
k2_Phi = dPhi_dXi(theta+0.5*step*k1_Theta, xi+0.5*step, n)
k3_Theta = dTheta_dXi(phi+0.5*step*k2_Phi, xi+0.5*step)
k3_Phi = dPhi_dXi(theta+0.5*step*k2_Theta, xi+0.5*step, n)
k4_Theta = dTheta_dXi(phi+step*k3_Phi, xi+step)
k4_Phi = dPhi_dXi(theta+step*k3_Theta, xi+step, n)
dTheta = (step/6)*(k1_Theta+2*k2_Theta+2*k3_Theta+k4_Theta)
dPhi = (step/6)*(k1_Phi+2*k2_Phi+2*k3_Phi+k4_Phi)
return dTheta, dPhi
On the singularity of the Lane-Emden equation
For the solution to exist at xi=0 one needs at least that phi ~ xi^k with k>=2. This gives that theta is almost constant, which in turn leads to an integration phi = theta0^n*xi^3/3 which then in the other equation gives theta = theta0 - theta0^n*xi^2/6. This allows to take the first step away from the singularity without using the numerical method.
xi = step
theta, phi = theta0 - theta0**n*xi**2/6, theta0**n*xi**3/3
Xi[i] = [0, xi]
Theta[i] = [theta0, theta]
Phi[i] = [0, phi]
Then the main loop can be written as
for i in range(N):
n = i/2
xi = step
theta, phi = theta0 - theta0**n*xi**2/6, theta0**n*xi**3/3
Xi[i] = [0, xi]
Theta[i] = [theta0, theta]
Phi[i] = [0, phi]
while xi < xi_max:
if theta < 0:
break
dTheta, dPhi = RK4_update(theta,phi,xi,step,n)
theta += dTheta
phi += dPhi
xi += step
Theta[i].append(theta)
Phi[i].append(phi)
Xi[i].append(xi)
Then plotting with
for i in range(N):
plt.plot(Xi[i],Theta[i], label=f"n={i/2}")
plt.grid(); plt.legend(); plt.show()
results in
Trick used: To avoid rational powers of negative values, replace theta**n with theta*abs(theta)**(n-1) or similar continuations.
Old contents
You should once again explore what update goes where. xi is the independent variable and thus only gets updates 0.5*step and step, the updates of theta use the derivatives dTheta_dXi and similarly phi is updated using the slopes dPhi_dXi
def r2(phi,xi):
return dTheta_dXi(phi+0.5*step*dPhi_dXi(theta,xi,n),xi+0.5*step)
def k2(theta,xi,n):
return dPhi_dXi(theta+0.5*step*dTheta_dXi(phi,xi),xi+0.5*step,n)
def r3(phi,xi):
return dTheta_dXi(phi+0.5*step*k2(theta,xi,n),xi+0.5*step)
etc.
Now one can see that due to the coupled nature of the equation you need both theta and phi as arguments everywhere. Further, even if that works in the end you end up computing many of the values multiple times where assembling everything in one loop only requires one computation.

Related

Explicit Euler method is unable to complete the solution to a system of differential equations

I am trying to solve the following nonlinear system of differential equation with the explicit Euler method:
x' = f1(x,y),
y' = f2(x,y)
And I know the fact that the curve corresponding to the solution must connect (x_{initial},y_{initial}) to (0,1) in the x-y plane, but the obtained curve stops prematurely at around (0.17,0.98). I tried to vary the parameters but again I can't push that value any further towards (0,1). First, I thought my equations becomes stiff towards the end point; now it doesn't seem to be the case when I read about the stiff ODEs. What might be the problem?
The code I wrote in python is:
import math
import numpy as np
import matplotlib.pyplot as plt
q=1
#my f1 and f2 functions:
def l(lna,x,y,m,n,xi,yi):
return n *m**(-1)*(np.divide((yi**2)*(np.float64(1)-np.power(x,2)-np.power(y,2))*np.exp(3*(lna-lnai)),(y**2)*(1-xi**2-yi**2)))**(-1/n)
def f1 (x,y,l):
return -3*x + l*np.sqrt(3/2)* y**2+ 3/2 *x*(2*(x**2)+q*(1-x**2-y**2))
def f2 (x,y,l):
return -l*np.sqrt(3/2) *y*x + 3/2 *y*(2*x**2+q*(1-x**2-y**2))
#my code for the explicit Euler:
def e_E(xa,xb,dlna,m,n,xi,yi):
N = int(round((lnaf-lnai)/dlna))
lna = np.linspace(0, N*dlna, N+1)
x = np.empty(N+1)
y = np.empty(N+1)
x[0],y[0] = xi,yi
for i in range(N):
sd = l(lna[i],x[i],y[i],m,n,xi,yi)
x[i+1] = x[i] + dlna * f1(x[i],y[i],sd)
y[i+1] = y[i] + dlna * f2(x[i],y[i],sd)
return x,y,lna
#range for the independent variable (in my case it is lna)
lnai = np.float64(0)
lnaf = np.float64(15)
#step size
dlna = np.float64(1e-3)
#initial conditions
yi = np.float64(1e-5)
xi = 0
x1,y1,lna1 = e_E(lnai, lnaf, dlna, np.float64(0.1), np.float64(2), xi, yi)
plt.plot(x1,y1,'b',label = ('x1'))
plt.legend()
plt.grid()
plt.ylabel('y')
plt.xlabel('x')
plt.show()
My solution in the x-y plane:
Full/correct solution:

Solving an Involved Set of Coupled Differential Equations

I am trying to solve a set of complicated differential equations in Python. These equations contain five functions (defined in the function 'ODEs' in the code below) that are functions of a variable n (greek name is eta--- I used n and eta interchangeably as variable names). These coupled differential equations contain a function (called a) which is a function of a parameter t. n (or eta) is also a function of t and so my first goal was to express, again numerically, the function a as a function of n (eta). Hence I had to solve a less involved pair of differential equations, which I defined in the function 'coupleODE'. I got a plot of a(t) and n(t) and used interpolation to get a model relating function a to function n. This function is a numerical estimation for a(n). I called this interpolation model 'f_quad' --- f, means function, and quad represents quadratic interpolation.
Now, the original five differential equations actually contain a'(n)/a(n), where ' is derivative with respect to n. I numerically found an interpolator for this as well and called it 'deriv_quad'.
Now in 'ODEs' I am modelling the five differential equations, and as you can see in the code, the function body contains segments that use the interpolators 'f_quad' and 'deriv_quad'; these have argument n, which represents the a(n) and a'(n) respectively. I then use odeint python function to numerically solve these differential equations, but I get the error message:
'A value in x_new is above the interpolation range.' My computer says that the error occurred in the line 'f = odeint(ODEs, initials, n_new, atol=1.0e-8, rtol=1.0e-6)'.
I am not sure why this happened; I used the same eta values that I used when finding the interpolators 'f_quad' and 'deriv_quad'. Can anyone help me get rid of this error?
Here is the code:
import numpy as np
from scipy.misc import derivative
from scipy.integrate import odeint
from scipy.interpolate import interp1d
import matplotlib.pyplot as plt
import math
def coupledODE(x,t):
#define important constants
m = 0.315
r = 0.0000926
d = 0.685
H_0 = 67.4
a = x[0]
eta = x[1]
dndt = a**(-1)
dadt = H_0 * (m*a**(-1) + r*a**(-2) + d*a**2)**(1/2)
return [dadt, dndt]
#initial condtions
x0 = [1e-7, 1e-8]
t = np.linspace(0,0.01778301154,7500)
x = odeint(coupledODE, x0, t, atol=1.0e-8, rtol=1.0e-6) #vector of the functions a(t), n(t)
a = x[:,0]
n = x[:,1] #Eta; n is the greek letter eta
plt.semilogx(t,a)
plt.xlabel('time')
plt.ylabel('a(t)')
plt.show()
plt.semilogx(t,n)
plt.xlabel('time')
plt.ylabel('Eta')
plt.show()
plt.plot(n,a)
plt.xlabel('Eta')
plt.ylabel('a(t)')
plt.show()
##############################################################################
# Calculate the Derivative a' (i.e. da/d(eta) Numerically
##############################################################################
derivative_values = []
for i in range(7499):
numerator = x[i+1,0] - x[i,0]
denominator = x[i+1,1] - x[i,1]
ratio = numerator / denominator
derivative_values.append(ratio)
x_axis = n
x_axis = np.delete(x_axis, -1)
plt.plot(x_axis,derivative_values)
plt.xlabel('Eta')
plt.ylabel('Derivative of a')
plt.show()
##############################################################################
#Interpolation
##############################################################################
#Using quadratic interpolation
f_quad = interp1d(n, a, kind = 'quadratic')
deriv_quad = interp1d(x_axis, derivative_values, kind = 'quadratic')
n_new = np.linspace(1.0e-8, 0.0504473, num = 20000, endpoint = True)
plt.plot(n_new, f_quad(n_new))
plt.xlabel('Eta')
plt.ylabel('a')
plt.show()
plt.plot(n_new, deriv_quad(n_new))
plt.xlabel('Eta')
plt.ylabel('Derivative of a')
plt.show()
#print(x[0,1])
#print(eta_new[0])
#print(deriv_quad(1e-8))
##############################################################################
# The Main Coupled Equations
##############################################################################
def ODEs(x,n):
fourPiG_3 = 1
CDM_0 = 1
Rad_0 = 1
k = 0.005
Phi = x[0]
Theta1 = x[1]
iSpeed = x[2]
DeltaC = x[3]
Theta0 = x[4]
dPhi_dn = fourPiG_3 *((CDM_0 * DeltaC)/ deriv_quad(n) + (4*Rad_0)/(f_quad(n)*deriv_quad(n))) -
(k**2*Phi*f_quad(n))/(deriv_quad(n)) - (deriv_quad(n) * Phi)/(f_quad(n))
dTheta1_dn = (k/3)*(Theta0 - Phi)
diSpeed_dn = -k*Phi - (deriv_quad(n)/f_quad(n))*iSpeed
dDeltaC_dn = -k*iSpeed - 3*dPhi_dn
dTheta0_dn = -k*Theta1 - dPhi_dn
return [dPhi_dn, dTheta1_dn, diSpeed_dn, dDeltaC_dn, dTheta0_dn]
hub_0 = deriv_quad(1e-8)/f_quad(1e-8)
#Now the initial conditions
Phi_k_0 = 1.0e-5
Theta1_k_0 = -1*Phi_k_0/hub_0 #Ask about the k
iSpeed_k_0 = 3*Theta1_k_0
DeltaC_k_0 = 1.5 * Phi_k_0
Theta0_k_0 = 0.5 * Phi_k_0
initials = [Phi_k_0, Theta1_k_0, iSpeed_k_0, DeltaC_k_0, Theta0_k_0]
####Error Happens Here ####
f = odeint(ODEs, initials, n_new, atol=1.0e-8, rtol=1.0e-6)
Phi = f[:,0]
Theta1 = f[:,1]
iSpeed = f[:,2]
DeltaC = f[:,3]
Theta0 = f[:,4]

Velocity-Verlet Double-Well Algorithm Python

I am implemententing the Verlet algorithm for a double well potential V(x) = x^4-20x^2, as to create a simple phase portrait.The generated phase portrait has an augmented oval shape and is clearly incorrect. I have a feeling that my problem is occurring in my definition of the of x^3 but I am not sure. I have also included the algorithm for a classical harmonic oscillator to show that my code works correctly.
import numpy as np
import matplotlib.pyplot as plt
###Constants
w = 2
m=1
N=500
dt=0.05
t = np.linspace(0, N*dt, N+1)
np.shape(t)
x = np.zeros(N+1)
p = np.zeros(N+1)
p_0 = 0
x_0 = 1
x[0] = x_0
p[0] = p_0
#Velocity Verlet Tuckerman
#x(dt) = x(0) +p(0)/m*dt + 1/(2m) * F(x(0))
#p(dt = p(0) + dt/2[F(x(0)) + F(x(dt))]
#Harmonic Oscillator F(x) = -kx = -mw^2x
for n in range(N):
x[n+1] = x[n] + (p[n]/m)*dt - (0.5)*w**2*x[n]*dt*dt
p[n+1] = p[n] - m*(0.5)*w**2*x[n]*dt - m*0.5*w**2*x[n+1]*dt
plt.plot(x,p)
#Symmetric Double Well: F(x) = -4x^3 + 40x
#V(x) = x^4 -20x^2
for n in range(N):
x[n+1] = x[n] + (p[n]/m)*dt +1/(2*m)*( -4*(x[n]*x[n]*x[n])*dt*dt +40*x[n]*dt*dt)
p[n+1] = p[n] + (1/2)*(-4*m*(x[n]*x[n]*x[n])*dt +40*m*x[n]*dt - 4*m*(x[n+1]*x[n+1]*x[n+1])*dt +40*m*x[n+1]*dt)
plt.plot(x,p)
Thanks!
To be more precise, V has a minimum at x=+-sqrt(10) with value -100, the local maximum at x=0 gives value 0. The initial position x0=1, v0=0 places the solution in the right valley, oscillating around sqrt(10).
To get a figure-8 shape you need an initial point with V(x0) slightly larger than zero. For instance with x0=5 one gets V=25*(25-20)=125. Or take x0=4.5 ==> x0^2=20.25 ==> V ~ 5.

Numerical solution to a differential equation containing a Dirac delta function

I am trying to use scipy to numerically solve the following differential equation
x''+x=\sum_{k=1}^{20}\delta(t-k\pi), y(0)=y'(0)=0.
Here is the code
from scipy.integrate import odeint
import numpy as np
import matplotlib.pyplot as plt
from sympy import DiracDelta
def f(t):
sum = 0
for i in range(20):
sum = sum + 1.0*DiracDelta(t-(i+1)*np.pi)
return sum
def ode(X, t):
x = X[0]
y = X[1]
dxdt = y
dydt = -x + f(t)
return [dxdt, dydt]
X0 = [0, 0]
t = np.linspace(0, 80, 500)
sol = odeint(ode, X0, t)
x = sol[:, 0]
y = sol[:, 1]
plt.plot(t,x, t, y)
plt.xlabel('t')
plt.legend(('x', 'y'))
# phase portrait
plt.figure()
plt.plot(x,y)
plt.plot(x[0], y[0], 'ro')
plt.xlabel('x')
plt.ylabel('y')
plt.show()
However what I got from python is zero solution, which is different from what I got from Mathematica. Here are the mathematica code and the graph
so=NDSolve[{x''(t)+x(t)=\sum _{i=1}^{20} DiraDelta (t-i \pi ),x(0)=0,x'(0)=0},x(t),{t,0,80}]
It seems to me that scipy ignores the Dirac delta function. Where am I wrong? Any help is appreciated.
Dirac delta is not a function. Writing it as density in an integral is still only a symbolic representation. It is, as mathematical object, a functional on the space of continuous functions. delta(t0,f)=f(t0), not more, not less.
One can approximate the evaluation, or "sifting" effect of the delta operator by continuous functions. The usual good approximations have the form N*phi(N*t) where N is a large number and phi a non-negative function, usually with a somewhat compact shape, that has integral one. Popular examples are box functions, tent functions, the Gauß bell curve, ... So you could take
def tentfunc(t): return max(0,1-abs(t))
N = 10.0
def rhs(t): return sum( N*tentfunc(N*(t-(i+1)*np.pi)) for i in range(20))
X0 = [0, 0]
t = np.linspace(0, 80, 1000)
sol = odeint(lambda x,t: [ x[1], rhs(t)-x[0]], X0, t, tcrit=np.pi*np.arange(21), atol=1e-8, rtol=1e-10)
x,v = sol.T
plt.plot(t,x, t, v)
which gives
Note that the density of the t array also influences the accuracy, while the tcrit critical points did not do much.
Another way is to remember that delta is the second derivative of max(0,x), so one can construct a function that is the twice primitive of the right side,
def u(t): return sum(np.maximum(0,t-(i+1)*np.pi) for i in range(20))
so that now the equation is equivalent to
(x(t)-u(t))'' + x(t) = 0
set y = x-u then
y''(t) + y(t) = -u(t)
which now has a continuous right side.
X0 = [0, 0]
t = np.linspace(0, 80, 1000)
sol = odeint(lambda y,t: [ y[1], -u(t)-y[0]], X0, t, atol=1e-8, rtol=1e-10)
y,v = sol.T
x=y+u(t)
plt.plot(t,x)
odeint :
does not handle sympy symbolic objects
it's unlikely it can ever handle Dirac Delta terms.
The best bet is probably to turn dirac deltas into boundary conditions: assume that the function is continuous at the location of the Dirac delta, but the first derivative jumps. Integrating over infinitesimal interval around the location of the delta function gives you the boundary condition for the derivative just left and just right from the delta.

implement an integration math equation using odeint in Python

I am trying to solve the following equation in python using the scipy.odeint function.
Currently I am able to implement this form of the equation
in python using the following script:
def dY(y1, x):
a = 0.001
yin = 1
C = 0.01
N = 1
dC = C/N
b1 = 0
return (a/dC)*(yin-y1)+b1*dC
x = np.linspace(0,20,1000)
y0 = 0
res = odeint(dY, y0, x)
plt.plot(t,res, '-')
plt.show()
My problem with the first equation is 'i'. I don't know how to integrate the equation and still be able to provide the current and previous 'y'(yi-1 and yi) values. 'i' is simply a sequence number that is within a range of 0..100.
Edit 1:
The original equation is:
Which I rewrote using y,x,a,b and C
Edit2:
I edited Pierre de Buyl' code and changed the N value. Luckily I have a validation table to validate the outcome against. Unfortunately, the results are not equal.
Here is my validation table:
and here is the numpy output:
Used code:
def dY(y, x):
a = 0.001
yin = 1
C = 0.01
N = 3
dC = C/N
b1 = 0.01
y_diff = -np.copy(y)
y_diff[0] += yin
y_diff[1:] += y[:-1]
return (a/dC)*(y_diff)+b1*dC
x = np.linspace(0,20,11)
y0 = np.zeros(3)
res = odeint(dY, y0, x)
plt.plot(x,res, '-')
as you can see the values are different by an offset of 0.02..
Am I missing something that results in this offset?
The equation is a "coupled" ordinary differential equation (see "System of ODEs" on Wikipedia.
The variable is a vector containing y[0], y[1], etc. To solve the ODE you must feed a vector as the initial condition and the function dY must return a vector as well.
I have modified your code to achieve this result:
def dY(y, x):
a = 0.001
yin = 1
C = 0.01
N = 1
dC = C/N
b1 = 0
y_diff = -np.copy(y)
y_diff[0] += yin
y_diff[1:] += y[:-1]
return (a/dC)*y_diff+b1*dC
I have written the part y[i-1] - y[i] as a NumPy vector operation and special cased the coordinate y[0] (that is the y1 in your notation but arrays start at 0 in Python).
The solution, using an initial value of 0 for all yi is
x = np.linspace(0,20,1000)
y0 = np.zeros(4)
res = odeint(dY, y0, x)
plt.plot(x,res, '-')

Categories

Resources