Solve_ivp output, into orbital plot? - python

I'm trying to plot an orbit of a moon around Jupiter using gravitational acceleration.
I cannot seem to determine how to use the solve_ivp function appropriately. Something is just not clicking... I have created ODEs for a moon, related to Jupiter at the origin.
year = np.arange(0,31536000,3600)
G = 6.67408e-11
jupiter_xpos = 0
jupiter_ypos = 0
jupiter_vel = (0,0)
jupiter_mass = 1.89819e27
Io_orbit = 421700000
Io_xpos = -421700000
Io_ypos = 0
Io_xvel = 0
Io_yvel = -1773400
Io_mass = 8.9319e22
Io = [Io_xpos,Io_xvel,Io_ypos,Io_yvel]
def ode(Moon,t_max):
#Moon[0,1,2,3]=[x,v_x,y,v_y]
output=[0,0,0,0]
R = ((Moon[0]**2 + Moon[2]**2)**(3/2))
output[0]=Moon[1]
output[1]= -G*jupiter_mass*Moon[0]/R
output[2]=Moon[3]
output[3]= -G*jupiter_mass*Moon[2]/R
return output
#This is where the problem is
sol= solve_ivp(ode,Io,year)
plt.plot(sol[:,0],sol[:,2],0,0,'ro')
plt.axis('equal')
plt.grid(True)
plt.show()
I'm hoping to achieve a 2D orbital plot like this...
and also track and plot each change in x and y position and velocity against time.

The documentation for solve_ivp shows that the parameters are
sol = solve_ivp(ode, [t0,tf], u0, tspan=year, atol = 100, rtol=1e-9)
where year=np.arange(t0,tf,hour). Then you find the solution values in sol.t for the repeated times and sol.y for the values.

Related

plotting issue with matplotlib

I have an issue with plotting a graph over some values in python and matplotlib. What i want is to plot the i's (iterations) on the x-axis and optimal q*'s on the y-axis in the code below.
#Question 1
#i)
#Defining the parameters using SimpleNameSpace. par = Parameters
par = SimpleNamespace()
par.y = 1 #assets
par.p = 0.2 #probability
par.theta = -2 #elasticity
#Defining utility function for agent
def utility(z,par):
return (z**(1+par.theta))/(1+par.theta)
#Defining premium
def premium(q,par):
return par.p*q
#Defining expected value
#Note that z_1, z_2 represents first and second part of the objective function - just in a compressed version
def exp_value (i,q,par):
z_1 = par.y-i+q-premium(q,par)
z_2 = par.y-premium(q,par)
return par.p*utility(z_1,par)+(1-par.p)*utility(z_2,par)
def opt_q(i,par):
obj = lambda q: -exp_value(i,q,par) #defining the objective function
solution = minimize_scalar(obj, bounds=(0,0.9), method='bounded') #bounded solution within [0, 0.9]
q = solution.x
return q
# ii), iii) Creating a for loop to make a grid of the optimum q
for i in np.linspace(0.01,0.9,num=100):
res = opt_q(i,par)
print(res)
#iv) plotting the is and q*s with matplotlib
plt.plot(res,i)
# function to show the plot
plt.show()
When i run the above code i get nothing out on the graph. Can somebody explain what is missing?

Decay overlaid with function

I am trying to plot decay (1/r, 1/r^2 and 1/r^3) with my functions figure
My issue is that the decay lines are not near the function plots so it's difficult to see which decay line fits which function. I would like the decay lines to overlay the function.
I have tried subtracting a number from the 1/x function to shift it down but that did not work.
#load data from matlab
mat = loadmat('model01.mat')
#unpack data from matlab, one distance and velocities in 4 different directions
dist = mat['Dist_A_mm01']
distar = np.array(dist)
vpos = mat['Model_Posterior01_m']
vposar = np.array(vpos)
vant = mat['Model_Anterior01_m']
vantar = np.array(vant)
vleft = mat['Model_Left01_m']
vleftar = np.array(vleft)
vright = mat['Model_Right01_m']
vrightar = np.array(vright)
# transpose data
distar = np.transpose(distar)
vposar = np.transpose(vposar)
vantar = np.transpose(vantar)
vleftar = np.transpose(vleftar)
vrightar = np.transpose(vrightar)
#select numbers from array to plot (number in place 0 is 0 which gives an error when dividing by zero later)
dd = distar[1:50]
#plot the data from matlab in a log log graph
ax = plt.axes()
plt.loglog(distar,vposar)
plt.loglog(distar,vantar)
plt.loglog(distar,vleftar)
plt.loglog(distar,vrightar)
plt.loglog(dd,1/dd,dd,1/dd**2,dd,1/dd**3)
ax.set_title('Decay away from centroid')
ax.set_ylabel('Velocity in m/s')
ax.set_xlabel('mm')
plt.show()
Here is the mat file I am importing .mat file
I want the decay lines to be overlaid with the data so it's easy to see the decay of each line on the plot.
To shift the decay functions you need to multiply them with something smaller than one, not subtract, since log(A * 1 / x^2) = log(A) - 2 * log(x), i.e. by selecting appropriate values for A you can shift them up and down as you please. In practice this would look like this:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(1, 10)
y = 1 / x**2
y2 = 0.1 * 1 / x**2
plt.loglog(x, y, label="1/x2")
plt.loglog(x, y2, label="A + 1/x2")
plt.legend()
If these lines are supposed to be just guides to the eye, that should suffice. Otherwise you should probably do a fit to your actual data.

Interpolating a complex-valued boundary function inside a circular disk with the Cauchy Intergral?

I have heard that the Cauchy integration formula can be used to interpolate complex-valued functions along a closed boundary of a disk to points inside the disk. For my current project, this sounds rather valuable, so I attempted to give this a shot. Unfortunately, my experiments were not very successful so far, and I am not certain what is going wrong. Some degree of interpolation is certainly going on, but the results do not seem to be correct along the boundaries. Here is what my code returns:
Here is my initial code example:
import scipy.stats
import numpy as np
import scipy.integrate
import scipy.interpolate
import matplotlib.pyplot as plt
plt.close('all')
# This is the interpolation function, which takes as input a position on the
# boundary in radians (x), a complex evaluation point (eval_point), and the
# function which returns the boundary condition
def f(x,eval_point,itp):
# What is the complex coordinate of this point on the boundary?
zi = np.cos(x) + 1j*np.sin(x)
# Get the boundary condition value
fz = itp(x)
return fz/(zi-eval_point)
# Complex quadrature for integration, adapted from
# https://stackoverflow.com/questions/57325919/using-scipy-quad-with-i%ce%b5-trick-bad-results
def cquad(func, a, b, **kwargs):
real_integral = scipy.integrate.quad(lambda x: np.real(func(x, **kwargs)), a, b, limit=200)
imag_integral = scipy.integrate.quad(lambda x: np.imag(func(x, **kwargs)), a, b, limit=200)
return (real_integral[0] + 1j*imag_integral[0], real_integral[1:], imag_integral[1:])
# Define the interpolation function for the boundary values
itp = scipy.interpolate.interp1d(
x = [0,np.pi/2,np.pi,1.5*np.pi,2*np.pi],
y = [0+0j,0+1j,1+1j,1+0j,0+0j])
# Get some evaluation points
X,Y = np.meshgrid(np.linspace(-1,1,51),
np.linspace(-1,1,51))
XY = X+1j*Y
x = np.ndarray.flatten(XY)
# Throw away all points outside the unit disk; avoid evaluting at radius 1 to
# dodge singularities
x = x[np.where(np.abs(x) <= 0.99)]
# Calculate the result for each evaluation point
res = []
for val in x:
res.append(cquad(
func = f,
a = 0,
b = 2*np.pi,
eval_point = val,
itp = itp)[0]/(2*np.pi*1j))
# Convert the results into an array
res = np.asarray(res)
# Plot the real part of the results
plt.tricontour(
np.real(x),
np.imag(x),
np.real(res),
cmap = 'jet')
plt.colorbar(label='real part')
# Plot the imaginary part of the results
plt.tricontour(
np.real(x),
np.imag(x),
np.imag(res),
cmap = 'Greys')
plt.colorbar(label='imaginary part')
Does anybody have an idea what is going wrong?
You can get an easy approximation of that function by employing the FFT. The inverse FFT can be interpreted as polynomial evaluation at the corresponding points on the unit circle, so that the polynomial in total is an approximation of the Cauchy-formula
c = np.fft.fft(itp(np.linspace(0,2*np.pi,401)[:-1]))
c=c[::-1]/len(c)
np.polyval(c,[1,1j,-1,-1j])
returns
[5.55111512e-17+5.55111512e-17j, 5.55111512e-17+1.00000000e+00j,
1.00000000e+00+1.00000000e+00j, 1.00000000e+00+5.55111512e-17j]
these are the values that were expected.
X,Y = np.meshgrid(np.linspace(-1,1,151),
np.linspace(-1,1,151))
Z = (X+1j*Y).flatten()
Z = Z[np.where(np.abs(Z) <= 0.99)]
W = np.polyval(c,Z)
# Plot the real part of the results
plt.tricontour( Z.real, Z.imag, W.real, cmap = 'jet')
plt.colorbar(label='real part')
# Plot the imaginary part of the results
plt.tricontour( Z.real, Z.imag, W.imag, cmap = 'Greys')
plt.colorbar(label='imaginary part')
plt.tight_layout(); plt.show()
This then gives the picture
The dominant terms of the polynomial are
(1+1j)*(0.500000 - 0.045040*z^3 - 0.008279*z^7
- 0.005012*z^391 - 0.016220*z^395 - 0.405293*z^399)
As far as I could see, the leading degree 3 after the constant term is constant under refinement of the sampling sequence.

Planetary orbit shown as linear graph using rk4

I am trying to simulate the orbit of a planet around a star using the Runge-Kutta 4 method. After speaking to tutors my code should be correct. However, I am not generating my expected 2D orbital plot but instead a linear plot. This is my first time using solve_ivp to solve a second order differential. Can anyone explain why my plots are wrong?
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import solve_ivp
# %% Define derivative function
def f(t, z):
x = z[0] # Position x
y = z[1] # Position y
dx = z[2] # Velocity x
dy = z[3] # Velocity y
G = 6.674 * 10**-11 # Gravitational constant
M = 2 # Mass of binary stars in solar masses
c = 2*G*M
r = np.sqrt(y**2 + x**2) # Distance of planet from stars
zdot = np.empty(6) # Array for integration solutions
zdot[0] = x
zdot[1] = y
zdot[2] = dx # Velocity x
zdot[3] = dy #Velocity y
zdot[4] = (-c/(r**3))*(x) # Acceleration x
zdot[5] = (-c/(r**3))*(y) # Acceleration y
return zdot
# %% Define time spans, initial values, and constants
tspan = np.linspace(0., 10000., 100000000)
xy0 = [0.03, -0.2, 0.008, 0.046, 0.2, 0.3] # Initial positions x,y in R and velocities
# %% Solve differential equation
sol = solve_ivp(lambda t, z: f(t, z), [tspan[0], tspan[-1]], xy0, t_eval=tspan)
# %% Plot
#plot
plt.grid()
plt.subplot(2, 2, 1)
plt.plot(sol.y[0],sol.y[1], color='b')
plt.subplot(2, 2, 2)
plt.plot(sol.t,sol.y[2], color='g')
plt.subplot(2, 2, 3)
plt.plot(sol.t,sol.y[4], color='r')
plt.show()
With the ODE function as given, you are solving in the first components the system
xdot = x
ydot = y
which has well-known exponential solutions. As the exponential factor is the same long both solutions, the xy-plot will move along a line through the origin.
The solution is of course to fill zdot[0:2] with dx,dy, and zdot[2:4] with ax,ay or ddx,ddy or however you want to name the components of the acceleration. Then the initial state also has only 4 components. Or you need to make and treat position and velocity as 3-dimensional.
You need to put units to your constants and care that all use the same units. G as cited is in m^3/kg/s^2, so that any M you define will be in kg, any length is in m and any velocity in m/s. Your constants might appear ridiculously small in that context.
It does not matter what the comment in the code says, there will be no magical conversion. You need to use actual conversion computations to get realistic numbers. For instance using the numbers
G = 6.67408e-11 # m^3 s^-2 kg^-1
AU = 149.597e9 # m
Msun = 1.988435e30 # kg
hour = 60*60 # seconds in an hour
day = hour * 24 # seconds in one day
year = 365.25*day # seconds in a year (not very astronomical)
one could guess that for a sensible binary system of two stars of equal mass one has
M = 2*Msun # now actually 2 sun masses
x0 = 0.03*AU
y0 = -0.2*AU
vx0 = 0.008*AU/day
vy0 = 0.046*AU/day
For the position only AU makes sense as unit, the speed could also be in AU/hour. By https://math.stackexchange.com/questions/4033996/developing-keplers-first-law and Cannot get RK4 to solve for position of orbiting body in Python the speed for a circular orbit of radius R=0.2AU around a combined mass of 2*M is
sqrt(2*M*G/R)=sqrt(4*Msun*G/(0.2*AU)) = 0.00320 * AU/hour = 0.07693 AU/day
which is ... not too unreasonable if the given speeds are actually in AU/day. Invoke the computations from https://math.stackexchange.com/questions/4050575/application-of-the-principle-of-conservation to compute if the Kepler ellipse would look sensible
r0 = (x0**2+y0**2)**0.5
dotr0 = (x0*vx0+y0*vy0)/r0
L = x0*vy0-y0*vx0 # r^2*dotphi = L constant, L^2 = G*M_center*R
dotphi0 = L/r0**2
R = L**2/(G*2*M)
wx = R/r0-1; wy = -dotr0*(R/(G*2*M))**0.5
E = (wx*wx+wy*wy)**0.5; psi = m.atan2(wy,wx)
print(f"half-axis R={R/AU} AU, eccentr. E={E}, init. angle psi={psi}")
print(f"min. rad. = {R/(1+E)/AU} AU, max. rad. = {R/(1-E)/AU} AU")
which returns
half-axis R=0.00750258 AU, eccentr. E=0.96934113, init. angle psi=3.02626659
min. rad. = 0.00380969 AU, max. rad. = 0.24471174 AU
This gives an extremely thin ellipse, which is not that astonishing as the initial velocity points almost directly to the gravity center.
orbit variants with half-day steps marked, lengths in AU
If the velocity components were swapped one would get
half-axis R=0.07528741 AU, eccentr. E=0.62778767, init. angle psi=3.12777251
min. rad. = 0.04625137 AU, max. rad. = 0.20227006 AU
This is a little more balanced.

Programming of 4th order Runge-Kutta in advection equation in python

%matplotlib notebook
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from math import pi
# wave speed
c = 1
# spatial domain
xmin = 0
xmax = 1
#time domain
m=500; # num of time steps
tmin=0
T = tmin + np.arange(m+1);
tmax=500
n = 50 # num of grid points
# x grid of n points
X, dx = np.linspace(xmin, xmax, n+1, retstep=True);
X = X[:-1] # remove last point, as u(x=1,t)=u(x=0,t)
# for CFL of 0.1
CFL = 0.3
dt = CFL*dx/c
# initial conditions
def initial_u(x):
return np.sin(2*pi*x)
# each value of the U array contains the solution for all x values at each timestep
U = np.zeros((m+1,n),dtype=float)
U[0] = u = initial_u(X);
def derivatives(t,u,c,dx):
uvals = [] # u values for this time step
for j in range(len(X)):
if j == 0: # left boundary
uvals.append((-c/(2*dx))*(u[j+1]-u[n-1]))
elif j == n-1: # right boundary
uvals.append((-c/(2*dx))*(u[0]-u[j-1]))
else:
uvals.append((-c/(2*dx))*(u[j+1]-u[j-1]))
return np.asarray(uvals)
# solve for 500 time steps
for k in range(m):
t = T[k];
k1 = derivatives(t,u,c,dx)*dt;
k2 = derivatives(t+0.5*dt,u+0.5*k1,c,dx)*dt;
k3 = derivatives(t+0.5*dt,u+0.5*k2,c,dx)*dt;
k4 = derivatives(t+dt,u+k3,c,dx)*dt;
U[k+1] = u = u + (k1+2*k2+2*k3+k4)/6;
# plot solution
plt.style.use('dark_background')
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
line, = ax1.plot(X,U[0],color='cyan')
ax1.grid(True)
ax1.set_ylim([-2,2])
ax1.set_xlim([0,1])
def animate(i):
line.set_ydata(U[i])
return line,
I want to program in Python an advection equation which is (∂u/∂t) +c (∂u/∂x) = 0. Time should be discretized with Runge-kutta 4th order. Spatial discretiziation is 2nd order finite difference. When I run my code, I get straight line which transforms into sine wave. But I gave as initial condition sine wave. Why does it start as straight line? And I want to have sine wave moving forward. Do you have any idea on how to get sine wave moving forward? I appreciate your help. Thanks in advance!
While superficially your computation steps are related to the RK4 method, they deviate from the RK4 method and the correct space discretization too much to mention it all.
The traditional way to apply ODE integration methods is to have a function derivatives(t, state, params) and then apply that to compute the Euler step or the RK4 step. In your case it would be
def derivatives(t,u,c,dx):
du = np.zeros(len(u));
p = c/(2*dx);
du[0] = p*(u[1]-u[-1]);
du[1:-1] = p*(u[2:]-u[:-2]);
du[-1] = p*(u[0]-u[-2]);
return du;
Then you can do
X, dx = np.linspace(xmin, xmax, n+1, retstep=True);
X = X[:-1] # remove last point, as u(x=1,t)=u(x=0,t)
m=500; # number of time steps
T = tmin + np.arange(m+1);
U = np.zeros((m+1,n),dtype=float)
U[0] = u = initial_u(X);
for k in range(m):
t = T[k];
k1 = derivatives(t,u,c,dx)*dt;
k2 = derivatives(t+0.5*dt,u+0.5*k1,c,dx)*dt;
k3 = derivatives(t+0.5*dt,u+0.5*k2,c,dx)*dt;
k4 = derivatives(t+dt,u+k3,c,dx)*dt;
U[k+1] = u = u + (k1+2*k2+2*k3+k4)/6;
This uses dt as computed as the primary variable in the time stepping, then constructs the arithmetic sequence from tmin with step dt. Other ways are possible, but one has to make tmax and the number of time steps compatible.
The computation up to this point should now be successful and can be used in the animation. In my understanding, you do not produce a new plot in each frame, you only draw the graph once and after that just change the line data
# animate the time data
line, = ax1.plot(X,U[0],color='cyan')
ax1.grid(True)
ax1.set_ylim([-2,2])
ax1.set_xlim([0,1])
def animate(i):
line.set_ydata(U[i])
return line,
etc.

Categories