Custom POSYDON steps and flow chart

The evolution of binaries in POSYDON is determined by the flow chart. It uses the stellar states, S1_state and S2_state, the binary state and event to determine to which step the binary goes next.

For example, in the default flow chart, the state ('H-rich_Core_C_depleted','H-rich_Core_H_burning', 'detached', 'CC1') will be sent to step_SN.

If your specific need requires, it is possible to change the flow and introduce your own steps. This is possible through

  1. Importing an additional flow chart and step from the population_params.ini file

  2. Changing a step inside a notebook

Population_params.ini changes

If you want to run large populations with an adapted flowchart or custom step, this is the location to change the loaded steps.

Importing an adapted flowchart

One of the first things the population_params.ini file defines is the flow chart. By default, the POSYDON default flow_chart is imported:

[flow]
  import = ['posydon.binary_evol.flow_chart', 'flow_chart']
    # builtin posydon flow
  absolute_import = None
    # If given, use an absolute filepath to user defined flow: ['<PATH_TO_PY_FILE>', '<NAME>']

We can change this to import our custom flow. In this example, the binary is immediately send to step_end without any evolution. Make sure to change the path to your absolute path of the example file, if you want to run a population with it.

Additionally, you can put the python file into the user_modules folder to make it part of the POSYDON namespace.

[flow]
  import = None
    # builtin posydon flow
  absolute_import = ['custom_step_and_flow.py', 'end_flow_chart']
    # If given, use an absolute filepath to user defined flow: ['<PATH_TO_PY_FILE>', '<NAME>']

We can check this change by loading the Simulation Property arguments. Below you can see the example, where we send all the states straight to step_end.

[ ]:
from posydon.popsyn.io import simprop_kwargs_from_ini
from posydon.binary_evol.simulationproperties import SimulationProperties

sim_kwargs = simprop_kwargs_from_ini('population_params.ini')
sim_prop = SimulationProperties(**sim_kwargs)
sim_prop.flow[0]()
[ ]:
sim_kwargs = simprop_kwargs_from_ini('population_params_end.ini')
sim_prop = SimulationProperties(**sim_kwargs)
sim_prop.flow[0]()

Changing an existing step to a custom step

Similarly, you can change one of standard POSYDON steps to a custom one. We will replace the common envelope step by one that halves the orbital period.

[step_CE]
  import = ['posydon.binary_evol.CE.step_CEE', 'StepCEE']
    # builtin posydon step
  absolute_import = None
    # If given, use an absolute filepath to user defined step: ['<PATH_TO_PY_FILE>', '<NAME>']
[step_CE]
  import = None
    # builtin posydon step
  absolute_import = ['custom_step_and_flow.py', 'my_CE_step']
    # If given, use an absolute filepath to user defined step: ['<PATH_TO_PY_FILE>', '<NAME>']

You can see the step class in the example file custom_step_and_flow.py.

[ ]:
sim_kwargs = simprop_kwargs_from_ini('population_params_end.ini')
sim_kwargs['step_CE']

Adapting the flow and steps inside your notebook

Although you can always load in the population parameters file into your notebook, maybe you want to create a unique flow inside a notebook. For this you need to set up all the steps manually inside the notebook.

This is definitely not the easiest way to change option within POSYDON.

[ ]:
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


col = ['time', 'step_names', 'state', 'event', 'orbital_period', 'eccentricity', 'S1_state', 'S2_state', 'S1_mass', 'S2_mass']

Adapted flow chart

You might want to use the detached_step instead 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 further evolved.

[ ]:
# 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

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

Adapted step

We implement a custom common envelope step, which is the same as the on in the custom_step_and_flow.py custom step.

[ ]:
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]