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.
@@ -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)