Boundary conditions for stokes flow around a sphere using FiPy - python

I have tried to solve the Stokes flow around a sphere using FiPy. To do that, I chose a cylindrical 2-D mesh (since my problem is axisymmetric). The z-axis passes through the center of the sphere, and the size of the mesh is Lr x Lz. The boundary conditions I have used are shown in the figure below:
I solved the problem above using FiPy library for Python, see the code below.
from fipy import *
from fipy.tools import numerix
from fipy.variables.faceGradVariable import _FaceGradVariable
viscosity = 5.55555555556e-06
pfi = 10000. #Penalization for being inside sphere
v0 = 1. #Speed far from sphere
tol = 1.e-6 #Tolerance
Lr=2. #Length of the grid
#No. of cells in the r and z directions
Nr=400
Nz=800
Lz=Lr*Nz/Nr #Height of the grid (=4)
dL=Lr/Nr
mesh = CylindricalGrid2D(nr=Nr, nz=Nz, dr=dL, dz=dL)
R, Z = mesh.faceCenters
r, z = mesh.cellCenters
#Under-relaxation factors
pressureRelaxation = 0.8
velocityRelaxation = 0.5
#Radius of the sphere
rad=0.1
#Distance to the center of the mesh (r=0, z=2)
var1 = DistanceVariable(name='distance to center', mesh=mesh, value=numerix.sqrt(r**2+(z-Lz/2.)**2))
#Pressure and pressure correction variables
pressure = CellVariable(mesh=mesh, value = 0., hasOld=True, name='press')
pressureCorrection = CellVariable(mesh=mesh, value = 0., hasOld=True)
#Cell velocities
zVelocity = CellVariable(mesh=mesh, hasOld=True, name='Z vel')
rVelocity = CellVariable(mesh=mesh,hasOld=True, name='R vel')
#face velocities
velocity = FaceVariable(mesh=mesh, rank=1)
velocityold = FaceVariable(mesh=mesh,rank=1)
#BOUNDARY CONDITIONS (no-flux by default)
zVelocity.constrain(v0, mesh.facesBottom)
zVelocity.constrain(v0, mesh.facesTop)
rVelocity.constrain(0., mesh.facesRight)
rVelocity.constrain(0., mesh.facesBottom)
rVelocity.constrain(0., mesh.facesTop)
pressureCorrection.constrain(0., mesh.facesBottom & (R < dL))
#Penalization term
pi_fi= CellVariable(mesh=mesh, value=0.,name='Penalization term')
pi_fi.setValue(pfi, where=(var1 <= rad) )
rFaces=numerix.array([]) #vertical faces
zFaces=numerix.array([]) #horizontal faces
#Number of cells in each processor
Nr_in_proc = mesh.nx
Nz_in_proc = mesh.ny
for zfcount in range(Nr_in_proc*(1+Nz_in_proc)) :
rFaces=numerix.append(rFaces,[False])
zFaces=numerix.append(zFaces,[True])
for rfcount in range(Nz_in_proc*(1+Nr_in_proc)) :
rFaces=numerix.append(rFaces,[True])
zFaces=numerix.append(zFaces,[False])
#Correct pressure gradient
pressure_correct_grad = CellVariable(mesh=mesh, rank=1)
pressure_correct_grad[0] = pressure.grad[0] - pressure / r
pressure_correct_grad[1] = pressure.grad[1]
#Correct pressure face gradient
pressure_correct_facegrad = FaceVariable(mesh=mesh,rank=1)
pressure_correct_facegrad0 = FaceVariable(mesh=mesh)
pressure_correct_facegrad0.setValue(pressure.faceGrad[0])
pressure_correct_facegrad0.setValue(pressure.faceGrad[0] - pressure.grad[0].arithmeticFaceValue + \
pressure_correct_grad[0].arithmeticFaceValue, where = zFaces)
pressure_correct_facegrad1 = FaceVariable(mesh=mesh)
pressure_correct_facegrad1.setValue(pressure.faceGrad[1])
pressure_correct_facegrad.setValue([pressure_correct_facegrad0.value, pressure_correct_facegrad1.value])
#Correct pressureCorrection gradient
pressureCorrection_correct_grad = CellVariable(mesh=mesh, rank=1)
pressureCorrection_correct_grad[0] = pressureCorrection.grad[0] - pressureCorrection / r
pressureCorrection_correct_grad[1] = pressureCorrection.grad[1]
#Correct pressureCorrection face gradient
pressureCorrection_correct_facegrad = FaceVariable(mesh=mesh,rank=1)
pressureCorrection_correct_facegrad0 = FaceVariable(mesh=mesh)
pressureCorrection_correct_facegrad0.setValue(pressureCorrection.faceGrad[0])
pressureCorrection_correct_facegrad0.setValue(pressureCorrection.faceGrad[0] - pressureCorrection.grad[0].arithmeticFaceValue + \
pressureCorrection_correct_grad[0].arithmeticFaceValue, where = zFaces)
pressureCorrection_correct_facegrad1 = FaceVariable(mesh=mesh)
pressureCorrection_correct_facegrad1.setValue(pressureCorrection.faceGrad[1])
pressureCorrection_correct_facegrad.setValue([pressureCorrection_correct_facegrad0.value, pressureCorrection_correct_facegrad1.value])
coeff = FaceVariable(mesh=mesh,value=1.)
#Navie Stokes equation (no inertia, cylindrical coordinates) + pressure correction equation
rVelocityEq = DiffusionTerm(coeff=viscosity) - pressure_correct_grad.dot([1.,0.]) - ImplicitSourceTerm(pi_fi + viscosity/r**2.)
zVelocityEq = DiffusionTerm(coeff=viscosity) - pressure_correct_grad.dot([0.,1.]) - ImplicitSourceTerm(pi_fi)
pressureCorrectionEq = DiffusionTerm(coeff=coeff) - velocity.divergence
#Matrix for Rhie-Chow interpolation
apr = CellVariable(mesh=mesh, value=1.)
apz = CellVariable(mesh=mesh, value=1.)
ap = FaceVariable(mesh=mesh, value=1.)
volume = CellVariable(mesh=mesh, value=mesh.cellVolumes, name='Volume')
contrvolume = R * dL * dL #Control volume for the faces
sweep=0.
#Residue from sweep methods
rres=1000.
zres=1000.
pres=1000.
cont=1000. #Checks if continuity equation is satisfied
pcorrmax=1000. #Max of pressure correction (from using SIMPLE algorithm)
pressure.updateOld()
pressureCorrection.updateOld()
rVelocity.updateOld()
zVelocity.updateOld()
while (rres > tol or zres > tol or pres > tol or cont > tol or pcorrmax > tol) :
sweep=sweep+1
#Solve the Navier Stokes equations to obtain starred values
rVelocityEq.cacheMatrix()
rres = rVelocityEq.sweep(var=rVelocity,underRelaxation=velocityRelaxation)
rmat = rVelocityEq.matrix
zVelocityEq.cacheMatrix()
zres = zVelocityEq.sweep(var=zVelocity,underRelaxation=velocityRelaxation)
zmat = zVelocityEq.matrix
#Update matrix with diagonal coefficients to be used in Rhie-Chow interpolation
apr[:] = -rmat.takeDiagonal()
apz[:] = -zmat.takeDiagonal()
ap.setValue(apr.arithmeticFaceValue,where=rFaces)
ap.setValue(apz.arithmeticFaceValue,where=zFaces)
#Update the face velocities based on starred values with the Rhie-Chow correction
#Final solution independent of the under-relaxation factor
velocity[0] = (rVelocity.arithmeticFaceValue + (volume / apr * pressure_correct_grad[0]).arithmeticFaceValue - \
contrvolume * (1. / apr).arithmeticFaceValue * pressure_correct_facegrad[0] + (1 - velocityRelaxation) * \
(velocityold[0] - rVelocity.old.arithmeticFaceValue))
velocity[1] = (zVelocity.arithmeticFaceValue + (volume / apz * pressure_correct_grad[1]).arithmeticFaceValue - \
contrvolume * (1. / apz).arithmeticFaceValue * pressure_correct_facegrad[1] + (1 - velocityRelaxation) * \
(velocityold[1] - zVelocity.old.arithmeticFaceValue))
#Boundary conditions (again)
velocity[0, mesh.facesRight.value] = 0.
velocity[0, mesh.facesBottom.value] = 0.
velocity[0, mesh.facesTop.value] = 0.
velocity[1, mesh.facesBottom.value] = v0
velocity[1, mesh.facesTop.value] = v0
#Solve the pressure correction equation
coeff.setValue(contrvolume * (1. / apr).arithmeticFaceValue, where=rFaces)
coeff.setValue(contrvolume * (1. / apz).arithmeticFaceValue, where=zFaces)
pressureCorrectionEq.cacheRHSvector()
pres = pressureCorrectionEq.sweep(var=pressureCorrection)
#Correct pressureCorrection gradient
pressureCorrection_correct_grad[0] = pressureCorrection.grad[0] - pressureCorrection / r
pressureCorrection_correct_grad[1] = pressureCorrection.grad[1]
#Correct pressureCorrection face gradient
pressureCorrection_correct_facegrad0.setValue(pressureCorrection.faceGrad[0])
pressureCorrection_correct_facegrad0.setValue(pressureCorrection.faceGrad[0] - pressureCorrection.grad[0].arithmeticFaceValue + \
pressureCorrection_correct_grad[0].arithmeticFaceValue, where = zFaces)
pressureCorrection_correct_facegrad1.setValue(pressureCorrection.faceGrad[1])
pressureCorrection_correct_facegrad.setValue([pressureCorrection_correct_facegrad0.value, pressureCorrection_correct_facegrad1.value])
#Update the pressure using the corrected value
pressure.setValue(pressure + pressureRelaxation * pressureCorrection )
#Correct pressure gradient
pressure_correct_grad[0] = pressure.grad[0] - pressure / r
pressure_correct_grad[1] = pressure.grad[1]
#Correct pressure face gradient
pressure_correct_facegrad0.setValue(pressure.faceGrad[0])
pressure_correct_facegrad0.setValue(pressure.faceGrad[0] - pressure.grad[0].arithmeticFaceValue + \
pressure_correct_grad[0].arithmeticFaceValue, where = zFaces)
pressure_correct_facegrad1.setValue(pressure.faceGrad[1])
pressure_correct_facegrad.setValue([pressure_correct_facegrad0.value, pressure_correct_facegrad1.value])
#Update the velocity using the corrected pressure
rVelocity.setValue(rVelocity - pressureCorrection_correct_grad[0] / apr * volume)
zVelocity.setValue(zVelocity - pressureCorrection_correct_grad[1] / apz * volume)
velocity[0] = velocity[0] - pressureCorrection_correct_facegrad[0] * contrvolume * (1. / apr).arithmeticFaceValue
velocity[1] = velocity[1] - pressureCorrection_correct_facegrad[1] * contrvolume * (1. / apz).arithmeticFaceValue
#Boundary conditions (again)
velocity[0, mesh.facesRight.value] = 0.
velocity[0, mesh.facesBottom.value] = 0.
velocity[0, mesh.facesTop.value] = 0.
velocity[1, mesh.facesTop.value] = v0
velocity[1, mesh.facesBottom.value] = v0
velocityold[0] = velocity[0]
velocityold[1] = velocity[1]
rVelocity.updateOld()
zVelocity.updateOld()
pcorrmax = max(abs(pressureCorrection.globalValue))
cont = max(abs(velocity.divergence.globalValue))
if sweep % 10 == 0 :
print ('sweep:', sweep,', r residual:',rres, ', z residual',zres, ', p residual:',pres, ', continuity:',cont, 'pcorrmax: ', pcorrmax)
The code converges after 140 iterations. There are many lines in this code (sorry about that), but a great part of them are only to correct the grad method for cylindrical coordinates in Fipy.
Most of the professors with whom I discussed advised me not to set v=v0 at z=Lz (not sure why). Instead, they have suggested me to use Neumann boundary conditions at the exit (i.e., dvr/dz = 0 and dvz/dz = 0). I believe this is the boundary condition by default in FiPy, so all I did was commenting a few lines in my code.
#zVelocity.constrain(v0, mesh.facesTop)
#rVelocity.constrain(0., mesh.facesTop)
#velocity[0, mesh.facesTop.value] = 0.
#velocity[1, mesh.facesTop.value] = v0
The problem is that my code no longer converges after commenting these lines. The residual error of the rVelocity equation (rres) goes to 0, and so does the residual error of the pressure correction equation (pres). But the remaining criteria in the while loop (residual error for the zVelocity equation, pressure correction factor and velocity divergence) do not go to 0.
So my question is: why changing the exit condition from (vr=0,vz=v0) to (dvr/dz=0, dvz/dz=0) is causing a convergence issue?

It seems that setting velocity[1, mesh.facesTop.value] = v0 ensures that the inflow and outflows are balanced making it easier to achieve continuity. Now, for this problem,
https://pages.nist.gov/pfhub/benchmarks/benchmark5-hackathon.ipynb/
it's suggested that the zero pressure correction value is set near the outlet. Trying that with your code seems to improve things,
pressureCorrection.constrain(0., mesh.facesTop & (R < dL))
whilst commenting velocity[1, mesh.facesTop.value] = v0 gets quite low residuals. Also, setting
pressureCorrection.constrain(0., mesh.facesTop)
gets even lower residuals, but that might not be physical.
This fipy code (courtesy of #jeguyer) solves the problem above. It uses a source term to constrain a cell to be zero rather than using a boundary constraint. That might also give you an additional benefit.

Related

Simulating Torsion Springs

Probably a bit more of a physics question than a programming question...
Basically I am trying to simulate a robot with a torsion spring in its knee (see picture) and measure what forces are applied to the knee joint and how much the spring deforms.
picture of robot
The program I've written produces graphs that have the correct shape, but for some reason the angle of the spring seems to be scaling with the size of my timesteps, which is definitely not correct.
I have attached my code below, but here is also a detailed explanation of what I think I am doing:
I assume that besides gravitation, there is only one force ever applied to the robot, and that force is applied at the start for a certain duration and is aimed straight down. From that force I calculate the angular velocity of my joint.
After that, I enter a loop where in every timestep I update the forces and the angle of the spring. I calculate the gravitational force dependent on the angle. I assume that my model resembles an inverted pendulum, and that the term for the mass of the leg is negligible. Then I calculate the counterforce exerted by the spring, and get the total force from the difference between the two.
I calculate the velocity change from the force, and update the velocity. From the velocity I get the change in angle and update the angle as well. I repeat that loop until a time limit is reached.
The rest of the code is just for plotting.
For some reason, the scale of the angle changes drastically with the size of the timesteps I choose. Other than that, however, the graphs look as I would expect: Oscillating briefly and then settling on one value. One problem I see is that the force of the spring directly depends on the timestep, while the gravitational force does not. Since everything else depends on the difference between the two forces, this changes the scale. However, I do not know how to fix this.
Thanks for any help!
import math
from scipy import constants
import matplotlib.pyplot as plt
class Spring:
def __init__(self, k, l=0.17, m=6.0):
self.k = k
self.l = l
self.m = m
self.alphaplt = []
self.forceplt = []
def impulse(self, force, duration, at_angle, stept):
time = 0
alpha = at_angle
i = self.m * self.l ** 2
vk = (force / self.m * duration) / self.l
while time <= 100:
tm = 0.5 * self.m * constants.g * self.l * math.sin(alpha)
tf = self.k * vk * stept / self.l
tg = tm - tf
vk = vk + (tg * stept * self.l) / i
alpha = alpha + vk * stept
time += stept
self.alphaplt.append(alpha)
self.forceplt.append(tg)
plt.plot(self.alphaplt)
plt.show()
plt.plot(self.forceplt)
plt.show()
if __name__ == "__main__":
s = Spring(0.75)
s.impulse(90, 0.5, 0, 0.01)
If you want to see an animated simulation of this system, go to
Online VPython's webpage, register (it's free), paste the script I have written below and run it. Some explanations of the code are written included as comments in the script
Web VPython 3.2
# system of differential equations of the mathematical model of the physical system:
def f(state, param, F):
m, L, k, theta_0, g = param
a = state.x
p = state.y
s_a = sin(a)
c_a = cos(a)
da_dt = p / (s_a**2)
dp_dt = c_a * p**2 / (s_a**3) - (k/(2*m*L**2)) * (2*a + theta_0 - pi) + ((g + F/m) / (2*L)) * s_a
return vector(da_dt, dp_dt, 0.)
# one Runge-Kutta time-step propagation of the system's dynamics:
def propagate_RK4(f, state, param, F, dt):
k1 = f(state, param, F)
k2 = f(state + dt*k1/2., param, F)
k3 = f(state + dt*k2/2., param, F)
k4 = f(state + dt*k3, param, F)
return state + dt*(k1 + 2*k2 + 2*k3 + k4) / 6.
# transforming the dynamical variable angle into the actual, physical configuration of the system
def configure(struct, state):
struct[0].pos = vector( L*sin(state.x), L*cos(state.x), 0. )
struct[1].pos = vector( 0., 2*L*cos(state.x), 0. )
struct[2].axis = struct[0].pos
struct[3].pos = struct[0].pos
struct[3].axis = struct[1].pos - struct[0].pos
return None
# initialization and animation of the system's dynamics:
# constant parameters of the model:
m=6.0
L=0.17
k=55.75
theta_0 = 2*pi/3
g = 9.8
F = 150.
param = (m, L, k, theta_0, g)
a_0 = pi/10
# fixed location of the lowest, ground level, joint:
sphere(pos = vector(0., 0., 0.), radius = .005, color=color.red)
# initial positions of the moving middle joint, where the torsion spring is
joint_1 = sphere(pos = vector(L*sin(a_0), L*cos(a_0), 0.), radius = .01, color=color.red, make_trail=True)
# initial positions of the upper joint, where the main load (box) is positioned
joint_2 = sphere(pos = vector(0., 2*L*cos(a_0), 0. ), radius = .01, color=color.red, make_trail=True)
# initial configuration of the bar between ground joint and joint 1
bar_01 = cylinder(pos=vector(0,0,0), axis=vector(0, L,0), radius=0.003, color=color.orange)
# initial configuration of the bar between ground joint and joint 2
bar_12 = cylinder(pos=vector(0,L,0), axis=vector(0,L,0), radius=0.003, color=color.orange)
# initial state of the system, given by an initial angle a_0:
state = vector(a_0, 0., 0.)
t = 0.
dt = 0.01
force_duration = 0.7
frame_rate = 15
force_duration = force_duration / frame_rate
# drawing in yellow the locations of joint 1 and 2 at time t=0
sphere(pos = vector(L*sin(a_0), L*cos(a_0), 0.), radius = .005, color=color.yellow)
sphere(pos = vector(0., 2*L*cos(a_0), 0. ), radius = .005, color=color.yellow)
# animation of the dynamics of the system while the constant external force is acting:
while t <= force_duration:
rate(frame_rate)
state = propagate_RK4(f, state, param, F, dt)
configure([joint_1, joint_2, bar_01, bar_12], state)
t=t+dt
# drawing in yellow the locations of joint 1 and 2 at the time moment when the external force stops acting
sphere(pos = vector(L*sin(state.x), L*cos(state.x), 0.), radius = .005, color=color.yellow)
sphere(pos = vector(0., 2*L*cos(state.x), 0. ), radius = .005, color=color.yellow)
# animation of the dynamics of the system after the constant external force has stopped acting:
while force_duration < t <= 10:
rate(frame_rate)
state = propagate_RK4(f, state, param, 0, dt)
configure([joint_1, joint_2, bar_01, bar_12], state)
t=t+dt

3 Body Problem Outputs a spikey ball rather than an orbital path

I'm trying to solve the 3 body problem with solve_ivp and its runge kutta sim, but instead of a nice orbital path it outputs a spiked ball of death. I've tried changing the step sizes and step lengths all sorts, I have no idea why the graphs are so spikey, it makes no sense to me.
i have now implemented the velocity as was suggested but i may have done it wrong
What am I doing wrong?
Updated Code:
from scipy.integrate import solve_ivp
import numpy as np
import matplotlib.pyplot as plt
R = 150000000 #radius from centre of mass to stars orbit
#G = 1/(4*np.pi*np.pi) #Gravitational constant in AU^3/solar mass * years^2
G = 6.67e-11
M = 5e30 #mass of the stars assumed equal mass in solar mass
Omega = np.sqrt(G*M/R**3.0) #inverse of the orbital period of the stars
t = np.arange(0, 1000, 1)
x = 200000000
y = 200000000
vx0 = -0.0003
vy0 = 0.0003
X1 = R*np.cos(Omega*t)
X2 = -R*np.cos(Omega*t)
Y1 = R*np.sin(Omega*t)
Y2 = -R*np.sin(Omega*t) #cartesian coordinates of both stars 1 and 2
r1 = np.sqrt((x-X1)**2.0+(y-Y1)**2.0) #distance from planet to star 1 or 2
r2 = np.sqrt((x-X2)**2.0+(y-Y2)**2.0)
xacc = -G*M*((1/r1**2.0)*((x-X1)/r1)+(1/r2**2.0)*((x-X2)/r2))
yacc = -G*M*((1/r1**2.0)*((y-Y1)/r1)+(1/r2**2.0)*((y-Y2)/r2)) #x double dot and y double dot equations of motions
#when t = 0 we get the initial contditions
r1_0 = np.sqrt((x-R)**2.0+(y-0)**2.0)
r2_0 = np.sqrt((x+R)**2.0+(y+0)**2.0)
xacc0 = -G*M*((1/r1_0**2.0)*((x-R)/r1_0)+(1/r2_0**2.0)*((x+R)/r2_0))
yacc0 = -G*M*((1/r1_0**2.0)*((y-0)/r1_0)+(1/r2_0**2.0)*((y+0)/r2_0))
#inputs for runge-kutta algorithm
tp = Omega*t
r1p = r1/R
r2p = r2/R
xp = x/R
yp = y/R
X1p = X1/R
X2p = X2/R
Y1p = Y1/R
Y2p = Y2/R
#4 1st ode
#vx = dx/dt
#vy = dy/dt
#dvxp/dtp = -(((xp-X1p)/r1p**3.0)+((xp-X2p)/r2p**3.0))
#dvyp/dtp = -(((yp-Y1p)/r1p**3.0)+((yp-Y2p)/r2p**3.0))
epsilon = x*np.cos(Omega*t)+y*np.sin(Omega*t)
nave = -x*np.sin(Omega*t)+y*np.cos(Omega*t)
# =============================================================================
# def dxdt(x, t):
# return vx
#
# def dydt(y, t):
# return vy
# =============================================================================
def dvdt(t, state):
xp, yp = state
X1p = np.cos(Omega*t)
X2p = -np.cos(Omega*t)
Y1p = np.sin(Omega*t)
Y2p = -np.sin(Omega*t)
r1p = np.sqrt((xp-X1p)**2.0+(yp-Y1p)**2.0)
r2p = np.sqrt((xp-X2p)**2.0+(yp-Y2p)**2.0)
return (-(((xp-X1p)/(r1p**3.0))+((xp-X2p)/(r2p**3.0))),-(((yp-Y1p)/(r1p**3.0))+((yp-Y2p)/(r2p**3.0))))
def vel(t, state):
xp, yp, xv, yv = state
return (np.concatenate([[xv, yv], dvdt(t, [xp, yp]) ]))
p = (R, G, M, Omega)
initial_state = [xp, yp, vx0, vy0]
t_span = (0.0, 1000) #1000 years
result_solve_ivp_dvdt = solve_ivp(vel, t_span, initial_state, atol=0.1) #Runge Kutta
fig = plt.figure()
plt.plot(result_solve_ivp_dvdt.y[0,:], result_solve_ivp_dvdt.y[1,:])
plt.plot(X1p, Y1p)
plt.plot(X2p, Y2p)
Output:
Green is the stars plot and blue remains the velocity
Km and seconds
Years, AU and Solar Masses
You have produced the equation
dv/dt = a(x)
But then you used the acceleration, the derivative of the velocity, as the derivative of the position. This is physically wrong.
You should pass the function
lambda t, xv: np.concantenate([xv[2:], dvdt(xv[:2]) ])
to the solver, with a suitable initial state containing velocity components in addition to the position components.
In the 2-star system with the fixed orbit, the stars have distance 1. This distance, not the distance 0.5 to the center, should enter the computation of the angular velocity.
z_1 = 0.5*exp(2*pi*i*t), z_2 = -z_1 ==> z_1-z_2=2*z_1, abs(z_1-z_2)=1
z_1'' = -GM * (z_1-z_2)/abs(z_1-z_2)^3
-0.5*4*pi^2 = -GM or GM = 2*pi^2
Now insert a satellite into a circular radius at some radius R as if there was only one central mass 2M stationary at the origin
z_3 = R*exp(i*w*t)
z_3'' = -2GM * z_3/abs(z_3)^3
R^3*w^2=2GM
position (R,0), velocity (0,w*R)=(0,sqrt(2GM/R))
In python code
GM = 2*np.pi**2
R = 1.9
def kepler(t,u):
z1 = 0.5*np.exp(2j*np.pi*t)
z3 = u[0]+1j*u[1]
a = -GM*((z3-z1)/abs(z3-z1)**3+(z3+z1)/abs(z3+z1)**3)
return [u[2],u[3],a.real,a.imag]
res = solve_ivp(kepler,(0,17),[R,0,0,2*np.pi*(1/R)**0.5], atol=1e-8, rtol=1e-11)
print(res.message)
This gives a trajectory plot of
The effect of the binary system on the satellite is a continuous sequence of swing-by maneuvers, accelerating the angular speed until escape velocity is reached. With R=1.5 or smaller this happens with the first close encounter of satellite and closest star, so that the satellite is ejected immediately from the system.
Never-the-less, one can still get "spiky-ball" orbits. Setting R=1.6 in the above code, with tighter error tolerances and integrating to t=27 gives the trajectory

Eucledian distance to point source

I am stimulating a model via a point source, which is located above (z-direction)-- to be able to compute the impact of the stimulation i need to compute the eucledian distance from this point power source to each mid of compartment (see picute).
I tried it this way, but the results are strange -- maybe the computation of the distance is wrong...
x_Mid = np.zeros(nComp)
y_Mid = np.zeros(nComp)
z_Mid = np.zeros(nComp)
for i in range(0, nComp):
y_Mid[i] = 0.
if i == 0:
x_Mid[i] = (lComp[i] / 2.)
z_Mid[i] = 1*elecShift
compDist[i] = distance.euclidean(x_Mid,y_Mid,z_Mid)*10**(-4)
else:
x_Mid[i] = x_Mid[i - 1] + (lComp[i - 1] / 2.) + (lComp[i] / 2.)
z_Mid[i] = 1*elecShift
compDist[i] = distance.euclidean(x_Mid,y_Mid,z_Mid)*10**(-4)
lcomp is the length of the compartment. y - direction is zero, because its a 2D Model. elecshift is the distance of the point source in z-direction and the units are micrometer (therefore then its multiplied by 10^-4 to give it in centimeter). nComp is the number of compartments.
Is the computation of the eucledean distance from the source to each compartment center correct?
I'm assuming the source is at [0, 0, 0].
You can calculate three vectors in a simpler way:
x_Mid = np.cumsum(lComp) - lComp / 2.
y_Mid = np.zeros_like(x_Mid)
z_Mid = elecShift * np.ones_like(x_Mid)
Then the simplest calculation of distance is just:
compDist = np.sqrt(x_Mid**2 + y_Mid**2 + z_Mid**2) * 1.e-4
or even:
compDist = np.sqrt(x_Mid**2 + elecShift**2) * 1.e-4
And if you want to use function from scipy, then according to API, use:
for i in range(0, nComp):
compDist[i] = distance.euclidean([x_Mid[i], y_Mid[i], z_Mid[i]], 0.)*10**(-4)
Your code was providing current x_Mid as one point, y_Mid as second one and z_Mid as weights to distance.euclidean().

Elliptical orbit in vpython

I have the following code. This code is simulation of orbiting objects around other objects, E.g. Solar system. As you run it, the objects orbit in circular trajectory.
import math
from vpython import *
lamp = local_light(pos=vector(0,0,0), color=color.yellow)
# Data in units according to the International System of Units
G = 6.67 * math.pow(10,-11)
# Mass of the Earth
ME = 5.973 * math.pow(10,24)
# Mass of the Moon
MM = 7.347 * math.pow(10,22)
# Mass of the Mars
MMa = 6.39 * math.pow(10,23)
# Mass of the Sun
MS = 1.989 * math.pow(10,30)
# Radius Earth-Moon
REM = 384400000
# Radius Sun-Earth
RSE = 149600000000
RMS = 227900000000
# Force Earth-Moon
FEM = G*(ME*MM)/math.pow(REM,2)
# Force Earth-Sun
FES = G*(MS*ME)/math.pow(RSE,2)
# Force Mars-Sun
FEMa = G*(MMa*MS)/math.pow(RMS,2)
# Angular velocity of the Moon with respect to the Earth (rad/s)
wM = math.sqrt(FEM/(MM * REM))
# Velocity v of the Moon (m/s)
vM = wM * REM
print("Angular velocity of the Moon with respect to the Earth: ",wM," rad/s")
print("Velocity v of the Moon: ",vM/1000," km/s")
# Angular velocity of the Earth with respect to the Sun(rad/s)
wE = math.sqrt(FES/(ME * RSE))
# Angular velocity of the Mars with respect to the Sun(rad/s)
wMa = math.sqrt(FEMa/(MMa * RMS))
# Velocity v of the Earth (m/s)
vE = wE * RSE
# Velocity v of the Earth (m/s)
vMa = wMa * RMS
print("Angular velocity of the Earth with respect to the Sun: ",wE," rad/s")
print("Velocity v of the Earth: ",vE/1000," km/s")
# Initial angular position
theta0 = 0
# Position at each time
def positionMoon(t):
theta = theta0 + wM * t
return theta
def positionMars(t):
theta = theta0 + wMa * t
return theta
def positionEarth(t):
theta = theta0 + wE * t
return theta
def fromDaysToS(d):
s = d*24*60*60
return s
def fromStoDays(s):
d = s/60/60/24
return d
def fromDaysToh(d):
h = d * 24
return h
# Graphical parameters
print("\nSimulation Earth-Moon-Sun motion\n")
days = 365
seconds = fromDaysToS(days)
print("Days: ",days)
print("Seconds: ",seconds)
v = vector(384,0,0)
E = sphere(pos = vector(1500,0,0), color = color.blue, radius = 60, make_trail=True)
Ma = sphere(pos = vector(2300,0,0), color = color.orange, radius = 30, make_trail=True)
M = sphere(pos = E.pos + v, color = color.white,radius = 10, make_trail=True)
S = sphere(pos = vector(0,0,0), color = color.yellow, radius=700)
t = 0
thetaTerra1 = 0
dt = 5000
dthetaE = positionEarth(t+dt)- positionEarth(t)
dthetaM = positionMoon(t+dt) - positionMoon(t)
dthetaMa = positionMars(t+dt) - positionMars(t)
print("delta t:",dt,"seconds. Days:",fromStoDays(dt),"hours:",fromDaysToh(fromStoDays(dt)),sep=" ")
print("Variation angular position of the Earth:",dthetaE,"rad/s that's to say",degrees(dthetaE),"degrees",sep=" ")
print("Variation angular position of the Moon:",dthetaM,"rad/s that's to say",degrees(dthetaM),"degrees",sep=" ")
while t < seconds:
rate(500)
thetaEarth = positionEarth(t+dt)- positionEarth(t)
thetaMoon = positionMoon(t+dt) - positionMoon(t)
thetaMars = positionMars(t+dt) - positionMars(t)
# Rotation only around z axis (0,0,1)
E.pos = rotate(E.pos,angle=thetaEarth,axis=vector(0,1,0))
Ma.pos = rotate(Ma.pos,angle=thetaMars,axis=vector(0,1,0))
v = rotate(v,angle=thetaMoon,axis=vector(0,1,0))
M.pos = E.pos + v
t += dt
I am wondering How to change the path of orbit to elliptical?
I have tried several ways but I could not manage to find any solution.
Thank you.
Thank you
This seems like more of a physics issue as opposed to a programming issue. The problem is that you are assuming that each of the orbits are circular when calculating velocity and integrating position linearly (e.g v * dt). This is not how you would go about calculating the trajectory of an orbiting body.
For the case of simplicity, we will assume all the masses are point masses so there aren't any weird gravity gradients or attitude dynamics to account for.
From there, you can refer to this MIT page. (http://web.mit.edu/12.004/TheLastHandout/PastHandouts/Chap03.Orbital.Dynamics.pdf) on orbit dynamics. On the 7th page, there is an equation relating the radial position from your centerbody as a function of a multitude of orbital parameters. It seems like you have every parameter except the eccentricity of the orbit. You can either look that up online or calculate it if you have detailed ephemeral data or apoapsis/periapsis information.
From that equation, you will see a phi - phi_0 term in the denominator. That is colloquially known as the true anomaly of the satellite. Instead of time, you would iterate on this true anomaly parameter from 0 to 360 to find your radial distance, and from true anomaly, inclination, right angle to the ascending node, and the argument of periapses, you can find the 3D cartesian coordinates at a specific true anomaly.
Going from true anomaly is a little less trivial. You will need to find the eccentric anomaly and then the mean anomaly at each eccentric anomaly step. You now have mean anomaly as a function of time. You can linearly interpolate between "nodes" at which you calculate the position with v * dt. You can calculate the velocity from using the vis-viva equation and dt would be the difference between the calculated time steps.
At each time step you can update the satellite's position in your python program and it will properly draw your trajectories.
For more information of the true anomaly, wikipedia has a good description of it: https://en.wikipedia.org/wiki/True_anomaly
For more information about orbital elements (which are needed to convert from radial position to cartesian coordinates): https://en.wikipedia.org/wiki/Orbital_elements

Make a total vector for three vectors in python

We have a class that have three functions called(Bdisk, Bhalo,and BX).
all of these functions accept arrays (e.g. shape (1000))not matrices (e.g. shape (2,1000)).
I want to get the total of all these functions( total= Bdisk + Bhalo+BX), total these all functions give the magnetic field in all three components (B_r, B_phi, B_z) for thousand coordinate points (r, phi, z).
the code is here:
import numpy as np
import logging
import warnings
import gmf
signum = lambda x: (x < 0.) * -1. + (x >= 0) * 1.
pi = np.pi
#Class with analytical functions that describe the GMF according to the model of JF12
class GMF(object):
def __init__(self): # self:is automatically set to reference the newly created object that needs to be initialized
self.Rsun = -8.5 # position of the sun along the x axis in kpc
############################################################################
# Disk Parameters
############################################################################
self.bring, self.bring_unc = 0.1,0.1 # floats, field strength in ring at 3 kpc < r < 5 kpc
self.hdisk, self.hdisk_unc = 0.4, 0.03 # float, disk/halo transition height
self.wdisk, self.wdisk_unc = 0.27,0.08 # floats, transition width
self.b = np.array([0.1,3.,-0.9,-0.8,-2.0,-4.2,0.,2.7]) # (8,1)-dim np.arrays, field strength of spiral arms at 5 kpc
self.b_unc = np.array([1.8,0.6,0.8,0.3,0.1,0.5,1.8,1.8]) # uncertainty
self.rx = np.array([5.1,6.3,7.1,8.3,9.8,11.4,12.7,15.5])# (8,1)-dim np.array,dividing lines of spiral lines coordinates of neg. x-axes that intersect with arm
self.idisk = 11.5 * pi/180. # float, spiral arms pitch angle
#############################################################################
# Halo Parameters
#############################################################################
self.Bn, self.Bn_unc = 1.4,0.1 # floats, field strength northern halo
self.Bs, self.Bs_unc = -1.1,0.1 # floats, field strength southern halo
self.rn, self.rn_unc = 9.22,0.08 # floats, transition radius south, lower limit
self.rs, self.rs_unc = 16.7,0. # transition radius south, lower limit
self.whalo, self.whalo_unc = 0.2,0.12 # floats, transition width
self.z0, self.z0_unc = 5.3, 1.6 # floats, vertical scale height
##############################################################################
# Out of plaxe or "X" component Parameters
##############################################################################
self.BX0, self.BX_unc = 4.6,0.3 # floats, field strength at origin
self.ThetaX0, self.ThetaX0_unc = 49. * pi/180., pi/180. # elev. angle at z = 0, r > rXc
self.rXc, self.rXc_unc = 4.8, 0.2 # floats, radius where thetaX = thetaX0
self.rX, self.rX_unc = 2.9, 0.1 # floats, exponential scale length
# striated field
self.gamma, self.gamma_unc = 2.92,0.14 # striation and/or rel. elec. number dens. rescaling
return
##################################################################################
##################################################################################
# Transition function given by logistic function eq.5
##################################################################################
def L(self,z,h,w):
if np.isscalar(z):
z = np.array([z]) # scalar or numpy array with positions (height above disk, z; distance from center, r)
ones = np.ones(z.shape[0])
return 1./(ones + np.exp(-2. *(np.abs(z)- h)/w))
####################################################################################
# return distance from center for angle phi of logarithmic spiral
# r(phi) = rx * exp(b * phi) as np.array
####################################################################################
def r_log_spiral(self,phi):
if np.isscalar(phi): #Returns True if the type of num is a scalar type.
phi = np.array([phi])
ones = np.ones(phi.shape[0])
# self.rx.shape = 8
# phi.shape = p
# then result is given as (8,p)-dim array, each row stands for one rx
# vstack : Take a sequence of arrays and stack them vertically to make a single array
# tensordot(a, b, axes=2):Compute tensor dot product along specified axes for arrays >=1D.
result = np.tensordot(self.rx , np.exp((phi - 3.*pi*ones) / np.tan(pi/2. - self.idisk)),axes = 0)
result = np.vstack((result, np.tensordot(self.rx , np.exp((phi - pi*ones) / np.tan(pi/2. - self.idisk)),axes = 0) ))
result = np.vstack((result, np.tensordot(self.rx , np.exp((phi + pi*ones) / np.tan(pi/2. - self.idisk)),axes = 0) ))
return np.vstack((result, np.tensordot(self.rx , np.exp((phi + 3.*pi*ones) / np.tan(pi/2. - self.idisk)),axes = 0) ))
#############################################################################################
# Disk component in galactocentric cylindrical coordinates (r,phi,z)
#############################################################################################
def Bdisk(self,r,phi,z):
# Bdisk is purely azimuthal (toroidal) with the field strength b_ring
"""
r: N-dim np.array, distance from origin in GC cylindrical coordinates, is in kpc
z: N-dim np.array, height in kpc in GC cylindrical coordinates
phi:N-dim np.array, polar angle in GC cylindircal coordinates, in radian
Bdisk: (3,N)-dim np.array with (r,phi,z) components of disk field for each coordinate tuple
|Bdisk|: N-dim np.array, absolute value of Bdisk for each coordinate tuple
"""
if (not r.shape[0] == phi.shape[0]) and (not z.shape[0] == phi.shape[0]):
warnings.warn("List do not have equal shape! returning -1", RuntimeWarning)
return -1
# Return a new array of given shape and type, filled with zeros.
Bdisk = np.zeros((3,r.shape[0])) # Bdisk vector in r, phi, z
ones = np.ones(r.shape[0])
r_center = (r >= 3.) & (r < 5.1)
r_disk = (r >= 5.1) & (r <= 20.)
Bdisk[1,r_center] = self.bring
# Determine in which arm we are
# this is done for each coordinate individually
if np.sum(r_disk):
rls = self.r_log_spiral(phi[r_disk])
rls = np.abs(rls - r[r_disk])
arms = np.argmin(rls, axis = 0) % 8
# The magnetic spiral defined at r=5 kpc and fulls off as 1/r ,the field direction is given by:
Bdisk[0,r_disk] = np.sin(self.idisk)* self.b[arms] * (5. / r[r_disk])
Bdisk[1,r_disk] = np.cos(self.idisk)* self.b[arms] * (5. / r[r_disk])
Bdisk *= (ones - self.L(z,self.hdisk,self.wdisk)) # multiplied by L
return Bdisk, np.sqrt(np.sum(Bdisk**2.,axis = 0)) # the Bdisk, the normalization
# axis=0 : sum over index 0(row)
# axis=1 : sum over index 1(columns)
##############################################################################################
# Halo component
###############################################################################################
def Bhalo(self,r,z):
# Bhalo is purely azimuthal (toroidal), i.e. has only a phi component
if (not r.shape[0] == z.shape[0]):
warnings.warn("List do not have equal shape! returning -1", RuntimeWarning)
return -1
Bhalo = np.zeros((3,r.shape[0])) # Bhalo vector in r, phi, z rows: r, phi and z component
ones = np.ones(r.shape[0])
m = ( z != 0. )
# SEE equation 6.
Bhalo[1,m] = np.exp(-np.abs(z[m])/self.z0) * self.L(z[m], self.hdisk, self.wdisk) * \
( self.Bn * (ones[m] - self.L(r[m], self.rn, self.whalo)) * (z[m] > 0.) \
+ self.Bs * (ones[m] - self.L(r[m], self.rs, self.whalo)) * (z[m] < 0.) )
return Bhalo , np.sqrt(np.sum(Bhalo**2.,axis = 0))
##############################################################################################
# BX component (OUT OF THE PLANE)
###############################################################################################
def BX(self,r,z):
#BX is purely ASS and poloidal, i.e. phi component = 0
if (not r.shape[0] == z.shape[0]):
warnings.warn("List do not have equal shape! returning -1", RuntimeWarning)
return -1
BX= np.zeros((3,r.shape[0])) # BX vector in r, phi, z rows: r, phi and z component
m = np.sqrt(r**2. + z**2.) >= 1.
bx = lambda r_p: self.BX0 * np.exp(-r_p / self.rX) # eq.7
thetaX = lambda r,z,r_p: np.arctan(np.abs(z)/(r - r_p)) # eq.10
r_p = r[m] *self.rXc/(self.rXc + np.abs(z[m] ) / np.tan(self.ThetaX0)) # eq 9
m_r_b = r_p > self.rXc # region with constant elevation angle
m_r_l = r_p <= self.rXc # region with varying elevation angle
theta = np.zeros(z[m].shape[0])
b = np.zeros(z[m].shape[0])
r_p0 = (r[m])[m_r_b] - np.abs( (z[m])[m_r_b] ) / np.tan(self.ThetaX0) # eq.8
b[m_r_b] = bx(r_p0) * r_p0/ (r[m])[m_r_b] # the field strength in the constant elevation angle (b_x(r_p)r_p/r)
theta[m_r_b] = self.ThetaX0 * np.ones(theta.shape[0])[m_r_b]
b[m_r_l] = bx(r_p[m_r_l]) * (r_p[m_r_l]/(r[m])[m_r_l] )**2. # the field strength with varying elevation angle (b_x(r_p)(r_p/r)**2)
theta[m_r_l] = thetaX((r[m])[m_r_l] ,(z[m])[m_r_l] ,r_p[m_r_l])
mz = (z[m] == 0.)
theta[mz] = np.pi/2.
BX[0,m] = b * (np.cos(theta) * (z[m] >= 0) + np.cos(pi*np.ones(theta.shape[0]) - theta) * (z[m] < 0))
BX[2,m] = b * (np.sin(theta) * (z[m] >= 0) + np.sin(pi*np.ones(theta.shape[0]) - theta) * (z[m] < 0))
return BX, np.sqrt(np.sum(BX**2.,axis=0))
then, I create three arrays, one for r, one for phi, one for z. Each of these arrays has (e.g: thousand elements). like this:
import gmf
gmfm = gmf.GMF()
x = np.linspace(-20.,20.,100)
y = np.linspace(-20.,20.,100)
z = np.linspace(-1.,1.,x.shape[0])
xx,yy = np.meshgrid(x,y)
rr = np.sqrt(xx**2. + yy**2.)
theta = np.arctan2(yy,xx)
for i,r in enumerate(rr[:]):
Bdisk, Babs_d = gmfm.Bdisk(r,theta[i],z)
Bhalo, Babs_h = gmfm.Bhalo(r,z)
BX, Babs_x = gmfm.BX(r,z)
Btotal = Bdisk + Bhalo + BX
but I am getting when I make the addition of the three functions Btotal= Bdisk + Bhalo+BX) in 2d matrix with 3 rows and 100 columns.
My question is how can I add these three functions together to get Btotal in shape (n,) e.g( shape(100,)
because as I said in the beginning the three functions accept accept arrays (e.g. shape (1000) )then when we adding the three functions together we have to get the total also in the same shape (shape (n,)?
I do not know how can I do it, could you please tell me how can I make it.
thank you for your cooperation.
You need to correct the indention, for example in the def Bdisk method.
More importantly in
for i,r in enumerate(rr[:]):
Bdisk, Babs_d = gmfm.Bdisk(r,theta[i],z)
Bhalo, Babs_h = gmfm.Bhalo(r,z)
BX, Babs_x = gmfm.BX(r,z)
Btotal = Bdisk + Bhalo + BX
are you doing this addition for each iteration, or once at the end of the loop? You aren't accumulating any values over iterations. You are just throwing away the old ones, leaving you with the final iteration.
As for adding the array - it appears that all your arrays are initialed like:
Bdisk = np.zeros((3,r.shape[0]))
If that's what the method returns, then
Bdisk + Bhalo + BX
will just sum the corresponding elements of each array, resulting in a Btotal with the same shape. If you don't not like the shape of Btotal then change how Bdisk is calculated, because it has the same shape.

Categories