How to solve second degree differential equation? - python

For the differential equation mx'' + kx = 0 (where x'' is double derivative of x with respect to t), how to solve this for x(t)? I mean how to get this equation:
x(t) = c1*cos(sqrt(k/m)*t) + c2*sin(sqrt(k/m)*t)
What I tried:
t, g, k, m, w0, a_0, b_0, c1, c2 = symbols('t g k m w0 a_0 b_0 c1 c2')
x = symbols('x', cls=Function)
w0 = sqrt(k/m)
diffeq = Eq(x(t).diff(t, t) + k*x, 0)
but the statement diffeq = Eq(x(t).diff(t, t) + k*x, 0) throws an error:
TypeError: unbound method as_base_exp() must be called with x instance as first argument (got nothing instead)

I made it work and able to solve the equation for coefficients C1 and C2. Here is the code:
t, a0, b0, k, m, g = symbols('t a0 b0 k m g')
x = Function('f')
diffeq = m*Derivative(x(t), t, t) + k*x(t) + m*g
# print(diffeq)
x_sl30 = dsolve(diffeq, x(t)).rhs
print(x_sl30)
# Initial condition, x(0) = a0 and x'(0) = b0
c_0 = Eq(x_sl30.subs(t, 0), a0)
c_1 = Eq(x_sl30.diff(t).subs(t, 0), b0)
# print(c_0)
# print(c_1)
C1, C2 = symbols("C1, C2")
C1C2_sl = solve([c_0, c_1], (C1, C2))
#Substitute the value of C1 and C2 in the original equation
x_sl31 = x_sl30.subs(C1C2_sl)
print(x_sl31)

Related

How to convert symbolic expressions to an ODE and solve it in Python?

Please quickly go through the question I asked in MATLAB Ask:
https://www.mathworks.com/matlabcentral/answers/827750-convert-the-symbolic-expression-into-the-expression-which-can-be-put-on-the-rhs-of-an-ode?s_tid=srchtitle
Now I want to reproduce the same work in Python and meet some problems. Given 2 state equations:
f1 = x2
f2 = -c*sinx1-(a + b*cosx1)*x2
The sensitivity equation is given by
dSdt = PfPx*S+PfPw=RHS # dSdt: time derivative of S (sensitivity matrix),in this case,2x3 matrix
PfPx # Jacobian matrix of F = (f1,f2) wrt x = (x1,x2),2x2 matrix
S # Sensitivity matrix S = [[x3, x5, x7],[x4, x6, x8]],2x3 matrix
PfPw # Jacobian matrix of F wrt parameters w = (a,b,c),2x3 matrix
By symbolic matrix calculation, RHS = PfPx*S + PfPw
Sensitivity equation is given as dSdt(i,j) = RHS(i,j) by vector form for dx3dt~dx8dt appending with state equation for dx1dt and dx2dt (see the picture at the end in that Link), when a=1,b=0,c=1, it generates 8 ODEs:
dx1dt= x2;
dx2dt = -sinx1-x2; # dx1dt~dx2dt are state eqns
dx3dt = x4;
dx4dt = -x3cosx1-x2-x4;
dx5dt = x6;
dx6dt = -x5cosx1-x6-x2cosx1;
dx7dt = x8;
dx8dt = -cos(x1)x7-x8-sinx1; # dx3dt – dx8dt are sensitivity eqns
Try to code the symbolic calculation in Python,
import sympy as sym
import numpy as np
from scipy.integrate import odeint
def model(x, t):
a, b, c = sym.symbols('a,b,c')
f1 = x[1]
f2 = -c * sym.sin(x[0]) - (a + b * sym.cos(x[0])) * x[1]
funcs = sym.Matrix([f1, f2])
pfpx = funcs.jacobian([x[0], x[1]]) # pfpx = Jacobian('x1,x2,x3',[f1, f2, f3])
pfpw = funcs.jacobian([a, b, c])
s = sym.Matrix([[x[2], x[4], x[6]], [x[3], x[5], x[7]]])
pfpx = pfpx.subs([(a, 1), (b, 0), (c, 1)])
pfpw = pfpw.subs([(a, 1), (b, 0), (c, 1)])
res = pfpx * s + pfpw
dydt = [f1, f2, res[0], res[3], res[1], res[4], res[2], res[5]]
return dydt
tspan = np.linspace(0, 10)
z0 = np.zeros(8)
z0[0:2] = [1, 1]
zrec = odeint(model, z0, tspan)
it is saying error on the last line zrec = odeint(model, z0, tspan) and the error msg is:
ValueError:
Can't calculate derivative wrt 1.00000000000000.
Any suggestions are appreciated!

Why are some of my equations ignored when solving a non-linear system of equations

I am trying to solve a system of non-linear equations. The issue is that my input values are being returned to me as a valid solution. In order for this to happen, some equations have to be ignored. I have tried both sympy.solvers.nsolve() and scipy.optimize.fsolve(). Both give the same answers. I have posted both codes starting with scipy and then sympy. Lastly, I have posted example results.
from scipy.optimize import fsolve # required library
import sympy as sp
import scipy
# known values hard coded for testing
a = 1
b = 3
c = a + b
d = 0
kp_NO = 0.00051621
kp_H2O = 0.0000000127
kp_CO2 = 0.00000001733
p = 5 # pressure
dec = 3 # decimal point precision
# Solving the system of equations
def equations(vars):
(e, f, g, h, j, k, l) = vars
f1 = e + j - a
f2 = e + 2*g + 2*j + k + l - a - 2*c
f3 = f + k - b
f4 = 2*h + l - 2*d - (2*79/21)*c
f5 = kp_NO - (l/(c*(79/21)*c))
f6 = kp_H2O - (k/(b*sp.sqrt((c*p)/(e + f+ g + h + j + k + l))))
f7 = kp_CO2 - (j/(a*sp.sqrt((c*p)/(e + f + g + h + j + k + l))))
return[f1, f2, f3, f4, f5, f6, f7]
e, f, g, h, j, k, l = scipy.optimize.root_scalar(equations, (0, 0, 0, 0, 0, 0, 0))
# CO, H2, O2, N2, CO2, H2O, NO
print(e, f, g, h, j, k, l)
import sympy as sp # required library
import math as m
# known values hard coded for testing
a = 1
b = 3
c = a + b
d = 0
kp_NO = 0.0000000015346
kp_H2O = 1.308*10**-23
kp_CO2 = 9.499*10**-46
p = 5 # pressure
dec = 10 # decimal point precision
# Solving the system of equations
e, f, g, h, j, k, l = sp.symbols('e, f, g, h, j, k, l')
f1 = e + j - a
f2 = e + 2*g + 2*j + k + l - a - 2*c
f3 = f + k - b
f4 = 2*h + l - 2*d - (2*79/21)*c
f5 = kp_NO - (l / (c * (79 / 21) * c))
f6 = kp_H2O - (k / (b * sp.sqrt((c * p) / (e + f + g + h + j + k + l))))
f7 = kp_CO2 - (j / (a * sp.sqrt((c * p) / (e + f + g + h + j + k + l))))
# f5 = m.log(kp_NO, p) + h/2 + g/2 - l
# f6 = m.log(kp_H2O, p) + g/2 + f - k
# f7 = m.log(kp_CO2, p) + e + g/2 - j
results = sp.solvers.nsolve([f1, f2, f3, f4, f5, f6, f7], [e, f, g, h, j, k, l], [0.00004, 0.00004, 0.49, 3.76, 0.25, 0.75, 0.01], manual=True)
e = round(results[0], dec)
f = round(results[1], dec)
g = round(results[2], dec)
h = round(results[3], dec)
j = round(results[4], dec)
k = round(results[5], dec)
l = round(results[6], dec)
# CO, H2, O2, N2, CO2, H2O, NO
print(e, f, g, h, j, k, l)
1.00000000000000 3.00000000000000 3.9999999538 15.0476190014 0.0 0.0 9.24e-8
You will also get no change from the initial guess if each of the equations is within the tolerance of the solver. And with the scaling of your equations, I suspect this is the case. Here is another approach:
Let's work with rationals instead of decimals:
>>> from sympy import nsimplify
>>> eqs = [nsimplify(i, rational=True) for i in [f1, f2, f3, f4, f5, f6, f7]]
>>> v = [e, f, g, h, j, k, l]
All but the last equation is easy to solve for all but g; there is 1 solution:
>>> lpart = solve(eqs[:-1], set(v) - {g}, dict=True)[0]
When radicals are removed from the numerator of the last equation, it yields a cubic for which roots can be found
>>> from sympy import real_roots
>>> from sympy.solvers.solvers import unrad
>>> u, cov = unrad(eqs[-1].subs(lpart).as_numer_denom()[0]); assert cov == []
>>> gsol = list(real_roots(u))
It looks like the first two real roots are solutions of the last equation:
>>> [abs(eqs[-1].simplify().subs(g, i).n()) for i in gsol]
[0.e-145, 0.e-145, 7.84800000000000e-23]
You can check to see if they are also solutions of the others. (The real roots are not compact solutions in terms of radicals.)
You might leave the constants as symbols and repeat the above and only solve the last equation with values when needed -- if needed.

how do i create a graph of multiple x,y points inside a loop?

so I saw this code for RK4 on stack and I found it very useful. However, I cannot figure out a way to plot for each y value at each increment(h) of x.
def f(x,y):
return 2*x**2-4*x+y
def RK4(x0,y0):
while x0 < b:
k1 = h*f(x0,y0)
k2 = h*f(x0+0.5*h,y0+0.5*k1)
k3 = h*f(x0+0.5*h,y0+0.5*k2)
k4 = h*f(x0+h,y0+k3)
y0+=(k1+2*k2+2*k3+k4)/6
x0+=h
return y0
b=3
h=0.001
print(RK4(1,0.7182818))
You can append each point in a list as a tuple, and then perform the line plot operation on the list of tuples. You can find it in the commented code below.
import matplotlib.pyplot as plt
def f(x, y):
return 2 * x ** 2 - 4 * x + y
def RK4(x0, y0):
pts = [] # empty list
while x0 < b:
k1 = h * f(x0, y0)
k2 = h * f(x0 + 0.5 * h, y0 + 0.5 * k1)
k3 = h * f(x0 + 0.5 * h, y0 + 0.5 * k2)
k4 = h * f(x0 + h, y0 + k3)
y0 += (k1 + 2 * k2 + 2 * k3 + k4) / 6
x0 += h
pts.append((x0, y0)) # appending the tuple
plt.plot(*zip(*pts)) # plotting the list of tuple
plt.show()
return y0
b = 3
h = 0.001
print(RK4(1, 0.7182818))
You can see the plot as follows
From a design perspective, it would be preferred if the RK4 code and the plotting code were separated, the numerical solver should not be concerned with how its results are used afterwards.
Then the next decision would be about the construction of the time array, it could be passed to the RK4 method, or be constructed inside and returned, both have advantages. If speed is a concern, the arrays should be constructed explicitly in their final form (see example on math.SE), for expediency one can also construct them incrementally. Thus the code could be changed as
def RK4(f,x0,y0,xb,dx):
x, y = [x0],[y0]
while x0 < xb:
k1 = dx*f(x0,y0)
k2 = dx*f(x0+0.5*dx,y0+0.5*k1)
k3 = dx*f(x0+0.5*dx,y0+0.5*k2)
k4 = dx*f(x0+dx,y0+k3)
y0 += (k1+2*k2+2*k3+k4)/6
x0 += dx
x.append(x0); y.append(y0) # for vector y use y0.copy()
return x,y
and then call as
x,y = RK4(f=f,x0=1.0,y0=0.7182818,xb=3.0,dx=1e-3)
plt.plot(x,y)
#title, axis labels
plt.grid(); plt.show()

Issue on Runge Kutta Fehlberg algorithm

I have wrote a code for Runge-Kutta 4th order, which works perfectly fine for a system of differential equations:
import numpy as np
import matplotlib.pyplot as plt
import numba
import time
start_time = time.clock()
#numba.jit()
def V(u,t):
x1,dx1, x2, dx2=u
ddx1=-w**2 * x1 -b * dx1
ddx2=-(w+0.5)**2 * x2 -(b+0.1) * dx2
return np.array([dx1,ddx1,dx2,ddx2])
#numba.jit()
def rk4(f, u0, t0, tf , n):
t = np.linspace(t0, tf, n+1)
u = np.array((n+1)*[u0])
h = t[1]-t[0]
for i in range(n):
k1 = h * f(u[i], t[i])
k2 = h * f(u[i] + 0.5 * k1, t[i] + 0.5*h)
k3 = h * f(u[i] + 0.5 * k2, t[i] + 0.5*h)
k4 = h * f(u[i] + k3, t[i] + h)
u[i+1] = u[i] + (k1 + 2*(k2 + k3) + k4) / 6
return u, t
u, t = rk4(V,np.array([0,0.2,0,0.3]) ,0,100, 20000)
print("Execution time:",time.clock() - start_time, "seconds")
x1,dx1,x2,dx2 = u.T
plt.plot(x1,x2)
plt.xlabel('X1')
plt.ylabel('X2')
plt.show()
The above code, returns the desired result:
And thanks to Numba JIT, this code works really fast. However, this method doesn't use adaptive step size and hence, it is not very suitable for a system of stiff differential equations. Runge Kutta Fehlberg method, solves this problem by using a straight forward algorithm. Based on the algorithm (https://en.wikipedia.org/wiki/Runge%E2%80%93Kutta%E2%80%93Fehlberg_method) I wrote this code which works for only one differential equation :
import numpy as np
def rkf( f, a, b, x0, tol, hmax, hmin ):
a2 = 2.500000000000000e-01 # 1/4
a3 = 3.750000000000000e-01 # 3/8
a4 = 9.230769230769231e-01 # 12/13
a5 = 1.000000000000000e+00 # 1
a6 = 5.000000000000000e-01 # 1/2
b21 = 2.500000000000000e-01 # 1/4
b31 = 9.375000000000000e-02 # 3/32
b32 = 2.812500000000000e-01 # 9/32
b41 = 8.793809740555303e-01 # 1932/2197
b42 = -3.277196176604461e+00 # -7200/2197
b43 = 3.320892125625853e+00 # 7296/2197
b51 = 2.032407407407407e+00 # 439/216
b52 = -8.000000000000000e+00 # -8
b53 = 7.173489278752436e+00 # 3680/513
b54 = -2.058966861598441e-01 # -845/4104
b61 = -2.962962962962963e-01 # -8/27
b62 = 2.000000000000000e+00 # 2
b63 = -1.381676413255361e+00 # -3544/2565
b64 = 4.529727095516569e-01 # 1859/4104
b65 = -2.750000000000000e-01 # -11/40
r1 = 2.777777777777778e-03 # 1/360
r3 = -2.994152046783626e-02 # -128/4275
r4 = -2.919989367357789e-02 # -2197/75240
r5 = 2.000000000000000e-02 # 1/50
r6 = 3.636363636363636e-02 # 2/55
c1 = 1.157407407407407e-01 # 25/216
c3 = 5.489278752436647e-01 # 1408/2565
c4 = 5.353313840155945e-01 # 2197/4104
c5 = -2.000000000000000e-01 # -1/5
t = a
x = np.array(x0)
h = hmax
T = np.array( [t] )
X = np.array( [x] )
while t < b:
if t + h > b:
h = b - t
k1 = h * f( x, t )
k2 = h * f( x + b21 * k1, t + a2 * h )
k3 = h * f( x + b31 * k1 + b32 * k2, t + a3 * h )
k4 = h * f( x + b41 * k1 + b42 * k2 + b43 * k3, t + a4 * h )
k5 = h * f( x + b51 * k1 + b52 * k2 + b53 * k3 + b54 * k4, t + a5 * h )
k6 = h * f( x + b61 * k1 + b62 * k2 + b63 * k3 + b64 * k4 + b65 * k5, \
t + a6 * h )
r = abs( r1 * k1 + r3 * k3 + r4 * k4 + r5 * k5 + r6 * k6 ) / h
if len( np.shape( r ) ) > 0:
r = max( r )
if r <= tol:
t = t + h
x = x + c1 * k1 + c3 * k3 + c4 * k4 + c5 * k5
T = np.append( T, t )
X = np.append( X, [x], 0 )
h = h * min( max( 0.84 * ( tol / r )**0.25, 0.1 ), 4.0 )
if h > hmax:
h = hmax
elif h < hmin:
raise RuntimeError("Error: Could not converge to the required tolerance %e with minimum stepsize %e." % (tol,hmin))
break
return ( T, X )
but I'm struggling to convert it to a function like the first code, where I can input a system of differential equations. The most confusing part for me, is how can I vectorize everything in the second code without messing things up. In other words, I cannot reproduce the first result using the RKF algorithm. Can anyone point me in the right direction?
I'm not really sure where your problem lies. Setting the not given parameters to w=1; b=0.1 and calling, without changing anything
T, X = rkf( f=V, a=0, b=100, x0=[0,0.2,0,0.3], tol=1e-6, hmax=1e1, hmin=1e-16 )
gives the phase plot
The step sizes grow as the system slows down as
which is the expected behavior for an unfiltered step size controller.

Non-linear least square minimization of 2 variables (different dimension) in python

I have a function of two variables k and T.
If have the value of the function for a number of (k,T) couple. However I do not have the same amount for each. For example I know the values f of the function at 2 T and 3 k:
F(k1,T1) = f1
F(k1,T2) = f2
F(k2,T1) = f3
F(k2,T2) = f4
F(k3,T1) = f5
F(k3,T2) = f6
I also know the form of the function F:
def func(X, a, b, c, omega):
T,k = X # The two variables
n = 1.0 / ( np.exp(omega / T ) - 1.0 )
return a * k * n + b * k**2 * (n + 1.0)
I would like to find the value of a,b,c and omega that minimize the error.
I tried with curve_fit:
k = [k1,k2,k3]
T = [T1,T2]
F[k1,T1] = f1
F[k1,T2] = f2
F[k2,T1] = f3
F[k2,T2] = f4
F[k3,T1] = f5
F[k3,T2] = f6
popt, pcov = curve_fit(func, (T,k), F )
However I get the following error (in my practical case I have 19 k values and 4 T values):
return a * k * n + b * k**2 * (n + 1.0)
ValueError: operands could not be broadcast together with shapes (19,) (4,)
Now if I create an array of higher dimension:
X = np.zeros((4,19,2))
for ii in np.arange(19):
X[0,ii,:] = np.array([T[0],k[ii]])
X[1,ii,:] = np.array([T[1],k[ii]])
X[2,ii,:] = np.array([T[2],k[ii]])
X[3,ii,:] = np.array([T[3],k[ii]])
and pass that:
def func(X, a, b, c, omega):
T = X[:,:,0]
k = X[:,:,1]
n = 1.0 / ( np.exp(omega / T ) - 1.0 )
return a * k * n + b * k**2 * (n + 1.0)
popt, pcov = curve_fit(func, X, F )
then I get the following issue:
minpack.error: Result from function call is not a proper array of floats.
Thank you in advance.
You need an array of pairs of data with the input X (probably your original dataset already looks like that) and the corresponding output array F:
X = np.array([k1,T1],[k1,T2],[k2,T1],[k2,T2],[k3,T1],[k3,T2])
F = [f1,f2,f3,f4,f5,f6]
Then calling the curve_fit function is directly:
popt, pcov = curve_fit(func, (X[:,0],X[:,1]),F)
Alternatively you can use single arrays for the k and T and use them in place of X[:,0] and X[:,1], but note that they should have the same dimensions since each element corresponds with the individual value of k and T of each observation/experiment. In other words, the index in the k or T array tells you the label of the corresponding observation.

Categories

Resources