Tried to find the MN disk parameters from the particles' coordinate medians
This commit is contained in:
parent
c2051e9596
commit
80e6a8d635
4 changed files with 166 additions and 49 deletions
29
main.cpp
29
main.cpp
|
|
@ -112,40 +112,11 @@ int integrate(const double y0[], const double t_max, const double stride_size, d
|
|||
}
|
||||
}
|
||||
|
||||
double circular_energy(double(*pot)(double), double L)
|
||||
{
|
||||
auto effective_potential = [&](double r) { return pot(r)+0.5*(L*L)/(r*r); };
|
||||
return pot(3);
|
||||
}
|
||||
|
||||
/*def circular_energy(Phi, L, PhiPrime=None, InitialGuess=1.0, GetRadius=False):
|
||||
EffectivePotential = lambda r: Phi(r) + 0.5*(L/r)**2
|
||||
EffectivePotentialPrime = None
|
||||
if not PhiPrime is None: EffectivePotentialPrime = lambda r: PhiPrime(r) - L**2/r**3
|
||||
Minimization = scipy.optimize.minimize(EffectivePotential, InitialGuess, method='BFGS', jac=EffectivePotentialPrime, tol=1.0E-08)
|
||||
# Minimization = scipy.optimize.minimize(EffectivePotential, InitialGuess, method='L-BFGS-B', bounds=[(0,None)], jac=EffectivePotentialPrime)
|
||||
# There is some risk that the negative value of r will be found as the minimum. To avoid this we can either set boundary conditions which might complicate the the numerical procedure, or just accept this and return the absolute value, which we can do here.
|
||||
if GetRadius: return Minimization.fun, abs(Minimization.x[0])
|
||||
return Minimization.fun*/
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cout << "Hi" << std::endl;
|
||||
|
||||
/*std::vector<double> x(500);
|
||||
std::vector<double> y(500);
|
||||
for (int i=0; i<500; i++) {
|
||||
x[i] = i;
|
||||
y[i] = 2*i;
|
||||
}
|
||||
// populate x, y, then:
|
||||
boost::math::barycentric_rational<double> interpolant(std::move(x), std::move(y));
|
||||
|
||||
|
||||
std::cout << interpolant(5.5) << std::endl;*/
|
||||
|
||||
|
||||
|
||||
double y[12];
|
||||
double y0[] = {80,0,0,0,80,0};
|
||||
for (int i=0; i<8000; i++)
|
||||
|
|
|
|||
31
miyamoto-nagai-properties.dat
Normal file
31
miyamoto-nagai-properties.dat
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
00 1.56250000E-02 1.73057284E+00 9.02110006E-03
|
||||
01 2.06173111E-02 1.73110977E+00 1.19034129E-02
|
||||
02 2.72047051E-02 1.73198691E+00 1.57066475E-02
|
||||
03 3.58968236E-02 1.73341356E+00 2.07250456E-02
|
||||
04 4.73661427E-02 1.73572341E+00 2.73468616E-02
|
||||
05 6.25000000E-02 1.73943887E+00 3.60844002E-02
|
||||
06 8.24692444E-02 1.74537072E+00 4.76136516E-02
|
||||
07 1.08818820E-01 1.75475734E+00 6.28265898E-02
|
||||
08 1.43587294E-01 1.76945143E+00 8.29001808E-02
|
||||
09 1.89464571E-01 1.79216210E+00 1.09387441E-01
|
||||
10 2.50000000E-01 1.82674174E+00 1.44337593E-01
|
||||
11 3.29876978E-01 1.87850325E+00 1.90454611E-01
|
||||
12 4.35275282E-01 1.95455938E+00 2.51306403E-01
|
||||
13 5.74349177E-01 2.06418987E+00 3.31600826E-01
|
||||
14 7.57858283E-01 2.21929669E+00 4.37549973E-01
|
||||
15 1.00000000E+00 2.43505215E+00 5.77350688E-01
|
||||
16 1.31950791E+00 2.73086484E+00 7.61818695E-01
|
||||
17 1.74110113E+00 3.13178242E+00 1.00522526E+00
|
||||
18 2.29739671E+00 3.67043573E+00 1.32640189E+00
|
||||
19 3.03143313E+00 4.38963656E+00 1.75019627E+00
|
||||
20 4.00000000E+00 5.34579194E+00 2.30939421E+00
|
||||
21 5.27803164E+00 6.61335009E+00 3.04725724E+00
|
||||
22 6.96440451E+00 8.29064669E+00 4.02086772E+00
|
||||
23 9.18958684E+00 1.05075098E+01 5.30554377E+00
|
||||
24 1.21257325E+01 1.34352650E+01 7.00066134E+00
|
||||
25 1.60000000E+01 1.72998194E+01 9.23734398E+00
|
||||
26 2.11121266E+01 2.23986356E+01 1.21885968E+01
|
||||
27 2.78576180E+01 2.91226778E+01 1.60826553E+01
|
||||
28 3.67583474E+01 3.79840572E+01 2.12206733E+01
|
||||
29 4.85029301E+01 4.96499050E+01 2.79998605E+01
|
||||
30 6.40000000E+01 6.49807950E+01 3.69442333E+01
|
||||
112
miyamoto-nagai-properties.py
Normal file
112
miyamoto-nagai-properties.py
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
#pylint: disable=W0401,W0614,W0622
|
||||
#All pylint codes: http://pylint.pycqa.org/en/latest/technical_reference/features.html
|
||||
|
||||
# This script finds the median values of the radius (cylindrical) and height of
|
||||
# a Miyamoto-Nagai disk as a function of the b parameter by numerical
|
||||
# integration. After obtaining the results of a large number of points, we find
|
||||
# analytical fitting formulae.
|
||||
|
||||
from pylab import *
|
||||
import scipy.integrate, scipy.interpolate
|
||||
|
||||
write_data = False # Generating the data file takes a long time; do it just once
|
||||
file_name = 'miyamoto-nagai-properties.dat'
|
||||
|
||||
rho_mn = lambda d, z, M, a, b: \
|
||||
(b**2 * M / (4*pi)) * \
|
||||
(a*d**2 + (a + 3*sqrt(z**2+b**2))*(a+sqrt(z**2+b**2))**2) / \
|
||||
((d**2 + (a+sqrt(z**2+b**2))**2)**2.5 * (z**2+b**2)**1.5)
|
||||
|
||||
if write_data:
|
||||
file_obj = open(file_name, 'w')
|
||||
n = 4096*2
|
||||
a = 1
|
||||
f = 1000
|
||||
b_array = logspace(-log10(64), log10(64), 31)
|
||||
for i, b in enumerate(b_array):
|
||||
dist_lambda = lambda z: scipy.integrate.quad(lambda d: 4*pi*d*rho_mn(d, z, 1, a, b), 0, inf)[0]
|
||||
z = logspace(log10(b/f), log10(b*f), n-1)
|
||||
z = insert(z, 0, 0)
|
||||
dist = array([dist_lambda(z_) for z_ in z])
|
||||
cumulative = scipy.integrate.cumtrapz(dist, z, initial=0)
|
||||
cumulative /= cumulative[-1]
|
||||
interp = scipy.interpolate.interp1d(z, cumulative, fill_value=(cumulative[0], cumulative[-1]), bounds_error=False)
|
||||
m_z = scipy.optimize.brentq(lambda x: interp(x)-0.5, b/f, b*f)
|
||||
|
||||
dist_lambda = lambda d: 2*scipy.integrate.quad(lambda z: 4*pi*d*rho_mn(d, z, 1, a, b), 0, inf)[0]
|
||||
d = logspace(-log10(f), log10(f), n-1)
|
||||
d = insert(d, 0, 0)
|
||||
dist = array([dist_lambda(d_) for d_ in d])
|
||||
cumulative = scipy.integrate.cumtrapz(dist, d, initial=0)
|
||||
cumulative /= cumulative[-1]
|
||||
interp = scipy.interpolate.interp1d(d, cumulative, fill_value=(cumulative[0], cumulative[-1]), bounds_error=False)
|
||||
m_d = scipy.optimize.brentq(lambda x: interp(x)-0.5, 1/f, f)
|
||||
|
||||
file_obj.write(f'{i:02d} {b:.8E} {m_d:.8E} {m_z:.8E}')
|
||||
file_obj.close()
|
||||
|
||||
i, b, m_d, m_z = np.loadtxt(file_name, unpack=True)
|
||||
|
||||
# m_z appears to be proportional to b; we just need to find the slope
|
||||
_, slope = polyfit(log(b), log(m_z), 1)
|
||||
slope = exp(slope)
|
||||
m_z_pred = slope*b
|
||||
scatter = std((m_z - m_z_pred)/m_z)
|
||||
max_err = abs((m_z - m_z_pred)/m_z).max()
|
||||
print(f'Slope: {slope:.5E}')
|
||||
print(f'Scatter: {scatter:.5E} Maximum error: {max_err:.5E}')
|
||||
|
||||
# m_d appears to be constant for low b and rise linearly for high b. We use a simple model that blends these two linear functions
|
||||
def func(x, p1, p2, p3, p4):
|
||||
blend_func = lambda x: np.arctan(x)/np.pi+0.5
|
||||
return p1 + (p2*x+p3)*blend_func(x/p4)
|
||||
cost = lambda p: sum((log(m_d) - func(log(b), *p))**2)
|
||||
p = scipy.optimize.minimize(cost, [1,1,-1,1]).x
|
||||
m_d_pred = exp(func(log(b), *p))
|
||||
scatter = std((m_d - m_d_pred)/m_d)
|
||||
max_err = abs((m_d - m_d_pred)/m_d).max()
|
||||
print(f'Parameters: {p}')
|
||||
print(f'Scatter: {scatter:.5E} Maximum error: {max_err:.5E}')
|
||||
|
||||
def generate_particles(n, a, b, d_max=5, z_max=5, batch_mult=1):
|
||||
rho_max = rho_mn(0, 0, 1, a, b)
|
||||
n_batch = int(2**ceil(log(n)/log(2)))*batch_mult
|
||||
x_new = empty(0)
|
||||
y_new = empty(0)
|
||||
z_new = empty(0)
|
||||
while len(x_new) < n:
|
||||
x_ = (rand(n_batch)*2-1)*d_max*sqrt(2)
|
||||
y_ = (rand(n_batch)*2-1)*d_max*sqrt(2)
|
||||
d_ = sqrt(x_**2 + y_**2)
|
||||
mask = d_ < d_max
|
||||
x_ = x_[mask]
|
||||
y_ = y_[mask]
|
||||
d_ = d_[mask]
|
||||
z_ = rand(len(d_))*z_max
|
||||
|
||||
p = rho_mn(d_, z_, 1, a, b)/rho_max
|
||||
|
||||
random_number = rand(len(d_))
|
||||
mask = random_number < p
|
||||
x_ = x_[mask]
|
||||
y_ = y_[mask]
|
||||
z_ = z_[mask]
|
||||
|
||||
z_sign = np.round(rand(len(z_)))*2-1
|
||||
z_ *= z_sign
|
||||
|
||||
x_new = append(x_new, x_)
|
||||
y_new = append(y_new, y_)
|
||||
z_new = append(z_new, z_)
|
||||
print(len(x_new))
|
||||
|
||||
x_new = x_new[:n]
|
||||
y_new = y_new[:n]
|
||||
z_new = z_new[:n]
|
||||
return x_new, y_new, z_new
|
||||
|
||||
x, y, z = generate_particles(43807, 2.98, 1.61, d_max=50, z_max=50, batch_mult=16)
|
||||
figure()
|
||||
plot(x, z, ',')
|
||||
xlim(-40,40); ylim(-20,20); gca().set_aspect('equal')
|
||||
show()
|
||||
|
|
@ -61,11 +61,11 @@ d = sqrt(x**2 + y**2)
|
|||
# Cread a two-dimensional grid
|
||||
# The grid sizes don't _have to_ be equal in both directions, but there is some
|
||||
# logic in keeping them the same.
|
||||
n_grid = 16
|
||||
n_grid_d, n_grid_z = 16, 16
|
||||
d_max = 2*median(d)
|
||||
z_max = 2*median(abs(z))
|
||||
d_grid = linspace(0, d_max, n_grid+1)
|
||||
z_grid = linspace(0, z_max, n_grid+1)
|
||||
d_grid = linspace(0, d_max, n_grid_d+1)
|
||||
z_grid = linspace(0, z_max, n_grid_z+1)
|
||||
|
||||
# Calculates the mass in each (d,z)-cell
|
||||
# We fold negative z-values assuming symmetry with respect to the xy-plane
|
||||
|
|
@ -90,19 +90,23 @@ rho_mn = lambda d, z, M, a, b: \
|
|||
# The minimization procedure
|
||||
means = lambda arr: .5*(arr[:-1]+arr[1:]) # small helper function
|
||||
# Define a grid of the centre of each cell from the histogram we created earlier
|
||||
dd, zz = meshgrid(means(d_grid), means(z_grid))
|
||||
dd, zz = meshgrid(means(d_grid), means(z_grid), indexing='xy')
|
||||
def cost(args):
|
||||
f_plummer, b_plummer, a_mn, b_mn = args
|
||||
f_mn = 1 - f_plummer
|
||||
rho = rho_plummer(sqrt(dd**2+zz**2), f_plummer, b_plummer) + rho_mn(dd, zz, f_mn, a_mn, b_mn)
|
||||
a_mn, b_mn = args
|
||||
rho = rho_mn(dd, zz, 1, a_mn, b_mn)
|
||||
square_diff = (rho - rho_measured_normalized)**2
|
||||
return sum(square_diff)
|
||||
minimization_result = \
|
||||
scipy.optimize.minimize(cost, [0.5, rh, median(abs(z)), median(d)],
|
||||
method='Nelder-Mead', tol=1e-6, options={'maxiter':5000})
|
||||
f_plummer, b_plummer, a_mn, b_mn = minimization_result.x
|
||||
print(f'f_plummer = {f_plummer:.4f} b_plummer = {b_plummer:.4f} kpc a_mn = {a_mn:.4f} kpc b_mn = {b_mn:.4f} kpc')
|
||||
print(f'M_plummer = {f_plummer*M_tot:.2e} MSun M_mn = {(1-f_plummer)*M_tot:.2e} MSun')
|
||||
scipy.optimize.minimize(cost, [median(abs(z)), median(d)], method='Nelder-Mead', tol=1e-6, options={'maxiter':5000})
|
||||
a_mn, b_mn = minimization_result.x
|
||||
####
|
||||
if False:
|
||||
print('!!!Setting parameters artificially!!!')
|
||||
a_mn, b_mn = 2.98, 1.61
|
||||
####
|
||||
rho_best_fit = rho_mn(dd, zz, 1, a_mn, b_mn)
|
||||
print(f'a_mn = {a_mn:.4f} kpc b_mn = {b_mn:.4f} kpc')
|
||||
print(f'M_mn = {M_tot:.2e} MSun')
|
||||
|
||||
# Compare with Matteo's results
|
||||
with h5py.File(file_name, 'r') as f:
|
||||
|
|
@ -126,8 +130,7 @@ print(f'M_bulge = {M_matteo_bulge:.2e} MSun M_disk = {M_matteo_disk:.2e} MSun'
|
|||
print(f'rh_bulge = {rh_matteo_bulge:.4f} kpc (equivalent Plummer radius: {0.76642*rh_matteo_bulge:.4f}) kpc')
|
||||
|
||||
# Plot the density as a function of d for three values of z.
|
||||
f_mn = 1 - f_plummer
|
||||
rho = lambda d, z: rho_plummer(sqrt(d**2+z**2), f_plummer, b_plummer) + rho_mn(d, z, f_mn, a_mn, b_mn)
|
||||
rho = lambda d, z: rho_mn(d, z, 1, a_mn, b_mn)
|
||||
for c, i in enumerate([0, 3, 6, 9]):
|
||||
semilogy(dd[i,:], M_tot*rho_measured_normalized[i,:], c=f'C{c}', ls='-', label=f'h={1000*zz[i,0]:.0f} pc')
|
||||
semilogy(dd[i,:], M_tot*rho(dd[i,:], zz[i,0]), c=f'C{c}', ls='--', alpha=0.5)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue