I want to randomly distribute N particles within a volume such that they satisfy the Plummer potential distribution. I trying to work from "The Art of Computational Science" by Hut, which has a description but I can't seem to implement it. Where I differ from Hut is that I require 3 velocity components for each particle. Here's what I have done so far:
f=0
g=0.1
if g >f*f*(1-f*f)**3.5:
f=np.random.uniform(0,1,N)
g=np.random.uniform(0,0.1,N)
vel_x= f*np.sqrt(2)*(1+x*x)**(-0.25)
vel_y= f*np.sqrt(2)*(1+y*y)**(-0.25)
vel_z= f*np.sqrt(2)*(1+z*z)**(-0.25)
vel = np.zeros((N,3))
vel[:,0]=vel_x
vel[:,1]=vel_y
vel[:,2]=vel_z
However, when I run the energy check described by Hut, such that the kinetic energy ~0.147 in N body units, this code fails. Any advice on where Im going wrong would be greatly appreciated
You are probably misreading the Ruby code in Hut's book since it is also generating 3-dimensional velocity vectors:
x = 0.0
y = 0.1
while y > x*x*(1.0-x*x)**3.5
x = frand(0,1)
y = frand(0,0.1)
end
velocity = x * sqrt(2.0) * ( 1.0 + radius*radius)**(-0.25)
theta = acos(frand(-1, 1))
phi = frand(0, 2*PI)
b.vel[0] = velocity * sin( theta ) * cos( phi )
b.vel[1] = velocity * sin( theta ) * sin( phi )
b.vel[2] = velocity * cos( theta )
The first part generates |v| by rejection sampling from the velocity distribution. The second part generates a random direction in space (in polar coordinates) and the last part of the code transforms from polar to Cartesian coordinates.
Your code does something completely different. You should instead adapt the code fragment shown above in Python, e.g.:
f = 0.0
g = 0.1
while g > f*f*(1.0-f*f)**3.5:
f = np.random.uniform(0,1)
g = np.random.uniform(0,0.1)
velocity = f * np.sqrt(2.0) * (1.0 + radius*radius)**(-0.25)
theta = np.arccos(np.random.uniform(-1, 1))
phi = np.random.uniform(0, 2*np.pi)
vel[n,0] = velocity * np.sin(theta) * np.cos(phi)
vel[n,1] = velocity * np.sin(theta) * np.sin(phi)
vel[n,2] = velocity * np.cos(theta)
The code could possibly be vectorised, but in reality it makes little sense since the rejection sampling is not vectorisable (it might and most likely will take different number of iterations for each sample).
Related
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 the following data points: There are 5 sublists in this list of data. What I am trying to do is find the points where there is a maximum amount of curvature.
for i in range(len(smallest_5)):
x = [x for x,y in smallest_5[i]]
y = [y for x,y in smallest_5[i]]
plt.scatter(x,y)
plt.savefig('bend'+str(count)+'.png')
plt.show()
I've used this code to plot the points.
sub_curvature = []
for i in range(len(smallest_5)):
a = np.array(smallest_5[i])
dx_dt = np.gradient(a[:,0])
dy_dt = np.gradient(a[:,1])
velocity = np.array([ [dx_dt[i], dy_dt[i]] for i in range(dx_dt.size)])
ds_dt = np.sqrt(dx_dt * dx_dt + dy_dt * dy_dt)
tangent = np.array([1/ds_dt] * 2).transpose() * velocity
tangent_x = tangent[:, 0]
tangent_y = tangent[:, 1]
deriv_tangent_x = np.gradient(tangent_x)
deriv_tangent_y = np.gradient(tangent_y)
dT_dt = np.array([ [deriv_tangent_x[i], deriv_tangent_y[i]] for i in range(deriv_tangent_x.size)])
length_dT_dt = np.sqrt(deriv_tangent_x * deriv_tangent_x + deriv_tangent_y * deriv_tangent_y)
normal = np.array([1/length_dT_dt] * 2).transpose() * dT_dt
d2s_dt2 = np.gradient(ds_dt)
d2x_dt2 = np.gradient(dx_dt)
d2y_dt2 = np.gradient(dy_dt)
curvature = np.abs(d2x_dt2 * dy_dt - dx_dt * d2y_dt2) / (dx_dt * dx_dt + dy_dt * dy_dt)**1.5
t_component = np.array([d2s_dt2] * 2).transpose()
n_component = np.array([curvature * ds_dt * ds_dt] * 2).transpose()
acceleration = t_component * tangent + n_component * normal
sub_curvature.append(curvature)
I used the code above to calculate the curvature of individual points on the data.
Above are some of the graphs I created using the data. As you can see, the first one has no real bend but the last two have a point where there is a large bend. How could I go about identifying this region? Is it correct to calculate the curvature for individual points or should I look at the curvature over a sliding window of points? Thank you!
If we assume "curvature" to mean circular curvature, then you'll need a sliding window over 3 points (since 3 points determine a circle).
For any three points (a,b,c) the curvature is 2 * |(a-b) x (b-c)| / (|a-b| * |b-c| * |c-b|).
We can get a-b and b-c from
ab = smallest_5[1:] - smallest_5[:-1]
and a-c from:
ac = smallest_5[2:] - smallest_5[:-2]
Then the squared curvature is:
curv_sq = 4 * (np.cross(ab[1:], ab[:-1])**2).sum() / ((ab[1:]**2).sum() * (ab[:-1]**2).sum() * (ac**2).sum())
Since we're just looking for a maximum curvature, we don't actually have to take the square root of that. We can find the index of the point with maximum curvature with
max_curv_index = np.argmax(curv_sq)
As an idea, you can find the minimum y which is not the first or the last value in the y-dimension of the array. For example:
s4 = np.array(smallest_5[4]).T # exctract a sub-array
min_y = np.agrmin(s4[1]) # gives 13
min_y == (0 or len(s4[1]-1) # gives False, so the minimum is in the middle of the curve
s0 = np.array(smallest_5[0]).T # exctract a sub-array
min_y = np.agrmin(s0[1]) # gives 16
min_y == (0 or len(s0[1]-1) # gives True, so the minimum is not in the middle of the curve
I have dataframe with measurements coordinates and cell coordinates.
I need to find for each row angle (azimuth angle) between a line that connects these two points and the north pole.
df:
id cell_lat cell_long meas_lat meas_long
1 53.543643 11.636235 53.44758 11.03720
2 52.988823 10.0421645 53.03501 9.04165
3 54.013442 9.100981 53.90384 10.62370
I have found some code online, but none if that really helps me get any closer to the solution.
I have used this function but not sure if get it right and I guess there is simplier solution.
Any help or hint is welcomed, thanks in advance.
The trickiest part of this problem is converting geodetic (latitude, longitude) coordinates to Cartesian (x, y, z) coordinates. If you look at https://en.wikipedia.org/wiki/Geographic_coordinate_conversion you can see how to do this, which involves choosing a reference system. Assuming we choose ECEF (https://en.wikipedia.org/wiki/ECEF), the following code calculates the angles you are looking for:
def vector_calc(lat, long, ht):
'''
Calculates the vector from a specified point on the Earth's surface to the North Pole.
'''
a = 6378137.0 # Equatorial radius of the Earth
b = 6356752.314245 # Polar radius of the Earth
e_squared = 1 - ((b ** 2) / (a ** 2)) # e is the eccentricity of the Earth
n_phi = a / (np.sqrt(1 - (e_squared * (np.sin(lat) ** 2))))
x = (n_phi + ht) * np.cos(lat) * np.cos(long)
y = (n_phi + ht) * np.cos(lat) * np.sin(long)
z = ((((b ** 2) / (a ** 2)) * n_phi) + ht) * np.sin(lat)
x_npole = 0.0
y_npole = 6378137.0
z_npole = 0.0
v = ((x_npole - x), (y_npole - y), (z_npole - z))
return v
def angle_calc(lat1, long1, lat2, long2, ht1=0, ht2=0):
'''
Calculates the angle between the vectors from 2 points to the North Pole.
'''
# Convert from degrees to radians
lat1_rad = (lat1 / 180) * np.pi
long1_rad = (long1 / 180) * np.pi
lat2_rad = (lat2 / 180) * np.pi
long2_rad = (long2 / 180) * np.pi
v1 = vector_calc(lat1_rad, long1_rad, ht1)
v2 = vector_calc(lat2_rad, long2_rad, ht2)
# The angle between two vectors, vect1 and vect2 is given by:
# arccos[vect1.vect2 / |vect1||vect2|]
dot = np.dot(v1, v2) # The dot product of the two vectors
v1_mag = np.linalg.norm(v1) # The magnitude of the vector v1
v2_mag = np.linalg.norm(v2) # The magnitude of the vector v2
theta_rad = np.arccos(dot / (v1_mag * v2_mag))
# Convert radians back to degrees
theta = (theta_rad / np.pi) * 180
return theta
angles = []
for row in range(df.shape[0]):
cell_lat = df.iloc[row]['cell_lat']
cell_long = df.iloc[row]['cell_long']
meas_lat = df.iloc[row]['meas_lat']
meas_long = df.iloc[row]['meas_long']
angle = angle_calc(cell_lat, cell_long, meas_lat, meas_long)
angles.append(angle)
This will read each row out of your dataframe, calculate the angle and append it to the list angles. Obviously you can do what you like with those angles after they've been calculated.
Hope that helps!
Similar to many tutorials on the web, I've tried implementing a windowed-sinc lowpass filter using the following python functions:
def black_wind(w):
''' blackman window of width w'''
samps = np.arange(w)
return (0.42 - 0.5 * np.cos(2 * np.pi * samps/ (w-1)) + 0.08 * np.cos(4 * np.pi * samps/ (w-1)))
def lp_win_sinc(tw, fc, n):
''' lowpass sinc impulse response
Parameters:
tw = approximate transition width [fraction of nyquist freq]
fc = cutoff freq [fraction of nyquest freq]
n = length of output.
Returns:
s = impulse response of windowed-sinc filter appended zero-padding
to make len(s) = n
'''
m = int(np.ceil( 4./tw / 2) * 2)
samps = np.arange(m+1)
shift = samps - m/2
shift[m/2] = 1
h = np.sin(2 * np.pi * fc * shift)/shift
h[m/2] = 2 * np.pi * fc
h = h * black_wind(m+1)
h = h / h.sum()
s = np.zeros(n)
s[:len(h)] = h
return s
For input: 'tw = 0.05', 'fc = 0.2', 'n = 6000', the magnitude of the fft seems reasonable.
tw = 0.05
fc = 0.2
n = 6000
lp = lp_win_sinc(tw, fc, n)
f_lp = np.fft.rfft(lp)
plt.figure()
x = np.linspace(0, 0.5, len(f_lp))
plt.plot(x, np.abs(f_lp))
magnitude of lowpass filter response
however, the phase is non-linear above ~fc.
plt.figure()
x = np.linspace(0, 0.5, len(f_lp))
plt.plot(x, np.unwrap(np.angle(f_lp)))
phase of lowpass filter response
Given the symmetry of the non-zero-padded portion of the impulse response, I would expect the resulting phase to be linear. Can someone explain what is going on? Perhaps I'm using a numpy function incorrectly, or maybe my expectations are incorrect. I'm very grateful for any help.
***********************EDIT***********************
based on some of the helpful comments to this question and some more work, I wrote a function that produces zero phase delay and is therefore a bit easier to interpret the np.angle() results.
def lp_win_sinc(tw, fc, n):
m = int(np.ceil( 2./tw) * 2)
samps = np.arange(m+1)
shift = samps - m/2
shift[m/2] = 1
h = np.sin(2 * np.pi * fc * shift)/shift
h[m/2] = 2 * np.pi * fc
h = h * np.blackman(m+1)
h = h / h.sum()
s = np.zeros(n)
s[:len(h)] = h
return np.roll(s, -m/2)
The main change here is using np.roll() to place the line of symmetry at t=0.
The magnitudes in the stop band are crossing zero. The phase of the coefficient after a zero crossing will jump by 180 degrees, which is confusing np.angle()/np.unwrap(). -1*180° = 1*0°
The phase as shown in your graph is in fact linear. It's a constant slope in the passband, corresponding to a constant delay in the time domain. It's a much steeper slope, which renders as wrapping around at 2pi boundaries, in the stopband. But the value of the phase in the stopband is not particularly important since those frequencies aren't going to come through the filter anyway.
I am now working to make rotational trajectories. In the beginning I need to define the initial position of an rotating object. How to make 1000 random initial position in three dimensions of this kind of object by Python or NumPy? I think a python function can solve the problem.
If I understand the question, you need to pick up point uniformly distributed on a sphere. See here for details.
import math
import numpy as np
cos_theta = 2.0 * np.random.random(100) - 1.0
phi = 2.0 * math.pi * np.random.random(100)
sin_theta = np.sqrt( (1.0 - cos_theta)*(1.0 + cos_theta) )
x = sin_theta * np.cos(phi)
y = sin_theta * np.sin(phi)
z = cos_theta
print(x, y, z)
print("---------------------")
print(np.square(x) + np.square(y) + np.square(z))