{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "879b596b-d70c-4f90-b668-563b4ad93ffc",
   "metadata": {},
   "source": [
    "# Using custom logging routines with binarycpython\n",
    "In this notebook you'll learn how to use the custom logging functionality"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "696ecbb9-1efd-48f4-a57e-2cf6dfe416f1",
   "metadata": {},
   "outputs": [],
   "source": [
    "from binarycpython import _binary_c_bindings\n",
    "from binarycpython.utils.custom_logging_functions import (\n",
    "    autogen_C_logging_code,\n",
    "    binary_c_log_code,\n",
    "    create_and_load_logging_function,\n",
    ")\n",
    "from binarycpython.utils.run_system_wrapper import run_system\n",
    "from binarycpython.utils.grid import Population"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d4d721cc-df4f-4ac2-b6f9-62e85ca0c1e5",
   "metadata": {},
   "source": [
    "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.\n",
    "\n",
    "Technically, the following steps are taken:\n",
    "- User creates a logging print statement from within python\n",
    "- The logging print statement string gets wrapped into a proper c-function by `binary_c_log_code`\n",
    "- The c-function string gets compiled and loaded into memory by `create_and_load_logging_function`\n",
    "- The memory adress of the compiled and loaded print function can now be passed to C\n",
    "- binary_c uses the custom print function \n",
    "\n",
    "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\n",
    "\n",
    "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."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "be137151-bb57-43d7-bab1-0167512ac727",
   "metadata": {},
   "source": [
    "## Usage"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ac4e5f4c-81e6-4980-b852-aca84ca74f4c",
   "metadata": {},
   "source": [
    "There are two methods to create the C-code that will be compiled:\n",
    "- Automatically generate the print statement and use the wrapper to generate the full function string, by using `autogen_C_logging_code`\n",
    "- 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"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "236cf821-09ac-4237-9b8f-6e36d2edf446",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Printf(\"MY_STELLAR_DATA %g %g\\n\",((double)stardata->model.time),((double)stardata->star[0].mass));\n"
     ]
    }
   ],
   "source": [
    "# generate logging lines. Here you can choose whatever you want to have logged, and with what header\n",
    "# this generates working print statements\n",
    "logging_line = autogen_C_logging_code(\n",
    "    {\n",
    "        \"MY_STELLAR_DATA\": [\"model.time\", \"star[0].mass\"],\n",
    "    }\n",
    ")\n",
    "print(logging_line)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "feb423d5-5cc3-433c-9801-f8017abbc03a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Printf(\"MY_STELLAR_DATA time=%g mass=%g\\n\", stardata->model.time, stardata->star[0].mass)\n"
     ]
    }
   ],
   "source": [
    "# You can also decide to `write` your own logging_line, which allows you to write a more complex logging statement with conditionals.\n",
    "logging_line = 'Printf(\"MY_STELLAR_DATA time=%g mass=%g\\\\n\", stardata->model.time, stardata->star[0].mass)'\n",
    "print(logging_line)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "2f5defbf-c623-49ed-a238-fba52a563a58",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "#pragma push_macro(\"Max\")\n",
      "#pragma push_macro(\"Min\")\n",
      "#undef Max\n",
      "#undef Min\n",
      "#include \"binary_c.h\"\n",
      "\n",
      "// add visibility __attribute__ ((visibility (\"default\"))) to it \n",
      "void binary_c_API_function custom_output_function(struct stardata_t * stardata);\n",
      "void binary_c_API_function custom_output_function(struct stardata_t * stardata)\n",
      "{\n",
      "    // struct stardata_t * stardata = (struct stardata_t *)x;\n",
      "    Printf(\"MY_STELLAR_DATA time=%g mass=%g\\n\", stardata->model.time, stardata->star[0].mass);\n",
      "}\n",
      "\n",
      "#undef Max \n",
      "#undef Min\n",
      "#pragma pop_macro(\"Min\")\n",
      "#pragma pop_macro(\"Max\")    \n"
     ]
    }
   ],
   "source": [
    "# Generate the entire 'script' by wrapping the logging line\n",
    "custom_logging_code = binary_c_log_code(logging_line)\n",
    "print(custom_logging_code)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "efa7f1e9-247e-4196-a883-bcff05265d02",
   "metadata": {},
   "source": [
    "Combining the above with e.g. run_system() (see notebook_individual_systems for more examples):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "dcd74bbc-478b-43e4-b495-8c456e8d1d88",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "MY_STELLAR_DATA time=0 mass=2\n",
      "MY_STELLAR_DATA time=0 mass=2\n",
      "MY_STELLAR_DATA time=1e-06 mass=2\n",
      "MY_STELLAR_DATA time=2e-06 mass=2\n"
     ]
    }
   ],
   "source": [
    "# logging statement\n",
    "logging_line = 'Printf(\"MY_STELLAR_DATA time=%g mass=%g\\\\n\", stardata->model.time, stardata->star[0].mass)'\n",
    "\n",
    "# Entire script\n",
    "custom_logging_code = binary_c_log_code(logging_line)\n",
    "\n",
    "# Run system\n",
    "output = run_system(M_1=2, custom_logging_code=custom_logging_code)\n",
    "\n",
    "# print (abridged) output\n",
    "print(\"\\n\".join(output.splitlines()[:4]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1998ee8f-8c0a-462b-b1e0-54f5963902cc",
   "metadata": {},
   "source": [
    "### Using custom logging with the population object\n",
    "Custom logging can be used for a whole population by setting the print statement (so not the entire logging script) in `C_logging_code`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "77bd09b0-1a94-499d-97db-a1f991c67c12",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "EXAMPLE_ABOVE_MS             1.041660877905e+02 4.99198 4.99198 6.1357 6.1357 2 1\n",
      "EXAMPLE_ABOVE_MS             1.041662558619e+02 4.99198 4.99198 6.14057 6.1357 2 2\n",
      "EXAMPLE_ABOVE_MS             1.041662560111e+02 4.99198 4.99198 6.14057 6.14057 2 2\n",
      "EXAMPLE_ABOVE_MS             1.041662564579e+02 4.99198 4.99198 6.14059 6.14057 2 2\n"
     ]
    }
   ],
   "source": [
    "# Set up population\n",
    "pop = Population()\n",
    "\n",
    "# Set some BSE parameters\n",
    "pop.set(\n",
    "    M_1=5\n",
    ")\n",
    "\n",
    "# Example logging that prints only if the star is post main-sequence\n",
    "example_logging_string_post_MS = \"\"\"\n",
    "if(stardata->star[0].stellar_type>MS)\n",
    "{\n",
    "    Printf(\"EXAMPLE_ABOVE_MS %30.12e %g %g %g %g %d %d\\\\n\",\n",
    "        // \n",
    "        stardata->model.time, // 1\n",
    "\n",
    "        stardata->star[0].mass, //2\n",
    "        stardata->previous_stardata->star[0].mass, //3\n",
    "\n",
    "        stardata->star[0].radius, //4\n",
    "        stardata->previous_stardata->star[0].radius, //5\n",
    "\n",
    "        stardata->star[0].stellar_type, //6\n",
    "        stardata->previous_stardata->star[0].stellar_type //7\n",
    "  );\n",
    "};\n",
    "\"\"\"\n",
    "\n",
    "# Set the logging\n",
    "pop.set(\n",
    "    C_logging_code=example_logging_string_post_MS\n",
    ")\n",
    "out = pop.evolve_single()\n",
    "\n",
    "# Print (abridged) output\n",
    "print('\\n'.join(out.splitlines()[:4]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "93397ff3-9b71-470d-8bc4-08fe5b1a5dca",
   "metadata": {},
   "source": [
    "### Using custom logging when running directly from the API\n",
    "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)`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "30142286-34ce-433e-82c8-565e2160ff5b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "MY_STELLAR_DATA 0 15\n",
      "MY_STELLAR_DATA 0 15\n",
      "MY_STELLAR_DATA 1e-06 15\n",
      "MY_STELLAR_DATA 2e-06 15\n"
     ]
    }
   ],
   "source": [
    "# generate logging lines\n",
    "logging_line = autogen_C_logging_code(\n",
    "    {\n",
    "        \"MY_STELLAR_DATA\": [\"model.time\", \"star[0].mass\"],\n",
    "    }\n",
    ")\n",
    "\n",
    "# Generate code around logging lines\n",
    "custom_logging_code = binary_c_log_code(logging_line)\n",
    "\n",
    "# Generate library and get memaddr\n",
    "custom_logging_memaddr, shared_lib_filename = create_and_load_logging_function(\n",
    "    custom_logging_code\n",
    ")\n",
    "\n",
    "#\n",
    "m1 = 15.0  # Msun\n",
    "m2 = 14.0  # Msun\n",
    "separation = 0  # 0 = ignored, use period\n",
    "orbital_period = 4530.0  # days\n",
    "eccentricity = 0.0\n",
    "metallicity = 0.02\n",
    "max_evolution_time = 15000\n",
    "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(\n",
    "    m1,\n",
    "    m2,\n",
    "    separation,\n",
    "    orbital_period,\n",
    "    eccentricity,\n",
    "    metallicity,\n",
    "    max_evolution_time,\n",
    ")\n",
    "output = _binary_c_bindings.run_system(\n",
    "    argstring, custom_logging_func_memaddr=custom_logging_memaddr\n",
    ")\n",
    "\n",
    "# print (abridged) output\n",
    "print('\\n'.join(output.splitlines()[:4]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "39c76b1d-d968-4eef-b5ae-2542ed9557c3",
   "metadata": {},
   "source": [
    "## Examples of logging strings\n",
    "Below are some examples of logging strings"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2ac4af72-6dab-4cc9-986e-5b5b1fa31b73",
   "metadata": {},
   "source": [
    "### Compact object\n",
    "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"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "6f0edc65-a788-4706-a0c5-2ace030765ec",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "SINGLE_STAR_LIFETIME 10 27.7358\n",
      "EXAMPLE_LOG_CO             2.773581245005e+01 1.33524 9.19314 1.72498e-05 730.446 13 5\n"
     ]
    }
   ],
   "source": [
    "example_logging_string_CO = \"\"\"\n",
    "if(stardata->star[0].stellar_type>=NS)\n",
    "{\n",
    "    if (stardata->model.time < stardata->model.max_evolution_time)\n",
    "    {\n",
    "        Printf(\"EXAMPLE_LOG_CO %30.12e %g %g %g %g %d %d\\\\n\",\n",
    "            // \n",
    "            stardata->model.time, // 1\n",
    "\n",
    "            stardata->star[0].mass, //2\n",
    "            stardata->previous_stardata->star[0].mass, //3\n",
    "\n",
    "            stardata->star[0].radius, //4\n",
    "            stardata->previous_stardata->star[0].radius, //5\n",
    "\n",
    "            stardata->star[0].stellar_type, //6\n",
    "            stardata->previous_stardata->star[0].stellar_type //7\n",
    "      );\n",
    "    };\n",
    "    /* Kill the simulation to save time */\n",
    "    stardata->model.max_evolution_time = stardata->model.time - stardata->model.dtm;\n",
    "};\n",
    "\"\"\"\n",
    "\n",
    "# Entire script\n",
    "custom_logging_code = binary_c_log_code(example_logging_string_CO)\n",
    "\n",
    "# Run system\n",
    "output = run_system(M_1=10, custom_logging_code=custom_logging_code)\n",
    "\n",
    "# print (abridged) output\n",
    "print(\"\\n\".join(output.splitlines()[:4]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "51c51592-6406-43bd-a879-10ace64aaf28",
   "metadata": {},
   "source": [
    "### Logging mass evolution and the supernova\n",
    "This logging code prints the mass evolution and the moment the star goes supernova"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "id": "8f58fdf9-3e76-4c18-a1c5-eed0980d4133",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "EXAMPLE_MASSLOSS             9.878236827680e+00 1.61349 8.38063 20 13 1\n",
      "EXAMPLE_SN             9.878236827680e+00 1.61349 8.38063 20 12 13 5 1 6.74037 4.92267 6.74037 0 0\n"
     ]
    }
   ],
   "source": [
    "example_logging_string_CO = \"\"\"\n",
    "Printf(\"EXAMPLE_MASSLOSS %30.12e %g %g %g %d %g\\\\n\",\n",
    "    // \n",
    "    stardata->model.time, // 1\n",
    "    stardata->star[0].mass, //2\n",
    "    stardata->previous_stardata->star[0].mass, //3\n",
    "    stardata->common.zero_age.mass[0], //4\n",
    "\n",
    "    stardata->star[0].stellar_type, //5\n",
    "    stardata->model.probability //6\n",
    ");\n",
    "if(stardata->star[0].SN_type != SN_NONE)\n",
    "{\n",
    "    if (stardata->model.time < stardata->model.max_evolution_time)\n",
    "    {\n",
    "        if(stardata->pre_events_stardata != NULL)\n",
    "        {\n",
    "            Printf(\"EXAMPLE_SN %30.12e \" // 1\n",
    "                \"%g %g %g %d \" // 2-5\n",
    "                \"%d %d %g %g \" // 6-9\n",
    "                \"%g %g %g %g\\\\n\", // 10-13\n",
    "\n",
    "                // \n",
    "                stardata->model.time, // 1\n",
    "\n",
    "                stardata->star[0].mass, //2\n",
    "                stardata->pre_events_stardata->star[0].mass, //3\n",
    "                stardata->common.zero_age.mass[0], //4\n",
    "                stardata->star[0].SN_type, //5\n",
    "\n",
    "                stardata->star[0].stellar_type, //6\n",
    "                stardata->pre_events_stardata->star[0].stellar_type, //7\n",
    "                stardata->model.probability, //8\n",
    "                stardata->pre_events_stardata->star[0].core_mass[ID_core(stardata->pre_events_stardata->star[0].stellar_type)],           // 9\n",
    "\n",
    "                stardata->pre_events_stardata->star[0].core_mass[CORE_CO],     // 10\n",
    "                stardata->pre_events_stardata->star[0].core_mass[CORE_He],    // 11\n",
    "                stardata->star[0].fallback, // 12\n",
    "                stardata->star[0].fallback_mass // 13\n",
    "            );\n",
    "        }\n",
    "        else\n",
    "        {\n",
    "            Printf(\"EXAMPLE_SN %30.12e \" // 1\n",
    "                \"%g %g %g %d \" // 2-5\n",
    "                \"%d %d %g %g \" // 6-9\n",
    "                \"%g %g %g %g\\\\n\", // 10-13\n",
    "\n",
    "                // \n",
    "                stardata->model.time, // 1\n",
    "\n",
    "                stardata->star[0].mass, //2\n",
    "                stardata->previous_stardata->star[0].mass, //3\n",
    "                stardata->common.zero_age.mass[0], //4\n",
    "                stardata->star[0].SN_type, //5\n",
    "\n",
    "                stardata->star[0].stellar_type, //6\n",
    "                stardata->previous_stardata->star[0].stellar_type, //7\n",
    "                stardata->model.probability, //8\n",
    "                stardata->previous_stardata->star[0].core_mass[ID_core(stardata->previous_stardata->star[0].stellar_type)],           // 9\n",
    "\n",
    "                stardata->previous_stardata->star[0].core_mass[CORE_CO],     // 10\n",
    "                stardata->previous_stardata->star[0].core_mass[CORE_He],    // 11\n",
    "                stardata->star[0].fallback, // 12\n",
    "                stardata->star[0].fallback_mass // 13\n",
    "            );\n",
    "        }\n",
    "    };\n",
    "    /* Kill the simulation to save time */\n",
    "    stardata->model.max_evolution_time = stardata->model.time - stardata->model.dtm;\n",
    "};\n",
    "\"\"\"\n",
    "\n",
    "# Entire script\n",
    "custom_logging_code = binary_c_log_code(example_logging_string_CO)\n",
    "\n",
    "# Run system\n",
    "output = run_system(M_1=20, custom_logging_code=custom_logging_code)\n",
    "\n",
    "# print (abridged) output\n",
    "print(\"\\n\".join(output.splitlines()[-2:]))"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
