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,251 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright (C) Scott Coughlin (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
|
+
"""`InitialBinaryTable`
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import numpy as np
|
|
23
|
+
import math
|
|
24
|
+
import sys
|
|
25
|
+
import astropy.units as u
|
|
26
|
+
import astropy.constants as const
|
|
27
|
+
|
|
28
|
+
import pandas as pd
|
|
29
|
+
|
|
30
|
+
__author__ = "Katelyn Breivik <katie.breivik@gmail.com>"
|
|
31
|
+
__credits__ = "Scott Coughlin <scott.coughlin@ligo.org>"
|
|
32
|
+
__all__ = ["InitialBinaryTable"]
|
|
33
|
+
|
|
34
|
+
INITIAL_CONDITIONS_COLUMNS = []
|
|
35
|
+
|
|
36
|
+
INITIAL_CONDITIONS_COLUMNS_CORE = [
|
|
37
|
+
"kstar_1",
|
|
38
|
+
"kstar_2",
|
|
39
|
+
"mass_1",
|
|
40
|
+
"mass_2",
|
|
41
|
+
"porb",
|
|
42
|
+
"ecc",
|
|
43
|
+
"metallicity",
|
|
44
|
+
"tphysf",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
INITIAL_CONDITIONS_COLUMNS_EXTRAS = [
|
|
48
|
+
"mass0_1",
|
|
49
|
+
"mass0_2",
|
|
50
|
+
"rad_1",
|
|
51
|
+
"rad_2",
|
|
52
|
+
"lum_1",
|
|
53
|
+
"lum_2",
|
|
54
|
+
"massc_1",
|
|
55
|
+
"massc_2",
|
|
56
|
+
"radc_1",
|
|
57
|
+
"radc_2",
|
|
58
|
+
"menv_1",
|
|
59
|
+
"menv_2",
|
|
60
|
+
"renv_1",
|
|
61
|
+
"renv_2",
|
|
62
|
+
"omega_spin_1",
|
|
63
|
+
"omega_spin_2",
|
|
64
|
+
"B_1",
|
|
65
|
+
"B_2",
|
|
66
|
+
"bacc_1",
|
|
67
|
+
"bacc_2",
|
|
68
|
+
"tacc_1",
|
|
69
|
+
"tacc_2",
|
|
70
|
+
"epoch_1",
|
|
71
|
+
"epoch_2",
|
|
72
|
+
"tms_1",
|
|
73
|
+
"tms_2",
|
|
74
|
+
"bhspin_1",
|
|
75
|
+
"bhspin_2",
|
|
76
|
+
"tphys",
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
INITIAL_CONDITIONS_COLUMNS.extend(INITIAL_CONDITIONS_COLUMNS_CORE)
|
|
80
|
+
INITIAL_CONDITIONS_COLUMNS.extend(INITIAL_CONDITIONS_COLUMNS_EXTRAS)
|
|
81
|
+
|
|
82
|
+
INITIAL_CONDITIONS_MISC_COLUMNS = ["binfrac"]
|
|
83
|
+
|
|
84
|
+
if sys.version_info.major == 2 and sys.version_info.minor == 7:
|
|
85
|
+
INITIAL_CONDITIONS_COLUMNS_ALL = INITIAL_CONDITIONS_COLUMNS[:]
|
|
86
|
+
else:
|
|
87
|
+
INITIAL_CONDITIONS_COLUMNS_ALL = INITIAL_CONDITIONS_COLUMNS.copy()
|
|
88
|
+
|
|
89
|
+
INITIAL_CONDITIONS_COLUMNS_ALL.extend(INITIAL_CONDITIONS_MISC_COLUMNS)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class InitialBinaryTable:
|
|
93
|
+
@classmethod
|
|
94
|
+
def InitialBinaries(
|
|
95
|
+
cls, m1, m2, porb, ecc, tphysf, kstar1, kstar2, metallicity, **kwargs
|
|
96
|
+
):
|
|
97
|
+
"""Create single binary
|
|
98
|
+
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
m1 : float
|
|
102
|
+
Primary mass [Msun]
|
|
103
|
+
m2 : float
|
|
104
|
+
Secondary mass [Msun]
|
|
105
|
+
porb : float
|
|
106
|
+
Orbital period [days]
|
|
107
|
+
ecc : float
|
|
108
|
+
Eccentricity
|
|
109
|
+
tphysf : float
|
|
110
|
+
Time to evolve the binary [Myr]
|
|
111
|
+
kstar1 : array
|
|
112
|
+
0-14 Initial stellar type of the larger object;
|
|
113
|
+
main sequence stars are 0 if m < 0.7 Msun and 1 otherwise
|
|
114
|
+
kstar2 : array
|
|
115
|
+
0-14 Initial stellar type of the smaller object;
|
|
116
|
+
main sequence stars are 0 if m < 0.7 Msun and 1 otherwise
|
|
117
|
+
metallicity : float
|
|
118
|
+
Metallicity of the binaries; Z_sun = 0.02
|
|
119
|
+
|
|
120
|
+
**kwargs
|
|
121
|
+
|
|
122
|
+
binfrac : float
|
|
123
|
+
System-specific probability of the primary star being in a binary
|
|
124
|
+
|
|
125
|
+
mass0_1,mass0_2,rad1,rad2,lumin1,lumin2,
|
|
126
|
+
massc1,massc2,radc1,radc2,menv1,menv2,renv1,renv2,
|
|
127
|
+
ospin1,ospin2,b_0_1,b_0_2,bacc1,bacc2,
|
|
128
|
+
tacc1,tacc2,epoch1,epoch2,tms1,tms2
|
|
129
|
+
bhspin1,bhspin2
|
|
130
|
+
|
|
131
|
+
Returns
|
|
132
|
+
-------
|
|
133
|
+
InitialBinaries : DataFrame
|
|
134
|
+
Single binary initial conditions
|
|
135
|
+
|
|
136
|
+
"""
|
|
137
|
+
# System-specific probability of the primary star being in a binary
|
|
138
|
+
binfrac = kwargs.pop("binfrac", np.ones(np.array(m1).size))
|
|
139
|
+
|
|
140
|
+
# The following are in general not needed for COSMIC, however
|
|
141
|
+
# if you wish to start a binary at a very specific point in its evolution
|
|
142
|
+
# these kwarg arguments can help you do this.
|
|
143
|
+
# For instance the Globular Cluster code CMC requires this behavior.
|
|
144
|
+
mass0_1 = kwargs.pop("mass0_1", m1)
|
|
145
|
+
mass0_2 = kwargs.pop("mass0_2", m2)
|
|
146
|
+
rad1 = kwargs.pop("rad_1", np.zeros(np.array(m1).size))
|
|
147
|
+
rad2 = kwargs.pop("rad_2", np.zeros(np.array(m1).size))
|
|
148
|
+
lumin1 = kwargs.pop("lumin_1", np.zeros(np.array(m1).size))
|
|
149
|
+
lumin2 = kwargs.pop("lumin_2", np.zeros(np.array(m1).size))
|
|
150
|
+
massc1 = kwargs.pop("massc_1", np.zeros(np.array(m1).size))
|
|
151
|
+
massc2 = kwargs.pop("massc_2", np.zeros(np.array(m1).size))
|
|
152
|
+
radc1 = kwargs.pop("radc_1", np.zeros(np.array(m1).size))
|
|
153
|
+
radc2 = kwargs.pop("radc_2", np.zeros(np.array(m1).size))
|
|
154
|
+
menv1 = kwargs.pop("menv_1", np.zeros(np.array(m1).size))
|
|
155
|
+
menv2 = kwargs.pop("menv_2", np.zeros(np.array(m1).size))
|
|
156
|
+
renv1 = kwargs.pop("renv_1", np.zeros(np.array(m1).size))
|
|
157
|
+
renv2 = kwargs.pop("renv_2", np.zeros(np.array(m1).size))
|
|
158
|
+
ospin1 = kwargs.pop("ospin_1", np.zeros(np.array(m1).size))
|
|
159
|
+
ospin2 = kwargs.pop("ospin_2", np.zeros(np.array(m1).size))
|
|
160
|
+
b_0_1 = kwargs.pop("B_1", np.zeros(np.array(m1).size))
|
|
161
|
+
b_0_2 = kwargs.pop("B_2", np.zeros(np.array(m1).size))
|
|
162
|
+
bacc1 = kwargs.pop("bacc_1", np.zeros(np.array(m1).size))
|
|
163
|
+
bacc2 = kwargs.pop("bacc_2", np.zeros(np.array(m1).size))
|
|
164
|
+
tacc1 = kwargs.pop("tacc_1", np.zeros(np.array(m1).size))
|
|
165
|
+
tacc2 = kwargs.pop("tacc_2", np.zeros(np.array(m1).size))
|
|
166
|
+
epoch1 = kwargs.pop("epoch_1", np.zeros(np.array(m1).size))
|
|
167
|
+
epoch2 = kwargs.pop("epoch_2", np.zeros(np.array(m1).size))
|
|
168
|
+
tms1 = kwargs.pop("tms_1", np.zeros(np.array(m1).size))
|
|
169
|
+
tms2 = kwargs.pop("tms_2", np.zeros(np.array(m1).size))
|
|
170
|
+
bhspin1 = kwargs.pop("bhspin_1", np.zeros(np.array(m1).size))
|
|
171
|
+
bhspin2 = kwargs.pop("bhspin_2", np.zeros(np.array(m1).size))
|
|
172
|
+
tphys = kwargs.pop("tphys", np.zeros(np.array(m1).size))
|
|
173
|
+
|
|
174
|
+
# check for sep, if it's given convert to porb, unless both, then throw error
|
|
175
|
+
sep = kwargs.pop("sep", None)
|
|
176
|
+
if sep is not None:
|
|
177
|
+
if porb is not None:
|
|
178
|
+
raise ValueError("Cannot set both sep and porb, set porb=None to use sep")
|
|
179
|
+
porb = 2.0 * np.pi * np.sqrt((sep * u.Rsun)**3 / (const.G * (m1 + m2) * u.Msun)).to(u.day).value
|
|
180
|
+
|
|
181
|
+
# actually warn the user if they passed any unexpected kwargs
|
|
182
|
+
if len(kwargs) > 0:
|
|
183
|
+
raise TypeError("Unexpected **kwargs: %r" % (kwargs,))
|
|
184
|
+
|
|
185
|
+
bin_dat = pd.DataFrame(
|
|
186
|
+
np.vstack(
|
|
187
|
+
[
|
|
188
|
+
kstar1,
|
|
189
|
+
kstar2,
|
|
190
|
+
m1,
|
|
191
|
+
m2,
|
|
192
|
+
porb,
|
|
193
|
+
ecc,
|
|
194
|
+
metallicity,
|
|
195
|
+
tphysf,
|
|
196
|
+
mass0_1,
|
|
197
|
+
mass0_2,
|
|
198
|
+
rad1,
|
|
199
|
+
rad2,
|
|
200
|
+
lumin1,
|
|
201
|
+
lumin2,
|
|
202
|
+
massc1,
|
|
203
|
+
massc2,
|
|
204
|
+
radc1,
|
|
205
|
+
radc2,
|
|
206
|
+
menv1,
|
|
207
|
+
menv2,
|
|
208
|
+
renv1,
|
|
209
|
+
renv2,
|
|
210
|
+
ospin1,
|
|
211
|
+
ospin2,
|
|
212
|
+
b_0_1,
|
|
213
|
+
b_0_2,
|
|
214
|
+
bacc1,
|
|
215
|
+
bacc2,
|
|
216
|
+
tacc1,
|
|
217
|
+
tacc2,
|
|
218
|
+
epoch1,
|
|
219
|
+
epoch2,
|
|
220
|
+
tms1,
|
|
221
|
+
tms2,
|
|
222
|
+
bhspin1,
|
|
223
|
+
bhspin2,
|
|
224
|
+
tphys,
|
|
225
|
+
binfrac,
|
|
226
|
+
]
|
|
227
|
+
).T,
|
|
228
|
+
columns=INITIAL_CONDITIONS_COLUMNS_ALL,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
return bin_dat
|
|
232
|
+
|
|
233
|
+
@classmethod
|
|
234
|
+
def sampler(cls, format_, *args, **kwargs):
|
|
235
|
+
"""Fetch a method to generate an initial binary sample
|
|
236
|
+
|
|
237
|
+
Parameters
|
|
238
|
+
----------
|
|
239
|
+
format : str
|
|
240
|
+
the method name; Choose from 'independent' or 'multidim'
|
|
241
|
+
|
|
242
|
+
*args
|
|
243
|
+
the arguments necessary for the registered sample
|
|
244
|
+
method; see help(InitialBinaryTable.sampler('independent')
|
|
245
|
+
to see the arguments necessary for the independent sample
|
|
246
|
+
"""
|
|
247
|
+
# standard registered fetch
|
|
248
|
+
from .sampler.sampler import get_sampler
|
|
249
|
+
|
|
250
|
+
sampler = get_sampler(format_, cls)
|
|
251
|
+
return sampler(*args, **kwargs)
|
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright (C) Scott Coughlin (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
|
+
"""`InitialCMCTable`
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import pandas as pd
|
|
23
|
+
import numpy as np
|
|
24
|
+
import h5py
|
|
25
|
+
from astropy.io import fits
|
|
26
|
+
from astropy.table import Table
|
|
27
|
+
|
|
28
|
+
__author__ = "Scott Coughlin <scottcoughlin2014@u.northwestern.edu>"
|
|
29
|
+
__credits__ = "Carl Rodriguez <carllouisrodriguez@gmail.com>"
|
|
30
|
+
__all__ = ["InitialCMCTable"]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
INITIAL_CONDITIONS_COLUMNS_CMC_SINGLES = [
|
|
34
|
+
"id",
|
|
35
|
+
"k",
|
|
36
|
+
"m",
|
|
37
|
+
"Reff",
|
|
38
|
+
"r",
|
|
39
|
+
"vr",
|
|
40
|
+
"vt",
|
|
41
|
+
"binind",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
INITIAL_CONDITIONS_COLUMNS_CMC_BINARIES = [
|
|
45
|
+
"index",
|
|
46
|
+
"id1",
|
|
47
|
+
"k1",
|
|
48
|
+
"m1",
|
|
49
|
+
"Reff1",
|
|
50
|
+
"id2",
|
|
51
|
+
"k2",
|
|
52
|
+
"m2",
|
|
53
|
+
"Reff2",
|
|
54
|
+
"a",
|
|
55
|
+
"e",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class InitialCMCTable(pd.DataFrame):
|
|
60
|
+
scaled_to_nbody_units = False
|
|
61
|
+
metallicity = None
|
|
62
|
+
mass_of_cluster = None
|
|
63
|
+
virial_radius = None
|
|
64
|
+
tidal_radius = None
|
|
65
|
+
central_bh = 0.0
|
|
66
|
+
scale_with_central_bh = False
|
|
67
|
+
|
|
68
|
+
def ScaleCentralBHMass(self, Mtotal):
|
|
69
|
+
"""Rescale the central BH mass; needed since this is a class attribute
|
|
70
|
+
Parameters
|
|
71
|
+
----------
|
|
72
|
+
|
|
73
|
+
Mtotal : float
|
|
74
|
+
total mass of the cluster
|
|
75
|
+
"""
|
|
76
|
+
self.central_bh /= Mtotal
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def ScaleToNBodyUnits(cls, Singles, Binaries, virial_radius=1, central_bh=0, scale_with_central_bh=False):
|
|
80
|
+
"""Rescale the single masses, radii, and velocities into N-body units
|
|
81
|
+
i.e. \sum m = M = 1
|
|
82
|
+
Kinetic Energy = 0.25
|
|
83
|
+
Potential Energy = -0.5
|
|
84
|
+
|
|
85
|
+
Note that this is already done for r, vr, and vt from the profile generators.
|
|
86
|
+
However, after the stellar masses are assigned we need to redo it, and
|
|
87
|
+
the stellar radii and seperations need to be converted from RSUN to code units
|
|
88
|
+
|
|
89
|
+
Parameters
|
|
90
|
+
----------
|
|
91
|
+
Singles : DataFrame
|
|
92
|
+
Pandas DataFrame from the InitialCMCSingles function
|
|
93
|
+
Binaries : DataFrame
|
|
94
|
+
Pandas DataFrame from the InitialCMCSingles function
|
|
95
|
+
virial_radius : float
|
|
96
|
+
Virial radius of the cluster in parsec (default 1pc)
|
|
97
|
+
|
|
98
|
+
Returns
|
|
99
|
+
-------
|
|
100
|
+
None: Pandas dataframes are modified in place
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
# Normalize the masses to the total cluster mass
|
|
104
|
+
M_total = sum(Singles["m"])
|
|
105
|
+
Singles["m"] /= M_total
|
|
106
|
+
Binaries["m1"] /= M_total
|
|
107
|
+
Binaries["m2"] /= M_total
|
|
108
|
+
|
|
109
|
+
# Note if there's an central BH, we want to add it
|
|
110
|
+
# to the mass of the already normalized stars (i.e. M_tot = 1+BH)
|
|
111
|
+
Singles.ScaleCentralBHMass(M_total)
|
|
112
|
+
|
|
113
|
+
# Take the radii, and offset by one
|
|
114
|
+
radius = np.array(Singles["r"])
|
|
115
|
+
radius_p1 = np.append(radius[1:], [1e100])
|
|
116
|
+
|
|
117
|
+
# Masses and velocities
|
|
118
|
+
mass = np.array(Singles["m"])
|
|
119
|
+
cumul_mass = np.cumsum(mass)
|
|
120
|
+
vr = np.array(Singles["vr"])
|
|
121
|
+
vt = np.array(Singles["vt"])
|
|
122
|
+
|
|
123
|
+
# Then compute the total kinetic and potential energy
|
|
124
|
+
# There's probably a cleaner way to do the PE (this is a one-line version
|
|
125
|
+
# of the for loop we use in CMC; vectorized and pythonic, but sloppy)
|
|
126
|
+
KE = 0.5 * np.sum(mass * (vr ** 2 + vt ** 2))
|
|
127
|
+
PE = 0.5 * np.sum(
|
|
128
|
+
mass[::-1]
|
|
129
|
+
* np.cumsum((cumul_mass * (1.0 / radius - 1.0 / radius_p1))[::-1])
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# If scaling with central BH, add the potential from it
|
|
133
|
+
if scale_with_central_bh:
|
|
134
|
+
PE += np.sum(Singles.central_bh*mass / radius)
|
|
135
|
+
|
|
136
|
+
# Compute the position and velocity scalings
|
|
137
|
+
rfac = 2 * PE
|
|
138
|
+
vfac = 1.0 / np.sqrt(4 * KE)
|
|
139
|
+
|
|
140
|
+
# Scale the positions and velocities s.t. KE=0.25, PE=-0.5
|
|
141
|
+
Singles["r"] *= rfac
|
|
142
|
+
Singles["vr"] *= vfac
|
|
143
|
+
Singles["vt"] *= vfac
|
|
144
|
+
|
|
145
|
+
# Finally, scale the radii and seperations from BSE into code units
|
|
146
|
+
PARSEC_PER_RSUN = 2.2546101516664447e-08
|
|
147
|
+
DistConv = PARSEC_PER_RSUN / virial_radius
|
|
148
|
+
|
|
149
|
+
Singles["Reff"] *= DistConv
|
|
150
|
+
Binaries["a"] *= DistConv
|
|
151
|
+
Binaries["Reff1"] *= DistConv
|
|
152
|
+
Binaries["Reff2"] *= DistConv
|
|
153
|
+
|
|
154
|
+
Singles.scaled_to_nbody_units = True
|
|
155
|
+
Binaries.scaled_to_nbody_units = True
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def InitialCMCSingles(cls, id_idx, k, m, Reff, r, vr, vt, binind):
|
|
160
|
+
"""Create A Table of CMC Singles
|
|
161
|
+
|
|
162
|
+
Parameters
|
|
163
|
+
----------
|
|
164
|
+
m1 : float
|
|
165
|
+
Primary mass [Msun]
|
|
166
|
+
m2 : float
|
|
167
|
+
Secondary mass [Msun]
|
|
168
|
+
porb : float
|
|
169
|
+
Orbital period [days]
|
|
170
|
+
ecc : float
|
|
171
|
+
Eccentricity
|
|
172
|
+
kstar1 : array
|
|
173
|
+
0-14 Initial stellar type of the larger object;
|
|
174
|
+
main sequence stars are 0 if m < 0.7 Msun and 1 otherwise
|
|
175
|
+
kstar2 : array
|
|
176
|
+
0-14 Initial stellar type of the smaller object;
|
|
177
|
+
main sequence stars are 0 if m < 0.7 Msun and 1 otherwise
|
|
178
|
+
metallicity : float
|
|
179
|
+
Metallicity of the binaries; Z_sun = 0.02
|
|
180
|
+
|
|
181
|
+
**kwargs
|
|
182
|
+
|
|
183
|
+
binfrac : float
|
|
184
|
+
System-specific probability of the primary star being in a binary
|
|
185
|
+
|
|
186
|
+
Returns
|
|
187
|
+
-------
|
|
188
|
+
InitialBinaries : DataFrame
|
|
189
|
+
Single binary initial conditions
|
|
190
|
+
|
|
191
|
+
"""
|
|
192
|
+
bin_dat = cls(
|
|
193
|
+
np.vstack(
|
|
194
|
+
[
|
|
195
|
+
id_idx,
|
|
196
|
+
k,
|
|
197
|
+
m,
|
|
198
|
+
Reff,
|
|
199
|
+
r,
|
|
200
|
+
vr,
|
|
201
|
+
vt,
|
|
202
|
+
binind,
|
|
203
|
+
]
|
|
204
|
+
).T,
|
|
205
|
+
columns=INITIAL_CONDITIONS_COLUMNS_CMC_SINGLES,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
return bin_dat
|
|
209
|
+
|
|
210
|
+
@classmethod
|
|
211
|
+
def InitialCMCBinaries(cls, index, id1, k1, m1, Reff1, id2, k2, m2, Reff2, a, e):
|
|
212
|
+
"""Create A Table of CMC Binaries
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
m1 : float
|
|
217
|
+
Primary mass [Msun]
|
|
218
|
+
m2 : float
|
|
219
|
+
Secondary mass [Msun]
|
|
220
|
+
porb : float
|
|
221
|
+
Orbital period [days]
|
|
222
|
+
ecc : float
|
|
223
|
+
Eccentricity
|
|
224
|
+
kstar1 : array
|
|
225
|
+
0-14 Initial stellar type of the larger object;
|
|
226
|
+
main sequence stars are 0 if m < 0.7 Msun and 1 otherwise
|
|
227
|
+
kstar2 : array
|
|
228
|
+
0-14 Initial stellar type of the smaller object;
|
|
229
|
+
main sequence stars are 0 if m < 0.7 Msun and 1 otherwise
|
|
230
|
+
metallicity : float
|
|
231
|
+
Metallicity of the binaries; Z_sun = 0.02
|
|
232
|
+
|
|
233
|
+
**kwargs
|
|
234
|
+
|
|
235
|
+
binfrac : float
|
|
236
|
+
System-specific probability of the primary star being in a binary
|
|
237
|
+
|
|
238
|
+
Returns
|
|
239
|
+
-------
|
|
240
|
+
InitialBinaries : DataFrame
|
|
241
|
+
Single binary initial conditions
|
|
242
|
+
|
|
243
|
+
"""
|
|
244
|
+
bin_dat = cls(
|
|
245
|
+
np.vstack([index, id1, k1, m1, Reff1, id2, k2, m2, Reff2, a, e]).T,
|
|
246
|
+
columns=INITIAL_CONDITIONS_COLUMNS_CMC_BINARIES,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
return bin_dat
|
|
250
|
+
|
|
251
|
+
@classmethod
|
|
252
|
+
def write(cls, Singles, Binaries, filename="input.hdf5", **kwargs):
|
|
253
|
+
"""Save Singles and Binaries to HDF5 or FITS file
|
|
254
|
+
|
|
255
|
+
Parameters
|
|
256
|
+
----------
|
|
257
|
+
Singles : DataFrame
|
|
258
|
+
Pandas DataFrame from the InitialCMCSingles function
|
|
259
|
+
Binaries : DataFrame
|
|
260
|
+
Pandas DataFrame from the InitialCMCBinaries function
|
|
261
|
+
filename : (str)
|
|
262
|
+
Must end in ".fits" or ".hdf5/h5"
|
|
263
|
+
|
|
264
|
+
Optional Parameteres
|
|
265
|
+
--------------------
|
|
266
|
+
These are automatically set in the Singles df, but can be OVERWRITTEN here
|
|
267
|
+
|
|
268
|
+
virial_radius : `float`
|
|
269
|
+
the initial virial radius of the cluster, in parsecs
|
|
270
|
+
|
|
271
|
+
tidal_radius : `float`
|
|
272
|
+
the initial tidal radius of the cluster, in units of the virial_radius
|
|
273
|
+
|
|
274
|
+
metallicity : `float`
|
|
275
|
+
the stellar metallicity of the cluster
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
Returns
|
|
279
|
+
-------
|
|
280
|
+
None:
|
|
281
|
+
|
|
282
|
+
"""
|
|
283
|
+
|
|
284
|
+
# verify parameters
|
|
285
|
+
if (".hdf5" in filename) or (".h5" in filename):
|
|
286
|
+
savehdf5 = True
|
|
287
|
+
savefits = False
|
|
288
|
+
elif ".fits" in filename:
|
|
289
|
+
savefits = True
|
|
290
|
+
savehdf5 = False
|
|
291
|
+
else:
|
|
292
|
+
raise ValueError(
|
|
293
|
+
"File extension not recognized, valid file types are fits and hdf5"
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
virial_radius = kwargs.pop('virial_radius',Singles.virial_radius)
|
|
297
|
+
tidal_radius = kwargs.pop('tidal_radius',Singles.tidal_radius)
|
|
298
|
+
metallicity = kwargs.pop('metallicity',Singles.metallicity)
|
|
299
|
+
central_bh = kwargs.pop('central_bh',Singles.central_bh)
|
|
300
|
+
scale_with_central_bh = kwargs.pop('scale_with_central_bh',Singles.scale_with_central_bh)
|
|
301
|
+
|
|
302
|
+
# If a user has not already scaled the units of the Singles and Binaries tables,
|
|
303
|
+
# and the attribute mass_of_cluster is None, then
|
|
304
|
+
# we can calculate it now
|
|
305
|
+
if (not Singles.scaled_to_nbody_units) and (Singles.mass_of_cluster is None):
|
|
306
|
+
Singles.mass_of_cluster = np.sum(Singles["m"])
|
|
307
|
+
InitialCMCTable.ScaleToNBodyUnits(
|
|
308
|
+
Singles, Binaries, virial_radius=virial_radius, central_bh=central_bh, scale_with_central_bh=scale_with_central_bh
|
|
309
|
+
)
|
|
310
|
+
elif (not Singles.scaled_to_nbody_units) and (Singles.mass_of_cluster is not None):
|
|
311
|
+
InitialCMCTable.ScaleToNBodyUnits(
|
|
312
|
+
Singles, Binaries, virial_radius=virial_radius, central_bh=central_bh, scale_with_central_bh=scale_with_central_bh
|
|
313
|
+
)
|
|
314
|
+
elif (Singles.scaled_to_nbody_units) and (Singles.mass_of_cluster is None):
|
|
315
|
+
# we cannot get the pre-scaled mass of the cluster
|
|
316
|
+
raise ValueError(
|
|
317
|
+
"In order to save the initial conditions correctly, "
|
|
318
|
+
"you cannot feed in Singles and Binaries which have already been scaled"
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
if Singles.metallicity is None:
|
|
322
|
+
raise ValueError(
|
|
323
|
+
"The user has not supplied a metallicity for the cluster. Please set the Singles.metallicity attribute"
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
# Need to append special rows to the start and end of the Singles table
|
|
327
|
+
singles = pd.DataFrame(
|
|
328
|
+
np.zeros((1, Singles.shape[1])), index=[0], columns=Singles.columns
|
|
329
|
+
)
|
|
330
|
+
singles_bottom = pd.DataFrame(
|
|
331
|
+
np.zeros((1, Singles.shape[1])), index=[0], columns=Singles.columns
|
|
332
|
+
)
|
|
333
|
+
singles = pd.concat([singles, Singles])
|
|
334
|
+
singles = pd.concat([singles, singles_bottom])
|
|
335
|
+
|
|
336
|
+
singles.iloc[-1, singles.columns.get_loc("r")] = 1e40
|
|
337
|
+
singles.iloc[0, singles.columns.get_loc("r")] = 2.2250738585072014e-308
|
|
338
|
+
singles.iloc[0, singles.columns.get_loc("m")] = Singles.central_bh
|
|
339
|
+
|
|
340
|
+
# Add a special row to the end of Bianries table
|
|
341
|
+
binaries = pd.DataFrame(
|
|
342
|
+
np.zeros((1, Binaries.shape[1])), index=[0], columns=Binaries.columns
|
|
343
|
+
)
|
|
344
|
+
binaries = pd.concat([binaries, Binaries])
|
|
345
|
+
|
|
346
|
+
if savehdf5:
|
|
347
|
+
singles.to_hdf(filename, key="CLUS_OBJ_DATA", mode="w")
|
|
348
|
+
binaries.to_hdf(filename, key="CLUS_BINARY_DATA")
|
|
349
|
+
with h5py.File(filename, "a") as f:
|
|
350
|
+
f["CLUS_OBJ_DATA/block0_values"].attrs["EXTNAME"] = "CLUS_OBJ_DATA"
|
|
351
|
+
f["CLUS_OBJ_DATA/block0_values"].attrs["NOBJ"] = int(len(singles)) - 2
|
|
352
|
+
f["CLUS_OBJ_DATA/block0_values"].attrs["NBINARY"] = (
|
|
353
|
+
int(len(binaries)) - 1
|
|
354
|
+
)
|
|
355
|
+
f["CLUS_OBJ_DATA/block0_values"].attrs[
|
|
356
|
+
"MCLUS"
|
|
357
|
+
] = Singles.mass_of_cluster
|
|
358
|
+
f["CLUS_OBJ_DATA/block0_values"].attrs["RVIR"] = virial_radius
|
|
359
|
+
f["CLUS_OBJ_DATA/block0_values"].attrs["RTID"] = tidal_radius
|
|
360
|
+
f["CLUS_OBJ_DATA/block0_values"].attrs["Z"] = metallicity
|
|
361
|
+
|
|
362
|
+
if savefits:
|
|
363
|
+
Singles_fits = Table.from_pandas(singles)
|
|
364
|
+
Binaries_fits = Table.from_pandas(binaries)
|
|
365
|
+
|
|
366
|
+
hdu1 = fits.table_to_hdu(Singles_fits)
|
|
367
|
+
hdu2 = fits.table_to_hdu(Binaries_fits)
|
|
368
|
+
|
|
369
|
+
# create a header
|
|
370
|
+
hdr = fits.Header()
|
|
371
|
+
hdr["COMMENT"] = "CMC Configured Initial Conditions"
|
|
372
|
+
hdr["COMMENT"] = "Produced by COSMIC"
|
|
373
|
+
primary_hdu = fits.PrimaryHDU(header=hdr)
|
|
374
|
+
|
|
375
|
+
hdu1.header["EXTNAME"] = "CLUS_OBJ_DATA"
|
|
376
|
+
hdu1.header["NOBJ"] = int(len(Singles_fits)) - 2
|
|
377
|
+
hdu1.header["NBINARY"] = int(len(Binaries_fits)) - 1
|
|
378
|
+
hdu1.header["MCLUS"] = Singles.mass_of_cluster
|
|
379
|
+
hdu1.header["RVIR"] = virial_radius
|
|
380
|
+
hdu1.header["RTID"] = tidal_radius
|
|
381
|
+
hdu1.header["Z"] = metallicity
|
|
382
|
+
|
|
383
|
+
# put all the HDUs together
|
|
384
|
+
hdul = fits.HDUList([primary_hdu, hdu1, hdu2])
|
|
385
|
+
|
|
386
|
+
# write it out
|
|
387
|
+
hdul.writeto(
|
|
388
|
+
filename,
|
|
389
|
+
overwrite=True,
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
return
|
|
393
|
+
|
|
394
|
+
@classmethod
|
|
395
|
+
def read(cls, filename):
|
|
396
|
+
"""Read Singles and Binaries to HDF5 or FITS file
|
|
397
|
+
|
|
398
|
+
Parameters
|
|
399
|
+
----------
|
|
400
|
+
filename : (str)
|
|
401
|
+
Must end in ".fits" or ".hdf5/h5"
|
|
402
|
+
|
|
403
|
+
Returns
|
|
404
|
+
-------
|
|
405
|
+
Singles : DataFrame
|
|
406
|
+
Pandas DataFrame from the InitialCMCSingles function
|
|
407
|
+
Binaries : DataFrame
|
|
408
|
+
Pandas DataFrame from the InitialCMCBinaries function
|
|
409
|
+
"""
|
|
410
|
+
# verify parameters
|
|
411
|
+
if (".hdf5" in filename) or (".h5" in filename):
|
|
412
|
+
savehdf5 = True
|
|
413
|
+
savefits = False
|
|
414
|
+
elif ".fits" in filename:
|
|
415
|
+
savefits = True
|
|
416
|
+
savehdf5 = False
|
|
417
|
+
else:
|
|
418
|
+
raise ValueError(
|
|
419
|
+
"File extension not recognized, valid file types are fits and hdf5"
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
if savehdf5:
|
|
423
|
+
Singles = pd.read_hdf(filename, "CLUS_OBJ_DATA")
|
|
424
|
+
Binaries = pd.read_hdf(filename, "CLUS_BINARY_DATA")
|
|
425
|
+
elif savefits:
|
|
426
|
+
Singles = cls(Table.read(filename,hdu=1).to_pandas())
|
|
427
|
+
Binaries = cls(Table.read(filename, hdu=2).to_pandas())
|
|
428
|
+
return Singles, Binaries
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
@classmethod
|
|
432
|
+
def sampler(cls, format_, *args, **kwargs):
|
|
433
|
+
"""Fetch a method to generate an initial binary sample
|
|
434
|
+
|
|
435
|
+
Parameters
|
|
436
|
+
----------
|
|
437
|
+
format : str
|
|
438
|
+
the method name; Choose from 'independent' or 'multidim'
|
|
439
|
+
|
|
440
|
+
*args
|
|
441
|
+
the arguments necessary for the registered sample
|
|
442
|
+
method; see help(InitialCMCTable.sampler('independent')
|
|
443
|
+
to see the arguments necessary for the independent sample
|
|
444
|
+
"""
|
|
445
|
+
# standard registered fetch
|
|
446
|
+
from .sampler.sampler import get_sampler
|
|
447
|
+
|
|
448
|
+
sampler = get_sampler(format_, cls)
|
|
449
|
+
return sampler(*args, **kwargs)
|