[general issue] `mass_binaries` != sum(mass_1 + mass_2)?
Closed this issue · 3 comments
I imagine I'm missing something here but I wanted to ask about this case in which I thought the total mass of the sampled binaries should be the same as mass_binaries
. Here's the call:
initial_binaries, _, mass_binaries, _, _ = InitialBinaryTable.sampler('independent',
np.linspace(0, 15, 16), np.linspace(0, 15, 16),
binfrac_model=1.0,
primary_model='kroupa01', ecc_model='sana12',
porb_model='sana12', qmin=-1,
SF_start=particle["t_form"],
SF_duration=0.0, met=particle["Z"],
size=int(np.ceil(mass)))
I figured that since my kstars have everything and the binfrac=1.0 then it should be the case that approximately
mass_binaries / (initial_binaries["mass_1"] + initial_binaries["mass_2"]).sum() == 1.0
since nothing would get masked. But I'm finding that ratio fluctuates around 1.08 (from ~1.075-1.085) (independent of metallicity/formation time/size)
Am I missing something that makes it so this isn't 1.0? Thanks!
Perhaps another clue - I checked the outputted n_binaries
vs. the length of initial_binaries
and it's always 50% higher.
Okay I believe I understand where the difference is coming from now - it's the sampled secondary stars!
I think this is because the minimum stellar mass that COSMIC/BSE can handle and the absolute minimum stellar mass are not the same. Although the primary stars are sampled with a minimum mass of 0.08, the secondaries are sampled with q
and so can have a mass as low as 0.008 (side note: is that big enough for a star?) and any binary with a secondary mass < 0.08 gets thrown out.
Apparently on average this comes to ~1/3 of the sampled population and ~7% of the mass...which I guess would be obvious if I took the time to integrate the IMF 🤷🏻
Okay I was curious so I did the integration:
@np.vectorize
def IMF(m, m1=0.01, m2=0.08, m3=0.5, m4=200.0, a12=0.3, a23=1.3, a34=2.3):
"""
Calculate the fraction of stellar mass between m and m + dm for a three part broken power law.
Default values follow Kroupa (2001)
zeta(m) ~ m^(-a_ij)
Args:
m --> [float, list of floats] mass or masses at which to evaluate
mi --> [float] masses at which to transition the slope
aij --> [float] slope of the IMF between mi and mj
Returns:
zeta(m) --> [float, list of floats] value or values of the IMF at m
"""
# calculate normalisation constants that ensure the IMF is continuous
b1 = 1 / (
(m2**(1 - a12) - m1**(1 - a12)) / (1 - a12) \
+ m2**(-(a12 - a23)) * (m3**(1 - a23) - m2**(1 - a23)) / (1 - a23) \
+ m2**(-(a12 - a23)) * m3**(-(a23 - a34)) * (m4**(1 - a34) - m3**(1 - a34)) / (1 - a34)
)
b2 = b1 * m2**(-(a12 - a23))
b3 = b2 * m3**(-(a23 - a34))
# evaluate IMF
if m < m1:
return 0
elif m < m2:
return b1 * m**(-a12)
elif m < m3:
return b2 * m**(-a23)
elif m < m4:
return b3 * m**(-a34)
else:
return 0
m_range = np.linspace(0.01, 0.08, 100000)
imf_vals = IMF(m_range, m1=0.01, m4=150)
print(np.trapz(imf_vals, m_range))
print(np.trapz(imf_vals * m_range, m_range))
This gives
0.3714647956325013
0.015493749559631911
Which checks out! About a 1/3 of the IMF is < 0.08 and though only ~1.5% of the mass is there, I guess the primary companions bring it up to 7% of the binary mass?