-
Notifications
You must be signed in to change notification settings - Fork 5
/
half_mass_radius.py
82 lines (64 loc) · 2.57 KB
/
half_mass_radius.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import numpy as np
import unyt
def get_half_mass_radius(radius, mass, total_mass):
if total_mass == 0.0 * total_mass.units or len(mass) < 1:
return 0.0 * radius.units
target_mass = 0.5 * total_mass
isort = np.argsort(radius)
sorted_radius = radius[isort]
# compute sum in double precision to avoid numerical overflow due to
# weird unit conversions in unyt
cumulative_mass = mass[isort].cumsum(dtype=np.float64)
# np.sum() and np.cumsum() use different orders, so we have to allow for
# some small difference
if cumulative_mass[-1] < 0.999 * total_mass:
raise RuntimeError(
"Masses sum up to less than the given total mass:"
f" cumulative_mass[-1] = {cumulative_mass[-1]},"
f" total_mass = {total_mass}!"
)
ihalf = np.argmax(cumulative_mass >= target_mass)
if ihalf == 0:
rmin = 0.0 * radius.units
Mmin = 0.0 * mass.units
else:
rmin = sorted_radius[ihalf - 1]
Mmin = cumulative_mass[ihalf - 1]
rmax = sorted_radius[ihalf]
Mmax = cumulative_mass[ihalf]
if Mmin == Mmax:
half_mass_radius = 0.5 * (rmin + rmax)
else:
half_mass_radius = rmin + (target_mass - Mmin) / (Mmax - Mmin) * (rmax - rmin)
# we cannot use '>=', since equality would happen if half_mass_radius==0
if half_mass_radius > sorted_radius[-1]:
raise RuntimeError(
"Half mass radius larger than input radii:"
f" half_mass_radius = {half_mass_radius},"
f" sorted_radius[-1] = {sorted_radius[-1]}!"
f" ihalf = {ihalf}, Npart = {len(radius)},"
f" target_mass = {target_mass},"
f" rmin = {rmin}, rmax = {rmax},"
f" Mmin = {Mmin}, Mmax = {Mmax},"
f" sorted_radius = {sorted_radius},"
f" cumulative_mass = {cumulative_mass}"
)
return half_mass_radius
def test_get_half_mass_radius():
np.random.seed(203)
for i in range(1000):
npart = np.random.choice([1, 10, 100, 1000, 10000])
radius = np.random.exponential(1.0, npart) * unyt.kpc
Mpart = 1.0e9 * unyt.Msun
mass = Mpart * (1.0 + 0.2 * (np.random.random(npart) - 0.5))
total_mass = mass.sum()
half_mass_radius = get_half_mass_radius(radius, mass, total_mass)
mask = radius <= half_mass_radius
Mtest = mass[mask].sum()
assert Mtest <= 0.5 * total_mass
fail = False
try:
half_mass_radius = get_half_mass_radius(radius, mass, 2.0 * total_mass)
except RuntimeError:
fail = True
assert fail