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,254 @@
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
+
26
+ import pandas as pd
27
+
28
+ __author__ = "Katelyn Breivik <katie.breivik@gmail.com>"
29
+ __credits__ = "Scott Coughlin <scott.coughlin@ligo.org>"
30
+ __all__ = ["InitialBinaryTable"]
31
+
32
+
33
+ G = 6.67384 * math.pow(10, -11.0)
34
+ c = 2.99792458 * math.pow(10, 8.0)
35
+ parsec = 3.08567758 * math.pow(10, 16)
36
+ Rsun = 6.955 * math.pow(10, 8)
37
+ Msun = 1.9891 * math.pow(10, 30)
38
+ day = 86400.0
39
+ rsun_in_au = 215.0954
40
+ day_in_year = 365.242
41
+ sec_in_day = 86400.0
42
+ sec_in_hour = 3600.0
43
+ hrs_in_day = 24.0
44
+ sec_in_year = 3.15569 * 10 ** 7.0
45
+ Tobs = 3.15569 * 10 ** 7.0
46
+ geo_mass = G / c ** 2
47
+
48
+ INITIAL_CONDITIONS_COLUMNS = []
49
+
50
+ INITIAL_CONDITIONS_COLUMNS_CORE = [
51
+ "kstar_1",
52
+ "kstar_2",
53
+ "mass_1",
54
+ "mass_2",
55
+ "porb",
56
+ "ecc",
57
+ "metallicity",
58
+ "tphysf",
59
+ ]
60
+
61
+ INITIAL_CONDITIONS_COLUMNS_EXTRAS = [
62
+ "mass0_1",
63
+ "mass0_2",
64
+ "rad_1",
65
+ "rad_2",
66
+ "lum_1",
67
+ "lum_2",
68
+ "massc_1",
69
+ "massc_2",
70
+ "radc_1",
71
+ "radc_2",
72
+ "menv_1",
73
+ "menv_2",
74
+ "renv_1",
75
+ "renv_2",
76
+ "omega_spin_1",
77
+ "omega_spin_2",
78
+ "B_1",
79
+ "B_2",
80
+ "bacc_1",
81
+ "bacc_2",
82
+ "tacc_1",
83
+ "tacc_2",
84
+ "epoch_1",
85
+ "epoch_2",
86
+ "tms_1",
87
+ "tms_2",
88
+ "bhspin_1",
89
+ "bhspin_2",
90
+ "tphys",
91
+ ]
92
+
93
+ INITIAL_CONDITIONS_COLUMNS.extend(INITIAL_CONDITIONS_COLUMNS_CORE)
94
+ INITIAL_CONDITIONS_COLUMNS.extend(INITIAL_CONDITIONS_COLUMNS_EXTRAS)
95
+
96
+ INITIAL_CONDITIONS_MISC_COLUMNS = ["binfrac"]
97
+
98
+ if sys.version_info.major == 2 and sys.version_info.minor == 7:
99
+ INITIAL_CONDITIONS_COLUMNS_ALL = INITIAL_CONDITIONS_COLUMNS[:]
100
+ else:
101
+ INITIAL_CONDITIONS_COLUMNS_ALL = INITIAL_CONDITIONS_COLUMNS.copy()
102
+
103
+ INITIAL_CONDITIONS_COLUMNS_ALL.extend(INITIAL_CONDITIONS_MISC_COLUMNS)
104
+
105
+
106
+ class InitialBinaryTable:
107
+ @classmethod
108
+ def InitialBinaries(
109
+ cls, m1, m2, porb, ecc, tphysf, kstar1, kstar2, metallicity, **kwargs
110
+ ):
111
+ """Create single binary
112
+
113
+ Parameters
114
+ ----------
115
+ m1 : float
116
+ Primary mass [Msun]
117
+ m2 : float
118
+ Secondary mass [Msun]
119
+ porb : float
120
+ Orbital period [days]
121
+ ecc : float
122
+ Eccentricity
123
+ tphysf : float
124
+ Time to evolve the binary [Myr]
125
+ kstar1 : array
126
+ 0-14 Initial stellar type of the larger object;
127
+ main sequence stars are 0 if m < 0.7 Msun and 1 otherwise
128
+ kstar2 : array
129
+ 0-14 Initial stellar type of the smaller object;
130
+ main sequence stars are 0 if m < 0.7 Msun and 1 otherwise
131
+ metallicity : float
132
+ Metallicity of the binaries; Z_sun = 0.02
133
+
134
+ **kwargs
135
+
136
+ binfrac : float
137
+ System-specific probability of the primary star being in a binary
138
+
139
+ mass0_1,mass0_2,rad1,rad2,lumin1,lumin2,
140
+ massc1,massc2,radc1,radc2,menv1,menv2,renv1,renv2,
141
+ ospin1,ospin2,b_0_1,b_0_2,bacc1,bacc2,
142
+ tacc1,tacc2,epoch1,epoch2,tms1,tms2
143
+ bhspin1,bhspin2
144
+
145
+ Returns
146
+ -------
147
+ InitialBinaries : DataFrame
148
+ Single binary initial conditions
149
+
150
+ """
151
+ # System-specific probability of the primary star being in a binary
152
+ binfrac = kwargs.pop("binfrac", np.ones(np.array(m1).size))
153
+
154
+ # The following are in general not needed for COSMIC, however
155
+ # if you wish to start a binary at a very specific point in its evolution
156
+ # these kwarg arguments can help you do this.
157
+ # For instance the Globular Cluster code CMC requires this behavior.
158
+ mass0_1 = kwargs.pop("mass0_1", m1)
159
+ mass0_2 = kwargs.pop("mass0_2", m2)
160
+ rad1 = kwargs.pop("rad_1", np.zeros(np.array(m1).size))
161
+ rad2 = kwargs.pop("rad_2", np.zeros(np.array(m1).size))
162
+ lumin1 = kwargs.pop("lumin_1", np.zeros(np.array(m1).size))
163
+ lumin2 = kwargs.pop("lumin_2", np.zeros(np.array(m1).size))
164
+ massc1 = kwargs.pop("massc_1", np.zeros(np.array(m1).size))
165
+ massc2 = kwargs.pop("massc_2", np.zeros(np.array(m1).size))
166
+ radc1 = kwargs.pop("radc_1", np.zeros(np.array(m1).size))
167
+ radc2 = kwargs.pop("radc_2", np.zeros(np.array(m1).size))
168
+ menv1 = kwargs.pop("menv_1", np.zeros(np.array(m1).size))
169
+ menv2 = kwargs.pop("menv_2", np.zeros(np.array(m1).size))
170
+ renv1 = kwargs.pop("renv_1", np.zeros(np.array(m1).size))
171
+ renv2 = kwargs.pop("renv_2", np.zeros(np.array(m1).size))
172
+ ospin1 = kwargs.pop("ospin_1", np.zeros(np.array(m1).size))
173
+ ospin2 = kwargs.pop("ospin_2", np.zeros(np.array(m1).size))
174
+ b_0_1 = kwargs.pop("B_1", np.zeros(np.array(m1).size))
175
+ b_0_2 = kwargs.pop("B_2", np.zeros(np.array(m1).size))
176
+ bacc1 = kwargs.pop("bacc_1", np.zeros(np.array(m1).size))
177
+ bacc2 = kwargs.pop("bacc_2", np.zeros(np.array(m1).size))
178
+ tacc1 = kwargs.pop("tacc_1", np.zeros(np.array(m1).size))
179
+ tacc2 = kwargs.pop("tacc_2", np.zeros(np.array(m1).size))
180
+ epoch1 = kwargs.pop("epoch_1", np.zeros(np.array(m1).size))
181
+ epoch2 = kwargs.pop("epoch_2", np.zeros(np.array(m1).size))
182
+ tms1 = kwargs.pop("tms_1", np.zeros(np.array(m1).size))
183
+ tms2 = kwargs.pop("tms_2", np.zeros(np.array(m1).size))
184
+ bhspin1 = kwargs.pop("bhspin_1", np.zeros(np.array(m1).size))
185
+ bhspin2 = kwargs.pop("bhspin_2", np.zeros(np.array(m1).size))
186
+ tphys = kwargs.pop("tphys", np.zeros(np.array(m1).size))
187
+
188
+ bin_dat = pd.DataFrame(
189
+ np.vstack(
190
+ [
191
+ kstar1,
192
+ kstar2,
193
+ m1,
194
+ m2,
195
+ porb,
196
+ ecc,
197
+ metallicity,
198
+ tphysf,
199
+ mass0_1,
200
+ mass0_2,
201
+ rad1,
202
+ rad2,
203
+ lumin1,
204
+ lumin2,
205
+ massc1,
206
+ massc2,
207
+ radc1,
208
+ radc2,
209
+ menv1,
210
+ menv2,
211
+ renv1,
212
+ renv2,
213
+ ospin1,
214
+ ospin2,
215
+ b_0_1,
216
+ b_0_2,
217
+ bacc1,
218
+ bacc2,
219
+ tacc1,
220
+ tacc2,
221
+ epoch1,
222
+ epoch2,
223
+ tms1,
224
+ tms2,
225
+ bhspin1,
226
+ bhspin2,
227
+ tphys,
228
+ binfrac,
229
+ ]
230
+ ).T,
231
+ columns=INITIAL_CONDITIONS_COLUMNS_ALL,
232
+ )
233
+
234
+ return bin_dat
235
+
236
+ @classmethod
237
+ def sampler(cls, format_, *args, **kwargs):
238
+ """Fetch a method to generate an initial binary sample
239
+
240
+ Parameters
241
+ ----------
242
+ format : str
243
+ the method name; Choose from 'independent' or 'multidim'
244
+
245
+ *args
246
+ the arguments necessary for the registered sample
247
+ method; see help(InitialBinaryTable.sampler('independent')
248
+ to see the arguments necessary for the independent sample
249
+ """
250
+ # standard registered fetch
251
+ from .sampler.sampler import get_sampler
252
+
253
+ sampler = get_sampler(format_, cls)
254
+ return sampler(*args, **kwargs)
@@ -0,0 +1,448 @@
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"]) + central_bh
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
+ singles["r"].iloc[-1] = 1e40
336
+ singles["r"].iloc[0] = 2.2250738585072014e-308
337
+ singles["m"].iloc[0] = Singles.central_bh
338
+
339
+ # Add a special row to the end of Bianries table
340
+ binaries = pd.DataFrame(
341
+ np.zeros((1, Binaries.shape[1])), index=[0], columns=Binaries.columns
342
+ )
343
+ binaries = pd.concat([binaries, Binaries])
344
+
345
+ if savehdf5:
346
+ singles.to_hdf(filename, key="CLUS_OBJ_DATA", mode="w")
347
+ binaries.to_hdf(filename, key="CLUS_BINARY_DATA")
348
+ with h5py.File(filename, "a") as f:
349
+ f["CLUS_OBJ_DATA/block0_values"].attrs["EXTNAME"] = "CLUS_OBJ_DATA"
350
+ f["CLUS_OBJ_DATA/block0_values"].attrs["NOBJ"] = int(len(singles)) - 2
351
+ f["CLUS_OBJ_DATA/block0_values"].attrs["NBINARY"] = (
352
+ int(len(binaries)) - 1
353
+ )
354
+ f["CLUS_OBJ_DATA/block0_values"].attrs[
355
+ "MCLUS"
356
+ ] = Singles.mass_of_cluster
357
+ f["CLUS_OBJ_DATA/block0_values"].attrs["RVIR"] = virial_radius
358
+ f["CLUS_OBJ_DATA/block0_values"].attrs["RTID"] = tidal_radius
359
+ f["CLUS_OBJ_DATA/block0_values"].attrs["Z"] = metallicity
360
+
361
+ if savefits:
362
+ Singles_fits = Table.from_pandas(singles)
363
+ Binaries_fits = Table.from_pandas(binaries)
364
+
365
+ hdu1 = fits.table_to_hdu(Singles_fits)
366
+ hdu2 = fits.table_to_hdu(Binaries_fits)
367
+
368
+ # create a header
369
+ hdr = fits.Header()
370
+ hdr["COMMENT"] = "CMC Configured Initial Conditions"
371
+ hdr["COMMENT"] = "Produced by COSMIC"
372
+ primary_hdu = fits.PrimaryHDU(header=hdr)
373
+
374
+ hdu1.header["EXTNAME"] = "CLUS_OBJ_DATA"
375
+ hdu1.header["NOBJ"] = int(len(Singles_fits)) - 2
376
+ hdu1.header["NBINARY"] = int(len(Binaries_fits)) - 1
377
+ hdu1.header["MCLUS"] = Singles.mass_of_cluster
378
+ hdu1.header["RVIR"] = virial_radius
379
+ hdu1.header["RTID"] = tidal_radius
380
+ hdu1.header["Z"] = metallicity
381
+
382
+ # put all the HDUs together
383
+ hdul = fits.HDUList([primary_hdu, hdu1, hdu2])
384
+
385
+ # write it out
386
+ hdul.writeto(
387
+ filename,
388
+ overwrite=True,
389
+ )
390
+
391
+ return
392
+
393
+ @classmethod
394
+ def read(cls, filename):
395
+ """Read Singles and Binaries to HDF5 or FITS file
396
+
397
+ Parameters
398
+ ----------
399
+ filename : (str)
400
+ Must end in ".fits" or ".hdf5/h5"
401
+
402
+ Returns
403
+ -------
404
+ Singles : DataFrame
405
+ Pandas DataFrame from the InitialCMCSingles function
406
+ Binaries : DataFrame
407
+ Pandas DataFrame from the InitialCMCBinaries function
408
+ """
409
+ # verify parameters
410
+ if (".hdf5" in filename) or (".h5" in filename):
411
+ savehdf5 = True
412
+ savefits = False
413
+ elif ".fits" in filename:
414
+ savefits = True
415
+ savehdf5 = False
416
+ else:
417
+ raise ValueError(
418
+ "File extension not recognized, valid file types are fits and hdf5"
419
+ )
420
+
421
+ if savehdf5:
422
+ Singles = pd.read_hdf(filename, "CLUS_OBJ_DATA")
423
+ Binaries = pd.read_hdf(filename, "CLUS_BINARY_DATA")
424
+ elif savefits:
425
+ Singles = cls(Table.read(filename,hdu=1).to_pandas())
426
+ Binaries = cls(Table.read(filename, hdu=2).to_pandas())
427
+ return Singles, Binaries
428
+
429
+
430
+ @classmethod
431
+ def sampler(cls, format_, *args, **kwargs):
432
+ """Fetch a method to generate an initial binary sample
433
+
434
+ Parameters
435
+ ----------
436
+ format : str
437
+ the method name; Choose from 'independent' or 'multidim'
438
+
439
+ *args
440
+ the arguments necessary for the registered sample
441
+ method; see help(InitialCMCTable.sampler('independent')
442
+ to see the arguments necessary for the independent sample
443
+ """
444
+ # standard registered fetch
445
+ from .sampler.sampler import get_sampler
446
+
447
+ sampler = get_sampler(format_, cls)
448
+ return sampler(*args, **kwargs)