# Tutorial: Using custom logging routines with binary_c-python

In this notebook you’ll learn how to use the custom logging functionality

    [1]:

    from binarycpython import _binary_c_bindings
    from binarycpython.utils.custom_logging_functions import (
        autogen_C_logging_code,
        binary_c_log_code,
        create_and_load_logging_function,
    )
    from binarycpython.utils.run_system_wrapper import run_system
    from binarycpython.utils.grid import Population

The custom logging functionality allows us to decide the output of binary_c without modifying the actual sourcecode of binary_c (i.e. editing `src/logging/log_every_timestep` in binary_c). Rather, we can create a logging routine from within python.

Technically, the following steps are taken:

- User creates a logging print statement from within python

- The logging print statement string gets wrapped into a proper c-function by `binary_c_log_code`

- The c-function string gets compiled and loaded into memory by `create_and_load_logging_function`

- The memory adress of the compiled and loaded print function can now be passed to C

- binary_c uses the custom print function

The custom logging functionality can be used when running systems via `run_system()`, via `Population.evolve()` and `Population.evolve_single()`, and directly via the API

Within the logging statement we can access information from the stardata object, as well as use logical statements to determine when to log information. What we cannot do, however, is access functions that are not publicly available. For very elaborate printing routines it is still advised to actually hardcode the print statement into binary_c itself.

## Usage

There are two methods to create the C-code that will be compiled: - Automatically generate the print statement and use the wrapper to generate the full function string, by using `autogen_C_logging_code` - Create your custom print statement and use the wrapper to generate the full function string, by writing out the print statement. Here the logging statement obviously has to be valid C code

    [2]:

    # generate logging lines. Here you can choose whatever you want to have logged, and with what header
    # this generates working print statements
    logging_line = autogen_C_logging_code(
        {
            "MY_STELLAR_DATA": ["model.time", "star[0].mass"],
        }
    )
    print(logging_line)

    Printf("MY_STELLAR_DATA %g %g\n",((double)stardata->model.time),((double)stardata->star[0].mass));

    [3]:

    # You can also decide to `write` your own logging_line, which allows you to write a more complex logging statement with conditionals.
    logging_line = 'Printf("MY_STELLAR_DATA time=%g mass=%g\\n", stardata->model.time, stardata->star[0].mass)'
    print(logging_line)

    Printf("MY_STELLAR_DATA time=%g mass=%g\n", stardata->model.time, stardata->star[0].mass)

    [4]:

    # Generate the entire 'script' by wrapping the logging line
    custom_logging_code = binary_c_log_code(logging_line)
    print(custom_logging_code)

    #pragma push_macro("Max")
    #pragma push_macro("Min")
    #undef Max
    #undef Min
    #include "binary_c.h"

    // add visibility __attribute__ ((visibility ("default"))) to it
    void binary_c_API_function custom_output_function(struct stardata_t * stardata);
    void binary_c_API_function custom_output_function(struct stardata_t * stardata)
    {
        // struct stardata_t * stardata = (struct stardata_t *)x;
        Printf("MY_STELLAR_DATA time=%g mass=%g\n", stardata->model.time, stardata->star[0].mass);
    }

    #undef Max
    #undef Min
    #pragma pop_macro("Min")
    #pragma pop_macro("Max")

Combining the above with e.g. run_system() (see notebook_individual_systems for more examples):

    [5]:

    # logging statement
    logging_line = 'Printf("MY_STELLAR_DATA time=%g mass=%g\\n", stardata->model.time, stardata->star[0].mass)'

    # Entire script
    custom_logging_code = binary_c_log_code(logging_line)

    # Run system
    output = run_system(M_1=2, custom_logging_code=custom_logging_code)

    # print (abridged) output
    print("\n".join(output.splitlines()[:4]))

    MY_STELLAR_DATA time=0 mass=2
    MY_STELLAR_DATA time=0 mass=2
    MY_STELLAR_DATA time=1e-06 mass=2
    MY_STELLAR_DATA time=2e-06 mass=2

### Using custom logging with the population object

Custom logging can be used for a whole population by setting the print statement (so not the entire logging script) in `C_logging_code`

    [6]:

    # Set up population
    pop = Population()

    # Set some BSE parameters
    pop.set(
        M_1=5
    )

    # Example logging that prints only if the star is post main-sequence
    example_logging_string_post_MS = """
    if(stardata->star[0].stellar_type>MS)
    {
        Printf("EXAMPLE_POST_MS %30.12e %g %g %g %g %d %d\\n",
            //
            stardata->model.time, // 1

            stardata->star[0].mass, //2
            stardata->previous_stardata->star[0].mass, //3

            stardata->star[0].radius, //4
            stardata->previous_stardata->star[0].radius, //5

            stardata->star[0].stellar_type, //6
            stardata->previous_stardata->star[0].stellar_type //7
      );
    };
    """

    # Set the logging
    pop.set(
        C_logging_code=example_logging_string_post_MS
    )
    out = pop.evolve_single()

    # Print (abridged) output
    print('\n'.join(out.splitlines()[:4]))

    EXAMPLE_POST_MS             1.044142002936e+02 4.99194 4.99194 6.13567 6.13567 2 1
    EXAMPLE_POST_MS             1.044572277695e+02 4.99192 4.99194 7.51803 6.13567 2 2
    EXAMPLE_POST_MS             1.044654032097e+02 4.99192 4.99192 7.81395 7.51803 2 2
    EXAMPLE_POST_MS             1.045084306856e+02 4.99191 4.99192 9.57443 7.81395 2 2

### Using custom logging when running directly from the API

When running a system directly with the API we need to manually load the custom logging into memory (via `create_and_load_logging_function`) and pass the memory address to the binary_c binding via `_binary_c_bindings.run_system(argstring, custom_logging_func_memaddr=custom_logging_memaddr)`

    [7]:

    # generate logging lines
    logging_line = autogen_C_logging_code(
        {
            "MY_STELLAR_DATA": ["model.time", "star[0].mass"],
        }
    )

    # Generate code around logging lines
    custom_logging_code = binary_c_log_code(logging_line)

    # Generate library and get memaddr
    custom_logging_memaddr, shared_lib_filename = create_and_load_logging_function(
        custom_logging_code
    )

    #
    m1 = 15.0  # Msun
    m2 = 14.0  # Msun
    separation = 0  # 0 = ignored, use period
    orbital_period = 4530.0  # days
    eccentricity = 0.0
    metallicity = 0.02
    max_evolution_time = 15000
    argstring = "binary_c M_1 {0:g} M_2 {1:g} separation {2:g} orbital_period {3:g} eccentricity {4:g} metallicity {5:g} max_evolution_time {6:g}".format(
        m1,
        m2,
        separation,
        orbital_period,
        eccentricity,
        metallicity,
        max_evolution_time,
    )
    output = _binary_c_bindings.run_system(
        argstring, custom_logging_func_memaddr=custom_logging_memaddr
    )

    # print (abridged) output
    print('\n'.join(output.splitlines()[:4]))

    MY_STELLAR_DATA 0 15
    MY_STELLAR_DATA 0 15
    MY_STELLAR_DATA 1e-06 15
    MY_STELLAR_DATA 2e-06 15

## Examples of logging strings

Below are some examples of logging strings

### Compact object

This logging will print the timestep when the star becomes a compact object. After it does, we change the maximum time to be the current time, effectively terminating the evolution

    [8]:

    example_logging_string_CO = """
    if(stardata->star[0].stellar_type>=NS)
    {
        if (stardata->model.time < stardata->model.max_evolution_time)
        {
            Printf("EXAMPLE_LOG_CO %30.12e %g %g %g %g %d %d\\n",
                //
                stardata->model.time, // 1

                stardata->star[0].mass, //2
                stardata->previous_stardata->star[0].mass, //3

                stardata->star[0].radius, //4
                stardata->previous_stardata->star[0].radius, //5

                stardata->star[0].stellar_type, //6
                stardata->previous_stardata->star[0].stellar_type //7
          );
        };
        /* Kill the simulation to save time */
        stardata->model.max_evolution_time = stardata->model.time - stardata->model.dtm;
    };
    """

    # Entire script
    custom_logging_code = binary_c_log_code(example_logging_string_CO)

    # Run system
    output = run_system(M_1=10, custom_logging_code=custom_logging_code)

    # print (abridged) output
    print("\n".join(output.splitlines()[:4]))

    SINGLE_STAR_LIFETIME 10 28.4838
    EXAMPLE_LOG_CO             2.848380621869e+01 1.33469 9.1865 1.72498e-05 724.338 13 5

### Logging mass evolution and the supernova

This logging code prints the mass evolution and the moment the star goes supernova

    [9]:

    example_logging_string_CO = """
    Printf("EXAMPLE_MASSLOSS %30.12e %g %g %g %d %g\\n",
        //
        stardata->model.time, // 1
        stardata->star[0].mass, //2
        stardata->previous_stardata->star[0].mass, //3
        stardata->common.zero_age.mass[0], //4

        stardata->star[0].stellar_type, //5
        stardata->model.probability //6
    );
    if(stardata->star[0].SN_type != SN_NONE)
    {
        if (stardata->model.time < stardata->model.max_evolution_time)
        {
            Printf("EXAMPLE_SN %30.12e " // 1
                "%g %g %g %d " // 2-5
                "%d %d %g %g " // 6-9
                "%g %g\\n", // 10-13

                //
                stardata->model.time, // 1

                stardata->star[0].mass, //2
                stardata->previous_stardata->star[0].mass, //3
                stardata->common.zero_age.mass[0], //4
                stardata->star[0].SN_type, //5

                stardata->star[0].stellar_type, //6
                stardata->previous_stardata->star[0].stellar_type, //7
                stardata->model.probability, //8
                stardata->previous_stardata->star[0].core_mass[ID_core(stardata->previous_stardata->star[0].stellar_type)],           // 9

                stardata->previous_stardata->star[0].core_mass[CORE_CO],     // 10
                stardata->previous_stardata->star[0].core_mass[CORE_He]    // 11
            );
        };
        /* Kill the simulation to save time */
        stardata->model.max_evolution_time = stardata->model.time - stardata->model.dtm;
    };
    """

    # Entire script
    custom_logging_code = binary_c_log_code(example_logging_string_CO)

    # Run system
    output = run_system(M_1=20, custom_logging_code=custom_logging_code)

    # print (abridged) output
    print("\n".join(output.splitlines()[-2:]))

    EXAMPLE_MASSLOSS             1.050651207308e+01 1.59452 9.34213 20 13 1
    EXAMPLE_SN             1.050651207308e+01 1.59452 9.34213 20 12 13 5 1 6.55458 4.71662 6.55458