cosmic-popsynth 3.4.17__cp310-cp310-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.
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ # Copyright (C) Katie Breivik (2017 - 2020)
4
+ #
5
+ # This file is part of COSMIC
6
+ #
7
+ # COSMIC is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # COSMIC is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with COSMIC. If not, see <http://www.gnu.org/licenses/>
19
+
20
+
21
+ from . import ( # pylint: disable=unused-import
22
+ independent, # Independent parameter distribution sample
23
+ multidim, # Multidimensional parameter distribution sample`
24
+ cmc, # create initial conditions for CMC
25
+ )
@@ -0,0 +1,418 @@
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
+ """`cmc`
20
+ """
21
+
22
+ import numpy as np
23
+
24
+ from .sampler import register_sampler
25
+ from .independent import Sample
26
+ from .. import InitialCMCTable, InitialBinaryTable
27
+ from ..cmc import elson, king
28
+ from ... import utils
29
+
30
+ __author__ = "Scott Coughlin <scott.coughlin@ligo.org>"
31
+ __credits__ = [
32
+ "Carl Rodriguez <carllouisrodriguez@gmail.com>",
33
+ "Newlin Weatherford <newlinweatherford2017@u.northwestern.edu>"
34
+ ]
35
+ __all__ = ["get_cmc_sampler", "CMCSample"]
36
+
37
+
38
+ def get_cmc_sampler(
39
+ cluster_profile, primary_model, ecc_model, porb_model, binfrac_model, met, size, **kwargs
40
+ ):
41
+ """Generates an initial cluster sample according to user specified models
42
+
43
+ Parameters
44
+ ----------
45
+ cluster_profile : `str`
46
+ Model to use for the cluster profile (i.e. sampling of the placement of objects in the cluster and their velocity within the cluster)
47
+ Options include:
48
+ 'plummer' : Standard Plummer sphere.
49
+ Additional parameters:
50
+ 'r_max' : `float`
51
+ the maximum radius (in virial radii) to sample the clsuter
52
+ 'elson' : EFF 1987 profile. Generalization of Plummer that better fits young massive clusters
53
+ Additional parameters:
54
+ 'gamma' : `float`
55
+ steepness paramter for Elson profile; note that gamma=4 is same is Plummer
56
+ 'r_max' : `float`
57
+ the maximum radius (in virial radii) to sample the clsuter
58
+ 'king' : King profile
59
+ 'w_0' : `float`
60
+ King concentration parameter
61
+ 'r_max' : `float`
62
+ the maximum radius (in virial radii) to sample the clsuter
63
+
64
+ primary_model : `str`
65
+ Model to sample primary mass; choices include: kroupa93, kroupa01, salpeter55, custom
66
+ if 'custom' is selected, must also pass arguemts:
67
+ alphas : `array`
68
+ list of power law indicies
69
+ mcuts : `array`
70
+ breaks in the power laws.
71
+ e.g. alphas=[-1.3,-2.3,-2.3],mcuts=[0.08,0.5,1.0,150.] reproduces standard Kroupa2001 IMF
72
+
73
+ ecc_model : `str`
74
+ Model to sample eccentricity; choices include: thermal, uniform, sana12
75
+
76
+ porb_model : `str`
77
+ Model to sample orbital period; choices include: log_uniform, sana12
78
+
79
+ msort : `float`
80
+ Stars with M>msort can have different pairing and sampling of companions
81
+
82
+ pair : `float`
83
+ Sets the pairing of stars M>msort only with stars with M>msort
84
+
85
+ binfrac_model : `str or float`
86
+ Model for binary fraction; choices include: vanHaaften, offner22, or a fraction where 1.0 is 100% binaries
87
+
88
+ binfrac_model_msort : `str or float`
89
+ Same as binfrac_model for M>msort
90
+
91
+ qmin : `float`
92
+ kwarg which sets the minimum mass ratio for sampling the secondary
93
+ where the mass ratio distribution is flat in q
94
+ if q > 0, qmin sets the minimum mass ratio
95
+ q = -1, this limits the minimum mass ratio to be set such that
96
+ the pre-MS lifetime of the secondary is not longer than the full
97
+ lifetime of the primary if it were to evolve as a single star
98
+
99
+ m2_min : `float`
100
+ kwarg which sets the minimum secondary mass for sampling
101
+ the secondary as uniform in mass_2 between m2_min and mass_1
102
+
103
+ qmin_msort : `float`
104
+ Same as qmin for M>msort; only applies if qmin is supplied
105
+
106
+ met : `float`
107
+ Sets the metallicity of the binary population where solar metallicity is zsun
108
+
109
+ size : `int`
110
+ Size of the population to sample
111
+
112
+ zsun : `float`
113
+ optional kwarg for setting effective radii, default is 0.02
114
+
115
+ Optional Parameters
116
+ -------------------
117
+ virial_radius : `float`
118
+ the initial virial radius of the cluster, in parsecs
119
+ Default -- 1 pc
120
+
121
+ tidal_radius : `float`
122
+ the initial tidal radius of the cluster, in units of the virial_radius
123
+ Default -- 1e6 rvir
124
+
125
+ central_bh : `float`
126
+ Put a central massive black hole in the cluster
127
+ Default -- 0 MSUN
128
+
129
+ scale_with_central_bh : `bool`
130
+ If True, then the potential from the central_bh is included when scaling the radii.
131
+ Default -- False
132
+
133
+ seed : `float`
134
+ seed to the random number generator, for reproducability
135
+
136
+ Returns
137
+ -------
138
+ Singles: `pandas.DataFrame`
139
+ DataFrame of Single objects in the format of the InitialCMCTable
140
+
141
+ Binaries: `pandas.DataFrame`
142
+ DataFrame of Single objects in the format of the InitialCMCTable
143
+ """
144
+ initconditions = CMCSample()
145
+
146
+ # if RNG seed is provided, then use it globally
147
+ rng_seed = kwargs.pop("seed", 0)
148
+ if rng_seed != 0:
149
+ np.random.seed(rng_seed)
150
+
151
+ # get radii, radial and transverse velocities
152
+ r, vr, vt = initconditions.set_r_vr_vt(cluster_profile, N=size, **kwargs)
153
+
154
+ # track the mass in singles and the mass in binaries
155
+ mass_singles = 0.0
156
+ mass_binaries = 0.0
157
+
158
+ # track the total number of stars sampled
159
+ n_singles = 0
160
+ n_binaries = 0
161
+
162
+ mass1, total_mass1 = initconditions.sample_primary(
163
+ primary_model, size=size, **kwargs)
164
+ (
165
+ mass1_binaries,
166
+ mass_single,
167
+ binfrac_binaries,
168
+ binary_index,
169
+ ) = initconditions.binary_select(mass1, binfrac_model=binfrac_model, **kwargs)
170
+
171
+ mass2_binaries = initconditions.sample_secondary(
172
+ mass1_binaries, **kwargs)
173
+
174
+ # track the mass sampled
175
+ mass_singles += np.sum(mass_single)
176
+ mass_binaries += np.sum(mass1_binaries)
177
+ mass_binaries += np.sum(mass2_binaries)
178
+
179
+ # set singles id
180
+ single_ids = np.arange(mass1.size)
181
+ binary_secondary_object_id = np.arange(mass1.size, mass1.size + mass2_binaries.size)
182
+
183
+ # set binind and correct masses of binaries
184
+ binind = np.zeros(mass1.size)
185
+ binind[binary_index] = np.arange(len(binary_index)) + 1
186
+ mass1[binary_index] += mass2_binaries
187
+
188
+ # track the total number sampled
189
+ n_singles += len(mass_single)
190
+ n_binaries += len(mass1_binaries)
191
+
192
+ # Obtain radii (technically this is done for the binaries in the independent sampler
193
+ # if set_radii_with_BSE is true, but that's not a huge amount of overhead)
194
+ zsun = kwargs.pop("zsun", 0.02)
195
+
196
+ Reff = initconditions.set_reff(mass1, metallicity=met, zsun=zsun)
197
+ Reff1 = Reff[binary_index]
198
+ Reff2 = initconditions.set_reff(mass2_binaries, metallicity=met, zsun=zsun)
199
+
200
+ # select out the primaries and secondaries that will produce the final kstars
201
+ porb_max = initconditions.calc_porb_max(mass1, vr, vt, binary_index, mass1_binaries, mass2_binaries, **kwargs)
202
+
203
+ porb,aRL_over_a = initconditions.sample_porb(
204
+ mass1_binaries, mass2_binaries, Reff1, Reff2, porb_model=porb_model, porb_max=porb_max, size=mass1_binaries.size
205
+ )
206
+ ecc = initconditions.sample_ecc(aRL_over_a, ecc_model, size=mass1_binaries.size)
207
+
208
+ sep = utils.a_from_p(porb, mass1_binaries, mass2_binaries)
209
+ kstar1 = initconditions.set_kstar(mass1_binaries)
210
+ kstar2 = initconditions.set_kstar(mass2_binaries)
211
+
212
+ singles_table = InitialCMCTable.InitialCMCSingles(
213
+ single_ids +
214
+ 1, initconditions.set_kstar(mass1), mass1, Reff, r, vr, vt, binind
215
+ )
216
+
217
+ binaries_table = InitialCMCTable.InitialCMCBinaries(
218
+ np.arange(mass1_binaries.size) + 1,
219
+ single_ids[binary_index] + 1,
220
+ kstar1,
221
+ mass1_binaries,
222
+ Reff1,
223
+ binary_secondary_object_id + 1,
224
+ kstar2,
225
+ mass2_binaries,
226
+ Reff2,
227
+ sep,
228
+ ecc,
229
+ )
230
+
231
+ singles_table.metallicity = met
232
+ binaries_table.metallicity = met
233
+ singles_table.virial_radius = kwargs.get("virial_radius",1)
234
+ singles_table.tidal_radius = kwargs.get("tidal_radius",1e13)
235
+ singles_table.central_bh = kwargs.get("central_bh",0)
236
+ singles_table.scale_with_central_bh = kwargs.get("scale_with_central_bh",False)
237
+ singles_table.mass_of_cluster = np.sum(singles_table["m"]) + singles_table.central_bh
238
+
239
+ return singles_table, binaries_table
240
+
241
+
242
+ register_sampler(
243
+ "cmc",
244
+ InitialCMCTable,
245
+ get_cmc_sampler,
246
+ usage="primary_model ecc_model porb_model binfrac_model met size",
247
+ )
248
+
249
+ def get_cmc_point_mass_sampler(
250
+ cluster_profile, size, **kwargs
251
+ ):
252
+ """Generates an CMC cluster model according to user-specified model.
253
+ Note here that masses will all be unity (with total cluster normalized accordingly)
254
+
255
+ Parameters
256
+ ----------
257
+ cluster_profile : `str`
258
+ Model to use for the cluster profile (i.e. sampling of the placement of objects in the cluster and their velocity within the cluster)
259
+ Options include:
260
+ 'plummer' : Standard Plummer sphere.
261
+ Additional parameters:
262
+ 'r_max' : `float`
263
+ the maximum radius (in virial radii) to sample the clsuter
264
+ 'elson' : EFF 1987 profile. Generalization of Plummer that better fits young massive clusters
265
+ Additional parameters:
266
+ 'gamma' : `float`
267
+ steepness paramter for Elson profile; note that gamma=4 is same is Plummer
268
+ 'r_max' : `float`
269
+ the maximum radius (in virial radii) to sample the clsuter
270
+ 'king' : King profile
271
+ 'w_0' : `float`
272
+ King concentration parameter
273
+ 'r_max' : `float`
274
+ the maximum radius (in virial radii) to sample the clsuter
275
+
276
+ size : `int`
277
+ Size of the population to sample
278
+
279
+ Optional Parameters
280
+ -------------------
281
+ virial_radius : `float`
282
+ the initial virial radius of the cluster, in parsecs
283
+ Default -- 1 pc
284
+
285
+ tidal_radius : `float`
286
+ the initial tidal radius of the cluster, in units of the virial_radius
287
+ Default -- 1e6 rvir
288
+
289
+ central_bh : `float`
290
+ Put a central massive black hole in the cluster. Note here units are
291
+ in code units where every particle has mass 1 (before normalization), e.g. if you
292
+ want a cluster with 10000 stars and a BH that is 10% of the total mass, central_bh=1000
293
+ Default -- 0
294
+
295
+ scale_with_central_bh : `bool`
296
+ If True, then the potential from the central_bh is included when scaling the radii.
297
+ Default -- False
298
+
299
+ seed : `float`
300
+ seed to the random number generator, for reproducability
301
+
302
+ Returns
303
+ -------
304
+ Singles: `pandas.DataFrame`
305
+ DataFrame of Single objects in the format of the InitialCMCTable
306
+ Binaries: `pandas.DataFrame`
307
+ DataFrame of Single objects in the format of the InitialCMCTable
308
+
309
+ """
310
+ initconditions = CMCSample()
311
+
312
+ # if RNG seed is provided, then use it globally
313
+ rng_seed = kwargs.pop("seed", 0)
314
+ if rng_seed != 0:
315
+ np.random.seed(rng_seed)
316
+
317
+ # get radii, radial and transverse velocities
318
+ r, vr, vt = initconditions.set_r_vr_vt(cluster_profile, N=size, **kwargs)
319
+
320
+ mass1 = np.ones(size)/size
321
+ Reff = np.zeros(size)
322
+ binind = np.zeros(size)
323
+ single_ids = np.arange(size)
324
+
325
+ singles_table = InitialCMCTable.InitialCMCSingles(
326
+ single_ids + 1, initconditions.set_kstar(mass1), mass1, Reff, r, vr, vt, binind
327
+ )
328
+
329
+ # We assume no binaries for the point mass models
330
+ binaries_table = InitialCMCTable.InitialCMCBinaries(1,1,0,0,0,1,0,0,0,0,0)
331
+
332
+ singles_table.metallicity = 0.02
333
+ binaries_table.metallicity = 0.02
334
+ singles_table.virial_radius = kwargs.get("virial_radius",1)
335
+ singles_table.tidal_radius = kwargs.get("tidal_radius",1e13)
336
+ singles_table.central_bh = kwargs.get("central_bh",0)
337
+ singles_table.scale_with_central_bh = kwargs.get("scale_with_central_bh",False)
338
+ singles_table.mass_of_cluster = np.sum(singles_table["m"])*size + singles_table.central_bh
339
+
340
+ # Already scaled from the IC generators (unless we've added a central BH)
341
+ if singles_table.central_bh != 0:
342
+ singles_table.scaled_to_nbody_units = False
343
+ else:
344
+ singles_table.scaled_to_nbody_units = True
345
+
346
+ # Already scaled from the IC generators (unless we've added a central BH)
347
+ if singles_table.central_bh != 0:
348
+ singles_table.scaled_to_nbody_units = False
349
+ else:
350
+ singles_table.scaled_to_nbody_units = True
351
+
352
+ return singles_table, binaries_table
353
+
354
+ register_sampler(
355
+ "cmc_point_mass",
356
+ InitialCMCTable,
357
+ get_cmc_point_mass_sampler,
358
+ usage="size",
359
+ )
360
+
361
+ class CMCSample(Sample):
362
+ def set_r_vr_vt(self,cluster_profile, **kwargs):
363
+ if cluster_profile == "elson":
364
+ elson_kwargs = {
365
+ k: v for k, v in kwargs.items() if k in ["gamma", "r_max", "N"]
366
+ }
367
+ r, vr, vt = elson.draw_r_vr_vt(**elson_kwargs)
368
+ elif cluster_profile == "plummer":
369
+ plummer_kwargs = {k: v for k, v in kwargs.items() if k in ["r_max", "N"]}
370
+ r, vr, vt = elson.draw_r_vr_vt(gamma=4, **plummer_kwargs)
371
+ elif cluster_profile == "king":
372
+ king_kwargs = {k: v for k, v in kwargs.items() if k in ["w_0", "N"]}
373
+ r, vr, vt = king.draw_r_vr_vt(**king_kwargs)
374
+ else:
375
+ raise ValueError("Cluster profile not defined! Please specify one of [elson, plummer, king]")
376
+
377
+ return r, vr, vt
378
+
379
+ def calc_porb_max(self, mass, vr, vt, binary_index, mass1_binary, mass2_binary, **kwargs):
380
+
381
+ ## First, compute a rolling average with window length of AVEKERNEL
382
+ ## NOTE: this is in cluster code units (i.e. M=G=1), same as vr and vt
383
+ AVEKERNEL = 20
384
+
385
+ ## Then compute the average mass and avergae of m*v^2
386
+ ## First, to do this properly, we need to reflect the boundary points (so that the average
387
+ ## at the boundary doesn't go to zero artifically))
388
+ m = np.concatenate([mass[AVEKERNEL-1::-1],mass,mass[:-AVEKERNEL-1:-1]])
389
+ mv2 = mass*(vr*vr + vt*vt)
390
+ mv2 = np.concatenate([mv2[AVEKERNEL-1::-1],mv2,mv2[:-AVEKERNEL-1:-1]])
391
+
392
+ ## Then compute the averages by convolving with a uniform array (note we trim the reflected points off here)
393
+ m_average = np.convolve(m,np.ones(AVEKERNEL),mode='same')[AVEKERNEL:-AVEKERNEL]/AVEKERNEL
394
+ mv2_average = np.convolve(mv2,np.ones(AVEKERNEL),mode='same')[AVEKERNEL:-AVEKERNEL]/AVEKERNEL
395
+
396
+ ## Finally return the mass-weighted average 3D velocity dispersion (in cluster code units)
397
+ sigma = np.sqrt(mv2_average/m_average)
398
+
399
+ ## Now compute the orbital velocity corresponding to the hard/soft boundary; we only want it for the binaries
400
+ v_orb = 0.7*1.30294*sigma[binary_index] #sigma * 4/sqrt(3/pi); 0.7 is a factor we use in CMC
401
+ #v_orb = 0.7*1.30294*np.mean(sigma[:20]) #old CMC way of using just core velocity dispersion; TODO: possibly make flag option?
402
+
403
+
404
+ ## Maximum semi-major axis just comes from Kepler's 3rd
405
+ ## Note, to keep in code units, we need to divide binary masses by total cluster mass
406
+ amax = (mass1_binary+mass2_binary) / v_orb**2 / np.sum(mass)
407
+
408
+ ## Convert from code units (virial radii) to RSUN
409
+ virial_radius = kwargs.get("virial_radius",1) ## get the virial radius of the cluster (uses 1pc if not given)
410
+ RSUN_PER_PARSEC = 4.435e+7
411
+ amax *= RSUN_PER_PARSEC * virial_radius
412
+
413
+ ## Finally go from sep to porb
414
+ porb_max = utils.p_from_a(amax, mass1_binary, mass2_binary)
415
+
416
+ return porb_max ## returns orbital period IN DAYS
417
+
418
+