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.
cosmic/evolve.py ADDED
@@ -0,0 +1,524 @@
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
+ """`evolve`
20
+ """
21
+
22
+ from cosmic import _evolvebin
23
+ from . import utils
24
+ from .sample import initialbinarytable
25
+ from .checkstate import set_checkstates
26
+
27
+ from schwimmbad import MultiPool
28
+
29
+ import numpy as np
30
+ import pandas as pd
31
+ import warnings
32
+ import os
33
+ import sys
34
+ try:
35
+ import multiprocessing
36
+ multiprocessing.set_start_method("fork")
37
+ except RuntimeError:
38
+ pass
39
+
40
+
41
+ __author__ = 'Scott Coughlin <scott.coughlin@ligo.org>'
42
+ __credits__ = ['Katelyn Breivik <katie.breivik@gmail.com>',
43
+ 'Michael Zevin <zevin@northwestern.edu>',
44
+ 'digman.12@osu.edu']
45
+ __all__ = ['Evolve']
46
+
47
+
48
+ BPP_COLUMNS = ['tphys', 'mass_1', 'mass_2', 'kstar_1', 'kstar_2',
49
+ 'sep', 'porb', 'ecc', 'RRLO_1', 'RRLO_2', 'evol_type',
50
+ 'aj_1', 'aj_2', 'tms_1', 'tms_2',
51
+ 'massc_1', 'massc_2', 'rad_1', 'rad_2',
52
+ 'mass0_1', 'mass0_2', 'lum_1', 'lum_2', 'teff_1', 'teff_2',
53
+ 'radc_1', 'radc_2', 'menv_1', 'menv_2', 'renv_1', 'renv_2',
54
+ 'omega_spin_1', 'omega_spin_2', 'B_1', 'B_2', 'bacc_1', 'bacc_2',
55
+ 'tacc_1', 'tacc_2', 'epoch_1', 'epoch_2',
56
+ 'bhspin_1', 'bhspin_2', 'bin_num']
57
+
58
+ BCM_COLUMNS = ['tphys', 'kstar_1', 'mass0_1', 'mass_1', 'lum_1', 'rad_1',
59
+ 'teff_1', 'massc_1', 'radc_1', 'menv_1', 'renv_1', 'epoch_1',
60
+ 'omega_spin_1', 'deltam_1', 'RRLO_1', 'kstar_2', 'mass0_2', 'mass_2',
61
+ 'lum_2', 'rad_2', 'teff_2', 'massc_2', 'radc_2', 'menv_2',
62
+ 'renv_2', 'epoch_2', 'omega_spin_2', 'deltam_2', 'RRLO_2',
63
+ 'porb', 'sep', 'ecc', 'B_1', 'B_2',
64
+ 'SN_1', 'SN_2', 'bin_state', 'merger_type', 'bin_num']
65
+
66
+ KICK_COLUMNS = ['star', 'disrupted', 'natal_kick', 'phi', 'theta', 'mean_anomaly',
67
+ 'delta_vsysx_1', 'delta_vsysy_1', 'delta_vsysz_1', 'vsys_1_total',
68
+ 'delta_vsysx_2', 'delta_vsysy_2', 'delta_vsysz_2', 'vsys_2_total',
69
+ 'delta_theta_total', 'omega', 'randomseed', 'bin_num']
70
+
71
+ # We use the list of column in the initialbinarytable function to initialize
72
+ # the list of columns that we will send to the fortran evolv2 function.
73
+ # we also send this in a specific order so this help ensures that the list that
74
+ # is created at the end has a consistent order
75
+ if sys.version_info.major == 2 and sys.version_info.minor == 7:
76
+ INITIAL_CONDITIONS_PASS_COLUMNS = initialbinarytable.INITIAL_CONDITIONS_COLUMNS[:]
77
+ else:
78
+ INITIAL_CONDITIONS_PASS_COLUMNS = initialbinarytable.INITIAL_CONDITIONS_COLUMNS.copy()
79
+
80
+ INITIAL_CONDITIONS_BSE_COLUMNS = ['neta', 'bwind', 'hewind', 'alpha1', 'lambdaf',
81
+ 'ceflag', 'tflag', 'ifflag', 'wdflag', 'pisn', 'rtmsflag',
82
+ 'bhflag', 'remnantflag', 'grflag', 'bhms_coll_flag', 'wd_mass_lim',
83
+ 'cekickflag', 'cemergeflag', 'cehestarflag',
84
+ 'mxns', 'pts1', 'pts2', 'pts3',
85
+ 'ecsn', 'ecsn_mlow', 'aic', 'ussn', 'sigma', 'sigmadiv',
86
+ 'bhsigmafrac', 'polar_kick_angle',
87
+ 'natal_kick_array', 'qcrit_array',
88
+ 'beta', 'xi', 'acc2', 'epsnov',
89
+ 'eddfac', 'gamma', 'don_lim', 'acc_lim',
90
+ 'bdecayfac', 'bconst', 'ck',
91
+ 'windflag', 'qcflag', 'eddlimflag',
92
+ 'fprimc_array', 'dtp', 'randomseed',
93
+ 'bhspinflag', 'bhspinmag', 'rejuv_fac', 'rejuvflag', 'htpmb',
94
+ 'ST_cr', 'ST_tide', 'rembar_massloss', 'zsun', 'kickflag']
95
+
96
+ INITIAL_CONDITIONS_MISC_COLUMN = ['bin_num']
97
+
98
+ # Add the BSE COLUMSN and MISC COLUMN to the PASS_COLUMNS list
99
+ INITIAL_CONDITIONS_PASS_COLUMNS.extend(INITIAL_CONDITIONS_BSE_COLUMNS)
100
+ INITIAL_CONDITIONS_PASS_COLUMNS.extend(INITIAL_CONDITIONS_MISC_COLUMN)
101
+
102
+ if sys.version_info.major == 2 and sys.version_info.minor == 7:
103
+ INITIAL_BINARY_TABLE_SAVE_COLUMNS = INITIAL_CONDITIONS_PASS_COLUMNS[:]
104
+ else:
105
+ INITIAL_BINARY_TABLE_SAVE_COLUMNS = INITIAL_CONDITIONS_PASS_COLUMNS.copy()
106
+
107
+ for col in ['natal_kick_array', 'qcrit_array', 'fprimc_array']:
108
+ INITIAL_BINARY_TABLE_SAVE_COLUMNS.remove(col)
109
+
110
+ NATAL_KICK_COLUMNS = ['natal_kick',
111
+ 'phi',
112
+ 'theta',
113
+ 'mean_anomaly',
114
+ 'randomseed']
115
+
116
+ FLATTENED_NATAL_KICK_COLUMNS = []
117
+ for sn_idx in range(2):
118
+ for idx, column_name in enumerate(NATAL_KICK_COLUMNS):
119
+ FLATTENED_NATAL_KICK_COLUMNS.append(column_name + '_{0}'.format(sn_idx + 1))
120
+
121
+ QCRIT_COLUMNS = ['qcrit_{0}'.format(kstar) for kstar in range(0, 16)]
122
+ FPRIMC_COLUMNS = ['fprimc_{0}'.format(kstar) for kstar in range(0, 16)]
123
+
124
+ INITIAL_BINARY_TABLE_SAVE_COLUMNS.extend(FLATTENED_NATAL_KICK_COLUMNS)
125
+ INITIAL_BINARY_TABLE_SAVE_COLUMNS.extend(QCRIT_COLUMNS)
126
+ INITIAL_BINARY_TABLE_SAVE_COLUMNS.extend(FPRIMC_COLUMNS)
127
+
128
+ # BSE doesn't need the binary fraction, so just add to columns for saving
129
+ INITIAL_BINARY_TABLE_SAVE_COLUMNS.insert(7, 'binfrac')
130
+
131
+
132
+ class Evolve(object):
133
+ def __init__():
134
+ '''
135
+ initialize Evolve
136
+ '''
137
+
138
+ @classmethod
139
+ def evolve(cls, initialbinarytable, pool=None, **kwargs):
140
+ """After setting a number of initial conditions we evolve the system.
141
+
142
+ Parameters
143
+ ----------
144
+ initialbinarytable : DataFrame
145
+ Initial conditions of the binary
146
+
147
+ **kwargs:
148
+ There are three ways to tell evolve and thus the fortran
149
+ what you want all the flags and other BSE specific
150
+ parameters to be. If you pass both a dictionary of flags and/or a inifile
151
+ and a table with the BSE parameters in the columns,
152
+ the column values will be overwritten by
153
+ what is in the dictionary or ini file.
154
+
155
+ NUMBER 1: PASS A DICTIONARY OF FLAGS
156
+
157
+ BSEDict
158
+
159
+ NUMBER 2: PASS A PANDAS DATA FRAME WITH PARAMS DEFINED AS COLUMNS
160
+
161
+ All you need is the initialbinarytable if the all
162
+ the BSE parameters are defined as columns
163
+
164
+ NUMBER 3: PASS PATH TO A INI FILE WITH THE FLAGS DEFINED
165
+
166
+ params
167
+
168
+ randomseed : `int`, optional, default let numpy choose for you
169
+ If you would like the random seed that the underlying fortran code
170
+ uses to be the same for all of the initial conditions you passed
171
+ then you can send this keyword argument in. It is recommended
172
+ to just let numpy choose a random number as the Fortran random seed
173
+ and then this number will be returned as a column in the
174
+ initial binary table so that you can reproduce the results.
175
+
176
+ nproc : `int`, optional, default: 1
177
+ number of CPUs to use to evolve systems
178
+ in parallel
179
+
180
+ idx : `int`, optional, default: 0
181
+ initial index of the bcm/bpp arrays
182
+
183
+ dtp : `float`, optional: default: tphysf
184
+ timestep size in Myr for bcm output where tphysf
185
+ is total evolution time in Myr
186
+
187
+ n_per_block : `int`, optional, default: -1
188
+ number of systems to evolve in a block with
189
+ _evolve_multi_system, to allow larger multiprocessing
190
+ queues and reduced overhead. If less than 1 use _evolve_single_system
191
+
192
+ Returns
193
+ -------
194
+ output_bpp : DataFrame
195
+ Evolutionary history of each binary
196
+
197
+ output_bcm : DataFrame
198
+ Final state of each binary
199
+
200
+ initialbinarytable : DataFrame
201
+ Initial conditions for each binary
202
+ """
203
+ idx = kwargs.pop('idx', 0)
204
+ nproc = min(kwargs.pop('nproc', 1), len(initialbinarytable))
205
+ n_per_block = kwargs.pop('n_per_block', -1)
206
+
207
+ # There are three ways to tell evolve and thus the fortran
208
+ # what you want all the flags and other BSE specific
209
+ # parameters to be
210
+
211
+ # NUMBER 1: PASS A DICTIONARY OF FLAGS
212
+ BSEDict = kwargs.pop('BSEDict', {})
213
+
214
+ # NUMBER 2: PASS A PANDAS DATA FRAME WITH PARAMS DEFINED AS COLUMNS
215
+
216
+ # All you need is the initialbinarytable with columns,
217
+ # If you pass both a dictionary of flags and/or a inifile
218
+ # and a table with the columns, the column values will be
219
+ # overwritten by what is in the dictionary or ini file
220
+
221
+ # NUMBER 3: PASS PATH TO A INI FILE WITH THE FLAGS DEFINED
222
+ params = kwargs.pop('params', None)
223
+
224
+ if BSEDict and params is not None:
225
+ raise ValueError('Please pass either a dictionary '
226
+ 'of BSE flags or a path to an inifle not both.')
227
+
228
+ if params is not None:
229
+ if not os.path.isfile(params):
230
+ raise ValueError("File does not exist, probably supplied incorrect "
231
+ "path to the inifile.")
232
+ BSEDict, _, _, _, _ = utils.parse_inifile(params)
233
+
234
+ # error check the parameters you are trying to pass to BSE
235
+ # if we sent in a table with the parameter names
236
+ # then we will temporarily create a dictionary
237
+ # in order to verify that the values in the table
238
+ # are valid
239
+ utils.error_check(BSEDict)
240
+
241
+ # check the initial conditions of the system and warn user if
242
+ # anything is weird about them, such as the star starts
243
+ # in Roche Lobe overflow
244
+ utils.check_initial_conditions(initialbinarytable)
245
+
246
+ # assign some columns based on keyword arguments but that
247
+ # can be overwritten by the params or BSEDict
248
+ if 'dtp' not in initialbinarytable.keys():
249
+ initialbinarytable = initialbinarytable.assign(dtp=kwargs.pop('dtp', initialbinarytable['tphysf']))
250
+ if 'randomseed' not in initialbinarytable.keys():
251
+ seed = np.random.randint(np.iinfo(np.int32).min, np.iinfo(np.int32).max, size=len(initialbinarytable))
252
+ initialbinarytable = initialbinarytable.assign(randomseed=kwargs.pop('randomseed', seed))
253
+ if 'bin_num' not in initialbinarytable.keys():
254
+ initialbinarytable = initialbinarytable.assign(bin_num=np.arange(idx, idx + len(initialbinarytable)))
255
+
256
+ for k, v in BSEDict.items():
257
+ if k in initialbinarytable.keys():
258
+ warnings.warn("The value for {0} in initial binary table is being "
259
+ "overwritten by the value of {0} from either the params "
260
+ "file or the BSEDict.".format(k))
261
+ # special columns that need to be handled differently
262
+ if k == 'natal_kick_array':
263
+ assign_natal_kick_array = [BSEDict['natal_kick_array']] * len(initialbinarytable)
264
+ initialbinarytable = initialbinarytable.assign(natal_kick_array=assign_natal_kick_array)
265
+ for idx, column_name in enumerate(NATAL_KICK_COLUMNS):
266
+ for sn_idx in range(2):
267
+ column_name_sn = column_name + '_{0}'.format(sn_idx + 1)
268
+ column_values = pd.Series([BSEDict['natal_kick_array'][sn_idx][idx]] * len(initialbinarytable),
269
+ index=initialbinarytable.index,
270
+ name=column_name_sn)
271
+ kwargs1 = {column_name_sn: column_values}
272
+ initialbinarytable = initialbinarytable.assign(**kwargs1)
273
+ elif k == 'qcrit_array':
274
+ initialbinarytable = initialbinarytable.assign(qcrit_array=[BSEDict['qcrit_array']] * len(initialbinarytable))
275
+ for kstar in range(0, 16):
276
+ columns_values = pd.Series([BSEDict['qcrit_array'][kstar]] * len(initialbinarytable),
277
+ index=initialbinarytable.index,
278
+ name='qcrit_{0}'.format(kstar))
279
+ initialbinarytable.loc[:, 'qcrit_{0}'.format(kstar)] = columns_values
280
+ elif k == 'fprimc_array':
281
+ columns_values = [BSEDict['fprimc_array']] * len(initialbinarytable)
282
+ initialbinarytable = initialbinarytable.assign(fprimc_array=columns_values)
283
+ for kstar in range(0, 16):
284
+ columns_values = pd.Series([BSEDict['fprimc_array'][kstar]] * len(initialbinarytable),
285
+ index=initialbinarytable.index,
286
+ name='fprimc_{0}'.format(kstar))
287
+ initialbinarytable.loc[:, 'fprimc_{0}'.format(kstar)] = columns_values
288
+ else:
289
+ # assigning values this way work for most of the parameters.
290
+ kwargs1 = {k: v}
291
+ initialbinarytable = initialbinarytable.assign(**kwargs1)
292
+
293
+ # Here we perform two checks
294
+ # First, if the BSE parameters are not in the initial binary table
295
+ # and either a dictionary or an inifile was not provided
296
+ # then we need to raise an ValueError and tell the user to provide
297
+ # either a dictionary or an inifile or add more columns
298
+ if not BSEDict:
299
+ if ((not set(INITIAL_BINARY_TABLE_SAVE_COLUMNS).issubset(initialbinarytable.columns)) and
300
+ (not set(INITIAL_CONDITIONS_PASS_COLUMNS).issubset(initialbinarytable.columns))):
301
+ raise ValueError("You are passing BSE parameters as columns in the "
302
+ "initial binary table but not all BSE parameters are defined. "
303
+ "Please pass a BSEDict or a params file or make sure "
304
+ "you have all BSE parameters as columns {0} or {1}.".format(
305
+ INITIAL_BINARY_TABLE_SAVE_COLUMNS, INITIAL_CONDITIONS_PASS_COLUMNS))
306
+
307
+ # If you did not supply the natal kick or qcrit_array or fprimc_array in the BSEdict then we construct
308
+ # it from the initial conditions table
309
+ if ((pd.Series(FLATTENED_NATAL_KICK_COLUMNS).isin(initialbinarytable.keys()).all()) and
310
+ ('natal_kick_array' not in BSEDict)):
311
+ column_values = initialbinarytable[FLATTENED_NATAL_KICK_COLUMNS].values.reshape(-1,
312
+ 2,
313
+ len(NATAL_KICK_COLUMNS)).tolist()
314
+ initialbinarytable = initialbinarytable.assign(natal_kick_array=column_values)
315
+
316
+ if (pd.Series(QCRIT_COLUMNS).isin(initialbinarytable.keys()).all()) and ('qcrit_array' not in BSEDict):
317
+ initialbinarytable = initialbinarytable.assign(qcrit_array=initialbinarytable[QCRIT_COLUMNS].values.tolist())
318
+
319
+ if (pd.Series(FPRIMC_COLUMNS).isin(initialbinarytable.keys()).all()) and ('fprimc_array' not in BSEDict):
320
+ initialbinarytable = initialbinarytable.assign(fprimc_array=initialbinarytable[FPRIMC_COLUMNS].values.tolist())
321
+
322
+ # need to ensure that the order of parameters that we pass to BSE
323
+ # is correct
324
+ initial_conditions = initialbinarytable[INITIAL_CONDITIONS_PASS_COLUMNS].to_dict('records')
325
+
326
+ # we use different columns to save the BSE parameters because some
327
+ # of the parameters are list/arrays which we instead save as
328
+ # individual values because it makes saving to HDF5 easier/more efficient.
329
+ initialbinarytable = initialbinarytable[INITIAL_BINARY_TABLE_SAVE_COLUMNS]
330
+
331
+ # Allow a user to specify a custom time step sampling for certain parts of the evolution
332
+ timestep_conditions = kwargs.pop('timestep_conditions', [])
333
+ set_checkstates(timestep_conditions=timestep_conditions)
334
+
335
+ # check if a pool was passed
336
+ if pool is None:
337
+ with MultiPool(processes=nproc) as pool:
338
+ # evolve systems
339
+ if n_per_block > 0:
340
+ initial_conditions = np.asarray(initial_conditions)
341
+ n_tot = initial_conditions.shape[0]
342
+ initial_conditions_blocked = []
343
+ itr_block = 0
344
+ while itr_block < n_tot:
345
+ itr_next = np.min([n_tot, itr_block+n_per_block])
346
+ initial_conditions_blocked.append(initial_conditions[itr_block:itr_next])
347
+ itr_block = itr_next
348
+ output = list(pool.map(_evolve_multi_system, initial_conditions_blocked))
349
+ else:
350
+ output = list(pool.map(_evolve_single_system, initial_conditions))
351
+ else:
352
+ # evolve systems
353
+ if n_per_block > 0:
354
+ initial_conditions = np.asarray(initial_conditions)
355
+ n_tot = initial_conditions.shape[0]
356
+ initial_conditions_blocked = []
357
+ itr_block = 0
358
+ while itr_block < n_tot:
359
+ itr_next = np.min([n_tot, itr_block+n_per_block])
360
+ initial_conditions_blocked.append(initial_conditions[itr_block:itr_next])
361
+ itr_block = itr_next
362
+ output = list(pool.map(_evolve_multi_system, initial_conditions_blocked))
363
+ else:
364
+ output = list(pool.map(_evolve_single_system, initial_conditions))
365
+
366
+ output = np.array(output, dtype=object)
367
+ bpp_arrays = np.vstack(output[:, 1])
368
+ bcm_arrays = np.vstack(output[:, 2])
369
+ kick_info_arrays = np.vstack(output[:, 3])
370
+
371
+ natal_kick_arrays = np.vstack(output[:, 4])
372
+ natal_kick_arrays = natal_kick_arrays.reshape(-1, 1, len(FLATTENED_NATAL_KICK_COLUMNS))
373
+ for idx, column in enumerate(FLATTENED_NATAL_KICK_COLUMNS):
374
+ # assigning values this way work for most of the parameters.
375
+ kwargs1 = {column: natal_kick_arrays[:, :, idx]}
376
+ initialbinarytable = initialbinarytable.assign(**kwargs1)
377
+
378
+ kick_info = pd.DataFrame(kick_info_arrays,
379
+ columns=KICK_COLUMNS,
380
+ index=kick_info_arrays[:, -1].astype(int))
381
+
382
+ bpp = pd.DataFrame(bpp_arrays,
383
+ columns=BPP_COLUMNS,
384
+ index=bpp_arrays[:, -1].astype(int))
385
+
386
+ bcm = pd.DataFrame(bcm_arrays,
387
+ columns=BCM_COLUMNS,
388
+ index=bcm_arrays[:, -1].astype(int))
389
+
390
+ bcm.merger_type = bcm.merger_type.astype(int).astype(str).apply(lambda x: x.zfill(4))
391
+ bcm.bin_state = bcm.bin_state.astype(int)
392
+ bpp.bin_num = bpp.bin_num.astype(int)
393
+ bcm.bin_num = bcm.bin_num.astype(int)
394
+
395
+ return bpp, bcm, initialbinarytable, kick_info
396
+
397
+
398
+ def _evolve_single_system(f):
399
+ try:
400
+ f["kick_info"] = np.zeros((2, len(KICK_COLUMNS)-1))
401
+ # determine if we already have a compact object, if yes than one SN has already occured
402
+ if (f["kstar_1"] in range(10, 15)) or (f["kstar_2"] in range(10, 15)):
403
+ f["kick_info"][0, 0] = 1
404
+ # kstar, mass, orbital period (days), eccentricity, metaliccity, evolution time (millions of years)
405
+ _evolvebin.windvars.neta = f["neta"]
406
+ _evolvebin.windvars.bwind = f["bwind"]
407
+ _evolvebin.windvars.hewind = f["hewind"]
408
+ _evolvebin.cevars.alpha1 = f["alpha1"]
409
+ _evolvebin.cevars.lambdaf = f["lambdaf"]
410
+ _evolvebin.ceflags.ceflag = f["ceflag"]
411
+ _evolvebin.flags.tflag = f["tflag"]
412
+ _evolvebin.flags.ifflag = f["ifflag"]
413
+ _evolvebin.flags.wdflag = f["wdflag"]
414
+ _evolvebin.flags.rtmsflag = f["rtmsflag"]
415
+ _evolvebin.snvars.pisn = f["pisn"]
416
+ _evolvebin.flags.bhflag = f["bhflag"]
417
+ _evolvebin.flags.remnantflag = f["remnantflag"]
418
+ _evolvebin.ceflags.cekickflag = f["cekickflag"]
419
+ _evolvebin.ceflags.cemergeflag = f["cemergeflag"]
420
+ _evolvebin.ceflags.cehestarflag = f["cehestarflag"]
421
+ _evolvebin.flags.grflag = f["grflag"]
422
+ _evolvebin.flags.bhms_coll_flag = f["bhms_coll_flag"]
423
+ _evolvebin.flags.wd_mass_lim = f["wd_mass_lim"]
424
+ _evolvebin.snvars.mxns = f["mxns"]
425
+ _evolvebin.points.pts1 = f["pts1"]
426
+ _evolvebin.points.pts2 = f["pts2"]
427
+ _evolvebin.points.pts3 = f["pts3"]
428
+ _evolvebin.snvars.ecsn = f["ecsn"]
429
+ _evolvebin.snvars.ecsn_mlow = f["ecsn_mlow"]
430
+ _evolvebin.flags.aic = f["aic"]
431
+ _evolvebin.ceflags.ussn = f["ussn"]
432
+ _evolvebin.snvars.sigma = f["sigma"]
433
+ _evolvebin.snvars.sigmadiv = f["sigmadiv"]
434
+ _evolvebin.snvars.bhsigmafrac = f["bhsigmafrac"]
435
+ _evolvebin.snvars.polar_kick_angle = f["polar_kick_angle"]
436
+ _evolvebin.snvars.natal_kick_array = f["natal_kick_array"]
437
+ _evolvebin.cevars.qcrit_array = f["qcrit_array"]
438
+ _evolvebin.mtvars.don_lim = f["don_lim"]
439
+ _evolvebin.mtvars.acc_lim = f["acc_lim"]
440
+ _evolvebin.windvars.beta = f["beta"]
441
+ _evolvebin.windvars.xi = f["xi"]
442
+ _evolvebin.windvars.acc2 = f["acc2"]
443
+ _evolvebin.windvars.epsnov = f["epsnov"]
444
+ _evolvebin.windvars.eddfac = f["eddfac"]
445
+ _evolvebin.windvars.gamma = f["gamma"]
446
+ _evolvebin.flags.bdecayfac = f["bdecayfac"]
447
+ _evolvebin.magvars.bconst = f["bconst"]
448
+ _evolvebin.magvars.ck = f["ck"]
449
+ _evolvebin.flags.windflag = f["windflag"]
450
+ _evolvebin.flags.qcflag = f["qcflag"]
451
+ _evolvebin.flags.eddlimflag = f["eddlimflag"]
452
+ _evolvebin.tidalvars.fprimc_array = f["fprimc_array"]
453
+ _evolvebin.rand1.idum1 = f["randomseed"]
454
+ _evolvebin.flags.bhspinflag = f["bhspinflag"]
455
+ _evolvebin.snvars.bhspinmag = f["bhspinmag"]
456
+ _evolvebin.mixvars.rejuv_fac = f["rejuv_fac"]
457
+ _evolvebin.flags.rejuvflag = f["rejuvflag"]
458
+ _evolvebin.flags.htpmb = f["htpmb"]
459
+ _evolvebin.flags.st_cr = f["ST_cr"]
460
+ _evolvebin.flags.st_tide = f["ST_tide"]
461
+ _evolvebin.snvars.rembar_massloss = f["rembar_massloss"]
462
+ _evolvebin.metvars.zsun = f["zsun"]
463
+ _evolvebin.snvars.kickflag = f["kickflag"]
464
+ _evolvebin.cmcpass.using_cmc = 0
465
+
466
+ [bpp_index, bcm_index, kick_info] = _evolvebin.evolv2([f["kstar_1"], f["kstar_2"]],
467
+ [f["mass_1"], f["mass_2"]],
468
+ f["porb"], f["ecc"], f["metallicity"], f["tphysf"], f["dtp"],
469
+ [f["mass0_1"], f["mass0_2"]],
470
+ [f["rad_1"], f["rad_2"]],
471
+ [f["lum_1"], f["lum_2"]],
472
+ [f["massc_1"], f["massc_2"]],
473
+ [f["radc_1"], f["radc_2"]],
474
+ [f["menv_1"], f["menv_2"]],
475
+ [f["renv_1"], f["renv_2"]],
476
+ [f["omega_spin_1"], f["omega_spin_2"]],
477
+ [f["B_1"], f["B_2"]],
478
+ [f["bacc_1"], f["bacc_2"]],
479
+ [f["tacc_1"], f["tacc_2"]],
480
+ [f["epoch_1"], f["epoch_2"]],
481
+ [f["tms_1"], f["tms_2"]],
482
+ [f["bhspin_1"], f["bhspin_2"]],
483
+ f["tphys"],
484
+ np.zeros(20),
485
+ np.zeros(20),
486
+ f["kick_info"])
487
+ bcm = _evolvebin.binary.bcm[:bcm_index].copy()
488
+ bpp = _evolvebin.binary.bpp[:bpp_index].copy()
489
+ _evolvebin.binary.bpp[:bpp_index] = np.zeros(bpp.shape)
490
+ _evolvebin.binary.bcm[:bcm_index] = np.zeros(bcm.shape)
491
+
492
+ bpp = np.hstack((bpp, np.ones((bpp.shape[0], 1))*f["bin_num"]))
493
+ bcm = np.hstack((bcm, np.ones((bcm.shape[0], 1))*f["bin_num"]))
494
+ kick_info = np.hstack((kick_info, np.ones((kick_info.shape[0], 1))*f["bin_num"]))
495
+
496
+ return f, bpp, bcm, kick_info, _evolvebin.snvars.natal_kick_array.copy()
497
+
498
+ except Exception as e:
499
+ print(e)
500
+ raise
501
+
502
+
503
+ def _evolve_multi_system(f):
504
+ try:
505
+ res_bcm = np.zeros(f.shape[0], dtype=object)
506
+ res_bpp = np.zeros(f.shape[0], dtype=object)
507
+ res_kick_info = np.zeros(f.shape[0], dtype=object)
508
+ res_natal_kick_array = np.zeros(f.shape[0], dtype=object)
509
+ for i in range(0, f.shape[0]):
510
+
511
+ # call evolve single system
512
+ _, bpp, bcm, kick_info, _ = _evolve_single_system(f[i])
513
+
514
+ # add results to pre-allocated list
515
+ res_bpp[i] = bpp
516
+ res_bcm[i] = bcm
517
+ res_kick_info[i] = kick_info
518
+ res_natal_kick_array[i] = _evolvebin.snvars.natal_kick_array
519
+
520
+ return f, np.vstack(res_bpp), np.vstack(res_bcm), np.vstack(res_kick_info), np.vstack(res_natal_kick_array)
521
+
522
+ except Exception as e:
523
+ print(e)
524
+ raise