Re-running binaries

COSMIC allows you to re-run a binary from a previous evolution from a COSMIC generated initC table. This can also allow you to modify binary physics assumptions to see how they affect the later evolution of a binary with otherwise identical initial conditions.

Re-run an identical binary

You can re-run a binary from a COSMIC generated initC table, that’s all you need! This means you can send your favourite initial conditions around to your friends and they will be able to reproduce the exact same evolution (assuming they use the same COSMIC version).

First, let’s evolve a binary and save the initC table.

In [1]: from cosmic.sample.initialbinarytable import InitialBinaryTable

In [2]: from cosmic.evolve import Evolve

In [3]: BSEDict = {'xi': 1.0, 'bhflag': 1, 'neta': 0.5, 'windflag': 3, 'wdflag': 1, 'alpha1': 1.0,
   ...:            'pts1': 0.001, 'pts3': 0.02, 'pts2': 0.01, 'epsnov': 0.001, 'hewind': 0.5, 'ck': 1000,
   ...:            'bwind': 0.0, 'lambdaf': 0.0, 'mxns': 3.0, 'beta': -1.0, 'tflag': 1, 'acc2': 1.5,
   ...:            'grflag' : 1, 'remnantflag': 4, 'ceflag': 0, 'eddfac': 1.0, 'ifflag': 0,
   ...:            'bconst': 3000, 'sigma': 265.0, 'gamma': -2.0, 'pisn': 45.0,
   ...:            'natal_kick_array' : [[-100.0,-100.0,-100.0,-100.0,0.0], [-100.0,-100.0,-100.0,-100.0,0.0]],
   ...:            'bhsigmafrac' : 1.0, 'polar_kick_angle' : 90,
   ...:            'qcrit_array' : [0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0],
   ...:            'cekickflag' : 2, 'cehestarflag' : 0, 'cemergeflag' : 0, 'ecsn' : 2.25,
   ...:            'ecsn_mlow' : 1.6, 'aic' : 1, 'ussn' : 0, 'sigmadiv' :-20.0, 'qcflag' : 1, 'eddlimflag' : 0,
   ...:            'fprimc_array' : [2.0/21.0,2.0/21.0,2.0/21.0,2.0/21.0,2.0/21.0,2.0/21.0,2.0/21.0,2.0/21.0,2.0/21.0,2.0/21.0,2.0/21.0,2.0/21.0,2.0/21.0,2.0/21.0,2.0/21.0,2.0/21.0],
   ...:            'bhspinflag' : 0, 'bhspinmag' : 0.0, 'rejuv_fac' : 1.0, 'rejuvflag' : 0, 'htpmb' : 1,
   ...:            'ST_cr' : 1, 'ST_tide' : 1, 'bdecayfac' : 1, 'rembar_massloss' : 0.5, 'kickflag' : 1,
   ...:            'zsun' : 0.014, 'bhms_coll_flag' : 0, 'don_lim' : -1, 'acc_lim' : -1, 'rtmsflag' : 0,
   ...:            'wd_mass_lim': 1}
   ...: 

In [4]: binary = InitialBinaryTable.InitialBinaries(
   ...:     m1=20, m2=15, porb=100, ecc=0.1, tphysf=13700.0, kstar1=1, kstar2=1, metallicity=0.02
   ...: )
   ...: 

In [5]: bpp, bcm, initC, kick_info = Evolve.evolve(initialbinarytable=binary, BSEDict=BSEDict)

We can check some of the output for this binary so that we can see it’s identical after we re-run it.

In [6]: print(bpp)
          tphys     mass_1     mass_2  ...  bhspin_1  bhspin_2  bin_num
0      0.000000  20.000000  15.000000  ...       0.0       0.0        0
0      8.299073  18.851403  14.922740  ...       0.0       0.0        0
0      8.306134  18.843319  14.923134  ...       0.0       0.0        0
0      8.309411  18.807419  14.954781  ...       0.0       0.0        0
0      8.309411  18.807419  14.954781  ...       0.0       0.0        0
0      8.309411  27.946501  14.954781  ...       0.0       0.0        0
0      8.312153  27.883063   0.000000  ...       0.0       0.0        0
0      8.701370  10.228449   0.000000  ...       0.0       0.0        0
0      8.982919   7.798860   0.000000  ...       0.0       0.0        0
0      9.019456   7.443100   0.000000  ...       0.0       0.0        0
0      9.019456   3.090176   0.000000  ...       0.0       0.0        0
0  13700.000000   3.090176   0.000000  ...       0.0       0.0        0

[12 rows x 46 columns]

In [7]: print(kick_info)
   star  disrupted  natal_kick  ...  psi_euler    randomseed  bin_num
0   1.0        1.0  256.688837  ...        0.0 -1.592283e+09      0.0
0   0.0        0.0    0.000000  ...        0.0  0.000000e+00      0.0

[2 rows x 19 columns]

Now we can re-run this exact same binary by passing in the initC table we just generated instead of the initial binary table. We also do not need to pass in the BSEDict again, as the physics assumptions are stored in the initC table. In fact, you should not pass in a BSEDict when re-running from an initC since things may get inconsistent (and confusing!).

In [8]: bpp_rerun, bcm_rerun, initC_rerun, kick_info_rerun = Evolve.evolve(initialbinarytable=initC)

In [9]: print(bpp_rerun)
          tphys     mass_1     mass_2  ...  bhspin_1  bhspin_2  bin_num
0      0.000000  20.000000  15.000000  ...       0.0       0.0        0
0      8.299073  18.851403  14.922740  ...       0.0       0.0        0
0      8.306134  18.843319  14.923134  ...       0.0       0.0        0
0      8.309411  18.807419  14.954781  ...       0.0       0.0        0
0      8.309411  18.807419  14.954781  ...       0.0       0.0        0
0      8.309411  27.946501  14.954781  ...       0.0       0.0        0
0      8.312153  27.883063   0.000000  ...       0.0       0.0        0
0      8.701370  10.228449   0.000000  ...       0.0       0.0        0
0      8.982919   7.798860   0.000000  ...       0.0       0.0        0
0      9.019456   7.443100   0.000000  ...       0.0       0.0        0
0      9.019456   3.090176   0.000000  ...       0.0       0.0        0
0  13700.000000   3.090176   0.000000  ...       0.0       0.0        0

[12 rows x 46 columns]

In [10]: print(kick_info_rerun)
   star  disrupted  natal_kick  ...  psi_euler    randomseed  bin_num
0   1.0        1.0  256.688837  ...        0.0 -1.592283e+09      0.0
0   0.0        0.0    0.000000  ...        0.0  0.000000e+00      0.0

[2 rows x 19 columns]

so overall, we can see that these outputs are exactly identical, even down to the kick information.

Warning

When re-running from an initC table, the BSEDict is no longer necessary since the physics assumptions are stored in the initC table. In fact, you should not pass in a BSEDict when re-running from an initC since things may get inconsistent (and confusing!).

Re-run with different physics

You can also re-run a binary from a COSMIC generated initC table but modify the physics assumptions. This allows you to see how different physics assumptions affect the later evolution of a binary with otherwise identical initial conditions.

Let’s re-run the same binary as before, but this time we will modify the common envelope efficiency parameter, alpha1, to be 10 instead of 1.0.

To do this, we modify the initC column corresponding to alpha1 before re-running the binary. We do not pass in a BSEDict this time, since we are modifying the physics assumptions directly in the initC table.

# modify alpha1 in the initC table
In [11]: initC_modified = initC.copy()

In [12]: initC_modified['alpha1'] = 10

# re-run the binary with modified physics
In [13]: bpp_rerun_mod, bcm_rerun_mod, initC_rerun_mod, kick_info_rerun_mod = Evolve.evolve(
   ....:     initialbinarytable=initC_modified
   ....: )
   ....: 

In [14]: print(bpp_rerun_mod)
          tphys     mass_1     mass_2  ...  bhspin_1  bhspin_2  bin_num
0      0.000000  20.000000  15.000000  ...       0.0       0.0        0
0      8.299073  18.851403  14.922740  ...       0.0       0.0        0
0      8.306134  18.843319  14.923134  ...       0.0       0.0        0
0      8.309411  18.807419  14.954781  ...       0.0       0.0        0
0      8.309411  18.807419  14.954781  ...       0.0       0.0        0
0      8.309411   5.136602  14.954781  ...       0.0       0.0        0
0      8.309411   5.136602  14.954781  ...       0.0       0.0        0
0      9.581295   4.086627  14.947479  ...       0.0       0.0        0
0      9.689485   3.902209  14.960042  ...       0.0       0.0        0
0      9.689485   1.406181  14.960042  ...       0.0       0.0        0
0     12.016484   1.406181  14.441141  ...       0.0       0.0        0
0     12.042140   1.406181  14.414958  ...       0.0       0.0        0
0     13.461405   1.406181   8.606520  ...       0.0       0.0        0
0     13.521348   1.406181   4.682423  ...       0.0       0.0        0
0     13.521348   1.406181   1.593143  ...       0.0       0.0        0
0  13700.000000   1.406181   1.593143  ...       0.0       0.0        0

[16 rows x 46 columns]

In [15]: print(kick_info_rerun_mod)
   star  disrupted  natal_kick  ...   psi_euler    randomseed  bin_num
0   1.0        1.0  256.688837  ...  247.930591 -1.592283e+09      0.0
0   2.0        1.0  527.944689  ...    0.000000  1.067690e+09      0.0

[2 rows x 19 columns]

Now as we can see, this output is very different from the original evolution, since the common envelope now proceeds quite differently with the higher efficiency parameter! The system goes from a merging binary that produces a black hole, to an unbound binary that produces two neutron stars.

Ensuring natal kicks account for modified physics

When re-running a binary with modified physics, you may want to ensure that the natal kicks are re-sampled appropriately given the new physics assumptions. For example, if you change the remnant mass prescription, you may want to re-sample the natal kicks since the remnant masses will be different. If you change the natal kick prescription itself, you will definitely want to re-sample the natal kicks.

In order to re-sample the natal kicks when re-running a binary, you need to erase the existing kick information from the initC table before re-running. This is done by setting the kick columns to -100.0, which is the flag value indicating that no kick has been assigned yet.

Let’s try this out by re-running our system with higher alpha, but this time re-sampling the natal kicks as well with a very small sigma value for both regular core-collapse supernovae and electron-capture supernovae.

# modify alpha1 in the initC table
In [16]: initC_modified_kick = initC.copy()

In [17]: initC_modified_kick['alpha1'] = 10

In [18]: initC_modified_kick['sigma'] = 1.0

In [19]: initC_modified_kick['sigmadiv'] = -1.0

In [20]: initC_modified_kick['kickflag'] = 1

# erase existing kick information by setting to -100.0
In [21]: kick_columns = [
   ....:     'natal_kick_1', 'natal_kick_2', 'phi_1', 'phi_2',
   ....:     'theta_1', 'theta_2', 'mean_anomaly_1', 'mean_anomaly_2'
   ....: ]
   ....: 

In [22]: for col in kick_columns:
   ....:     initC_modified_kick[col] = -100.0
   ....: 

# re-run the binary with modified physics and re-sampled kicks
In [23]: bpp_rerun_mod_kick, bcm_rerun_mod_kick, initC_rerun_mod_kick, kick_info_rerun_mod_kick = Evolve.evolve(
   ....:     initialbinarytable=initC_modified_kick,
   ....: )
   ....: 

In [24]: print(bpp_rerun_mod_kick)
          tphys     mass_1     mass_2  ...  bhspin_1  bhspin_2  bin_num
0      0.000000  20.000000  15.000000  ...       0.0       0.0        0
0      8.299073  18.851403  14.922740  ...       0.0       0.0        0
0      8.306134  18.843319  14.923134  ...       0.0       0.0        0
0      8.309411  18.807419  14.954781  ...       0.0       0.0        0
0      8.309411  18.807419  14.954781  ...       0.0       0.0        0
0      8.309411   5.136602  14.954781  ...       0.0       0.0        0
0      8.309411   5.136602  14.954781  ...       0.0       0.0        0
0      9.581295   4.086627  14.947479  ...       0.0       0.0        0
0      9.689485   3.902209  14.960042  ...       0.0       0.0        0
0      9.689485   1.406181  14.960042  ...       0.0       0.0        0
0     12.016484   1.406196  14.441141  ...       0.0       0.0        0
0     12.023027   1.406203  14.438592  ...       0.0       0.0        0
0     12.023027   1.406203  14.438592  ...       0.0       0.0        0
0     12.023027   1.406203   3.554457  ...       0.0       0.0        0
0     12.023027   1.406203   3.554457  ...       0.0       0.0        0
0     13.958374   1.421502   3.088799  ...       0.0       0.0        0
0     14.061607   1.423039   3.042581  ...       0.0       0.0        0
0     14.061607   1.423039   3.042581  ...       0.0       0.0        0
0     14.061607   1.423039   1.816577  ...       0.0       0.0        0
0     14.061607   1.423039   1.277584  ...       0.0       0.0        0
0     14.061607   1.423039   1.277584  ...       0.0       0.0        0
0     89.249977   1.423039   1.277584  ...       0.0       0.0        0
0     89.249977   2.700622   0.000000  ...       0.0       0.0        0
0  13700.000000   2.700622   0.000000  ...       0.0       0.0        0

[24 rows x 46 columns]

In [25]: print(kick_info_rerun_mod_kick)
   star  disrupted  natal_kick  ...   psi_euler    randomseed  bin_num
0   1.0        0.0    1.544394  ... -178.950656 -1.592283e+09      0.0
0   2.0        0.0    1.992244  ... -179.579222  1.067690e+09      0.0

[2 rows x 19 columns]

As one might expect, the binary no longer becomes unbound since the natal kicks are now very small! The system instead produces two neutron stars in a bound system which eventually inspirals due to gravitational wave emission and produces a NS-NS merger.