Skip to content

Commit 29c2eca

Browse files
offner22 primary mass dependent binary fraction (#644)
* added offner22 primary mass dependent binary fraction * pinning numpy version because of new numpy version released on june 16 (https://stackoverflow.com/questions/78634235/numpy-dtype-size-changed-may-indicate-binary-incompatibility-expected-96-from) * pinned numpy version so 3.7 works --------- Co-authored-by: katiebreivik <kbreivik@flatironinstitute.org>
1 parent eb4fac0 commit 29c2eca

File tree

5 files changed

+73
-8
lines changed

5 files changed

+73
-8
lines changed

bin/cosmic-pop

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def parse_commandline():
101101
parser.add_argument("--binary_state", nargs='+', type=int)
102102
parser.add_argument("--sampling_method")
103103
parser.add_argument("--primary_model", help="Chooses the initial primary mass function from: salpeter55, kroupa93, kroupa01", type=str)
104-
parser.add_argument("--binfrac_model", help="Chooses the binary fraction model from: a float between [0,1] and vanHaaften", type=float)
104+
parser.add_argument("--binfrac_model", help="Chooses the binary fraction model from: a float between [0,1], vanHaaften, and offner22", type=float)
105105
parser.add_argument("--ecc_model", help="Chooses the initial eccentricity distribution model from: thermal, uniform, and sana12", type=str)
106106
parser.add_argument("--porb_model", help="Chooses the initial orbital period distribution model from: log_uniform and sana12", type=str)
107107
parser.add_argument("--SF_start", help="Sets the time in the past when star formation initiates in Myr", type=float)

requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
scipy >= 0.12.1
2-
numpy >= 1.16
2+
numpy == 1.21.6
33
astropy >= 1.1.1
44
configparser
55
tqdm >= 4.0

src/cosmic/sample/sampler/cmc.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def get_cmc_sampler(
8383
Sets the pairing of stars M>msort only with stars with M>msort
8484
8585
binfrac_model : `str or float`
86-
Model for binary fraction; choices include: vanHaaften or a fraction where 1.0 is 100% binaries
86+
Model for binary fraction; choices include: vanHaaften, offner22, or a fraction where 1.0 is 100% binaries
8787
8888
binfrac_model_msort : `str or float`
8989
Same as binfrac_model for M>msort

src/cosmic/sample/sampler/independent.py

+53-5
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def get_independent_sampler(
114114
Duration of constant star formation beginning from SF_Start in Myr
115115
116116
binfrac_model : `str or float`
117-
Model for binary fraction; choices include: vanHaaften or a fraction where 1.0 is 100% binaries
117+
Model for binary fraction; choices include: vanHaaften, offner22, or a fraction where 1.0 is 100% binaries
118118
119119
binfrac_model_msort : `str or float`
120120
Same as binfrac_model for M>msort
@@ -601,6 +601,7 @@ def binary_select(self, primary_mass, binfrac_model=0.5, **kwargs):
601601
either a binary fraction specified by a float or a
602602
primary-mass dependent binary fraction following
603603
`van Haaften et al.(2009) <http://adsabs.harvard.edu/abs/2013A%26A...552A..69V>`_ in appdx
604+
or `Offner et al.(2022) <https://arxiv.org/abs/2203.10066>`_ in fig 1
604605
605606
Parameters
606607
----------
@@ -609,6 +610,9 @@ def binary_select(self, primary_mass, binfrac_model=0.5, **kwargs):
609610
610611
binfrac_model : str or float
611612
vanHaaften - primary mass dependent and ONLY VALID up to 100 Msun
613+
614+
offner22 - primary mass dependent
615+
612616
float - fraction of binaries; 0.5 means 2 in 3 stars are a binary pair while 1
613617
means every star is in a binary pair
614618
@@ -650,13 +654,35 @@ def binary_select(self, primary_mass, binfrac_model=0.5, **kwargs):
650654
binary_choose_low = np.random.uniform(
651655
0, 1.0, primary_mass[lowmassIdx].size)
652656

657+
(singleIdx_low,) = np.where(
658+
binary_fraction_low < binary_choose_low)
659+
(binaryIdx_low,) = np.where(
660+
binary_fraction_low >= binary_choose_low)
661+
elif binfrac_model == "offner22":
662+
from scipy.interpolate import BSpline
663+
t = [0.0331963853, 0.0331963853, 0.0331963853, 0.0331963853, 0.106066017,
664+
0.212132034, 0.424264069, 0.866025404, 1.03077641, 1.11803399,
665+
1.95959179, 3.87298335, 6.32455532, 11.6619038, 29.1547595,
666+
40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 150, 150, 150, 150]
667+
c = [0.08, 0.15812003, 0.20314101, 0.23842953, 0.33154153, 0.39131739,
668+
0.46020725, 0.59009569, 0.75306454, 0.81652502, 0.93518422, 0.92030594,
669+
0.96, 0.96, 0.96, 0.96, 0.96, 0.96, 0.96, 0.96, 0.96, 0.96, 0.96, 0.96, 0.96]
670+
k = 3
671+
def offner_curve(x):
672+
a = -0.16465041
673+
b = -0.11616329
674+
return np.piecewise(x, [x < 6.4, x >= 6.4], [BSpline(t,c,k), lambda x : a * np.exp(b * x) + 0.97])
675+
binary_fraction_low = offner_curve(primary_mass[lowmassIdx])
676+
binary_choose_low = np.random.uniform(
677+
0, 1.0, primary_mass[lowmassIdx].size)
678+
653679
(singleIdx_low,) = np.where(
654680
binary_fraction_low < binary_choose_low)
655681
(binaryIdx_low,) = np.where(
656682
binary_fraction_low >= binary_choose_low)
657683
else:
658684
raise ValueError(
659-
"You have supplied a non-supported binary fraction model. Please choose vanHaaften or a float"
685+
"You have supplied a non-supported binary fraction model. Please choose vanHaaften, offner22, or a float"
660686
)
661687
elif type(binfrac_model) == float:
662688
if (binfrac_model <= 1.0) & (binfrac_model >= 0.0):
@@ -675,7 +701,7 @@ def binary_select(self, primary_mass, binfrac_model=0.5, **kwargs):
675701
)
676702
else:
677703
raise ValueError(
678-
"You have not supplied a model or a fraction. Please choose either vanHaaften or a float"
704+
"You have not supplied a model or a fraction. Please choose either vanHaaften, offner22, or a float"
679705
)
680706

681707
# --- if using a different binary fraction for high-mass systems
@@ -686,13 +712,35 @@ def binary_select(self, primary_mass, binfrac_model=0.5, **kwargs):
686712
binary_choose_high = np.random.uniform(
687713
0, 1.0, primary_mass[highmassIdx].size)
688714

715+
(singleIdx_high,) = np.where(
716+
binary_fraction_high < binary_choose_high)
717+
(binaryIdx_high,) = np.where(
718+
binary_fraction_high >= binary_choose_high)
719+
elif binfrac_model_msort == "offner22":
720+
from scipy.interpolate import BSpline
721+
t = [0.0331963853, 0.0331963853, 0.0331963853, 0.0331963853, 0.106066017,
722+
0.212132034, 0.424264069, 0.866025404, 1.03077641, 1.11803399,
723+
1.95959179, 3.87298335, 6.32455532, 11.6619038, 29.1547595,
724+
40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 150, 150, 150, 150]
725+
c = [0.08, 0.15812003, 0.20314101, 0.23842953, 0.33154153, 0.39131739,
726+
0.46020725, 0.59009569, 0.75306454, 0.81652502, 0.93518422, 0.92030594,
727+
0.96, 0.96, 0.96, 0.96, 0.96, 0.96, 0.96, 0.96, 0.96, 0.96, 0.96, 0.96, 0.96]
728+
k = 3
729+
def offner_curve(x):
730+
a = -0.16465041
731+
b = -0.11616329
732+
return np.piecewise(x, [x < 6.4, x >= 6.4], [BSpline(t,c,k), lambda x : a * np.exp(b * x) + 0.97])
733+
binary_fraction_high = offner_curve(primary_mass[highmassIdx])
734+
binary_choose_high = np.random.uniform(
735+
0, 1.0, primary_mass[highmassIdx].size)
736+
689737
(singleIdx_high,) = np.where(
690738
binary_fraction_high < binary_choose_high)
691739
(binaryIdx_high,) = np.where(
692740
binary_fraction_high >= binary_choose_high)
693741
else:
694742
raise ValueError(
695-
"You have supplied a non-supported binary fraction model. Please choose vanHaaften or a float"
743+
"You have supplied a non-supported binary fraction model. Please choose vanHaaften, offner22, or a float"
696744
)
697745
elif (binfrac_model_msort is not None) and (type(binfrac_model_msort) == float):
698746
if (binfrac_model_msort <= 1.0) & (binfrac_model_msort >= 0.0):
@@ -711,7 +759,7 @@ def binary_select(self, primary_mass, binfrac_model=0.5, **kwargs):
711759
)
712760
elif (binfrac_model_msort is not None):
713761
raise ValueError(
714-
"You have not supplied a model or a fraction. Please choose either vanHaaften or a float"
762+
"You have not supplied a model or a fraction. Please choose either vanHaaften, offner22, or a float"
715763
)
716764

717765

src/cosmic/tests/test_sample.py

+17
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@
4242
N_BINARY_SELECT = 85
4343
VANHAAFTEN_BINFRAC_MAX = 0.9989087986493874
4444
VANHAAFTEN_BINFRAC_MIN = 0.6192803136799157
45+
OFFNER_MASS_RANGES = [(0.075,0.15), (0.15,0.30), (0.3,0.6), (0.75,1.00), (0.85,1.25), (1.00,1.25), (1.6,2.4), (3,5), (5,8), (8,17), (17,50)]
46+
OFFNER_DATA = [0.19, 0.23, 0.30, 0.42, 0.47, 0.50, 0.68, 0.81, 0.89, 0.93, 0.96]
47+
OFFNER_ERRORS = [0.03, 0.02, 0.02, 0.03, 0.03, 0.04, 0.07, 0.06, 0.05, 0.04, 0.04]
4548
MULTIDIM_BINFRAC_MAX = 0.6146916774140262
4649
MULTIDIM_BINFRAC_MIN = 0.13786300908773025
4750
CONST_SFR_SUM = 460028.2453521937
@@ -207,6 +210,20 @@ def test_binary_fraction(self):
207210
m1_b, m1_s, binfrac, bin_index = SAMPLECLASS.binary_select(primary_mass=np.arange(1,100), binfrac_model='vanHaaften')
208211
self.assertEqual(binfrac.max(), VANHAAFTEN_BINFRAC_MAX)
209212
self.assertEqual(binfrac.min(), VANHAAFTEN_BINFRAC_MIN)
213+
test_fracs = []
214+
test_errs = []
215+
primary_mass = np.array([float(x) for x in np.logspace(np.log10(0.08), np.log10(150), num=100000)])
216+
m1_b, m1_s, binfrac, bin_index = SAMPLECLASS.binary_select(primary_mass=primary_mass, binfrac_model='offner22')
217+
for i in range(len(OFFNER_MASS_RANGES)):
218+
low, high = OFFNER_MASS_RANGES[i][0], OFFNER_MASS_RANGES[i][1]
219+
offner_value = OFFNER_DATA[i]
220+
offner_error = OFFNER_ERRORS[i]
221+
bins_count = len(m1_b[(m1_b >= low) & (m1_b <= high)])
222+
singles_count = len(m1_s[(m1_s >= low) & (m1_s <= high)])
223+
bin_frac = bins_count / (bins_count + singles_count)
224+
error = abs(offner_value - bin_frac)
225+
self.assertLess(error, offner_error)
226+
210227

211228
def test_msort(self):
212229
np.random.seed(2)

0 commit comments

Comments
 (0)