Related
I am trying to solve this equation using Runge Kutta 4th order:
applying d2Q/dt2=F(y,x,v) and dQ/dt=u Q=y in my program.
I try to run the code but i get this error:
Traceback (most recent call last):
File "C:\Users\Egw\Desktop\Analysh\Askhsh1\asdasda.py", line 28, in <module>
k1 = F(y, u, x) #(x, v, t)
File "C:\Users\Egw\Desktop\Analysh\Askhsh1\asdasda.py", line 13, in F
return ((Vo/L -(R0/L)*u -(R1/L)*u**3 - y*(1/L*C)))
OverflowError: (34, 'Result too large')
I tried using the decimal library but I still couldnt make it work properly.I might have not used it properly tho.
My code is this one:
import numpy as np
from math import pi
from numpy import arange
from matplotlib.pyplot import plot, show
#parameters
R0 = 200
R1 = 250
L = 15
h = 0.002
Vo=1000
C=4.2*10**(-6)
t=0.93
def F(y, u, x):
return ((Vo/L -(R0/L)*u -(R1/L)*u**3 - y*(1/L*C)))
xpoints = arange(0,t,h)
ypoints = []
upoints = []
y = 0.0
u = Vo/L
for x in xpoints:
ypoints.append(y)
upoints.append(u)
m1 = u
k1 = F(y, u, x) #(x, v, t)
m2 = h*(u + 0.5*k1)
k2 = (h*F(y+0.5*m1, u+0.5*k1, x+0.5*h))
m3 = h*(u + 0.5*k2)
k3 = h*F(y+0.5*m2, u+0.5*k2, x+0.5*h)
m4 = h*(u + k3)
k4 = h*F(y+m3, u+k3, x+h)
y += (m1 + 2*m2 + 2*m3 + m4)/6
u += (k1 + 2*k2 + 2*k3 + k4)/6
plot(xpoints, upoints)
show()
plot(xpoints, ypoints)
show()
I expected to get the plots of u and y against t.
Turns out I messed up with the equations I was using for Runge Kutta
The correct code is the following:
import numpy as np
from math import pi
from numpy import arange
from matplotlib.pyplot import plot, show
#parameters
R0 = 200
R1 = 250
L = 15
h = 0.002
Vo=1000
C=4.2*10**(-6)
t0=0
#dz/dz
def G(x,y,z):
return Vo/L -(R0/L)*z -(R1/L)*z**3 - y/(L*C)
#dy/dx
def F(x,y,z):
return z
t = np.arange(t0, 0.93, h)
x = np.zeros(len(t))
y = np.zeros(len(t))
z = np.zeros(len(t))
y[0] = 0.0
z[0] = 0
for i in range(1, len(t)):
k0=h*F(x[i-1],y[i-1],z[i-1])
l0=h*G(x[i-1],y[i-1],z[i-1])
k1=h*F(x[i-1]+h*0.5,y[i-1]+k0*0.5,z[i-1]+l0*0.5)
l1=h*G(x[i-1]+h*0.5,y[i-1]+k0*0.5,z[i-1]+l0*0.5)
k2=h*F(x[i-1]+h*0.5,y[i-1]+k1*0.5,z[i-1]+l1*0.5)
l2=h*G(x[i-1]+h*0.5,y[i-1]+k1*0.5,z[i-1]+l1*0.5)
k3=h*F(x[i-1]+h,y[i-1]+k2,z[i-1]+l2)
l3 = h * G(x[i - 1] + h, y[i - 1] + k2, z[i - 1] + l2)
y[i]=y[i-1]+(k0+2*k1+2*k2+k3)/6
z[i] = z[i - 1] + (l0 + 2 * l1 + 2 * l2 + l3) / 6
Q=y
I=z
plot(t, Q)
show()
plot(t, I)
show()
If I may draw your attention to these 4 lines
m1 = u
k1 = F(y, u, x) #(x, v, t)
m2 = h*(u + 0.5*k1)
k2 = (h*F(y+0.5*m1, u+0.5*k1, x+0.5*h))
You should note a fundamental structural difference between the first two lines and the second pair of lines.
You need to multiply with the step size h also in the first pair.
The next problem is the step size and the cubic term. It contributes a term of size 3*(R1/L)*u^2 ~ 50*u^2 to the Lipschitz constant. In the original IVP per the question with u=Vo/L ~ 70 this term is of size 2.5e+5. To compensate only that term to stay in the stability region of the method, the step size has to be smaller 1e-5.
In the corrected initial conditions with u=0 at the start the velocity u remains below 0.001 so the cubic term does not determine stability, this is now governed by the last term contributing a Lipschitz term of 1/sqrt(L*C) ~ 125. The step size for stability is now 0.02, with 0.002 one can expect quantitatively useful results.
You can use decimal libary for more precision (handle more digits), but it's kind of annoying every value should be the same class (decimal.Decimal).
For example:
import numpy as np
from math import pi
from numpy import arange
from matplotlib.pyplot import plot, show
# Import decimal.Decimal as D
import decimal
from decimal import Decimal as D
# Precision
decimal.getcontext().prec = 10_000_000
#parameters
# Every value should be D class (decimal.Decimal class)
R0 = D(200)
R1 = D(250)
L = D(15)
h = D(0.002)
Vo = D(1000)
C = D(4.2*10**(-6))
t = D(0.93)
def F(y, u, x):
# Decomposed for use D
a = D(Vo/L)
b = D(-(R0/L)*u)
c = D(-(R1/L)*u**D(3))
d = D(-y*(D(1)/L*C))
return ((a + b + c + d ))
xpoints = arange(0,t,h)
ypoints = []
upoints = []
y = D(0.0)
u = D(Vo/L)
for x in xpoints:
ypoints.append(y)
upoints.append(u)
m1 = u
k1 = F(y, u, x) #(x, v, t)
m2 = (h*(u + D(0.5)*k1))
k2 = (h*F(y+D(0.5)*m1, u+D(0.5)*k1, x+D(0.5)*h))
m3 = h*(u + D(0.5)*k2)
k3 = h*F(y+D(0.5)*m2, u+D(0.5)*k2, x+D(0.5)*h)
m4 = h*(u + k3)
k4 = h*F(y+m3, u+k3, x+h)
y += (m1 + D(2)*m2 + D(2)*m3 + m4)/D(6)
u += (k1 + D(2)*k2 + D(2)*k3 + k4)/D(6)
plot(xpoints, upoints)
show()
plot(xpoints, ypoints)
show()
But even with ten million of precision I still get an overflow error. Check the components of the formula, their values are way too high. You can increase precision for handle them, but you'll notice it takes time to calculate them.
Problem implementation using scipy.integrate.odeint and scipy.integrate.solve_ivp.
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint, solve_ivp
# Input data initial conditions
ti = 0.0
tf = 0.5
N = 100000
h = (tf-ti)/N
# Initial conditions
u0 = 0.0
Q0 = 0.0
t_span = np.linspace(ti,tf,N)
r0 = np.array([Q0,u0])
# Parameters
R0 = 200
R1 = 250
L = 15
C = 4.2*10**(-6)
V0 = 1000
# Systems of First Order Equations
# This function is used with odeint, as specified in the documentation for scipy.integrate.odeint
def f(r,t,R0,R1,L,C,V0):
Q,u = r
ode1 = u
ode2 = -((R0/L)*u)-((R1/L)*u**3)-((1/(L*C))*Q)+(V0/L)
return np.array([ode1,ode2])
# This function is used in our 4Order Runge-Kutta implementation and in scipy.integrate.solve_ivp
def F(t,r,R0,R1,L,C,V0):
Q,u = r
ode1 = u
ode2 = -((R0/L)*u)-((R1/L)*u**3)-((1/(L*C))*Q)+(V0/L)
return np.array([ode1,ode2])
# Resolution with oedint
sol_1 = odeint(f,r0,t_span,args=(R0,R1,L,C,V0))
sol_2 = solve_ivp(fun=F,t_span=(ti,tf), y0=r0, method='LSODA',args=(R0,R1,L,C,V0))
Q_odeint, u_odeint = sol_1[:,0], sol_1[:,1]
Q_solve_ivp, u_solve_ivp = sol_2.y[0,:], sol_2.y[1,:]
# Figures
plt.figure(figsize=[30.0,10.0])
plt.subplot(3,1,1)
plt.grid(color = 'red',linestyle='--',linewidth=0.4)
plt.plot(t_span,Q_odeint,'r',t_span,u_odeint,'b')
plt.xlabel('t(s)')
plt.ylabel('Q(t), u(t)')
plt.subplot(3,1,2)
plt.plot(sol_2.t,Q_solve_ivp,'g',sol_2.t,u_solve_ivp,'y')
plt.grid(color = 'yellow',linestyle='--',linewidth=0.4)
plt.xlabel('t(s)')
plt.ylabel('Q(t), u(t)')
plt.subplot(3,1,3)
plt.plot(Q_solve_ivp,u_solve_ivp,'green')
plt.grid(color = 'yellow',linestyle='--',linewidth=0.4)
plt.xlabel('Q(t)')
plt.ylabel('u(t)')
plt.show()
Runge-Kutta 4th
# Code development of Runge-Kutta 4 Order
# Parameters
R0 = 200
R1 = 250
L = 15
C = 4.2*10**(-6)
V0 = 1000
# Input data initial conditions #
ti = 0.0
tf = 0.5
N = 100000
h = (tf-ti)/N
# Initial conditions
u0 = 0.0
Q0 = 0.0
# First order ordinary differential equations
def f1(t,Q,u):
return u
def f2(t,Q,u):
return -((R0/L)*u)-((R1/L)*u**3)-((1/(L*C))*Q)+(V0/L)
t = np.zeros(N); Q = np.zeros(N); u = np.zeros(N)
t[0] = ti
Q[0] = Q0
u[0] = u0
for i in range(0,N-1,1):
k1 = h*f1(t[i],Q[i],u[i])
l1 = h*f2(t[i],Q[i],u[i])
k2 = h*f1(t[i]+(h/2),Q[i]+(k1/2),u[i]+(l1/2))
l2 = h*f2(t[i]+(h/2),Q[i]+(k1/2),u[i]+(l1/2))
k3 = h*f1(t[i]+(h/2),Q[i]+(k2/2),u[i]+(l2/2))
l3 = h*f2(t[i]+(h/2),Q[i]+(k2/2),u[i]+(l2/2))
k4 = h*f1(t[i]+h,Q[i]+k3,u[i]+l3)
l4 = h*f2(t[i]+h,Q[i]+k3,u[i]+l3)
Q[i+1] = Q[i] + ((k1+2*k2+2*k3+k4)/6)
u[i+1] = u[i] + ((l1+2*l2+2*l3+l4)/6)
t[i+1] = t[i] + h
plt.figure(figsize=[20.0,10.0])
plt.subplot(1,2,1)
plt.plot(t,Q_solve_ivp,'r',t,Q_odeint,'y',t,Q,'b')
plt.grid(color = 'yellow',linestyle='--',linewidth=0.4)
plt.xlabel('t(s)')
plt.ylabel(r'$Q(t)_{Odeint}$, $Q(t)_{RK4}$')
plt.subplot(1,2,2)
plt.plot(t,Q_solve_ivp,'g',t,Q_odeint,'y',t,Q,'b')
plt.grid(color = 'yellow',linestyle='--',linewidth=0.4)
plt.xlabel('t(s)')
plt.ylabel(r'$Q(t)_{solve_ivp}$, $Q(t)_{RK4}$')
I'm trying to create a shallow simulation in 1d (sort of like this), and I've been experimenting with this guide to 2d Shallow Water Equations. However, I don't understand the math all too well, and was wondering if anybody could help me figure out a way to convert this to a 1d system. Any tips or direction would be useful. Thanks.
Here is my code thus far (pretty much all copy/pasted from tutorial):
import numpy
from matplotlib import pyplot
from numba import jit
pyplot.rcParams['font.family'] = 'serif'
pyplot.rcParams['font.size'] = 16
# Update Eta field
#jit(nopython=True) # use Just-In-Time (JIT) Compilation for C-performance
def update_eta_2D(eta, M, N, dx, dy, dt, nx, ny):
# loop over spatial grid
for i in range(1,nx-1):
for j in range(1,ny-1):
# compute spatial derivatives
dMdx = (M[j,i+1] - M[j,i-1]) / (2. * dx)
dNdy = (N[j+1,i] - N[j-1,i]) / (2. * dy)
# update eta field using leap-frog method
eta[j, i] = eta[j, i] - dt * (dMdx + dNdy)
# apply Neumann boundary conditions for eta at all boundaries
eta[0,:] = eta[1,:]
eta[-1,:] = eta[-2,:]
eta[:,0] = eta[:,1]
eta[:,-1] = eta[:,-2]
return eta
# Update M field
#jit(nopython=True) # use Just-In-Time (JIT) Compilation for C-performance
def update_M_2D(eta, M, N, D, g, h, alpha, dx, dy, dt, nx, ny):
# compute argument 1: M**2/D + 0.5 * g * eta**2
arg1 = M**2 / D
# compute argument 2: M * N / D
arg2 = M * N / D
# friction term
fric = g * alpha**2 * M * numpy.sqrt(M**2 + N**2) / D**(7./3.)
# loop over spatial grid
for i in range(1,nx-1):
for j in range(1,ny-1):
# compute spatial derivatives
darg1dx = (arg1[j,i+1] - arg1[j,i-1]) / (2. * dx)
darg2dy = (arg2[j+1,i] - arg2[j-1,i]) / (2. * dy)
detadx = (eta[j,i+1] - eta[j,i-1]) / (2. * dx)
# update M field using leap-frog method
M[j, i] = M[j, i] - dt * (darg1dx + darg2dy + g * D[j,i] * detadx + fric[j,i])
return M
# Update N field
#jit(nopython=True) # use Just-In-Time (JIT) Compilation for C-performance
def update_N_2D(eta, M, N, D, g, h, alpha, dx, dy, dt, nx, ny):
# compute argument 1: M * N / D
arg1 = M * N / D
# compute argument 2: N**2/D + 0.5 * g * eta**2
arg2 = N**2 / D
# friction term
fric = g * alpha**2 * N * numpy.sqrt(M**2 + N**2) / D**(7./3.)
# loop over spatial grid
for i in range(1,nx-1):
for j in range(1,ny-1):
# compute spatial derivatives
darg1dx = (arg1[j,i+1] - arg1[j,i-1]) / (2. * dx)
darg2dy = (arg2[j+1,i] - arg2[j-1,i]) / (2. * dy)
detady = (eta[j+1,i] - eta[j-1,i]) / (2. * dy)
# update N field using leap-frog method
N[j, i] = N[j, i] - dt * (darg1dx + darg2dy + g * D[j,i] * detady + fric[j,i])
return N
# 2D Shallow Water Equation code with JIT optimization
# -------------------------------------------------------
def Shallow_water_2D(eta0, M0, N0, h, g, alpha, nt, dx, dy, dt, X, Y):
"""
Computes and returns the discharge fluxes M, N and wave height eta from
the 2D Shallow water equation using the FTCS finite difference method.
Parameters
----------
eta0 : numpy.ndarray
The initial wave height field as a 2D array of floats.
M0 : numpy.ndarray
The initial discharge flux field in x-direction as a 2D array of floats.
N0 : numpy.ndarray
The initial discharge flux field in y-direction as a 2D array of floats.
h : numpy.ndarray
Bathymetry model as a 2D array of floats.
g : float
gravity acceleration.
alpha : float
Manning's roughness coefficient.
nt : integer
Number fo timesteps.
dx : float
Spatial gridpoint distance in x-direction.
dy : float
Spatial gridpoint distance in y-direction.
dt : float
Time step.
X : numpy.ndarray
x-coordinates as a 2D array of floats.
Y : numpy.ndarray
y-coordinates as a 2D array of floats.
Returns
-------
eta : numpy.ndarray
The final wave height field as a 2D array of floats.
M : numpy.ndarray
The final discharge flux field in x-direction as a 2D array of floats.
N : numpy.ndarray
The final discharge flux field in y-direction as a 2D array of floats.
"""
# Copy fields
eta = eta0.copy()
M = M0.copy()
N = N0.copy()
# Compute total thickness of water column D
D = eta + h
# Estimate number of grid points in x- and y-direction
ny, nx = eta.shape
# Define the locations along a gridline.
x = numpy.linspace(0, nx*dx, num=nx)
y = numpy.linspace(0, ny*dy, num=ny)
# Plot the initial wave height fields eta and bathymetry model
fig = pyplot.figure(figsize=(10., 6.))
cmap = 'seismic'
pyplot.tight_layout()
extent = [numpy.min(x), numpy.max(x),numpy.min(y), numpy.max(y)]
# Plot bathymetry model
topo = pyplot.imshow(numpy.flipud(-h), cmap=pyplot.cm.gray, interpolation='nearest', extent=extent)
# Plot wave height field at current time step
im = pyplot.imshow(numpy.flipud(eta), extent=extent, interpolation='spline36', cmap=cmap, alpha=.75, vmin = -0.4, vmax=0.4)
pyplot.xlabel('x [m]')
pyplot.ylabel('y [m]')
cbar = pyplot.colorbar(im)
cbar1 = pyplot.colorbar(topo)
pyplot.gca().invert_yaxis()
cbar.set_label(r'$\eta$ [m]')
cbar1.set_label(r'$-h$ [m]')
# activate interactive plot
pyplot.ion()
pyplot.show(block=False)
# write wave height field and bathymetry snapshots every nsnap time steps to image file
nsnap = 50
snap_count = 0
# Loop over timesteps
for n in range(nt):
# 1. Update Eta field
# -------------------
eta = update_eta_2D(eta, M, N, dx, dy, dt, nx, ny)
# 2. Update M field
# -----------------
M = update_M_2D(eta, M, N, D, g, h, alpha, dx, dy, dt, nx, ny)
# 3. Update N field
# -----------------
N = update_N_2D(eta, M, N, D, g, h, alpha, dx, dy, dt, nx, ny)
# 4. Compute total water column D
# -------------------------------
D = eta + h
# update wave height field eta
if (n % nsnap) == 0:
im.set_data(eta)
fig.canvas.draw()
# write snapshots to Tiff files
name_snap = "image_out/Shallow_water_2D_" + "%0.*f" %(0,numpy.fix(snap_count+1000)) + ".tiff"
pyplot.savefig(name_snap, format='tiff', bbox_inches='tight', dpi=125)
snap_count += 1
return eta, M, N
Lx = 100.0 # width of the mantle in the x direction []
Ly = 100.0 # thickness of the mantle in the y direction []
nx = 401 # number of points in the x direction
ny = 401 # number of points in the y direction
dx = Lx / (nx - 1) # grid spacing in the x direction []
dy = Ly / (ny - 1) # grid spacing in the y direction []
# Define the locations along a gridline.
x = numpy.linspace(0.0, Lx, num=nx)
y = numpy.linspace(0.0, Ly, num=ny)
# Define initial eta, M, N
X, Y = numpy.meshgrid(x,y) # coordinates X,Y required to define eta, h, M, N
# Define constant ocean depth profile h = 50 m
h = 50 * numpy.ones_like(X)
# Define initial eta Gaussian distribution [m]
eta0 = 0.5 * numpy.exp(-((X-50)**2/10)-((Y-50)**2/10))
# Define initial M and N
M0 = 100. * eta0
N0 = 0. * M0
# define some constants
g = 9.81 # gravity acceleration [m/s^2]
alpha = 0.025 # friction coefficient for natural channels in good condition
# Maximum wave propagation time [s]
Tmax = 6.
dt = 1/4500.
nt = (int)(Tmax/dt)
# Compute eta, M, N fields
eta, M, N = Shallow_water_2D(eta0, M0, N0, h, g, alpha, nt, dx, dy, dt, X, Y)
If all you want is the 1D version of the model:
I am revisiting a school project, which I did not complete to my satisfaction. Namely, I wrote an algorithm that takes an ALMOST arbitrary size set of equations and solves them iteratively. The problem being the "almost" part. Essentially, it must have at least two equations, and will not solve for a single one. This is because, I believe, that I don't understand how to use positional arguments correctly.
Below, in the main method, I define two functions y_prime and z_prime. If I pass them both, I get a beautiful graph of my solutions. But, if I only pass y_prime along with its initial conditions and the solution vector to the rungekutta() function, things go haywire:
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
def rungekutta(dt, y, t, *funcs):
"""
The following code was written in order to
reproduce the classic 4th order Runge-Kutta numerical
method of solving a system of differential equations.
The aim was to not only apply this to the budworm deforestation
model developed by Ludwig et al, but also to create an algorithm
that is generic enough to accept a wide range of ODEs and
systems of ODEs.
:param dt: time step "Delta t"
:param y: The solution vector at the last time step
:param t: The time at the last time step
:param funcs: the vector field dy/dt = f(t,y)
:return: The solution vector for the next time step
"""
k1 = [dt * f(*y, t) for f in funcs]
args = [y_n + 0.5 * k_1 for y_n, k_1 in zip((*y, t), (*k1, dt))]
k2 = [dt * f(*args) for f in funcs]
args = [y_n + 0.5 * k_2 for y_n, k_2 in zip((*y, t), (*k2, dt))]
k3 = [dt * f(*args) for f in funcs]
args = [y_n + k_3 for y_n, k_3 in zip((*y, t), (*k3, dt))]
k4 = [dt * f(*args) for f in funcs]
return [y_n + (k_1 + 2 * k_2 + 2 * k_3 + k_4) / 6 for y_n, k_1, k_2, k_3, k_4 in
zip(y, k1, k2, k3, k4)]
if __name__ == '__main__':
def y_prime(y, z, t):
return -t * y
def z_prime(y, z, t):
return z
t_0 = -10
t_n = 10
dt = .05
steps = int((t_n - t_0) / dt)
y_soln = [0] * steps
z_soln = [0] * steps
time = np.arange(t_0, t_n, dt)
y_soln[0] = 1.928749848e-22
z_soln[0] = .0000453999297625
for i in np.arange(1, steps):
y_soln[i] = rungekutta(dt, y_soln[i-1], time[i-1], y_prime)
The first error I received, when trying to pass a single equation was:
Traceback (most recent call last):
File "C:/Users/wesle/PycharmProjects/Budworms/RK4v2.py", line 57, in <module>
y_soln[i] = rungekutta(dt, y_soln[i-1], time[i-1], y_prime, z_prime)
File "C:/Users/wesle/PycharmProjects/Budworms/RK4v2.py", line 23, in rungekutta
k1 = [dt * f(*y, t) for f in funcs]
File "C:/Users/wesle/PycharmProjects/Budworms/RK4v2.py", line 23, in <listcomp>
k1 = [dt * f(*y, t) for f in funcs]
TypeError: y_prime() argument after * must be an iterable, not float
This was because, I think, I have "y_soln" as a positional argument, but now there is only one and it is no longer iterable. So, I made it a tuple of 1 when I passed it in the main method:
for i in np.arange(1, steps):
y_soln[i] = rungekutta(dt, (y_soln[i-1],), time[i-1], y_prime)
That bit me in the butt, however, because now I am passing a tuple into my y_prime equation, when what it really needs is a float:
Traceback (most recent call last):
File "C:/Users/wesle/PycharmProjects/Budworms/RK4v2.py", line 57, in <module>
y_soln[i] = rungekutta(dt, (y_soln[i-1],), time[i-1], y_prime)
File "C:/Users/wesle/PycharmProjects/Budworms/RK4v2.py", line 23, in rungekutta
k1 = [dt * f(*y, t) for f in funcs]
File "C:/Users/wesle/PycharmProjects/Budworms/RK4v2.py", line 23, in <listcomp>
k1 = [dt * f(*y, t) for f in funcs]
File "C:/Users/wesle/PycharmProjects/Budworms/RK4v2.py", line 38, in y_prime
return -t * y
TypeError: can't multiply sequence by non-int of type 'numpy.float64'
My only work-around so far has been to solve an extra, random equation like $y= y'$ in addition to whatever equation I'm interested in. This seems pretty inefficient though.
So, it seems like I'm damned if I do, or damned if I don't. Is there any remedy to this?
EDIT If you want to see the code actually work, replace this:
for i in np.arange(1, steps):
y_soln[i] = rungekutta(dt, (y_soln[i-1],), time[i-1], y_prime)
with the instance where I pass both equations and their solution vectors to the function:
for i in np.arange(1, steps):
y_soln[i], z_soln[i] = rungekutta(dt, (y_soln[i-1], z_soln[i-1]), time[i-1], y_prime, z_prime)
My solution ended up being to convert all lists to numpy arrays, which allowed me to take advantage of the built in element-wise scalar addition and multiplication. This made computing the "k" values much less cumbersome and convoluted:
def rk4(dt, t, field, y_0):
"""
:param dt: float - the timestep
:param t: array - the time mesh
:param field: method - the vector field y' = f(t, y)
:param y_0: array - contains initial conditions
:return: ndarray - solution
"""
# Initialize solution matrix. Each row is the solution to the system
# for a given time step. Each column is the full solution for a single
# equation.
y = np.asarray(len(t) * [y_0])
for i in np.arange(len(t) - 1):
k1 = dt * field(t[i], y[i])
k2 = dt * field(t[i] + 0.5 * dt, y[i] + 0.5 * k1)
k3 = dt * field(t[i] + 0.5 * dt, y[i] + 0.5 * k2)
k4 = dt * field(t[i] + dt, y[i] + k3)
y[i + 1] = y[i] + (k1 + 2 * k2 + 2 * k3 + k4) / 6
return y
I've written a code which looks at projectile motion of an object with drag. I'm using odeint from scipy to do the forward Euler method. The integration runs until a time limit is reached. I would like to stop integrating either when this limit is reached or when the output for ry = 0 (i.e. the projectile has landed).
def f(y, t):
# takes vector y(t) and time t and returns function y_dot = F(t,y) as a vector
# y(t) = [vx(t), vy(t), rx(t), ry(t)]
# magnitude of velocity calculated
v = np.sqrt(y[0] ** 2 + y[1] **2)
# define new drag constant k
k = cd * rho * v * A / (2 * m)
return [-k * y[0], -k * y[1] - g, y[0], y[1]]
def solve_f(v0, ang, h0, tstep, tmax=1000):
# uses the forward Euler time integration method to solve the ODE using f function
# create vector for time steps based on args
t = np.linspace(0, tmax, tmax / tstep)
# convert angle from degrees to radians
ang = ang * np.pi / 180
return odeint(f, [v0 * np.cos(ang), v0 * np.sin(ang), 0, h0], t)
solution = solve_f(v0, ang, h0, tstep)
I've tried several loops and similar to try to stop integrating when ry = 0. And found this question below but have not been able to implement something similar with odeint. solution[:,3] is the output column for ry. Is there a simple way to do this with odeint?
Does scipy.integrate.ode.set_solout work?
Checkout scipy.integrate.ode here. It is more flexible than odeint and helps with what you want to do.
A simple example using a vertical shot, integrated until it touches ground:
from scipy.integrate import ode, odeint
import scipy.constants as SPC
def f(t, y):
return [y[1], -SPC.g]
v0 = 10
y0 = 0
r = ode(f)
r.set_initial_value([y0, v0], 0)
dt = 0.1
while r.successful() and r.y[0] >= 0:
print('time: {:.3f}, y: {:.3f}, vy: {:.3f}'.format(r.t + dt, *r.integrate(r.t + dt)))
Each time you call r.integrate, r will store current time and y value. You can pass them to a list if you want to store them.
Let's solve this as the boundary value problem that it is. We have the conditions x(0)=0, y(0)=h0, vx(0)=0, vy(0)=0 and y(T)=0. To have a fixed integration interval, set t=T*s, which means that dx/ds=T*dx/dt=T*vx etc.
def fall_ode(t,u,p):
vx, vy, rx, ry = u
T = p[0]
# magnitude of velocity calculated
v = np.hypot(vx, vy)
# define new drag constant k
k = cd * rho * v * A / (2 * m)
return np.array([-k * vx, -k * vy - g, vx, vy])*T
def solve_fall(v0, ang, h0):
# convert angle from degrees to radians
ang = ang * np.pi / 180
vx0, vy0 = v0*np.cos(ang), v0*np.sin(ang)
def fall_bc(y0, y1, p): return [ y0[0]-vx0, y0[1]-vy0, y0[2], y0[3]-h0, y1[3] ]
t_init = [0,1]
u_init = [[0,0],[0,0],[0,0], [h0,0]]
p_init = [1]
res = solve_bvp(fall_ode, fall_bc, t_init, u_init, p_init)
print res.message
if res.success:
print "time to ground: ", res.p[0]
# res.sol is a dense output, evaluation interpolates the computed values
return res.sol
sol = solve_fall(300, 30, 20)
s = np.linspace(0,1,501)
u = sol(s)
vx, vy, rx, ry = u
plt.plot(rx, ry)
I am currently using Python to solve a function with the rk method. r8_rkf45 is a file, which helps to plot the function (http://people.sc.fsu.edu/~jburkardt/py_src/rkf45/rkf45.py).
import numpy as np
import matplotlib.pyplot as plt
from numpy import zeros, linspace, exp, sqrt
from rkf45 import *
from r8_rkf45 import *
def rungekutta(ode2, x0, t, n):
n = 200
z = zeros(n)
a = zeros(n)
f = ode2
neqn = 1
abserr = sqrt(finfo(double).eps)
relerr = sqrt(finfo(double).eps)
flag = 1
t_start = 0.0
t_stop = 10.0
t_out = t = 0.0
y = np.array([0.0])
yp[t, y] = ode2[t, y]
for i_step in xrange(0, n - 1):
t = ((n - i_step + 1) * t_start + (i_step - 1) * t_stop) / (n)
t_out = ((n - i_step) * t_start + (i_step) * t_stop) / (n)
y, yp, t = r8_rkf45(ode2, neqn, y, yp, t, t_out, relerr, abserr, flag)
z[i_step - 1] = t
a[i_step - 1] = y
def ode2(x0, t):
alpha = -1
xp = -alpha * x0
return xp
def main():
n = 200
c, b = (0.0, 10.0)
x0 = 1.0
t = linspace(c, b, n)
y = np.array([0.0])
yp[t, y] = ode2[t, y]
plt.plot()
result_rungekutta = rungekutta(yp, x0, t, n)
plt.plot(t, result_rungekutta, "r")
plt.xlabel(t)
plt.ylabel(xp)
plt.legend("Runge-Kutta")
plt.show()
if __name__ == "__main__":
main()
But I still get a Traceback:
Traceback (most recent call last):
File "C:/Python27/idea.py", line 50, in <module>
main()
File "C:/Python27/idea.py", line 40, in main
yp [t,y]= ode2 [t, y]
TypeError: 'function' object has no attribute '__getitem__'
What am I doing wrong?
ode2 is a function, not a list (or other object that has members that can be accessed via indices). Try, yp [t,y]= ode2(t, y)
You need to call a function with ():
yp [t,y]= ode2(t, y)