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)
Related
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'm trying to draw a path of a particle starting at (x0,y0) subject to a vortex located at (xv,yv). This is inspired by Lorena Barba's AeroPython. As stated in the lesson, the motion should be concentric circles about a given point.
I first calculate the velocity at point (x0,y0).
def get_velocity_vortex(strength, xv, yv, x0, y0):
u = +strength / (2 * np.pi) * (y0 - yv) / ((x0 - xv)**2 + (y0 - yv)**2)
v = -strength / (2 * np.pi) * (x0 - xv) / ((x0 - xv)**2 + (y0 - yv)**2)
return u, v
I then try to calculate the suceeding positions as follows.
def get_next_pos(x0,y0,u,v,dt):
x_next = x0 + u * dt
y_next = y0 + v * dt
return x_next, y_next
A complete example is as shown.
import numpy as np
import matplotlib.pyplot as plt
strength_vortex = 5.0
# coordinates of vortex
x_vortex = 0.0
y_vortex = 0.0
# coordinates of initial point
x0 = 0.1
y0 = 0.0
dt = 1e-3 # timestep
nt = 150 # number of iterations
# empty arrays for the positions
X = np.zeros(nt)
Y = np.zeros(nt)
# initial positions
X[0], Y[0] = x0, y0
# calculate the path
for i in range(1,nt):
u, v = get_velocity_vortex(
strength_vortex,
x_vortex,
y_vortex,
X[i-1],
Y[i-1]
)
X[i],Y[i] = get_next_pos(X[i-1], Y[i-1], u, v, dt)
# plot the path
plt.scatter(
x_vortex,
y_vortex,
color="red"
)
plt.scatter(
X,Y,
)
plt.xlim(-0.2,0.2)
plt.ylim(-0.2,0.2)
plt.grid()
However, my output does not result in a circle. I suspect that this is due to my get_next_pos function. How can I correct the path? Any help is appreciated. Thanks in advance.
In the center of a 2D-Plane a positive static charge Q is placed with position r_prime. This charge creates a static electrical Field E.
Now i want to place a test particle with charge Q and position vector r in this static E-Field and compute its trajectory using the 4th order Runge-Kutta method.
For the initial conditions
Q = 1, r_prime=(0,0)
q = -1, r = (9, 0), v = (0,0)
one would expect, that the negative charged test particle should move towards the positive charge in the center. Instead i get the following result for the time evolution of the test particles x component:
[9.0,
9.0,
8.999876557604697,
8.99839964155741,
8.992891977287334,
8.979313669171093,
8.95243555913327,
8.906134626441052,
8.83385018027209,
8.729257993736123,
8.587258805422984,
8.405449606446608,
8.186368339303788,
7.940995661159361,
7.694260386250479,
7.493501689700884,
7.420415546859942,
7.604287312065716,
8.226733652039988,
9.498656905483394,
11.60015461031076,
14.621662121713964,
18.56593806599109,
....
The results of the first iteration steps show the correct behavior, but then the particle is strangely repelled to infinity. There must be a major flaw in my implementation of the Runge-Kutta Method, but I checked it several times and I cant find any...
Could someone please take a quick look over my implementation and see if they can find a bug.
"""
Computes the trajectory of a test particle with Charge q with position vector r = R[:2] in a
electrostatic field that is generated by charge Q with fixed position r_prime
"""
import numpy as np
import matplotlib.pyplot as plt
def distance(r, r_prime, n):
"""
computes the euclidean distance to the power n between position x and x_prime
"""
return np.linalg.norm(r - r_prime)**n
def f(R, r_prime, q, Q):
"""
The equations of motion for the particle is given by:
d^2/dt^2 r(t) = F = constants * q * Q * (r - r_prime)/||r - r_prime||^3
To apply the Runge-Kutta-Method we transform the above (constants are set to 1)
two dimensional second order ODE into four one dimensional ODEs:
d/dt r_x = v_x
d/dt r_y = v_y
d/dt v_x = q * Q * (r_x - r_prime_x)/||r - r_prime||^3
d/dt v_y = q * Q * (r_y - r_prime_y)/||r - r_prime||^3 '''
"""
r_x, r_y = R[0], R[1]
v_x, v_y = R[2], R[3]
dist = 1 / distance(np.array([r_x, r_y]), r_prime, 3)
# Right Hand Side of the 1D Odes
f1 = v_x
f2 = v_y
f3 = q * Q * dist * (r_x - r_prime[0])
f4 = q * Q * dist * (r_y - r_prime[1])
return np.array([f1, f2, f3, f4], float)
# Constants for the Simulation
a = 0.0 # t_0
b = 10.0 # t_end
N = 100 # number of iterations
h = (b-a) / N # time step
tpoints = np.arange(a,b+h,h)
# Create lists to store the computed values
xpoints, ypoints = [], []
vxpoints, vypoints = [], []
# Initial Conditions
Q, r_prime = 1, np.array([0,0], float) # charge and position of particle that creates the static E-Field
q, R = -1, np.array([9,0,0,0], float) # charge and its initial position + velocity r=R[:2], v=[2:]
for dt in tpoints:
xpoints.append(R[0])
ypoints.append(R[1])
vxpoints.append(R[2])
vypoints.append(R[3])
# Runge-Kutta-4th Order Method
k1 = dt * f(R, r_prime, q, Q)
k2 = dt * f(R + 0.5 * k1, r_prime, q, Q)
k3 = dt * f(R + 0.5 * k2, r_prime, q, Q)
k4 = dt * f(R + k3, r_prime, q, Q)
R += (k1 + 2*k2 * 2*k3 + k4)
plt.plot(tpoints, xpoints) # should converge to 0
I am trying to solve a system of geodesics orbital equations using python. They are coupled ordinary equations. I've tried different approaches, but they all yielded me a wrong shape (the shape should be some periodic function when plotting r and phi). Any idea on how to do this?
Here are my constants
G = 4.30091252525 * (pow(10, -3)) #Gravitational constant in (parsec*km^2)/(Ms*sec^2)
c = 0.0020053761 #speed of light , AU/sec
M = 170000 #mass of the central body, in solar masses
m = 10 #mass of the orbiting body, in solar masses
rs = 2 * G * M / pow(c, 2) #Schwarzschild radius
Lz= 0.000024 #Angular momemntum
h = Lz / m #Just the constant in equation
E= 1.715488e-007 #energy
And initial conditions are:
Y(0) = rs
Phi(0) = math.pi
Orbital equations
The way I tried to do it:
def rhs(t, u):
Y, phi = u
dY = np.sqrt((E**2 / (m**2 * c**2) - (1 - rs / Y) * (c**2 + h**2 / Y**2)))
dphi = L / Y**2
return [dY, dphi]
Y0 = np.array([rs,math.pi])
sol = solve_ivp(rhs, [1, 1000], Y0, method='Radau', dense_output=True)
It seems like you are looking at the spacial coordinates in an invariant plane of the geodesic equations of an object moving in Schwarzschild gravity.
One can use many different methods, which preserve as much of the underlying geometric structure of the model as possible, like symplectic geometric integrators or perturbation theory. As Lutz Lehmann pointed out in the comments, the default method for 'solve_ivp' uses as default the Dormand-Prince (4)5 stepper that utilizes the extrapolation mode, that is, the order 5 step, with the step size selection driven by the error estimate of the order 4 step.
Warning: your initial condition for Y equals Schwarzschild's radius, so these equations may fail or require special treatment (especially the time component of the equations, which you have not included here!) It may be that you have to switch to different coordinates, that remove the singularity at the even horizon. Moreover, the solutions may not be periodic curves, but quasi-periodic, so they may not close up nicely.
For a quick and dirty treatment, but possibly a fairly accurate one, I would differentiate the first equation
(dr / dtau)^2 = (E2_mc2 - c2) + (2*GM)/r - (h^2)/(r^2) + (r_schw*h^2)/(r^3)
with respect to the proper time tau, then cancel out the first derivative dr / dtau with respect to r on both sides, and end up with an equation with second derivative for the radius r on the left. Then turn this second derivative equation into a pair of first derivative equations for r and its rate of change v, i.e
dphi / dtau = h / (r^2)
dr / dtau = v
dv / dtau = - GM / (r^2) + h^2 / (r^3) - 3*r_schw*(h^2) / (2*r^4)
and calculate from the original equation for r and its first derivative dr / dtau an initial value for the rate of change v = dr / dtau, i.e. I would solve for v the equations with r=r0:
(v0)^2 = (E2_mc2 - c2) + (2*GM)/r0 - (h^2)/(r0^2) + (r_schw*h^2)/(r0^3)
Maybe some kind of python code like this may work:
import math
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import solve_ivp
#from ode_helpers import state_plotter
# u = [phi, Y, V, t] or if time is excluded
# u = [phi, Y, V]
def f(tau, u, param):
E2_mc2, c2, GM, h, r_schw = param
Y = u[1]
f_phi = h / (Y**2)
f_Y = u[2] # this is the dr / dt auxiliary equation
f_V = - GM / (Y**2) + h**2 / (Y**3) - 3*r_schw*(h**2) / (2*Y**4)
#f_time = (E2_mc2 * Y) / (Y - r_schw) # this is the equation of the time coordinate
return [f_phi, f_Y, f_V] # or [f_phi, f_Y, f_V, f_time]
# from the initial value for r = Y0 and given energy E,
# calculate the initial rate of change dr / dtau = V0
def ivp(Y0, param, sign):
E2_mc2, c2, GM, h, r_schw = param
V0 = math.sqrt((E2_mc2 - c2) + (2*GM)/Y0 - (h**2)/(Y0**2) + (r_schw*h**2)/(Y0**3))
return sign*V0
G = 4.30091252525 * (pow(10, -3)) #Gravitational constant in (parsec*km^2)/(Ms*sec^2)
c = 0.0020053761 #speed of light , AU/sec
M = 170000 #mass of the central body, in solar masses
m = 10 #mass of the orbiting body, in solar masses
Lz= 0.000024 #Angular momemntum
h = Lz / m #Just the constant in equation
E= 1.715488e-007 #energy
c2 = c**2
E2_mc2 = (E**2) / (c2*m**2)
GM = G*M
r_schw = 2*GM / c2
param = [E2_mc2, c2, GM, h, r_schw]
Y0 = r_schw
sign = 1 # or -1
V0 = ivp(Y0, param, sign)
tau_span = np.linspace(1, 1000, num=1000)
u0 = [math.pi, Y0, V0]
sol = solve_ivp(lambda tau, u: f(tau, u, param), [1, 1000], u0, t_eval=tau_span)
Double check the equations, mistakes and inaccuracies are possible.
I have implemented the following explicit euler method in python:
def explicit_euler(df, x0, h, N):
"""Solves an ODE IVP using the Explicit Euler method.
Keyword arguments:
df - The derivative of the system you wish to solve.
x0 - The initial value of the system you wish to solve.
h - The step size.
N - The number off steps.
"""
x = np.zeros(N)
x[0] = x0
for i in range(0, N-1):
x[i+1] = x[i] + h * df(x[i])
return x
Following the article on wikipedia I can plot the function and verify that I get the same plot: . I believe that here the method I have written is working correctly.
Next I tried to use it to solve the last system given on this page and instead of the plot shown there I obtain this:
I am not sure why my plot doesn't match the one shown on the webpage. The explicit euler method seems to work fine when I use it to solve systems where the slope doesn't change, but for an oscillating function it never seems to mimic it at all. Not even showing the expected error gain as indicated on the linked webpage. I am not sure what is wrong with the method I have implemented.
Here is the code used for plotting and the derivative:
def g(t):
return -0.5 * np.exp(t * 0.5) * np.sin(5 * t) + 5 * np.exp(t * 0.5)
* np.cos(5 * t)
h = 0.001
x0 = 0
tn = 4
N = int(tn / h)
x = ee.explicit_euler(f, x0, h, N)
t = np.arange(0, tn, h)
fig = plt.figure()
plt.plot(t, x, label="Explicit Euler")
plt.plot(t, (np.exp(0.5 * t) * np.sin(5 * t)), label="Analytical
solution")
#plt.plot(t, np.exp(0.5 * t), label="Analytical solution")
plt.xlabel('Timesteps t')
plt.ylabel('x(t)=e^(0.5*t) * sin(5*t)')
plt.legend()
plt.grid()
plt.show()
Edit:
As requested here is the current equation I am applying the method to:
y'-y=-0.5*e^(t/2)*sin(5t)+5e^(t/2)*cos(5t)
Where y(0)=0.
I would like to make clear however that this behaviour doesn't occur just for this equation but all equations where the slope has a change in sign, or oscillating behaviour.
Edit 2:
Ok thanks. Yes the code below does indeed work. But I have one further question. In the simple example I had for the exponential function, I had defined a method:
def f(x):
return x
for the system f'(x)=x. This gave the output of my first graph which looks correct. I then defined another function:
def k(x):
return cos(x)
for the system f'(x)=cos(x), this does not give expected output. But when I change the function definition to
def k(t, x):
return cos(t)
I get the expected output. If I change my function
def f(t, x):
return t
I get an incorrect output. Am I always actually evaluating the function at a time step and is it just by chance for the system x'=x that at each time step the value is just the value of x?
I had understood that the Euler method used the value of the previously calculated value in order to get the next value. But if I run code for my function k(x)=cos(x), I get output pictured below, which must be incorrect. This now uses the updated code you provided.
def k(t, x):
return np.cos(x)
h = 0.1 # Step size
x0 = (0, 0) # Initial point of iteration
tn = 10 # Time step to iterate to
N = int(tn / h) # Number of steps
x = ee.explicit_euler(k, x0, h, N)
t = np.arange(0, tn, h)
The problem is that you have incorrectly raised the function g, you want to solve the equation:
From where we observe that:
y' = y -0.5*e^(t/2)*sin(5t)+5e^(t/2)*cos(5t)
Then we define the function f(t, y) = y -0.5*e^(t/2)*sin(5t)+5e^(t/2)*cos(5t) as:
def f(t, y):
return y -0.5 * np.exp(t * 0.5) * np.sin(5 * t) + 5 * np.exp(t * 0.5) * np.cos(5 * t)
The initial point of iteration is f0=(t(0), y(0)):
f0 = (0, 0)
Then from Euler's equations:
def explicit_euler(df, x0, h, N):
"""Solves an ODE IVP using the Explicit Euler method.
Keyword arguments:
df - The derivative of the system you wish to solve.
x0 - The initial value of the system you wish to solve.
h - The step size.
N - The number off steps.
"""
x = np.zeros(N)
t, x[0] = x0
for i in range(0, N-1):
x[i+1] = x[i] + h * df(t ,x[i])
t += h
return x
Complete Code:
def explicit_euler(df, x0, h, N):
"""Solves an ODE IVP using the Explicit Euler method.
Keyword arguments:
df - The derivative of the system you wish to solve.
x0 - The initial value of the system you wish to solve.
h - The step size.
N - The number off steps.
"""
x = np.zeros(N)
t, x[0] = x0
for i in range(0, N-1):
x[i+1] = x[i] + h * df(t ,x[i])
t += h
return x
def df(t, y):
return -0.5 * np.exp(t * 0.5) * np.sin(5 * t) + 5 * np.exp(t * 0.5) * np.cos(5 * t) + y
h = 0.001
f0 = (0, 0)
tn = 4
N = int(tn / h)
x = explicit_euler(df, f0, h, N)
t = np.arange(0, tn, h)
fig = plt.figure()
plt.plot(t, x, label="Explicit Euler")
plt.plot(t, (np.exp(0.5 * t) * np.sin(5 * t)), label="Analytical solution")
#plt.plot(t, np.exp(0.5 * t), label="Analytical solution")
plt.xlabel('Timesteps t')
plt.ylabel('x(t)=e^(0.5*t) * sin(5*t)')
plt.legend()
plt.grid()
plt.show()
Screenshot:
Dump y' and what is on the right side is what you should place in the df function.
We will modify the variables to maintain the same standard for the variables, and will y be the dependent variable, and t the independent variable.
Equation 2: In this case the equation f'(x)=cos(x) will be rewritten to:
y'=cos(t)
Then:
def df(t, y):
return np.cos(t)
In conclusion, if we have an equation of the following form:
y' = f(t, y)
Then:
def df(t, y):
return f(t, y)