RK4 Python Explanation - python

I'd like to use an implementation of RK4 I found online for something, but I'm having a bit of difficulty wrapping my head around the implementations I have found online.
For example:
def rk4(f, x0, y0, x1, n):
vx = [0] * (n + 1)
vy = [0] * (n + 1)
h = (x1 - x0) / float(n)
vx[0] = x = x0
vy[0] = y = y0
for i in range(1, n + 1):
k1 = h * f(x, y)
k2 = h * f(x + 0.5 * h, y + 0.5 * k1)
k3 = h * f(x + 0.5 * h, y + 0.5 * k2)
k4 = h * f(x + h, y + k3)
vx[i] = x = x0 + i * h
vy[i] = y = y + (k1 + k2 + k2 + k3 + k3 + k4) / 6
return vx, vy
Could someone please help me understand what exactly the parameters are? If possible, I'd like a more general explanation, but, if being more specific makes it easier to explain, I'm going to be using it specifically for an ideal spring system.

You are asking for the parameters here:
def rk4(f, x0, y0, x1, n):
...
return vx, vy
f is the ODE function, declared as def f(x,y) for the differential equation y'(x)=f(x,y(x)),
(x0,y0) is the initial point and value,
x1 is the end of the integration interval [x0,x1]
n is the number of sub-intervals or integration steps
vx,vx are the computed sample points, vy[k] approximates y(vx[k]).
You can not use this for the spring system, as that code only works for a scalar v. You would need to change it to work with numpy for vector operations.
def rk4(func, x0, y0, x1, n):
y0 = np.array(y0)
f = lambda x,y: np.array(func(x,y))
vx = [0] * (n + 1)
vy = np.zeros( (n + 1,)+y0.shape)
h = (x1 - x0) / float(n)
vx[0] = x = x0
vy[0] = y = y0[:]
for i in range(1, n + 1):
k1 = h * f(x, y)
k2 = h * f(x + 0.5 * h, y + 0.5 * k1)
k3 = h * f(x + 0.5 * h, y + 0.5 * k2)
k4 = h * f(x + h, y + k3)
vx[i] = x = x0 + i * h
vy[i] = y = y + (k1 + 2*(k2 + k3) + k4) / 6
return vx, vy

Related

Multivariate curve fit in python

Can somebody please point me in the right direction...
I need to find the parameters a,b,c,d of two functions:
Y1 = ( (a * X1 + b) * p0 + (c * X2 + d) * p1 ) / (a * X1 + b + c * X2 + d)
Y2 = ( (a * X2 + b) * p2 + (c * X2 + d) * p3 ) / (a * X1 + b + c * X2 + d)
X1, X2 (independent variables) and Y1, Y2 (dependent variables) are observations, i.e. one-dimensional arrays with thousands of entries each.
p0, p1, p2, p3 are known constants (scalars).
I successfully solved the problem with the first function only with a curve-fit (see below), but how do i solve the problem for Y1 and Y2 ?
Thank you.
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
X = [X1,X2]
def fitFunc(X, a,b,c,d):
X1, X2 = X
return ((a * X1 + b) * p0 + (c * X2 + d) * p1) / (a * X1 + b + c * X2 + d)
fitPar, fitCov = curve_fit(fitFunc, X, Y1)
print(fitPar)
One way would be to minimize both your functions together using scipy.optimize.minimze. In the example below, a function residual is passed a, b, c, and d as initial guesses. Using these guesses, Y1 and Y2 are evaluated, then the mean squared error is taken using the data and predicted values of respective functions. The error is returned as the mean error of the two functions. The optimized set of parameters is stored in res as res.x.
import numpy as np
from scipy.optimize import minimize
#p0 = ... known
#p1 = ... known
#p2 = ... known
#p3 = ... known
def Y1(X, a,b,c,d):
X1, X2 = X
return ((a * X1 + b) * p0 + (c * X2 + d) * p1) / (a * X1 + b + c * X2 + d)
def Y2(X, a,b,c,d):
X1, X2 = X
return ((a * X1 + b) * p2 + (c * X2 + d) * p3) / (a * X1 + b + c * X2 + d)
X1 = np.array([X1]) # your X1 array
X2 = np.array([X2]) # your X2 array
X = np.array([X1, X2])
y1_data = np.array([y1_data]) # your y1 data
y2_data = np.array([y2_data]) # your y2 data
def residual(x):
a = x[0]
b = x[1]
c = x[2]
d = x[3]
y1_pred = Y1(X,a,b,c,d)
y2_pred = Y2(X,a,b,c,d)
err1 = np.mean((y1_data - y1_pred)**2)
err2 = np.mean((y2_data - y2_pred)**2)
error = (err1 + err2) / 2
return error
x0 = [1, 1, 1, 1] # Initial guess for a, b, c, and d respectively
res = minimize(residual, x0, method="Nelder-Mead")
print(res.x)

how to pass two arrays into a fuction

I have a function that typically takes in constant args and calculates volatility. I want to pass in a vector of different C's and K's to get an array of volatilities each associated with C[i], K[i]
def vol_calc(S, T, C, K, r, q, sigma):
d1 = (np.log(S / K) + (r - q + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
vega = (1 / np.sqrt(2 * np.pi)) * np.exp(-q * T) * np.sqrt(T) * np.exp((-si.norm.cdf(d1, 0.0, 1.0) ** 2) * 0.5)
tolerance = 0.000001
x0 = sigma
xnew = x0
xold = x0 - 1
while abs(xnew - xold) > tolerance:
xold = xnew
xnew = (xnew - fx - C) / vega
return abs(xnew)
but if I want to pass two arrays without turning into a nested loop, I thought I could just do:
def myfunction(S, T, r, q, sigma):
for x in K,C:
return same size as K,C
but I can't get it to work
How about this?
def vol_calc(S, T, C, K, r, q, sigma):
import numpy as np
output = np.zeros(len(C))
for num, (c, k) in enumerate(zip(C, K)):
d1 = (np.log(S / k) + (r - q + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
vega = (1 / np.sqrt(2 * np.pi)) * np.exp(-q * T) * np.sqrt(T) * np.exp((-si.norm.cdf(d1, 0.0, 1.0) ** 2) * 0.5)
tolerance = 0.000001
x0 = sigma
xnew = x0
xold = x0 - 1
while abs(xnew - xold) > tolerance:
xold = xnew
xnew = (xnew - fx - c) / vega
output[num] = abs(xnew)
return output

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()

Solving system of coupled differential equations using Runge-Kutta in python

This python code can solve one non- coupled differential equation:
import numpy as np
import matplotlib.pyplot as plt
import numba
import time
start_time = time.clock()
#numba.jit()
# A sample differential equation "dy / dx = (x - y**2)/2"
def dydx(x, y):
return ((x - y**2)/2)
# Finds value of y for a given x using step size h
# and initial value y0 at x0.
def rungeKutta(x0, y0, x, h):
# Count number of iterations using step size or
# step height h
n = (int)((x - x0)/h)
# Iterate for number of iterations
y = y0
for i in range(1, n + 1):
"Apply Runge Kutta Formulas to find next value of y"
k1 = h * dydx(x0, y)
k2 = h * dydx(x0 + 0.5 * h, y + 0.5 * k1)
k3 = h * dydx(x0 + 0.5 * h, y + 0.5 * k2)
k4 = h * dydx(x0 + h, y + k3)
# Update next value of y
y = y + (1.0 / 6.0)*(k1 + 2 * k2 + 2 * k3 + k4)
# Update next value of x
x0 = x0 + h
return y
def dplot(start,end,steps):
Y=list()
for x in np.linspace(start,end,steps):
Y.append(rungeKutta(x0, y, x , h))
plt.plot(np.linspace(start,end,steps),Y)
print("Execution time:",time.clock() - start_time, "seconds")
plt.show()
start,end = 0, 10
steps = end* 100
x0 = 0
y = 1
h = 0.002
dplot(start,end,steps)
This code can solve this differential equation:
dydx= (x - y**2)/2
Now I have a system of coupled differential equations:
dydt= (x - y**2)/2
dxdt= x*3 + 3y
How can I implement these two as a system of coupled differential equations in the above code?
Is there any more generalized way for system of n-number of coupled differential equations?
With the help of others, I got to this:
import numpy as np
from math import sqrt
import matplotlib.pyplot as plt
import numba
import time
start_time = time.clock()
a=1
b=1
c=1
d=1
# Equations:
#numba.jit()
#du/dt=V(u,t)
def V(u,t):
x, y, vx, vy = u
return np.array([vy,vx,a*x+b*y,c*x+d*y])
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([1., 0., 1. , 0.]) , 0. , 10. , 100000)
x,y, vx,vy = u.T
# plt.plot(t, x, t,y)
plt.semilogy(t, x, t,y)
plt.grid('on')
print("Execution time:",time.clock() - start_time, "seconds")
plt.show()

Runge-Kutta code not converging with builtin method

I am trying to implement the runge-kutta method to solve a Lotka-Volterra systtem, but the code (bellow) is not working properly. I followed the recomendations that I found in other topics of the StackOverflow, but the results do not converge with the builtin Runge-Kutta method, like rk4 method available in Pylab, for example. Someone could help me?
import matplotlib.pyplot as plt
import numpy as np
from pylab import *
def meurk4( f, x0, t ):
n = len( t )
x = np.array( [ x0 ] * n )
for i in range( n - 1 ):
h = t[i+1] - t[i]
k1 = h * f( x[i], t[i] )
k2 = h * f( x[i] + 0.5 * h * k1, t[i] + 0.5 * h )
k3 = h * f( x[i] + 0.5 * h * k2, t[i] + 0.5 * h )
k4 = h * f( x[i] + h * k3, t[i] + h)
x[i+1] = x[i] + ( k1 + 2 * ( k2 + k3 ) + k4 ) * 6**-1
return x
def model(state,t):
x,y = state
a = 0.8
b = 0.02
c = 0.2
d = 0.004
k = 600
return np.array([ x*(a*(1-x*k**-1)-b*y) , -y*(c - d*x) ]) # corresponds to [dx/dt, dy/dt]
# initial conditions for the system
x0 = 500
y0 = 200
# vector of time
t = np.linspace( 0, 50, 100 )
result = meurk4( model, [x0,y0], t )
print result
plt.plot(t,result)
plt.xlabel('Time')
plt.ylabel('Population Size')
plt.legend(('x (prey)','y (predator)'))
plt.title('Lotka-Volterra Model')
plt.show()
I just updated the code following the comments. So, the function meurk4:
def meurk4( f, x0, t ):
n = len( t )
x = np.array( [ x0 ] * n )
for i in range( n - 1 ):
h = t[i+1] - t[i]
k1 = h * f( x[i], t[i] )
k2 = h * f( x[i] + 0.5 * h * k1, t[i] + 0.5 * h )
k3 = h * f( x[i] + 0.5 * h * k2, t[i] + 0.5 * h )
k4 = h * f( x[i] + h * k3, t[i] + h)
x[i+1] = x[i] + ( k1 + 2 * ( k2 + k3 ) + k4 ) * 6**-1
return x
Becomes now (corrected):
def meurk4( f, x0, t ):
n = len( t )
x = np.array( [ x0 ] * n )
for i in range( n - 1 ):
h = t[i+1] - t[i]
k1 = f( x[i], t[i] )
k2 = f( x[i] + 0.5 * h * k1, t[i] + 0.5 * h )
k3 = f( x[i] + 0.5 * h * k2, t[i] + 0.5 * h )
k4 = f( x[i] + h * k3, t[i] + h)
x[i+1] = x[i] + ( k1 + 2 * ( k2 + k3 ) + k4 ) * (h/6)
return x
Nevertheless, the results is the following:
While the buitin method rk4 (from Pylab) results the following:
So, certainly my code still is not correct, as its results are not the same of the builtin rk4 method. Please, someone can help me?
You are doing a very typical error,see for instance How to pass a hard coded differential equation through Runge-Kutta 4 or here Error in RK4 algorithm in Python
It is either
k2 = f( x+0.5*h*k1, t+0.5*h )
...
x[i+1]=x[i]+(k1+2*(k2+k3)+k4)*(h/6)
or
k2 = h*f( x+0.5*k1, t+0.5*h )
and so on, with x[i+1] as it was, but not both variants at the same time.
Update: A more insidious error is the inferred type of the initial values and in consequence of the array of x vectors. By the original definition, both are integers, and thus
x = np.array( [ x0 ] * n )
creates a list of integer vectors. Thus the update step
x[i+1] = x[i] + ( k1 + 2 * ( k2 + k3 ) + k4 ) * (h/6)
will always round to integer. And since there is a phase where both values fall below 1, the integration stabilizes at zero. Thus modify to
# initial conditions for the system
x0 = 500.0
y0 = 200.0
to avoid that problem.

Categories

Resources