Tried to find the MN disk parameters from the particles' coordinate medians

This commit is contained in:
Yohai Meiron 2020-04-22 23:32:05 -04:00
parent c2051e9596
commit 80e6a8d635
4 changed files with 166 additions and 49 deletions

View file

@ -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++)

View 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

View 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()

View file

@ -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)