Evolutionary Hooks

During the evolution of a binary, one may be interested in having access to the BinaryStar instance before, during, and after the evolutionary loop. You may check the attributes, or add your own data to the binary during the evolution. We use hooks to pass functions that are called during the evolution of all binaries.

The evolutionary loop is in BinaryStar.evolve, and the pseudo code looks like this:

def evolve(*args, **kwargs):
    pre_evolve(binary)
        while binary.event != 'END':
        pre_step(binary, step_name)
        ...
        binary.evolve()
        ...
        post_step(binary, step_name)
        ...
    post_evolve(binary)

All hooks functions and classes should be provided in the SimulationProperties with the name extra_hooks. Then extra_hooks points to a list of tuples, containing either:

  1. The corresponding extra step name and a callable or,

  2. a class deriving from EvolveHooks and a dictionary passed during initialization.

1. Passing extra hooks functions

 1# import evolutionary steps
 2from posydon.popsyn.binarypopulation import BinaryPopulation
 3from posydon.binary_evol.simulationproperties import SimulationProperties
 4from posydon.binary_evol.flow_chart import flow_chart
 5from posydon.binary_evol.CE.step_CEE import StepCEE
 6from posydon.binary_evol.SN.step_SN import StepSN
 7from posydon.binary_evol.step_end import step_end
 8from posydon.binary_evol.MESA.step_mesa import CO_HeMS_step, MS_MS_step, CO_HMS_RLO_step
 9from posydon.binary_evol.DT.step_detached import detached_step
10from posydon.binary_evol.DT.double_CO import DoubleCO
11
12def print_info_before(binary, step_name):
13    print( f'Step {step_name} for \nbefore:\t{binary}' )
14
15def print_info_after(binary, step_name):
16    print( f'after:\t{binary}\n' )
17
18sim_kwargs = dict(
19      flow = (flow_chart, {}),
20      step_HMS_HMS = (MS_MS_step, {}),
21      step_CO_HeMS = (CO_HeMS_step, {}),
22      step_CO_HMS_RLO = (CO_HMS_RLO_step, {}),
23      step_detached = (detached_step, {}),
24      step_CE = (StepCEE, {}),
25      step_SN = (StepSN, {}),
26      step_dco = (DoubleCO, {}),
27      step_end = (step_end, {}),
28      # put hooks here
29      extra_hooks = [('extra_pre_step', print_info_before),
30                    ('extra_post_step', print_info_after)]
31  )
32
33  sim_prop = SimulationProperties(**sim_kwargs)

With the corresponding output from evolving a single binary:

Step step_HMS_HMS for
before:     BinaryStar(detached, ZAMS, p=3.13, S1=(H-rich_Core_H_burning,M=58.14), S2=(H-rich_Core_H_burning,M=50.83))
after:      BinaryStar(contact, oCE1, p=4.05, S1=(H-rich_Core_H_burning,M=42.97), S2=(H-rich_Core_H_burning,M=44.23))

Step step_CE for
before:     BinaryStar(contact, oCE1, p=4.05, S1=(H-rich_Core_H_burning,M=42.97), S2=(H-rich_Core_H_burning,M=44.23))
after:      BinaryStar(merged, None, p=nan, S1=(H-rich_Core_H_burning,M=87.20), S2=(H-rich_Core_H_burning,M=nan))

Step step_end for
before:     BinaryStar(merged, None, p=nan, S1=(H-rich_Core_H_burning,M=87.20), S2=(H-rich_Core_H_burning,M=nan))
after:      BinaryStar(merged, END, p=nan, S1=(H-rich_Core_H_burning,M=87.20), S2=(H-rich_Core_H_burning,M=nan))

The options for step names and their arguments include:

  • 'extra_pre_evolve', functional form: f(binary)

  • 'extra_pre_step', functional form: f(binary, step_name)

  • 'extra_post_step', functional form: f(binary, step_name)

  • 'extra_post_evolve', functional form: f(binary)

2. Passing extra hooks classes

For more complex tasks, you can provide a class deriving from EvolveHooks. We have implemented two examples of hooks classes which add step timing and step names to the history of a binary in simulationproperties.py:

 1# import evolutionary steps
 2from posydon.popsyn.binarypopulation import BinaryPopulation
 3from posydon.binary_evol.simulationproperties import SimulationProperties
 4from posydon.binary_evol.flow_chart import flow_chart
 5from posydon.binary_evol.CE.step_CEE import StepCEE
 6from posydon.binary_evol.SN.step_SN import StepSN
 7from posydon.binary_evol.step_end import step_end
 8from posydon.binary_evol.MESA.step_mesa import CO_HeMS_step, MS_MS_step, CO_HMS_RLO_step
 9from posydon.binary_evol.DT.step_detached import detached_step
10from posydon.binary_evol.DT.double_CO import DoubleCO
11
12# import hooks classes
13from posydon.binary_evol.simulationproperties import TimingHooks, StepNamesHooks
14
15sim_kwargs = dict(
16      flow = (flow_chart, {}),
17      step_HMS_HMS = (MS_MS_step, {}),
18      step_CO_HeMS = (CO_HeMS_step, {}),
19      step_CO_HMS_RLO = (CO_HMS_RLO_step, {}),
20      step_detached = (detached_step, {}),
21      step_CE = (StepCEE, {}),
22      step_SN = (StepSN, {}),
23      step_dco = (DoubleCO, {}),
24      step_end = (step_end, {}),
25      # put hooks here
26      extra_hooks = [(TimingHooks, {}),(StepNamesHooks, {})]
27  )
28
29  sim_prop = SimulationProperties(**sim_kwargs)

The source for StepNamesHooks:

class StepNamesHooks(EvolveHooks):
    """Add history column 'step_name' to each binary.

    Name of evolutionary step as defined in SimulationProperties.

    >>> pop.to_df(extra_columns=['step_names'])
    """

    def pre_evolve(self, binary):
        """Initialize the step name to match history."""
        binary.step_names = ['initial_cond']
        return binary

    def pre_step(self, binary, step_name):
        """Do not do anything before the step."""
        return binary

    def post_step(self, binary, step_name):
        """Record the step name."""
        binary.step_names.append(step_name)
        len_binary_hist = len(binary.event_history)
        len_step_names = len(binary.step_names)
        diff = len_binary_hist - len_step_names
        if len_binary_hist > len_step_names:
            binary.step_names += [None] * (diff)
        elif len_binary_hist < len_step_names:
            binary.step_names = binary.step_names[-(len_binary_hist - 1):]
        return binary

    def post_evolve(self, binary):
        """Ensure None's are append to step_names to match rows in history."""
        if binary.event == 'END' or binary.event == 'FAILED':
            diff = int(len(binary.event_history) - len(binary.step_names))
            binary.step_names += [None]*diff
        return binary

You can simply combine multiple hooks classes and functions together by passing them in the list.

sim_kwargs = dict(
    ...
    extra_hooks = [(TimingHooks, {}),(StepNamesHooks, {}),
                  ('extra_pre_step', print_info_before)],
    ...
)