|
1 | 1 | import numpy as np |
2 | | -import scipy |
3 | | -from scipy.interpolate import interp1d, BSpline, splrep |
4 | 2 |
|
5 | | -def makemodel(func, M, funcargs, rvals = 10.**np.linspace(-2.,4.,2000), pfile='', plabel = '',verbose=True): |
6 | | - """make an EXP-compatible spherical basis function table |
7 | | - |
8 | | - inputs |
9 | | - ------------- |
10 | | - func : (function) the callable functional form of the density |
11 | | - M : (float) the total mass of the model, sets normalisations |
12 | | - funcargs : (list) a list of arguments for the density function. |
13 | | - rvals : (array of floats) radius values to evaluate the density function |
14 | | - pfile : (string) the name of the output file. If '', will not print file |
15 | | - plabel : (string) comment string |
16 | | - verbose : (boolean) |
17 | | -
|
18 | | - outputs |
19 | | - ------------- |
20 | | - R : (array of floats) the radius values |
21 | | - D : (array of floats) the density |
22 | | - M : (array of floats) the mass enclosed |
23 | | - P : (array of floats) the potential |
24 | | - |
| 3 | +def _write_table(tablename, radius, density, mass, potential): |
25 | 4 | """ |
26 | | - |
27 | | - R = np.nanmax(rvals) |
28 | | - |
29 | | - # query out the density values |
30 | | - if func == empirical_density_profile: |
31 | | - rvals, dvals = func(rvals,*funcargs) |
32 | | - else: |
33 | | - dvals = func(rvals,*funcargs) |
34 | | - |
35 | | - # make the mass and potential arrays |
36 | | - mvals = np.zeros(dvals.size) |
37 | | - pvals = np.zeros(dvals.size) |
38 | | - pwvals = np.zeros(dvals.size) |
39 | | - |
40 | | - # initialise the mass enclosed an potential energy |
41 | | - mvals[0] = 1.e-15 |
| 5 | + Write a table of radius, density, mass, and potential values to a text file. |
| 6 | +
|
| 7 | + Parameters |
| 8 | + ---------- |
| 9 | + tablename : str |
| 10 | + Name of the output file where the table will be written. |
| 11 | + radius : array-like |
| 12 | + Radius values. |
| 13 | + density : array-like |
| 14 | + Density values corresponding to radius. |
| 15 | + mass : array-like |
| 16 | + Mass values corresponding to radius. |
| 17 | + potential : array-like |
| 18 | + Potential values corresponding to radius. |
| 19 | +
|
| 20 | + Returns |
| 21 | + ------- |
| 22 | + None |
| 23 | + """ |
| 24 | + with open(tablename, 'w') as f: |
| 25 | + print('! ', tablename, file=f) |
| 26 | + print('! R D M P', file=f) |
| 27 | + print(radius.size, file=f) |
| 28 | + for r, d, m, p in zip(radius, density, mass, potential): |
| 29 | + print(f'{r} {d} {m} {p}', file=f) |
| 30 | + |
| 31 | +def makemodel(radius, density, Mtotal, output_filename='', physial_units=False, verbose=True): |
| 32 | + """ |
| 33 | + Generate an EXP-compatible spherical basis function table. |
| 34 | +
|
| 35 | + Parameters |
| 36 | + ---------- |
| 37 | + radius : array-like |
| 38 | + Radii at which the density values are evaluated. |
| 39 | + density : array-like |
| 40 | + Density values corresponding to radius. |
| 41 | + Mtotal : float |
| 42 | + Total mass of the model, used for normalization. |
| 43 | + output_filename : str, optional |
| 44 | + Name of the output file to save the table. If empty, no file is written. |
| 45 | + verbose : bool, optional |
| 46 | + If True, prints scaling information. |
| 47 | +
|
| 48 | + Returns |
| 49 | + ------- |
| 50 | + radius_scaled : ndarray |
| 51 | + Scaled radius values. |
| 52 | + density_scaled : ndarray |
| 53 | + Scaled density values. |
| 54 | + mass_scaled : ndarray |
| 55 | + Scaled enclosed mass values. |
| 56 | + potential_scaled : ndarray |
| 57 | + Scaled potential values. |
| 58 | + """ |
| 59 | + Rmax = np.nanmax(radius) |
| 60 | + |
| 61 | + mass = np.zeros_like(density) |
| 62 | + pwvals = np.zeros_like(density) |
| 63 | + |
| 64 | + mass[0] = 1.e-15 |
42 | 65 | pwvals[0] = 0. |
43 | 66 |
|
44 | | - # evaluate mass enclosed and potential energy by recursion |
45 | | - for indx in range(1,dvals.size): |
46 | | - mvals[indx] = mvals[indx-1] +\ |
47 | | - 2.0*np.pi*(rvals[indx-1]*rvals[indx-1]*dvals[indx-1] +\ |
48 | | - rvals[indx]*rvals[indx]*dvals[indx])*(rvals[indx] - rvals[indx-1]); |
49 | | - pwvals[indx] = pwvals[indx-1] + \ |
50 | | - 2.0*np.pi*(rvals[indx-1]*dvals[indx-1] + rvals[indx]*dvals[indx])*(rvals[indx] - rvals[indx-1]); |
| 67 | + #dr = radius[indx] - radius[indx - 1] |
| 68 | + dr = np.diff(radius) # differences between consecutive radii |
| 69 | + |
| 70 | + # Midpoint mass contribution terms |
| 71 | + mass_contrib = 2.0 * np.pi * ( |
| 72 | + radius[:-1]**2 * density[:-1] + radius[1:]**2 * density[1:] |
| 73 | + ) * dr |
| 74 | + |
| 75 | + pwvals_contrib = 2.0 * np.pi * ( |
| 76 | + radius[:-1] * density[:-1] + radius[1:] * density[1:] |
| 77 | + ) * dr |
| 78 | + |
| 79 | + # Now cumulative sum to get the arrays |
| 80 | + mass = np.concatenate(([1e-15], 1e-15 + np.cumsum(mass_contrib))) |
| 81 | + pwvals = np.concatenate(([0.0], np.cumsum(pwvals_contrib))) |
51 | 82 |
|
52 | | - # evaluate potential (see theory document) |
53 | | - pvals = -mvals/(rvals+1.e-10) - (pwvals[dvals.size-1] - pwvals) |
| 83 | + potential = -mass / (radius + 1.e-10) - (pwvals[-1] - pwvals) |
54 | 84 |
|
55 | | - # get the maximum mass and maximum radius |
56 | | - M0 = mvals[dvals.size-1] |
57 | | - R0 = rvals[dvals.size-1] |
| 85 | + M0 = mass[-1] |
| 86 | + R0 = radius[-1] |
| 87 | + |
| 88 | + Beta = (Mtotal / M0) * (R0 / Rmax) |
| 89 | + Gamma = np.sqrt((M0 * R0) / (Mtotal * Rmax)) * (R0 / Rmax) |
58 | 90 |
|
59 | | - # compute scaling factors |
60 | | - Beta = (M/M0) * (R0/R); |
61 | | - Gamma = np.sqrt((M0*R0)/(M*R)) * (R0/R); |
62 | 91 | if verbose: |
63 | | - print("! Scaling: R=",R," M=",M) |
| 92 | + print(f"! Scaling: R = {Rmax} M = {Mtotal}") |
64 | 93 |
|
65 | | - rfac = np.power(Beta,-0.25) * np.power(Gamma,-0.5); |
66 | | - dfac = np.power(Beta,1.5) * Gamma; |
67 | | - mfac = np.power(Beta,0.75) * np.power(Gamma,-0.5); |
68 | | - pfac = Beta; |
| 94 | + rfac = Beta**-0.25 * Gamma**-0.5 |
| 95 | + dfac = Beta**1.5 * Gamma |
| 96 | + mfac = Beta**0.75 * Gamma**-0.5 |
| 97 | + pfac = Beta |
69 | 98 |
|
70 | | - if verbose: |
71 | | - print(rfac,dfac,mfac,pfac) |
| 99 | + if physical_units == True: |
| 100 | + rfac=1 |
| 101 | + dfac=1 |
| 102 | + mfac=1 |
| 103 | + pfac=1 |
72 | 104 |
|
73 | | - # save file if desired |
74 | | - if pfile != '': |
75 | | - f = open(pfile,'w') |
76 | | - print('! ',plabel,file=f) |
77 | | - print('! R D M P',file=f) |
| 105 | + if verbose: |
| 106 | + print(f"Scaling factors: rfac = {rfac}, dfac = {dfac}, mfac = {mfac}, pfac = {pfac}") |
78 | 107 |
|
79 | | - print(rvals.size,file=f) |
| 108 | + if output_filename: |
| 109 | + _write_table( |
| 110 | + output_filename, |
| 111 | + radius * rfac, |
| 112 | + density * dfac, |
| 113 | + mass * mfac, |
| 114 | + potential * pfac |
| 115 | + ) |
80 | 116 |
|
81 | | - for indx in range(0,rvals.size): |
82 | | - print('{0} {1} {2} {3}'.format( rfac*rvals[indx],\ |
83 | | - dfac*dvals[indx],\ |
84 | | - mfac*mvals[indx],\ |
85 | | - pfac*pvals[indx]),file=f) |
86 | | - |
87 | | - f.close() |
88 | | - |
89 | | - return rvals*rfac,dfac*dvals,mfac*mvals,pfac*pvals |
| 117 | + return radius * rfac, density * dfac, mass * mfac, potential * pfac |
90 | 118 |
|
0 commit comments