cosmic-popsynth 3.6.2__cp313-cp313-macosx_14_0_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cosmic/.dylibs/libgcc_s.1.1.dylib +0 -0
- cosmic/.dylibs/libgfortran.5.dylib +0 -0
- cosmic/.dylibs/libquadmath.0.dylib +0 -0
- cosmic/Match.py +191 -0
- cosmic/__init__.py +32 -0
- cosmic/_commit_hash.py +1 -0
- cosmic/_evolvebin.cpython-313-darwin.so +0 -0
- cosmic/_version.py +1 -0
- cosmic/bse_utils/__init__.py +18 -0
- cosmic/bse_utils/zcnsts.py +570 -0
- cosmic/bse_utils/zdata.py +596 -0
- cosmic/checkstate.py +128 -0
- cosmic/data/cosmic-settings.json +1635 -0
- cosmic/evolve.py +607 -0
- cosmic/filter.py +214 -0
- cosmic/get_commit_hash.py +15 -0
- cosmic/output.py +466 -0
- cosmic/plotting.py +680 -0
- cosmic/sample/__init__.py +26 -0
- cosmic/sample/cmc/__init__.py +18 -0
- cosmic/sample/cmc/elson.py +411 -0
- cosmic/sample/cmc/king.py +260 -0
- cosmic/sample/initialbinarytable.py +251 -0
- cosmic/sample/initialcmctable.py +449 -0
- cosmic/sample/sampler/__init__.py +25 -0
- cosmic/sample/sampler/cmc.py +418 -0
- cosmic/sample/sampler/independent.py +1252 -0
- cosmic/sample/sampler/multidim.py +882 -0
- cosmic/sample/sampler/sampler.py +130 -0
- cosmic/test_evolve.py +108 -0
- cosmic/test_match.py +30 -0
- cosmic/test_sample.py +580 -0
- cosmic/test_utils.py +198 -0
- cosmic/utils.py +1574 -0
- cosmic_popsynth-3.6.2.data/scripts/cosmic-pop +544 -0
- cosmic_popsynth-3.6.2.dist-info/METADATA +55 -0
- cosmic_popsynth-3.6.2.dist-info/RECORD +38 -0
- cosmic_popsynth-3.6.2.dist-info/WHEEL +6 -0
|
@@ -0,0 +1,882 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright (C) Katelyn Breivik (2017 - 2021)
|
|
3
|
+
#
|
|
4
|
+
# This file is part of cosmic.
|
|
5
|
+
#
|
|
6
|
+
# cosmic is free software: you can redistribute it and/or modify
|
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
# (at your option) any later version.
|
|
10
|
+
#
|
|
11
|
+
# cosmic is distributed in the hope that it will be useful,
|
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
# GNU General Public License for more details.
|
|
15
|
+
#
|
|
16
|
+
# You should have received a copy of the GNU General Public License
|
|
17
|
+
# along with cosmic. If not, see <http://www.gnu.org/licenses/>.
|
|
18
|
+
|
|
19
|
+
"""`multidim`
|
|
20
|
+
"""
|
|
21
|
+
from schwimmbad import MultiPool, MPIPool
|
|
22
|
+
|
|
23
|
+
from .sampler import register_sampler
|
|
24
|
+
from .. import InitialBinaryTable
|
|
25
|
+
from ... import utils
|
|
26
|
+
|
|
27
|
+
import numpy as np
|
|
28
|
+
import pandas as pd
|
|
29
|
+
|
|
30
|
+
__author__ = "Katelyn Breivik <katie.breivik@gmail.com>"
|
|
31
|
+
__credits__ = "Scott Coughlin <scott.coughlin@ligo.org>"
|
|
32
|
+
__all__ = ["get_multidim_sampler", "MultiDim"]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_multidim_sampler(
|
|
36
|
+
final_kstar1,
|
|
37
|
+
final_kstar2,
|
|
38
|
+
rand_seed,
|
|
39
|
+
nproc,
|
|
40
|
+
SF_start,
|
|
41
|
+
SF_duration,
|
|
42
|
+
met,
|
|
43
|
+
size,
|
|
44
|
+
**kwargs
|
|
45
|
+
):
|
|
46
|
+
"""adapted version of Maxwell Moe's IDL code that generates a population of single and binary stars
|
|
47
|
+
|
|
48
|
+
Below is the adapted version of Maxwell Moe's IDL code
|
|
49
|
+
that generates a population of single and binary stars
|
|
50
|
+
based on the paper Mind your P's and Q's
|
|
51
|
+
By Maxwell Moe and Rosanne Di Stefano
|
|
52
|
+
|
|
53
|
+
The python code has been adopted by Mads Sørensen
|
|
54
|
+
|
|
55
|
+
Version history:
|
|
56
|
+
V. 0.1; 2017/02/03
|
|
57
|
+
By Mads Sørensen
|
|
58
|
+
- This is a pure adaption from IDL to Python.
|
|
59
|
+
- The function idl_tabulate is similar to
|
|
60
|
+
the IDL function int_tabulated except, this function seems to be slightly
|
|
61
|
+
more exact in its solution.
|
|
62
|
+
Therefore, relative to the IDL code, there are small numerical differences.
|
|
63
|
+
|
|
64
|
+
Comments below beginning with ; is the original nodes by Maxwell Moe.
|
|
65
|
+
Please read these careful for understanding the script.
|
|
66
|
+
; NOTE - This version produces only the statistical distributions of
|
|
67
|
+
; single stars, binaries, and inner binaries in hierarchical triples.
|
|
68
|
+
; Outer tertiaries in hierarchical triples are NOT generated.
|
|
69
|
+
; Moreover, given a set of companions, all with period P to
|
|
70
|
+
; primary mass M1, this version currently uses an approximation to
|
|
71
|
+
; determine the fraction of those companions that are inner binaries
|
|
72
|
+
; vs. outer triples. Nevertheless, this approximation reproduces
|
|
73
|
+
; the overall multiplicity statistics.
|
|
74
|
+
; Step 1 - Tabulate probably density functions of periods,
|
|
75
|
+
; mass ratios, and eccentricities based on
|
|
76
|
+
; analytic fits to corrected binary star populations.
|
|
77
|
+
; Step 2 - Implement Monte Carlo method to generate stellar
|
|
78
|
+
; population from those density functions.
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
----------
|
|
82
|
+
final_kstar1 : `list` or `int`
|
|
83
|
+
Int or list of final kstar1
|
|
84
|
+
|
|
85
|
+
final_kstar2 : `list` or `int`
|
|
86
|
+
Int or list of final kstar2
|
|
87
|
+
|
|
88
|
+
rand_seed : `int`
|
|
89
|
+
Int to seed random number generator
|
|
90
|
+
|
|
91
|
+
nproc : `int`
|
|
92
|
+
Number of processors to use to generate population
|
|
93
|
+
|
|
94
|
+
SF_start : `float`
|
|
95
|
+
Time in the past when star formation initiates in Myr
|
|
96
|
+
|
|
97
|
+
SF_duration : `float`
|
|
98
|
+
Duration of constant star formation beginning from SF_Start in Myr
|
|
99
|
+
|
|
100
|
+
met : `float`
|
|
101
|
+
Sets the metallicity of the binary population where solar metallicity is 0.02
|
|
102
|
+
|
|
103
|
+
size : `int`
|
|
104
|
+
Size of the population to sample
|
|
105
|
+
|
|
106
|
+
**porb_lo : `float`
|
|
107
|
+
Lower limit in days for the orbital period distribution
|
|
108
|
+
|
|
109
|
+
**porb_hi: `float`
|
|
110
|
+
Upper limit in days for the orbital period distribution
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
InitialBinaryTable : `pandas.DataFrame`
|
|
115
|
+
DataFrame in the format of the InitialBinaryTable
|
|
116
|
+
|
|
117
|
+
mass_singles : `float`
|
|
118
|
+
Total mass in single stars needed to generate population
|
|
119
|
+
|
|
120
|
+
mass_binaries : `float`
|
|
121
|
+
Total mass in binaries needed to generate population
|
|
122
|
+
|
|
123
|
+
n_singles : `int`
|
|
124
|
+
Number of single stars needed to generate a population
|
|
125
|
+
|
|
126
|
+
n_binaries : `int`
|
|
127
|
+
Number of binaries needed to generate a population
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
if type(final_kstar1) in [int, float]:
|
|
131
|
+
final_kstar1 = [final_kstar1]
|
|
132
|
+
if type(final_kstar2) in [int, float]:
|
|
133
|
+
final_kstar2 = [final_kstar2]
|
|
134
|
+
porb_lo = kwargs.pop("porb_lo", 0.15)
|
|
135
|
+
porb_hi = kwargs.pop("porb_hi", 8.0)
|
|
136
|
+
pool = kwargs.pop("pool", None)
|
|
137
|
+
mp_seeds = kwargs.pop("mp_seeds", None)
|
|
138
|
+
|
|
139
|
+
primary_min, primary_max, secondary_min, secondary_max = utils.mass_min_max_select(
|
|
140
|
+
final_kstar1, final_kstar2
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
initconditions = MultiDim()
|
|
144
|
+
|
|
145
|
+
(
|
|
146
|
+
mass1_binary,
|
|
147
|
+
mass2_binary,
|
|
148
|
+
porb,
|
|
149
|
+
ecc,
|
|
150
|
+
single_mass_list,
|
|
151
|
+
mass_singles,
|
|
152
|
+
mass_binaries,
|
|
153
|
+
n_singles,
|
|
154
|
+
n_binaries,
|
|
155
|
+
binfrac,
|
|
156
|
+
) = initconditions.initial_sample(
|
|
157
|
+
primary_min,
|
|
158
|
+
secondary_min,
|
|
159
|
+
primary_max,
|
|
160
|
+
secondary_max,
|
|
161
|
+
porb_lo,
|
|
162
|
+
porb_hi,
|
|
163
|
+
rand_seed,
|
|
164
|
+
size=size,
|
|
165
|
+
nproc=nproc,
|
|
166
|
+
pool=pool,
|
|
167
|
+
mp_seeds=mp_seeds,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
tphysf, metallicity = initconditions.sample_SFH(
|
|
171
|
+
SF_start=SF_start, SF_duration=SF_duration, met=met, size=mass1_binary.size
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
kstar1 = initconditions.set_kstar(mass1_binary)
|
|
175
|
+
kstar2 = initconditions.set_kstar(mass2_binary)
|
|
176
|
+
metallicity[metallicity < 1e-4] = 1e-4
|
|
177
|
+
metallicity[metallicity > 0.03] = 0.03
|
|
178
|
+
|
|
179
|
+
if kwargs.pop("keep_singles", True):
|
|
180
|
+
binary_table = InitialBinaryTable.InitialBinaries(
|
|
181
|
+
mass1_binary,
|
|
182
|
+
mass2_binary,
|
|
183
|
+
porb,
|
|
184
|
+
ecc,
|
|
185
|
+
tphysf,
|
|
186
|
+
kstar1,
|
|
187
|
+
kstar2,
|
|
188
|
+
metallicity,
|
|
189
|
+
binfrac=binfrac,
|
|
190
|
+
)
|
|
191
|
+
tphysf, metallicity = initconditions.sample_SFH(
|
|
192
|
+
SF_start=SF_start, SF_duration=SF_duration, met=met, size=single_mass_list.size
|
|
193
|
+
)
|
|
194
|
+
metallicity[metallicity < 1e-4] = 1e-4
|
|
195
|
+
metallicity[metallicity > 0.03] = 0.03
|
|
196
|
+
kstar1 = initconditions.set_kstar(single_mass_list)
|
|
197
|
+
singles_table = InitialBinaryTable.InitialBinaries(
|
|
198
|
+
single_mass_list,
|
|
199
|
+
np.ones_like(single_mass_list)*0,
|
|
200
|
+
np.ones_like(single_mass_list)*-1,
|
|
201
|
+
np.ones_like(single_mass_list)*-1,
|
|
202
|
+
tphysf,
|
|
203
|
+
kstar1,
|
|
204
|
+
np.ones_like(single_mass_list)*15, # # kstar2 is not used for singles
|
|
205
|
+
metallicity,
|
|
206
|
+
)
|
|
207
|
+
binary_table = pd.concat([binary_table, singles_table], ignore_index=True)
|
|
208
|
+
else:
|
|
209
|
+
binary_table = InitialBinaryTable.InitialBinaries(
|
|
210
|
+
mass1_binary,
|
|
211
|
+
mass2_binary,
|
|
212
|
+
porb,
|
|
213
|
+
ecc,
|
|
214
|
+
tphysf,
|
|
215
|
+
kstar1,
|
|
216
|
+
kstar2,
|
|
217
|
+
metallicity,
|
|
218
|
+
binfrac=binfrac,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
binary_table,
|
|
223
|
+
mass_singles,
|
|
224
|
+
mass_binaries,
|
|
225
|
+
n_singles,
|
|
226
|
+
n_binaries,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
register_sampler(
|
|
230
|
+
"multidim",
|
|
231
|
+
InitialBinaryTable,
|
|
232
|
+
get_multidim_sampler,
|
|
233
|
+
usage="final_kstar1, final_kstar2, rand_seed, nproc, SFH_model, component_age, metallicity, size, binfrac",
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class MultiDim:
|
|
238
|
+
|
|
239
|
+
# -----------------------------------
|
|
240
|
+
# Below is the adapted version of Maxwell Moe's IDL code
|
|
241
|
+
# that generates a population of single and binary stars
|
|
242
|
+
# based on the paper Mind your P's and Q's
|
|
243
|
+
# By Maxwell Moe and Rosanne Di Stefano
|
|
244
|
+
#
|
|
245
|
+
# The python code has been adopted by Mads Sørensen
|
|
246
|
+
# -----------------------------------
|
|
247
|
+
# Version history:
|
|
248
|
+
# V. 0.1; 2017/02/03
|
|
249
|
+
# By Mads Sørensen
|
|
250
|
+
# - This is a pure adaption from IDL to Python.
|
|
251
|
+
# - The function idl_tabulate is similar to
|
|
252
|
+
# the IDL function int_tabulated except, this function seems to be slightly
|
|
253
|
+
# more exact in its solution.
|
|
254
|
+
# Therefore, relative to the IDL code, there are small numerical differences.
|
|
255
|
+
# -----------------------------------
|
|
256
|
+
|
|
257
|
+
#
|
|
258
|
+
# Comments below beginning with ; is the original nodes by Maxwell Moe.
|
|
259
|
+
# Please read these careful for understanding the script.
|
|
260
|
+
# ; NOTE - This version produces only the statistical distributions of
|
|
261
|
+
# ; single stars, binaries, and inner binaries in hierarchical triples.
|
|
262
|
+
# ; Outer tertiaries in hierarchical triples are NOT generated.
|
|
263
|
+
# ; Moreover, given a set of companions, all with period P to
|
|
264
|
+
# ; primary mass M1, this version currently uses an approximation to
|
|
265
|
+
# ; determine the fraction of those companions that are inner binaries
|
|
266
|
+
# ; vs. outer triples. Nevertheless, this approximation reproduces
|
|
267
|
+
# ; the overall multiplicity statistics.
|
|
268
|
+
# ; Step 1 - Tabulate probably density functions of periods,
|
|
269
|
+
# ; mass ratios, and eccentricities based on
|
|
270
|
+
# ; analytic fits to corrected binary star populations.
|
|
271
|
+
# ; Step 2 - Implement Monte Carlo method to generate stellar
|
|
272
|
+
# ; population from those density functions.
|
|
273
|
+
# ;
|
|
274
|
+
def initial_sample(
|
|
275
|
+
self,
|
|
276
|
+
M1min=0.08,
|
|
277
|
+
M2min=0.08,
|
|
278
|
+
M1max=150.0,
|
|
279
|
+
M2max=150.0,
|
|
280
|
+
porb_lo=0.15,
|
|
281
|
+
porb_hi=8.0,
|
|
282
|
+
rand_seed=0,
|
|
283
|
+
size=None,
|
|
284
|
+
nproc=1,
|
|
285
|
+
pool=None,
|
|
286
|
+
mp_seeds=None,
|
|
287
|
+
):
|
|
288
|
+
"""Sample initial binary distribution according to Moe & Di Stefano (2017)
|
|
289
|
+
<http://adsabs.harvard.edu/abs/2017ApJS..230...15M>`_
|
|
290
|
+
|
|
291
|
+
Parameters
|
|
292
|
+
----------
|
|
293
|
+
M1min : `float`
|
|
294
|
+
minimum primary mass to sample [Msun]
|
|
295
|
+
DEFAULT: 0.08
|
|
296
|
+
M2min : `float`
|
|
297
|
+
minimum secondary mass to sample [Msun]
|
|
298
|
+
DEFAULT: 0.08
|
|
299
|
+
M1max : `float`
|
|
300
|
+
maximum primary mass to sample [Msun]
|
|
301
|
+
DEFAULT: 150.0
|
|
302
|
+
M2max : `float`
|
|
303
|
+
maximum primary mass to sample [Msun]
|
|
304
|
+
DEFAULT: 150.0
|
|
305
|
+
porb_lo : `float`
|
|
306
|
+
minimum orbital period to sample [log10(days)]
|
|
307
|
+
porb_hi : `float`
|
|
308
|
+
maximum orbital period to sample [log10(days)]
|
|
309
|
+
rand_seed : int
|
|
310
|
+
random seed generator
|
|
311
|
+
DEFAULT: 0
|
|
312
|
+
size : int, optional
|
|
313
|
+
number of evolution times to sample
|
|
314
|
+
NOTE: this is set in cosmic-pop call as Nstep
|
|
315
|
+
|
|
316
|
+
Returns
|
|
317
|
+
-------
|
|
318
|
+
primary_mass_list : array
|
|
319
|
+
array of primary masses with size=size
|
|
320
|
+
secondary_mass_list : array
|
|
321
|
+
array of secondary masses with size=size
|
|
322
|
+
porb_list : array
|
|
323
|
+
array of orbital periods in days with size=size
|
|
324
|
+
ecc_list : array
|
|
325
|
+
array of eccentricities with size=size
|
|
326
|
+
single_mass_list : array
|
|
327
|
+
array of mass of single stars
|
|
328
|
+
mass_singles : `float`
|
|
329
|
+
Total mass in single stars needed to generate population
|
|
330
|
+
mass_binaries : `float`
|
|
331
|
+
Total mass in binaries needed to generate population
|
|
332
|
+
n_singles : `int`
|
|
333
|
+
Number of single stars needed to generate a population
|
|
334
|
+
n_binaries : `int`
|
|
335
|
+
Number of binaries needed to generate a population
|
|
336
|
+
binfrac_list : array
|
|
337
|
+
array of binary probabilities based on primary mass and period with size=size
|
|
338
|
+
"""
|
|
339
|
+
if pool is None:
|
|
340
|
+
with MultiPool(processes=nproc) as pool:
|
|
341
|
+
if mp_seeds is not None:
|
|
342
|
+
if len(list(mp_seeds)) != nproc:
|
|
343
|
+
raise ValueError("Must supply a list of random seeds with length equal to number of processors")
|
|
344
|
+
else:
|
|
345
|
+
mp_seeds = [nproc * (task._identity[0]-1) for task in pool._pool]
|
|
346
|
+
|
|
347
|
+
inputs = [(M1min, M2min, M1max, M2max, porb_hi, porb_lo, size/nproc, rand_seed + mp_seed)
|
|
348
|
+
for mp_seed in mp_seeds]
|
|
349
|
+
worker = Worker()
|
|
350
|
+
results = list(pool.map(worker, inputs))
|
|
351
|
+
else:
|
|
352
|
+
if mp_seeds is not None:
|
|
353
|
+
if len(list(mp_seeds)) != nproc:
|
|
354
|
+
raise ValueError("Must supply a list of random seeds with length equal to number of processors")
|
|
355
|
+
else:
|
|
356
|
+
if isinstance(pool, MPIPool):
|
|
357
|
+
mp_seeds = [nproc * (task - 1) for task in pool.workers]
|
|
358
|
+
elif isinstance(pool, MultiPool):
|
|
359
|
+
mp_seeds = [nproc * (task._identity[0] - 1) for task in pool._pool]
|
|
360
|
+
else:
|
|
361
|
+
mp_seeds = [0 for i in range(nproc)]
|
|
362
|
+
|
|
363
|
+
inputs = [(M1min, M2min, M1max, M2max, porb_hi, porb_lo, size/nproc, rand_seed + mp_seed) for mp_seed in mp_seeds]
|
|
364
|
+
worker = Worker()
|
|
365
|
+
results = list(pool.map(worker, inputs))
|
|
366
|
+
|
|
367
|
+
dat_lists = [[], [], [], [], [], [], [], [], [], []]
|
|
368
|
+
|
|
369
|
+
for output_list in results:
|
|
370
|
+
ii = 0
|
|
371
|
+
for dat_list in output_list:
|
|
372
|
+
dat_lists[ii].append(dat_list)
|
|
373
|
+
ii += 1
|
|
374
|
+
|
|
375
|
+
primary_mass_list = np.hstack(dat_lists[0])
|
|
376
|
+
secondary_mass_list = np.hstack(dat_lists[1])
|
|
377
|
+
porb_list = np.hstack(dat_lists[2])
|
|
378
|
+
ecc_list = np.hstack(dat_lists[3])
|
|
379
|
+
single_mass_list = np.hstack(dat_lists[4])
|
|
380
|
+
mass_singles = np.sum(dat_lists[5])
|
|
381
|
+
mass_binaries = np.sum(dat_lists[6])
|
|
382
|
+
n_singles = np.sum(dat_lists[7])
|
|
383
|
+
n_binaries = np.sum(dat_lists[8])
|
|
384
|
+
binfrac_list = np.hstack(dat_lists[9])
|
|
385
|
+
|
|
386
|
+
return (
|
|
387
|
+
primary_mass_list,
|
|
388
|
+
secondary_mass_list,
|
|
389
|
+
porb_list,
|
|
390
|
+
ecc_list,
|
|
391
|
+
single_mass_list,
|
|
392
|
+
mass_singles,
|
|
393
|
+
mass_binaries,
|
|
394
|
+
n_singles,
|
|
395
|
+
n_binaries,
|
|
396
|
+
binfrac_list
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
def sample_SFH(self, SF_start=13700.0, SF_duration=0.0, met=0.02, size=None):
|
|
400
|
+
"""Sample an evolution time for each binary based on a user-specified
|
|
401
|
+
time at the start of star formation and the duration of star formation.
|
|
402
|
+
The default is a burst of star formation 13,700 Myr in the past.
|
|
403
|
+
|
|
404
|
+
Parameters
|
|
405
|
+
----------
|
|
406
|
+
SF_start : float
|
|
407
|
+
Time in the past when star formation initiates in Myr
|
|
408
|
+
SF_duration : float
|
|
409
|
+
Duration of constant star formation beginning from SF_Start in Myr
|
|
410
|
+
met : float
|
|
411
|
+
metallicity of the population [Z_sun = 0.02]
|
|
412
|
+
Default: 0.02
|
|
413
|
+
size : int, optional
|
|
414
|
+
number of evolution times to sample
|
|
415
|
+
NOTE: this is set in cosmic-pop call as Nstep
|
|
416
|
+
|
|
417
|
+
Returns
|
|
418
|
+
-------
|
|
419
|
+
tphys : array
|
|
420
|
+
array of evolution times of size=size
|
|
421
|
+
metallicity : array
|
|
422
|
+
array of metallicities
|
|
423
|
+
"""
|
|
424
|
+
|
|
425
|
+
if (SF_start > 0.0) & (SF_duration >= 0.0):
|
|
426
|
+
tphys = np.random.uniform(SF_start - SF_duration, SF_start, size)
|
|
427
|
+
metallicity = np.ones(size)*met
|
|
428
|
+
return tphys, metallicity
|
|
429
|
+
else:
|
|
430
|
+
raise ValueError('SF_start and SF_duration must be positive and SF_start must be greater than 0.0')
|
|
431
|
+
|
|
432
|
+
def set_kstar(self, mass):
|
|
433
|
+
"""Initialize stellar types according to BSE classification
|
|
434
|
+
kstar=1 if M>=0.7 Msun; kstar=0 if M<0.7 Msun
|
|
435
|
+
|
|
436
|
+
Parameters
|
|
437
|
+
----------
|
|
438
|
+
mass : array
|
|
439
|
+
array of masses
|
|
440
|
+
|
|
441
|
+
Returns
|
|
442
|
+
-------
|
|
443
|
+
kstar : array
|
|
444
|
+
array of initial stellar types
|
|
445
|
+
"""
|
|
446
|
+
|
|
447
|
+
kstar = np.zeros(mass.size)
|
|
448
|
+
low_cutoff = 0.7
|
|
449
|
+
lowIdx = np.where(mass < low_cutoff)[0]
|
|
450
|
+
hiIdx = np.where(mass >= low_cutoff)[0]
|
|
451
|
+
|
|
452
|
+
kstar[lowIdx] = 0
|
|
453
|
+
kstar[hiIdx] = 1
|
|
454
|
+
|
|
455
|
+
return kstar
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
class Worker(object):
|
|
459
|
+
def __call__(self, task):
|
|
460
|
+
M1min, M2min, M1max, M2max, porb_hi, porb_lo, size, seed = task
|
|
461
|
+
return self._sample_initial_pop(M1min, M2min, M1max, M2max, porb_hi, porb_lo, size, seed)
|
|
462
|
+
|
|
463
|
+
def _sample_initial_pop(self, M1min, M2min, M1max, M2max, porb_hi, porb_lo, size, seed):
|
|
464
|
+
# Tabulate probably density functions of periods,
|
|
465
|
+
# mass ratios, and eccentricities based on
|
|
466
|
+
# analytic fits to corrected binary star populations.
|
|
467
|
+
|
|
468
|
+
numM1 = 101
|
|
469
|
+
# use binwidths to maintain structure of original array
|
|
470
|
+
# default size is: numlogP=158
|
|
471
|
+
bwlogP = 0.05
|
|
472
|
+
numq = 91
|
|
473
|
+
nume = 100
|
|
474
|
+
|
|
475
|
+
# ; Vector of primary masses M1 (Msun), logarithmic orbital period P (days),
|
|
476
|
+
# ; mass ratios q = Mcomp/M1, and eccentricities e
|
|
477
|
+
#
|
|
478
|
+
# ; 0.8 < M1 < 40 (where we have statistics corrected for selection effects)
|
|
479
|
+
M1_lo = 0.8
|
|
480
|
+
M1_hi = 40
|
|
481
|
+
|
|
482
|
+
M1v = np.logspace(np.log10(M1_lo), np.log10(M1_hi), numM1)
|
|
483
|
+
# ; 0.15 < log P < 8.0
|
|
484
|
+
# ; or use user specified values
|
|
485
|
+
log10_porb_lo = porb_lo
|
|
486
|
+
log10_porb_hi = porb_hi
|
|
487
|
+
logPv = np.arange(log10_porb_lo, log10_porb_hi + bwlogP, bwlogP)
|
|
488
|
+
numlogP = len(logPv)
|
|
489
|
+
|
|
490
|
+
# ; 0.10 < q < 1.00
|
|
491
|
+
q_lo = 0.1
|
|
492
|
+
q_hi = 1.0
|
|
493
|
+
qv = np.linspace(q_lo, q_hi, numq)
|
|
494
|
+
|
|
495
|
+
# ; 0.0001 < e < 0.9901
|
|
496
|
+
# ; set minimum to non-zero value to avoid numerical errors
|
|
497
|
+
e_lo = 0.0
|
|
498
|
+
e_hi = 0.99
|
|
499
|
+
ev = np.linspace(e_lo, e_hi, nume) + 0.0001
|
|
500
|
+
# ; Note that companions outside this parameter space (e.g., q < 0.1,
|
|
501
|
+
# ; log P (days) > 8.0 are not constrained in M+D16 and therefore
|
|
502
|
+
# ; not considered.
|
|
503
|
+
|
|
504
|
+
# ; Distribution functions - define here, but evaluate within for loops.
|
|
505
|
+
|
|
506
|
+
# ; Frequency of companions with q > 0.1 per decade of orbital period.
|
|
507
|
+
# ; Bottom panel in Fig. 37 of M+D17
|
|
508
|
+
flogP_sq = np.zeros([numlogP, numM1])
|
|
509
|
+
|
|
510
|
+
# ; Given M1 and P, the cumulative distribution of mass ratios q
|
|
511
|
+
cumqdist = np.zeros([numq, numlogP, numM1])
|
|
512
|
+
|
|
513
|
+
# ; Given M1 and P, the cumulative distribution of eccentricities e
|
|
514
|
+
cumedist = np.zeros([nume, numlogP, numM1])
|
|
515
|
+
|
|
516
|
+
# ; Given M1 and P, the probability that the companion
|
|
517
|
+
# ; is a member of the inner binary (currently an approximation).
|
|
518
|
+
# ; 100% for log P < 1.5, decreases with increasing P
|
|
519
|
+
probbin = np.zeros([numlogP, numM1])
|
|
520
|
+
|
|
521
|
+
# ; Given M1, the cumulative period distribution of the inner binary
|
|
522
|
+
# ; Normalized so that max(cumPbindist) = total binary frac. (NOT unity)
|
|
523
|
+
cumPbindist = np.zeros([numlogP, numM1])
|
|
524
|
+
# ; Slope alpha of period distribution across intermediate periods
|
|
525
|
+
# ; 2.7 - DlogP < log P < 2.7 + DlogP, see Section 9.3 and Eqn. 23.
|
|
526
|
+
# ; Slightly updated from version 1.
|
|
527
|
+
alpha = 0.018
|
|
528
|
+
DlogP = 0.7
|
|
529
|
+
|
|
530
|
+
# ; Heaviside function for twins with 0.95 < q < 1.00
|
|
531
|
+
H = np.zeros(numq)
|
|
532
|
+
ind = np.where(qv >= 0.95)
|
|
533
|
+
H[ind] = 1.0
|
|
534
|
+
H = H / utils.idl_tabulate(qv, H) # ;normalize so that integral is unity
|
|
535
|
+
|
|
536
|
+
# ; Relevant indices with respect to mass ratio
|
|
537
|
+
indlq = np.where(qv >= 0.3)
|
|
538
|
+
indsq = np.where(qv < 0.3)
|
|
539
|
+
indq0p3 = np.min(indlq)
|
|
540
|
+
|
|
541
|
+
# FILL IN THE MULTIDIMENSIONAL DISTRIBUTION FUNCTIONS
|
|
542
|
+
# ; Loop through primary mass
|
|
543
|
+
for i in range(0, numM1):
|
|
544
|
+
myM1 = M1v[i]
|
|
545
|
+
# ; Twin fraction parameters that are dependent on M1 only; section 9.1
|
|
546
|
+
FtwinlogPle1 = 0.3 - 0.15 * np.log10(myM1) # ; Eqn. 6
|
|
547
|
+
logPtwin = 8.0 - myM1 # ; Eqn. 7a
|
|
548
|
+
if myM1 >= 6.5:
|
|
549
|
+
logPtwin = 1.5 # ; Eqn. 7b
|
|
550
|
+
# ; Frequency of companions with q > 0.3 at different orbital periods
|
|
551
|
+
# ; and dependent on M1 only; section 9.3 (slightly modified since v1)
|
|
552
|
+
flogPle1 = (
|
|
553
|
+
0.020 + 0.04 * np.log10(myM1) + 0.07 * (np.log10(myM1)) ** 2.0
|
|
554
|
+
) # ; Eqn. 20
|
|
555
|
+
flogPeq2p7 = (
|
|
556
|
+
0.039 + 0.07 * np.log10(myM1) + 0.01 * (np.log10(myM1)) ** 2.0
|
|
557
|
+
) # ; Eqn. 21
|
|
558
|
+
flogPeq5p5 = (
|
|
559
|
+
0.078 - 0.05 * np.log10(myM1) + 0.04 * (np.log10(myM1)) ** 2.0
|
|
560
|
+
) # ; Eqn. 22
|
|
561
|
+
# ; Loop through orbital period P
|
|
562
|
+
for j in range(0, numlogP):
|
|
563
|
+
mylogP = logPv[j]
|
|
564
|
+
# ; Given M1 and P, set excess twin fraction; section 9.1 and Eqn. 5
|
|
565
|
+
if mylogP <= 1.0:
|
|
566
|
+
Ftwin = FtwinlogPle1
|
|
567
|
+
else:
|
|
568
|
+
Ftwin = FtwinlogPle1 * (1.0 - (mylogP - 1.0) / (logPtwin - 1.0))
|
|
569
|
+
if mylogP >= logPtwin:
|
|
570
|
+
Ftwin = 0.0
|
|
571
|
+
|
|
572
|
+
# ; Power-law slope gamma_largeq for M1 < 1.2 Msun and various P; Eqn. 9
|
|
573
|
+
if mylogP <= 5.0:
|
|
574
|
+
gl_1p2 = -0.5
|
|
575
|
+
if mylogP > 5.0:
|
|
576
|
+
gl_1p2 = -0.5 - 0.3 * (mylogP - 5.0)
|
|
577
|
+
|
|
578
|
+
# ; Power-law slope gamma_largeq for M1 = 3.5 Msun and various P; Eqn. 10
|
|
579
|
+
if mylogP <= 1.0:
|
|
580
|
+
gl_3p5 = -0.5
|
|
581
|
+
if (mylogP > 1.0) and (mylogP <= 4.5):
|
|
582
|
+
gl_3p5 = -0.5 - 0.2 * (mylogP - 1.0)
|
|
583
|
+
if (mylogP > 4.5) and (mylogP <= 6.5):
|
|
584
|
+
gl_3p5 = -1.2 - 0.4 * (mylogP - 4.5)
|
|
585
|
+
if mylogP > 6.5:
|
|
586
|
+
gl_3p5 = -2.0
|
|
587
|
+
|
|
588
|
+
# ; Power-law slope gamma_largeq for M1 > 6 Msun and various P; Eqn. 11
|
|
589
|
+
if mylogP <= 1.0:
|
|
590
|
+
gl_6 = -0.5
|
|
591
|
+
if (mylogP > 1.0) and (mylogP <= 2.0):
|
|
592
|
+
gl_6 = -0.5 - 0.9 * (mylogP - 1.0)
|
|
593
|
+
if (mylogP > 2.0) and (mylogP <= 4.0):
|
|
594
|
+
gl_6 = -1.4 - 0.3 * (mylogP - 2.0)
|
|
595
|
+
|
|
596
|
+
if mylogP > 4.0:
|
|
597
|
+
gl_6 = -2.0
|
|
598
|
+
|
|
599
|
+
# ; Given P, interpolate gamma_largeq w/ respect to M1 at myM1
|
|
600
|
+
if myM1 <= 1.2:
|
|
601
|
+
gl = gl_1p2
|
|
602
|
+
if (myM1 > 1.2) and (myM1 <= 3.5):
|
|
603
|
+
gl = np.interp(
|
|
604
|
+
np.log10(myM1), np.log10([1.2, 3.5]), [gl_1p2, gl_3p5]
|
|
605
|
+
)
|
|
606
|
+
if (myM1 > 3.5) and (myM1 <= 6.0):
|
|
607
|
+
gl = np.interp(np.log10(myM1), np.log10([3.5, 6.0]), [gl_3p5, gl_6])
|
|
608
|
+
if myM1 > 6.0:
|
|
609
|
+
gl = gl_6
|
|
610
|
+
|
|
611
|
+
# ; Power-law slope gamma_smallq for M1 < 1.2 Msun and all P; Eqn. 13
|
|
612
|
+
gs_1p2 = 0.3
|
|
613
|
+
|
|
614
|
+
# ; Power-law slope gamma_smallq for M1 = 3.5 Msun and various P; Eqn. 14
|
|
615
|
+
if mylogP <= 2.5:
|
|
616
|
+
gs_3p5 = 0.2
|
|
617
|
+
if (mylogP > 2.5) and (mylogP <= 5.5):
|
|
618
|
+
gs_3p5 = 0.2 - 0.3 * (mylogP - 2.5)
|
|
619
|
+
if mylogP > 5.5:
|
|
620
|
+
gs_3p5 = -0.7 - 0.2 * (mylogP - 5.5)
|
|
621
|
+
|
|
622
|
+
# ; Power-law slope gamma_smallq for M1 > 6 Msun and various P; Eqn. 15
|
|
623
|
+
if mylogP <= 1.0:
|
|
624
|
+
gs_6 = 0.1
|
|
625
|
+
if (mylogP > 1.0) and (mylogP <= 3.0):
|
|
626
|
+
gs_6 = 0.1 - 0.15 * (mylogP - 1.0)
|
|
627
|
+
if (mylogP > 3.0) and (mylogP <= 5.6):
|
|
628
|
+
gs_6 = -0.2 - 0.50 * (mylogP - 3.0)
|
|
629
|
+
if mylogP > 5.6:
|
|
630
|
+
gs_6 = -1.5
|
|
631
|
+
|
|
632
|
+
# ; Given P, interpolate gamma_smallq w/ respect to M1 at myM1
|
|
633
|
+
if myM1 <= 1.2:
|
|
634
|
+
gs = gs_1p2
|
|
635
|
+
if (myM1 > 1.2) and (myM1 <= 3.5):
|
|
636
|
+
gs = np.interp(
|
|
637
|
+
np.log10(myM1), np.log10([1.2, 3.5]), [gs_1p2, gs_3p5]
|
|
638
|
+
)
|
|
639
|
+
if (myM1 > 3.5) and (myM1 <= 6.0):
|
|
640
|
+
gs = np.interp(np.log10(myM1), np.log10([3.5, 6.0]), [gs_3p5, gs_6])
|
|
641
|
+
if myM1 > 6.0:
|
|
642
|
+
gs = gs_6
|
|
643
|
+
|
|
644
|
+
# ; Given Ftwin, gamma_smallq, and gamma_largeq at the specified M1 & P,
|
|
645
|
+
# ; tabulate the cumulative mass ratio distribution across 0.1 < q < 1.0
|
|
646
|
+
fq = qv ** gl # ; slope across 0.3 < q < 1.0
|
|
647
|
+
fq = fq / utils.idl_tabulate(
|
|
648
|
+
qv[indlq], fq[indlq]
|
|
649
|
+
) # ; normalize to 0.3 < q < 1.0
|
|
650
|
+
fq = fq * (1.0 - Ftwin) + H * Ftwin # ; add twins
|
|
651
|
+
fq[indsq] = (
|
|
652
|
+
fq[indq0p3] * (qv[indsq] / 0.3) ** gs
|
|
653
|
+
) # ; slope across 0.1 < q < 0.3
|
|
654
|
+
cumfq = np.cumsum(fq) - fq[0] # ; cumulative distribution
|
|
655
|
+
cumfq = cumfq / np.max(cumfq) # ; normalize cumfq(q=1.0) = 1
|
|
656
|
+
cumqdist[:, j, i] = cumfq # ; save to grid
|
|
657
|
+
|
|
658
|
+
# ; Given M1 and P, q_factor is the ratio of all binaries 0.1 < q < 1.0
|
|
659
|
+
# ; to those with 0.3 < q < 1.0
|
|
660
|
+
q_factor = utils.idl_tabulate(qv, fq)
|
|
661
|
+
|
|
662
|
+
# ; Given M1 & P, calculate power-law slope eta of eccentricity dist.
|
|
663
|
+
if mylogP >= 0.7:
|
|
664
|
+
# ; For log P > 0.7 use fits in Section 9.2.
|
|
665
|
+
# ; Power-law slope eta for M1 < 3 Msun and log P > 0.7
|
|
666
|
+
eta_3 = 0.6 - 0.7 / (mylogP - 0.5) # ; Eqn. 17
|
|
667
|
+
# ; Power-law slope eta for M1 > 7 Msun and log P > 0.7
|
|
668
|
+
eta_7 = 0.9 - 0.2 / (mylogP - 0.5) # ; Eqn. 18
|
|
669
|
+
else:
|
|
670
|
+
# ; For log P < 0.7, set eta to fitted values at log P = 0.7
|
|
671
|
+
eta_3 = -2.9
|
|
672
|
+
eta_7 = -0.1
|
|
673
|
+
|
|
674
|
+
# ; Given P, interpolate eta with respect to M1 at myM1
|
|
675
|
+
if myM1 <= 3.0:
|
|
676
|
+
eta = eta_3
|
|
677
|
+
if (myM1 > 3.0) and (myM1 <= 7.0):
|
|
678
|
+
eta = np.interp(
|
|
679
|
+
np.log10(myM1), np.log10([3.0, 7.0]), [eta_3, eta_7]
|
|
680
|
+
)
|
|
681
|
+
if myM1 > 7.0:
|
|
682
|
+
eta = eta_7
|
|
683
|
+
|
|
684
|
+
# ; Given eta at the specified M1 and P, tabulate eccentricity distribution
|
|
685
|
+
if 10 ** mylogP <= 2.0:
|
|
686
|
+
# ; For P < 2 days, assume all systems are close to circular
|
|
687
|
+
# ; For adopted ev (spacing and minimum value), eta = -3.2 satisfies this
|
|
688
|
+
fe = ev ** (-3.2)
|
|
689
|
+
else:
|
|
690
|
+
fe = ev ** eta
|
|
691
|
+
e_max = 1.0 - (10 ** mylogP / 2.0) ** (
|
|
692
|
+
-2.0 / 3.0
|
|
693
|
+
) # ; maximum eccentricity for given P
|
|
694
|
+
ind = np.where(ev >= e_max)
|
|
695
|
+
fe[ind] = 0.0 # ; set dist. = 0 for e > e_max
|
|
696
|
+
# ; Assume e dist. has power-law slope eta for 0.0 < e / e_max < 0.8 and
|
|
697
|
+
# ; then linear turnover between 0.8 < e / e_max < 1.0 so that dist.
|
|
698
|
+
# ; is continuous at e / e_max = 0.8 and zero at e = e_max
|
|
699
|
+
ind = np.where((ev >= 0.8 * e_max) & (ev <= 1.0 * e_max))
|
|
700
|
+
ind_cont = np.min(ind) - 1
|
|
701
|
+
fe[ind] = np.interp(
|
|
702
|
+
ev[ind], [0.8 * e_max, 1.0 * e_max], [fe[ind_cont], 0.0]
|
|
703
|
+
)
|
|
704
|
+
|
|
705
|
+
cumfe = np.cumsum(fe) - fe[0] # ; cumulative distribution
|
|
706
|
+
cumfe = cumfe / np.max(cumfe) # ; normalize cumfe(e=e_max) = 1
|
|
707
|
+
cumedist[:, j, i] = cumfe # ; save to grid
|
|
708
|
+
|
|
709
|
+
# ; Given constants alpha and DlogP and
|
|
710
|
+
# ; M1 dependent values flogPle1, flogPeq2p7, and flogPeq5p5,
|
|
711
|
+
# ; calculate frequency flogP of companions with q > 0.3 per decade
|
|
712
|
+
# ; of orbital period at given P (Section 9.3 and Eqn. 23)
|
|
713
|
+
if mylogP <= 1.0:
|
|
714
|
+
flogP = flogPle1
|
|
715
|
+
if (mylogP > 1.0) and (mylogP <= 2.7 - DlogP):
|
|
716
|
+
flogP = flogPle1 + (mylogP - 1.0) / (1.7 - DlogP) * (
|
|
717
|
+
flogPeq2p7 - flogPle1 - alpha * DlogP
|
|
718
|
+
)
|
|
719
|
+
if (mylogP > 2.7 - DlogP) and (mylogP <= 2.7 + DlogP):
|
|
720
|
+
flogP = flogPeq2p7 + alpha * (mylogP - 2.7)
|
|
721
|
+
if (mylogP > 2.7 + DlogP) and (mylogP <= 5.5):
|
|
722
|
+
flogP = (
|
|
723
|
+
flogPeq2p7
|
|
724
|
+
+ alpha * DlogP
|
|
725
|
+
+ (mylogP - 2.7 - DlogP)
|
|
726
|
+
/ (2.8 - DlogP)
|
|
727
|
+
* (flogPeq5p5 - flogPeq2p7 - alpha * DlogP)
|
|
728
|
+
)
|
|
729
|
+
if mylogP > 5.5:
|
|
730
|
+
flogP = flogPeq5p5 * np.exp(-0.3 * (mylogP - 5.5))
|
|
731
|
+
|
|
732
|
+
# ; Convert frequency of companions with q > 0.3 to frequency of
|
|
733
|
+
# ; companions with q > 0.1 according to q_factor; save to grid
|
|
734
|
+
flogP_sq[j, i] = flogP * q_factor
|
|
735
|
+
|
|
736
|
+
# ; Calculate prob. that a companion to M1 with period P is the
|
|
737
|
+
# ; inner binary. Currently this is an approximation.
|
|
738
|
+
# ; 100% for log P < 1.5
|
|
739
|
+
# ; For log P > 1.5 adopt functional form that reproduces M1 dependent
|
|
740
|
+
# ; multiplicity statistics in Section 9.4, including a
|
|
741
|
+
# ; 41% binary star faction (59% single star fraction) for M1 = 1 Msun and
|
|
742
|
+
# ; 96% binary star fraction (4% single star fraction) for M1 = 28 Msun
|
|
743
|
+
if mylogP <= 1.5:
|
|
744
|
+
probbin[j, i] = 1.0
|
|
745
|
+
else:
|
|
746
|
+
probbin[j, i] = (
|
|
747
|
+
1.0 - 0.11 * (mylogP - 1.5) ** 1.43 * (myM1 / 10.0) ** 0.56
|
|
748
|
+
)
|
|
749
|
+
if probbin[j, i] <= 0.0:
|
|
750
|
+
probbin[j, i] = 0.0
|
|
751
|
+
|
|
752
|
+
# ; Given M1, calculate cumulative binary period distribution
|
|
753
|
+
mycumPbindist = (
|
|
754
|
+
np.cumsum(flogP_sq[:, i] * probbin[:, i])
|
|
755
|
+
- flogP_sq[0, i] * probbin[0, i]
|
|
756
|
+
)
|
|
757
|
+
# ; Normalize so that max(cumPbindist) = total binary star fraction (NOT 1)
|
|
758
|
+
mycumPbindist = (
|
|
759
|
+
mycumPbindist
|
|
760
|
+
/ np.max(mycumPbindist)
|
|
761
|
+
* utils.idl_tabulate(logPv, flogP_sq[:, i] * probbin[:, i])
|
|
762
|
+
)
|
|
763
|
+
cumPbindist[:, i] = mycumPbindist # ;save to grid
|
|
764
|
+
|
|
765
|
+
# ; Step 2
|
|
766
|
+
# ; Implement Monte Carlo method / random number generator to select
|
|
767
|
+
# ; single stars and binaries from the grids of distributions
|
|
768
|
+
|
|
769
|
+
# ; Create vector for PRIMARY mass function, which is the mass distribution
|
|
770
|
+
# ; of single stars and primaries in binaries.
|
|
771
|
+
# ; This is NOT the IMF, which is the mass distribution of single stars,
|
|
772
|
+
# ; primaries in binaries, and secondaries in binaries.
|
|
773
|
+
|
|
774
|
+
np.random.seed(seed)
|
|
775
|
+
|
|
776
|
+
mass_singles = 0.0
|
|
777
|
+
mass_binaries = 0.0
|
|
778
|
+
n_singles = 0
|
|
779
|
+
n_binaries = 0
|
|
780
|
+
primary_mass_list = []
|
|
781
|
+
secondary_mass_list = []
|
|
782
|
+
single_mass_list = []
|
|
783
|
+
porb_list = []
|
|
784
|
+
ecc_list = []
|
|
785
|
+
binfrac_list = []
|
|
786
|
+
|
|
787
|
+
# Full primary mass vector across 0.08 < M1 < 150
|
|
788
|
+
M1 = np.linspace(0, 150, 150000) + 0.08
|
|
789
|
+
# Slope = -2.3 for M1 > 1 Msun
|
|
790
|
+
fM1 = M1**(-2.3)
|
|
791
|
+
# Slope = -1.6 for M1 = 0.5 - 1.0 Msun
|
|
792
|
+
ind = np.where(M1 <= 1.0)
|
|
793
|
+
fM1[ind] = M1[ind]**(-1.6)
|
|
794
|
+
# Slope = -0.8 for M1 = 0.15 - 0.5 Msun
|
|
795
|
+
ind = np.where(M1 <= 0.5)
|
|
796
|
+
fM1[ind] = M1[ind]**(-0.8) / 0.5**(1.6 - 0.8)
|
|
797
|
+
# Cumulative primary mass distribution function
|
|
798
|
+
cumfM1 = np.cumsum(fM1) - fM1[0]
|
|
799
|
+
cumfM1 = cumfM1 / np.max(cumfM1)
|
|
800
|
+
# Value of primary mass CDF where M1 = M1min
|
|
801
|
+
# Minimum primary mass to generate (must be >0.080 Msun)
|
|
802
|
+
cumf_M1min = np.interp(0.08, M1, cumfM1)
|
|
803
|
+
while len(primary_mass_list) < size:
|
|
804
|
+
|
|
805
|
+
# Select primary M1 > M1min from primary mass function
|
|
806
|
+
myM1 = np.interp(cumf_M1min + (1.0 - cumf_M1min) * np.random.rand(), cumfM1, M1)
|
|
807
|
+
|
|
808
|
+
# Find index of M1v that is closest to myM1.
|
|
809
|
+
# ; For M1 = 40 - 150 Msun, adopt binary statistics of M1 = 40 Msun.
|
|
810
|
+
# ; For M1 = 0.08 - 0.8 Msun, adopt P and e dist of M1 = 0.8Msun,
|
|
811
|
+
# ; scale and interpolate the companion frequencies so that the
|
|
812
|
+
# ; binary star fraction of M1 = 0.08 Msun primaries is zero,
|
|
813
|
+
# ; and truncate the q distribution so that q > q_min = 0.08/M1
|
|
814
|
+
indM1 = np.where(abs(myM1 - M1v) == min(abs(myM1 - M1v)))
|
|
815
|
+
indM1 = indM1[0]
|
|
816
|
+
|
|
817
|
+
# ; Given M1, determine cumulative binary period distribution
|
|
818
|
+
mycumPbindist_flat = (cumPbindist[:, indM1]).flatten()
|
|
819
|
+
# If M1 < 0.8 Msun, rescale to appropriate binary star fraction
|
|
820
|
+
if(myM1 <= 0.8):
|
|
821
|
+
mycumPbindist_flat = mycumPbindist_flat * np.interp(np.log10(myM1), np.log10([0.08, 0.8]), [0.0, 1.0])
|
|
822
|
+
|
|
823
|
+
# ; Given M1, determine the binary star fraction
|
|
824
|
+
mybinfrac = np.max(mycumPbindist_flat)
|
|
825
|
+
|
|
826
|
+
# ; Generate random number myrand between 0 and 1
|
|
827
|
+
myrand = np.random.rand()
|
|
828
|
+
# If random number < binary star fraction, generate a binary
|
|
829
|
+
if(myrand < mybinfrac):
|
|
830
|
+
# Given myrand, select P and corresponding index in logPv
|
|
831
|
+
mylogP = np.interp(myrand, mycumPbindist_flat, logPv)
|
|
832
|
+
indlogP = np.where(abs(mylogP - logPv) == min(abs(mylogP - logPv)))
|
|
833
|
+
indlogP = indlogP[0]
|
|
834
|
+
|
|
835
|
+
# Given M1 & P, select e from eccentricity distribution
|
|
836
|
+
mye = np.interp(np.random.rand(), cumedist[:, indlogP, indM1].flatten(), ev)
|
|
837
|
+
|
|
838
|
+
# Given M1 & P, determine mass ratio distribution.
|
|
839
|
+
# If M1 < 0.8 Msun, truncate q distribution and consider
|
|
840
|
+
# only mass ratios q > q_min = 0.08 / M1
|
|
841
|
+
mycumqdist = cumqdist[:, indlogP, indM1].flatten()
|
|
842
|
+
if(myM1 < 0.8):
|
|
843
|
+
q_min = 0.08 / myM1
|
|
844
|
+
# Calculate cumulative probability at q = q_min
|
|
845
|
+
cum_qmin = np.interp(q_min, qv, mycumqdist)
|
|
846
|
+
# Rescale and renormalize cumulative distribution for q > q_min
|
|
847
|
+
mycumqdist = mycumqdist - cum_qmin
|
|
848
|
+
mycumqdist = mycumqdist / max(mycumqdist)
|
|
849
|
+
# Set probability = 0 where q < q_min
|
|
850
|
+
indq = np.where(qv <= q_min)
|
|
851
|
+
mycumqdist[indq] = 0.0
|
|
852
|
+
|
|
853
|
+
# Given M1 & P, select q from cumulative mass ratio distribution
|
|
854
|
+
myq = np.interp(np.random.rand(), mycumqdist, qv)
|
|
855
|
+
|
|
856
|
+
if ((myM1 > M1min) and (myq * myM1 > M2min) and (myM1 < M1max) and
|
|
857
|
+
(myq * myM1 < M2max) and (mylogP < porb_hi) and (mylogP > porb_lo)):
|
|
858
|
+
primary_mass_list.append(myM1)
|
|
859
|
+
secondary_mass_list.append(myq * myM1)
|
|
860
|
+
porb_list.append(10**mylogP)
|
|
861
|
+
ecc_list.append(mye)
|
|
862
|
+
binfrac_list.append(mybinfrac)
|
|
863
|
+
mass_binaries += myM1
|
|
864
|
+
mass_binaries += myq * myM1
|
|
865
|
+
n_binaries += 1
|
|
866
|
+
else:
|
|
867
|
+
single_mass_list.append(myM1)
|
|
868
|
+
mass_singles += myM1
|
|
869
|
+
n_singles += 1
|
|
870
|
+
|
|
871
|
+
return (
|
|
872
|
+
primary_mass_list,
|
|
873
|
+
secondary_mass_list,
|
|
874
|
+
porb_list,
|
|
875
|
+
ecc_list,
|
|
876
|
+
single_mass_list,
|
|
877
|
+
mass_singles,
|
|
878
|
+
mass_binaries,
|
|
879
|
+
n_singles,
|
|
880
|
+
n_binaries,
|
|
881
|
+
binfrac_list
|
|
882
|
+
)
|