Debugging POSYDON Binary Population Synthesis πŸžοƒ

If you haven’t done so yet, export the path POSYDON environment variables. Set these parameters in your .bash_profile or .zshrc if you use POSYDON regularly.

[ ]:
%env PATH_TO_POSYDON=/Users/simone/Google Drive/github/POSYDON-public/
%env PATH_TO_POSYDON_DATA=/Volumes/T7/

To debug the evolution of a binary systems you should load the POSYDON BinaryPopulation object in memory and then call the restore and evolve. This will run the evolution of the binary system and display any error traceback. You can debug the code by setting breakpoints in the POSYDON code until you devug the specific binary evolution.

The trick is to do this is not dump the binary population to disk but to keep them in memory. This is done by setting the optimize_ram=False.

[ ]:
from posydon.popsyn.binarypopulation import BinaryPopulation
from posydon.binary_evol.simulationproperties import SimulationProperties
from posydon.binary_evol.flow_chart import flow_chart
from posydon.binary_evol.MESA.step_mesa import CO_HeMS_step, MS_MS_step, CO_HMS_RLO_step, CO_HeMS_RLO_step
from posydon.binary_evol.DT.step_detached import detached_step
from posydon.binary_evol.DT.step_disrupted import DisruptedStep
from posydon.binary_evol.DT.step_merged import MergedStep
from posydon.binary_evol.CE.step_CEE import StepCEE
from posydon.binary_evol.SN.step_SN import StepSN
from posydon.binary_evol.DT.double_CO import DoubleCO
from posydon.binary_evol.step_end import step_end
from posydon.binary_evol.simulationproperties import StepNamesHooks

# STEP CUSTOMISATION
MESA_STEP = dict(
    interpolation_path = None, # found by default
    interpolation_filename = None, # found by default
    interpolation_method = 'nearest_neighbour', # 'nearest_neighbour' 'linear3c_kNN' '1NN_1NN'
    save_initial_conditions = True, # only for interpolation_method='nearest_neighbour'
    track_interpolation = False, # True False
    stop_method = 'stop_at_max_time', # 'stop_at_end' 'stop_at_max_time' 'stop_at_condition'
    stop_star = 'star_1', # only for stop_method='stop_at_condition' 'star_1' 'star_2'
    stop_var_name = None, # only for stop_method='stop_at_condition' str
    stop_value = None, # only for stop_method='stop_at_condition' float
    stop_interpolate = True, # True False
    verbose = False, # True False
)

DETACHED_STEP = dict(
    matching_method = 'minimize', #'minimize' 'root'
    do_wind_loss = True, # True False
    do_tides = True,  # True False
    do_gravitational_radiation = True,  # True False
    do_magnetic_braking = True,  # True False
    do_stellar_evolution_and_spin_from_winds = True,  # True False
    RLO_orbit_at_orbit_with_same_am = False,  # True False
    verbose = False,  # True False
)

DISRUPTED_STEP = dict(
    grid_name_Hrich=None,
    grid_name_strippedHe=None,
    metallicity=None,
    dt=None,
    n_o_steps_history=None,
    matching_method="minimize",
    initial_mass=None,
    rootm=None,
    verbose=False,
    do_wind_loss=True,
    do_tides=True,
    do_gravitational_radiation=True,
    do_magnetic_braking=True,
    magnetic_braking_mode="RVJ83",
    do_stellar_evolution_and_spin_from_winds=True,
    RLO_orbit_at_orbit_with_same_am=False,
    list_for_matching_HMS=None,
    list_for_matching_postMS=None,
    list_for_matching_HeStar=None
)

CE_STEP = dict(
    prescription='alpha-lambda', # 'alpha-lambda'
    common_envelope_efficiency=1.0, # float in (0, inf)
    common_envelope_option_for_lambda='lambda_from_grid_final_values', # (1) 'default_lambda', (2) 'lambda_from_grid_final_values',
                                                  # (3) 'lambda_from_profile_gravitational',
                                                  # (4) 'lambda_from_profile_gravitational_plus_internal',
                                                  # (5) 'lambda_from_profile_gravitational_plus_internal_minus_recombination'
    common_envelope_lambda_default=0.5, # float in (0, inf) used only for option (1)
    common_envelope_option_for_HG_star="optimistic", # 'optimistic', 'pessimistic'
    common_envelope_alpha_thermal=1.0, # float in (0, inf) used only for option for (4), (5)
    core_definition_H_fraction=0.1, # 0.01, 0.1, 0.3
    core_definition_He_fraction=0.1, # 0.1
    CEE_tolerance_err=0.001, # float (0, inf)
    common_envelope_option_after_succ_CEE = 'core_not_replaced_noMT', # 'core_not_replaced_noMT' 'core_replaced_noMT' 'core_not_replaced_stableMT' 'core_not_replaced_windloss'
    verbose = False, # True False
)

SN_STEP = dict(
    mechanism='Patton&Sukhbold20-engine', # 'direct', Fryer+12-rapid', 'Fryer+12-delayed', 'Sukhbold+16-engine', 'Patton&Sukhbold20-engine'
    engine='N20', # 'N20' for 'Sukhbold+16-engine', 'Patton&Sukhbold20-engine' or None for the others
    PISN="Marchant+19", # None, "Marchant+19"
    ECSN="Podsiadlowksi+04", # "Tauris+15", "Podsiadlowksi+04"
    conserve_hydrogen_envelope=True,
    max_neutrino_mass_loss=0.5, # float (0,inf)
    kick=True, # True, False
    kick_normalisation='one_over_mass', # "one_minus_fallback", "one_over_mass", "NS_one_minus_fallback_BH_one", "one", "zero"
    sigma_kick_CCSN_NS=265.0, # float (0,inf)
    sigma_kick_CCSN_BH=265.0, # float (0,inf)
    sigma_kick_ECSN=20.0, # float (0,inf)
    max_NS_mass=2.5,  # float (0,inf)
    use_interp_values=True,  # True, False
    use_profiles=True,  # True, False
    use_core_masses=True,  # True, False
    approx_at_he_depletion=False, # True, False
    verbose = False,  # True False
)

DCO_STEP = dict(
    n_o_steps_interval = None,
)

END_STEP = {}

# FLOW CHART CONFIGURATION
sim_kwargs = dict(
    flow = (flow_chart, {}),
    step_HMS_HMS = (MS_MS_step, MESA_STEP),
    step_CO_HeMS = (CO_HeMS_step, MESA_STEP),
    step_CO_HMS_RLO = (CO_HMS_RLO_step, MESA_STEP),
    step_CO_HeMS_RLO = (CO_HeMS_RLO_step, MESA_STEP),
    step_detached = (detached_step, DETACHED_STEP),
    step_disrupted = (DisruptedStep, DISRUPTED_STEP),
    step_merged = (MergedStep, {}),
    step_CE = (StepCEE, CE_STEP),
    step_SN = (StepSN, SN_STEP),
    step_dco = (DoubleCO, DCO_STEP),
    step_end = (step_end, END_STEP),
    extra_hooks = [(StepNamesHooks, {})]
)

sim_prop = SimulationProperties(**sim_kwargs)

# SIMULATION CONFIGURATION
kwargs = dict(
    file_path='./batches/',
    optimize_ram=False,
    ram_per_cpu=3., # limit ram usage at 3GB
    dump_rate=1000, # limit batch size

    metallicity = 0.0001, # 1e+00, 1e-01, 1e-02, 1e-03, 1e-04
    number_of_binaries=10, # int
    star_formation='burst', # 'constant' 'burst' 'custom_linear' 'custom_log10' 'custom_linear_histogram' 'custom_log10_histogram'
    max_simulation_time=13.8e9, # float (0,inf)

    primary_mass_scheme='Kroupa2001', # 'Salpeter', 'Kroupa1993', 'Kroupa2001'
    primary_mass_min=6.5, # float (0,130)
    primary_mass_max=250., # float (0,130)
    secondary_mass_scheme='flat_mass_ratio', # 'flat_mass_ratio', 'q=1'
    secondary_mass_min=0.35, # float (0,130)
    secondary_mass_max=250., # float (0,130)
    orbital_scheme = 'period', # 'separation', 'period'
    orbital_period_scheme = 'Sana+12_period_extended', # used only for orbital_scheme = 'period'
    orbital_period_min = 1., # float (0,inf)
    orbital_period_max = 1000., # float (0,inf)
    #orbital_separation_scheme='log_uniform', # used only for orbital_scheme = 'separation', 'log_uniform', 'log_normal'
    #orbital_separation_min=5., # float (0,inf)
    #orbital_separation_max=1e5, # float (0,inf)
    #log_orbital_separation_mean=None, # float (0,inf) used only for orbital_separation_scheme ='log_normal'
    #log_orbital_separation_sigma=None, # float (0,inf) used only for orbital_separation_scheme ='log_normal'
    eccentricity_sche='zero', # 'zero' 'thermal' 'uniform'

    # IMPORT CUSTOM HOOKS
    extra_columns={'step_names':'string'}, # 'step_times' with from posydon.binary_evol.simulationproperties import TimingHooks

    # LIST BINARY PROPERTIES TO SAVE
    only_select_columns=[
                        'state',
                        'event',
                        'time',
                        #'separation',
                        'orbital_period',
                        'eccentricity',
                        #'V_sys',
                        #'rl_relative_overflow_1',
                        #'rl_relative_overflow_2',
                        'lg_mtransfer_rate',
                        #'mass_transfer_case',
                        #'trap_radius',
                        #'acc_radius',
                        #'t_sync_rad_1',
                        #'t_sync_conv_1',
                        #'t_sync_rad_2',
                        #'t_sync_conv_2',
                        #'nearest_neighbour_distance',
                        ],
    scalar_names=[
                'interp_class_HMS_HMS',
                'interp_class_CO_HMS_RLO',
                'interp_class_CO_HeMS',
                'interp_class_CO_HeMS_RLO'
                ],

    # LIST STAR PROPERTIES TO SAVE
    include_S1=True , # True, False
    S1_kwargs=dict(only_select_columns=[
                                        'state',
                                        'metallicity',
                                        'mass',
                                        'log_R',
                                        'log_L',
                                        'lg_mdot',
                                        #'lg_system_mdot',
                                        #'lg_wind_mdot',
                                        'he_core_mass',
                                        'he_core_radius',
                                        #'c_core_mass',
                                        #'c_core_radius',
                                        #'o_core_mass',
                                        #'o_core_radius',
                                        'co_core_mass',
                                        'co_core_radius',
                                        'center_h1',
                                        'center_he4',
                                        #'center_c12',
                                        #'center_n14',
                                        #'center_o16',
                                        'surface_h1',
                                        'surface_he4',
                                        #'surface_c12',
                                        #'surface_n14',
                                        #'surface_o16',
                                        #'log_LH',
                                        #'log_LHe',
                                        #'log_LZ',
                                        #'log_Lnuc',
                                        #'c12_c12',
                                        #'center_gamma',
                                        #'avg_c_in_c_core',
                                        #'surf_avg_omega',
                                        'surf_avg_omega_div_omega_crit',
                                        #'total_moment_of_inertia',
                                        #'log_total_angular_momentum',
                                        'spin',
                                        #'conv_env_top_mass',
                                        #'conv_env_bot_mass',
                                        #'conv_env_top_radius',
                                        #'conv_env_bot_radius',
                                        #'conv_env_turnover_time_g',
                                        #'conv_env_turnover_time_l_b',
                                        #'conv_env_turnover_time_l_t',
                                        #'envelope_binding_energy',
                                        #'mass_conv_reg_fortides',
                                        #'thickness_conv_reg_fortides',
                                        #'radius_conv_reg_fortides',
                                        #'lambda_CE_1cent',
                                        #'lambda_CE_10cent',
                                        #'lambda_CE_30cent',
                                        #'lambda_CE_pure_He_star_10cent',
                                        #'profile',
                                        ],
                   scalar_names=['natal_kick_array',
                                 'SN_type',
                                 #'f_fb',
                                 #'spin_orbit_tilt',
                                ]),

    # LIST STAR PROPERTIES TO SAVE
    include_S2=True, # True, False
    S2_kwargs=dict(only_select_columns=[
                                        'state',
                                        'metallicity',
                                        'mass',
                                        'log_R',
                                        'log_L',
                                        'lg_mdot',
                                        #'lg_system_mdot',
                                        #'lg_wind_mdot',
                                        'he_core_mass',
                                        'he_core_radius',
                                        #'c_core_mass',
                                        #'c_core_radius',
                                        #'o_core_mass',
                                        #'o_core_radius',
                                        'co_core_mass',
                                        'co_core_radius',
                                        'center_h1',
                                        'center_he4',
                                        #'center_c12',
                                        #'center_n14',
                                        #'center_o16',
                                        'surface_h1',
                                        'surface_he4',
                                        #'surface_c12',
                                        #'surface_n14',
                                        #'surface_o16',
                                        #'log_LH',
                                        #'log_LHe',
                                        #'log_LZ',
                                        #'log_Lnuc',
                                        #'c12_c12',
                                        #'center_gamma',
                                        #'avg_c_in_c_core',
                                        #'surf_avg_omega',
                                        'surf_avg_omega_div_omega_crit',
                                        #'total_moment_of_inertia',
                                        #'log_total_angular_momentum',
                                        'spin',
                                        #'conv_env_top_mass',
                                        #'conv_env_bot_mass',
                                        #'conv_env_top_radius',
                                        #'conv_env_bot_radius',
                                        #'conv_env_turnover_time_g',
                                        #'conv_env_turnover_time_l_b',
                                        #'conv_env_turnover_time_l_t',
                                        #'envelope_binding_energy',
                                        #'mass_conv_reg_fortides',
                                        #'thickness_conv_reg_fortides',
                                        #'radius_conv_reg_fortides',
                                        #'lambda_CE_1cent',
                                        #'lambda_CE_10cent',
                                        #'lambda_CE_30cent',
                                        #'lambda_CE_pure_He_star_10cent',
                                        #'profile',
                                        ],
                   scalar_names=['natal_kick_array',
                                 'SN_type',
                                 #'f_fb',
                                 #'spin_orbit_tilt',
                                ]),
)


def run_simulation(sim_prop, kwargs, file=None, indices=None):


    # create binaries
    pop = BinaryPopulation(entropy=None,
                            population_properties=sim_prop,
                            file_name=file,
                            **kwargs)

    sim_prop.load_steps(verbose=True)

    # evolve binaries
    if file is not None:
        kwargs['from_hdf'] = True
        kwargs['indices'] = indices

    pop.evolve(breakdown_to_df=False, tqdm=True, **kwargs)

    # save binaries
    try:
        pop.save('./population.h5', **kwargs)
    except:
        pass

    return pop

if __name__ == '__main__' :
    pop = run_simulation(sim_prop, kwargs)

Let’s say we want to debug binary_index that follows.

[ ]:
i = 0

Re-evolving a Binary Star

[ ]:
col = ['time', 'step_names', 'state', 'event', 'orbital_period', 'eccentricity', 'S1_state', 'S2_state', 'S1_mass', 'S2_mass']
pop[i].to_df(extra_columns={'step_names':'string'})[col]
[ ]:
pop[i].restore()
[ ]:
pop[i].to_df()

Even thought we reset the binary, the natal kick properties stored in the natal kick array are not reset. This is because we want to be able to re-evolve the binary to the same final state. In case you want to reset the natal kick array values, then set the list to None values.

[ ]:
# kick magnitude km/s, azimuthal angle rad, polar angle rad and mean anomaly rad (TODO check documentation)
print(pop[i].star_1.natal_kick_array)
print(pop[i].star_2.natal_kick_array)
[ ]:
pop[i].evolve()
[ ]:
pop[i].to_df(extra_columns={'step_names':'string'})[col]

Re-evolving a Binary Star with a different kick

[ ]:
pop[i].restore()
pop[i].to_df()
[ ]:
pop[i].star_1.natal_kick_array = [1000., 0.858129274334538, 1.9157148786534735, 1.8675467897282945]
pop[i].star_2.natal_kick_array = [None, None, None, None] # default
print(pop[i].star_1.natal_kick_array)
print(pop[i].star_2.natal_kick_array)
[ ]:
pop[i].evolve()
pop[i].to_df(extra_columns={'step_names':'string'})[col]

Debugging a Binary Star from a population h5 file

Assume we run the population somewhere else and you have the h5 file associated with it and you want to load the population in memory and re-evolve a binary. Here is how you do it.

[ ]:
del pop # delete the above population
[ ]:
# let's load up and rerun the binary index i
pop = run_simulation(sim_prop, kwargs, file='./population.h5', indices=[i])
[ ]:
pop[0].to_df(extra_columns={'step_names':'string'})[col]
[ ]:
# do the same thing as above
pop[0].restore()
print(pop[i].star_1.natal_kick_array)
print(pop[i].star_2.natal_kick_array)
pop[i].star_1.natal_kick_array = [1000., 0.858129274334538, 1.9157148786534735, 1.8675467897282945]
pop[i].star_2.natal_kick_array = [None, None, None, None] # default
print(pop[i].star_1.natal_kick_array)
print(pop[i].star_2.natal_kick_array)
pop[i].evolve()
pop[i].to_df(extra_columns={'step_names':'string'})[col]

Evolving a Binary Star starting from an arbitrary state

If you want to evolve a custom binary star, you can do so by crafting yourself the BinaryStar object and providing the SimulationProperties object. For example, let’s evolve a neutron star with a low mass helium star in roche lobe overflow.

Caution!

While you can evolve a binary from an arbitrary state, you will need to provide data on the internal structure of the star, if you’re starting in the detached step. Otherwise the matching to a single star model will not work!

[ ]:
from posydon.binary_evol.singlestar import SingleStar
from posydon.binary_evol.binarystar import BinaryStar
sim_prop.load_steps(verbose=True)  # load steps
[ ]:
binary = BinaryStar(star_1 = SingleStar(**{'state' : 'NS',
                                           'mass' : 1.1,
                                           'spin' : 0.,}),
                    star_2 = SingleStar(**{'state' : 'stripped_He_Core_He_burning',
                                           'mass' : 2.5,
                                           'natal_kick_array' : [10., 0., 0., 0.]}),
                    **{'time' : 0.,
                       'state' : 'RLO2',
                       'event' : 'oRLO2',
                       'orbital_period' : 1.,
                       'eccentricity' : 0.},
                    properties = sim_prop,
                    )
binary.evolve()
binary.to_df(extra_columns={'step_names':'string'})[col]

Mapping a MESA step to the detached_step

You might want to use the detached_step instetad of a MESA step. Here is how you do it. As an example we replace the HMS-HMS MESA step with the detached step. Notice that the detached_step does not evolve the binary system post onset of RLO1. Hence, unless the flow_chart support such binary event, binary state and stellar states, else these systems will not be furthter evovlved.

[ ]:
# MODIFIED FLOW CHART CONFIGURATION
sim_kwargs = dict(
    flow = (flow_chart, {}),
    step_HMS_HMS = (detached_step, DETACHED_STEP), # <-- map this step to detached
    step_CO_HeMS = (CO_HeMS_step, MESA_STEP),
    step_CO_HMS_RLO = (CO_HMS_RLO_step, MESA_STEP),
    step_CO_HeMS_RLO = (CO_HeMS_RLO_step, MESA_STEP),
    step_detached = (detached_step, DETACHED_STEP),
    step_disrupted = (DisruptedStep, DISRUPTED_STEP),
    step_merged = (MergedStep, {}),
    step_CE = (StepCEE, CE_STEP),
    step_SN = (StepSN, SN_STEP),
    step_dco = (DoubleCO, DCO_STEP),
    step_end = (step_end, END_STEP),
    extra_hooks = [(StepNamesHooks, {})]
)

sim_prop = SimulationProperties(**sim_kwargs)

kwargs['from_hdf'] = False

if __name__ == '__main__' :
    pop = run_simulation(sim_prop, kwargs)
[ ]:
i = 0
pop[i].to_df(extra_columns={'step_names':'string'})[col]

Crating your own step

Here, you learn how to create your own step. As an example we create a step that does a fake common envelope by taking half the orbital period.

[ ]:
class my_CE_step(object):
    """Compute a fake CE event."""

    def __init__(self, verbose=False):
        self.verbose = verbose

    def __call__(self, binary):

        if self.verbose:
            print('The orbital separation post CE is half the pre CE orbital separation!')

        # Determine which star is the donor and which is the companion
        if binary.event in ["oCE1", "oDoubleCE1"]:
            donor_star = binary.star_1
            comp_star = binary.star_2
        elif binary.event in ["oCE2", "oDoubleCE2"]:
            donor_star = binary.star_2
            comp_star = binary.star_1
        else:
            raise ValueError("CEE does not apply if `event` is not "
                            "`oCE1`, 'oDoubleCE1' or `oCE2`, 'oDoubleCE1'")

        binary.orbital_period /= 2.
        donor_star.mass = donor_star.he_core_mass # lose envelope
        donor_star.state = donor_star.state.replace('H-rich', 'stripped_He')
        binary.state = 'detached'
        binary.event = None


# MODIFIED FLOW CHART CONFIGURATION
sim_kwargs = dict(
    flow = (flow_chart, {}),
    step_HMS_HMS = (MS_MS_step, MESA_STEP),
    step_CO_HeMS = (CO_HeMS_step, MESA_STEP),
    step_CO_HMS_RLO = (CO_HMS_RLO_step, MESA_STEP),
    step_CO_HeMS_RLO = (CO_HeMS_RLO_step, MESA_STEP),
    step_detached = (detached_step, DETACHED_STEP),
    step_disrupted = (DisruptedStep, DISRUPTED_STEP),
    step_merged = (MergedStep, {}),
    step_CE = (my_CE_step, {}),  # <-- map this step to my fake CE step
    step_SN = (StepSN, SN_STEP),
    step_dco = (DoubleCO, DCO_STEP),
    step_end = (step_end, END_STEP),
    extra_hooks = [(StepNamesHooks, {})]
)

sim_prop = SimulationProperties(**sim_kwargs)


pop = run_simulation(sim_prop, kwargs)
[ ]:
i = 0
pop[i].to_df(extra_columns={'step_names':'string'})[col]