Skip to content
Snippets Groups Projects
Commit 1daf160d authored by David Hendriks's avatar David Hendriks
Browse files

added notebook and pdf

parent c6062082
No related branches found
No related tags found
No related merge requests found
......@@ -8,3 +8,5 @@ output/*
*.nfs*
*.swp
.python-version
.ipynb_checkpoints/
#!/usr/bin/python3
import binary_c_python_api
############################################################
# Test script to run a binary using the binary_c Python
# module.
############################################################
def run_test_binary():
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
buffer = ""
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_python_api.run_binary(argstring)
print("\n\nBinary_c output:\n\n")
print(output)
# binary_star = binary_c_python_api.new_system()
# print(binary_star)
run_test_binary()
\ No newline at end of file
......@@ -39,8 +39,8 @@ You will require whatever libraries with which binary_c was compiled, as well as
If you want to be able to import the binary_c module correctly for child directories (or anywhere for that matter), execute or put the following code in your .bashrc/.zshrc:
```
export LD_LIBRARY_PATH=<full path to directory containing libbinary_c_api.so>:$LD_LIBRARY_PATH
export PYTHONPATH=<full path to directory containing libbinary_c_api.so>:$PYTHONPATH
export LD_LIBRARY_PATH=<full path to root dir of repo>:$LD_LIBRARY_PATH
export PYTHONPATH=<full path to root dir of repo>:$PYTHONPATH
```
Usage notes
......
import os
import textwrap
import subprocess
import socket
import tempfile
import ctypes
def autogen_C_logging_code(logging_dict):
"""
Function that autogenerates PRINTF statements for binaryc. intput is a dictionary where the key is the header of that logging line and items which are lists of parameters\
that will be put in that logging line
Example::
{'MY_STELLAR_DATA':
[
'model.time',
'star[0].mass',
'model.probability',
'model.dt'
]
}
"""
# Check if the input is of the correct form
if not type(logging_dict) == dict:
print("Error: please use a dictionary as input")
return None
code = ""
# Loop over dict keys
for key in logging_dict:
logging_dict_entry = logging_dict[key]
# Check if item is of correct type:
if type(logging_dict_entry) == list:
# Construct print statement
code += 'Printf("{}'.format(key)
code += " {}".format("%g " * len(logging_dict_entry))
code = code.strip()
code += '\\n"'
# Add format keys
for param in logging_dict_entry:
code += ",((double)stardata->{})".format(param)
code += ");\n"
else:
print(
"Error: please use a list for the list of parameters that you want to have logged"
)
code = code.strip()
# print("MADE AUTO CODE\n\n{}\n\n{}\n\n{}\n".format('*'*60, repr(code), '*'*60))
return code
####################################################################################
def binary_c_log_code(code):
"""
Function to construct the code to construct the custom logging function
"""
custom_logging_function_string = """\
#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;
{};
}}
#undef MAX
#undef MIN
#pragma pop_macro(\"MIN\")
#pragma pop_macro(\"MAX\")\
""".format(
code
)
# print(repr(textwrap.dedent(custom_logging_function_string)))
return textwrap.dedent(custom_logging_function_string)
def binary_c_write_log_code(code, filename):
"""
Function to write the generated logging code to a file
"""
cwd = os.getcwd()
filePath = os.path.join(cwd, filename)
if os.path.exists(filePath):
try:
os.remove(filePath)
except:
print("Error while deleting file {}".format(filePath))
with open(filePath, "w") as f:
f.write(code)
def from_binary_c_config(config_file, flag):
"""
Function to run the binaryc_config command with flags
"""
res = subprocess.check_output(
"{config_file} {flag}".format(config_file=config_file, flag=flag),
shell=True,
stderr=subprocess.STDOUT,
)
# convert and chop off newline
res = res.decode("utf").rstrip()
return res
def return_compilation_dict(verbose=False):
"""
Function to build the compile command for the shared library
inspired by binary_c_inline_config command in perl
TODO: this function still has some cleaning up to do wrt default values for the compile command
# https://developers.redhat.com/blog/2018/03/21/compiler-and-linker-flags-gcc/
returns:
- string containing the command to build the shared library
"""
# use binary_c-config to get necessary flags
BINARY_C_DIR = os.getenv("BINARY_C")
if BINARY_C_DIR:
BINARY_C_CONFIG = os.path.join(BINARY_C_DIR, "binary_c-config")
BINARY_C_SRC_DIR = os.path.join(BINARY_C_DIR, "src")
# TODO: build in check to see whether the file exists
else:
raise NameError("Envvar BINARY_C doesnt exist")
return None
# TODO: make more options for the compiling
cc = from_binary_c_config(BINARY_C_CONFIG, "cc")
# Check for binary_c
BINARY_C_EXE = os.path.join(BINARY_C_DIR, "binary_c")
if not os.path.isfile(BINARY_C_EXE):
print("We require binary_c executable; have you built binary_c?")
raise NameError("BINARY_C executable doesnt exist")
# TODO: debug
libbinary_c = "-lbinary_c"
binclibs = from_binary_c_config(BINARY_C_CONFIG, "libs")
libdirs = "{} -L{}".format(
from_binary_c_config(BINARY_C_CONFIG, "libdirs"), BINARY_C_SRC_DIR
)
bincflags = from_binary_c_config(BINARY_C_CONFIG, "cflags")
bincincdirs = from_binary_c_config(BINARY_C_CONFIG, "incdirs")
# combine
binclibs = " {} {} {}".format(libdirs, libbinary_c, binclibs)
# setup defaults:
defaults = {
"cc": "gcc", # default compiler
"ccflags": bincflags,
"ld": "ld", # 'ld': $Config{ld}, # default linker
"debug": 0,
"inc": "{} -I{}".format(bincincdirs, BINARY_C_SRC_DIR),
# inc => ' '.($Config{inc}//' ').' '.$bincincdirs." -I$srcdir ", # include the defaults plus # GSL and binary_c
# 'libname': libname, # libname is usually just binary_c corresponding to libbinary_c.so
"libs": binclibs,
}
# set values with defaults. TODO: make other input possile.
ld = defaults["ld"]
debug = defaults["debug"]
inc = defaults[
"inc"
] # = ($ENV{BINARY_GRID2_INC} // $defaults{inc}).' '.($ENV{BINARY_GRID2_EXTRAINC} // '');
libs = defaults[
"libs"
] # = ($ENV{BINARY_GRID2_LIBS} // $defaults{libs}).' '.($ENV{BINARY_GRID2_EXTRALIBS}//'');
ccflags = defaults[
"ccflags"
] # = $ENV{BINARY_GRID2_CCFLAGS} // ($defaults{ccflags}) . ($ENV{BINARY_GRID2_EXTRACCFLAGS} // '');
# you must define _SEARCH_H to prevent it being loaded twice
ccflags += " -shared -D_SEARCH_H"
# remove the visibility=hidden for this compilation
ccflags = ccflags.replace("-fvisibility=hidden", "")
# ensure library paths to the front of the libs:
libs_content = libs.split(" ")
library_paths = [el for el in libs_content if el.startswith("-L")]
non_library_paths = [
el for el in libs_content if (not el.startswith("-L") and not el == "")
]
libs = "{} {}".format(" ".join(library_paths), " ".join(non_library_paths))
if verbose:
print(
"Building shared library for custom logging with (binary_c.h) at {} on {}\n".format(
BINARY_C_SRC_DIR, socket.gethostname()
)
)
print(
"With options:\n\tcc = {cc}\n\tccflags = {ccflags}\n\tld = {ld}\n\tlibs = {libs}\n\tinc = {inc}\n\n".format(
cc=cc, ccflags=ccflags, ld=ld, libs=libs, inc=inc
)
)
return {"cc": cc, "ld": ld, "ccflags": ccflags, "libs": libs, "inc": inc}
def compile_shared_lib(code, sourcefile_name, outfile_name, verbose=False):
"""
Function to write the custom logging code to a file and then compile it.
"""
# Write code to file
binary_c_write_log_code(code, sourcefile_name)
# create compilation command
compilation_dict = return_compilation_dict()
# Construct full command
command = "{cc} {ccflags} {libs} -o {outfile_name} {sourcefile_name} {inc}".format(
cc=compilation_dict["cc"],
ccflags=compilation_dict["ccflags"],
libs=compilation_dict["libs"],
outfile_name=outfile_name,
sourcefile_name=sourcefile_name,
inc=compilation_dict["inc"],
)
# remove extra whitespaces:
command = " ".join(command.split())
# Execute compilation
if verbose:
print("Executing following command:\n{command}".format(command=command))
res = subprocess.check_output("{command}".format(command=command), shell=True)
if verbose:
if res:
print("Output of compilation command:\n{}".format(res))
def temp_custom_logging_dir():
"""
Function to return the path the custom logging library shared object and script will be written to.
Makes use of os.makedirs exist_ok which requires python 3.2+
"""
tmp_dir = tempfile.gettempdir()
path = os.path.join(tmp_dir, "binary_c_python")
#
os.makedirs(path, exist_ok=True)
return path
def create_and_load_logging_function(custom_logging_code):
"""
Function to automatically compile the shared library with the given custom logging code and load it with ctypes
returns:
memory adress of the custom logging function in a int type.
"""
#
compile_shared_lib(
custom_logging_code,
sourcefile_name=os.path.join(temp_custom_logging_dir(), "custom_logging.c"),
outfile_name=os.path.join(temp_custom_logging_dir(), "libcustom_logging.so"),
)
# Loading library
dll1 = ctypes.CDLL("libgslcblas.so", mode=ctypes.RTLD_GLOBAL)
dll2 = ctypes.CDLL("libgsl.so", mode=ctypes.RTLD_GLOBAL)
dll3 = ctypes.CDLL("libbinary_c.so", mode=ctypes.RTLD_GLOBAL)
libmean = ctypes.CDLL(
os.path.join(temp_custom_logging_dir(), "libcustom_logging.so"),
mode=ctypes.RTLD_GLOBAL,
) # loads the shared library
# Get memory adress of function. mimicking a pointer
func_memaddr = ctypes.cast(libmean.custom_output_function, ctypes.c_void_p).value
return func_memaddr
from collections import defaultdict
import binary_c_python_api
from binarycpython.utils.custom_logging_functions import (
create_and_load_logging_function,
)
def create_arg_string(arg_dict):
"""
Function that creates the arg string
"""
arg_string = ""
for key in arg_dict.keys():
arg_string += "{key} {value} ".format(key=key, value=arg_dict[key])
arg_string = arg_string.strip()
return arg_string
def get_defaults():
"""
Function that calls the binaryc get args function and cast it into a dictionary
All the values are strings
"""
default_output = binary_c_python_api.return_arglines()
default_dict = {}
for default in default_output.split("\n"):
if not default in ["__ARG_BEGIN", "__ARG_END", ""]:
key, value = default.split(" = ")
# Filter out NULLS (not compiled anyway)
if not value in ["NULL", "Function"]:
if not value == "":
default_dict[key] = value
return default_dict
def get_arg_keys():
"""
Function that return the list of possible keys to give in the arg string
"""
return get_defaults().keys()
def run_system(**kwargs):
"""
Wrapper to run a system with settings
This function determines which underlying python-c api function will be called based upon the arguments that are passed via kwargs.
- if custom_logging_code or custom_logging_dict is included in the kwargs then it will
- if
"""
# Load default args
args = get_defaults()
if "custom_logging_code" in kwargs:
# Use kwarg value to override defaults and add new args
for key in kwargs.keys():
if not key == "custom_logging_code":
args[key] = kwargs[key]
# Generate library and get memaddr
func_memaddr = create_and_load_logging_function(kwargs["custom_logging_code"])
# Construct arguments string and final execution string
arg_string = create_arg_string(args)
arg_string = "binary_c {}".format(arg_string)
# Run it and get output
output = binary_c_python_api.run_binary_custom_logging(arg_string, func_memaddr)
return output
elif "log_filename" in kwargs:
# Use kwarg value to override defaults and add new args
for key in kwargs.keys():
args[key] = kwargs[key]
# Construct arguments string and final execution string
arg_string = create_arg_string(args)
arg_string = "binary_c {}".format(arg_string)
# Run it and get output
output = binary_c_python_api.run_binary_with_logfile(arg_string)
return output
else: # run the plain basic type
# Use kwarg value to override defaults and add new args
for key in kwargs.keys():
args[key] = kwargs[key]
# Construct arguments string and final execution string
arg_string = create_arg_string(args)
arg_string = "binary_c {}".format(arg_string)
# Run it and get output
output = binary_c_python_api.run_binary(arg_string)
return output
def run_system_with_log(**kwargs):
"""
Wrapper to run a system with settings AND logs the files to a designated place defined by the log_filename parameter.
"""
# Load default args
args = get_defaults()
# args = {}
# For example
# physics_args['M_1'] = 20
# physics_args['separation'] = 0 # 0 = ignored, use period
# physics_args['orbital_period'] = 100000000000 # To make it single
# Use kwarg value to override defaults and add new args
for key in kwargs.keys():
args[key] = kwargs[key]
# Construct arguments string and final execution string
arg_string = create_arg_string(args)
arg_string = "binary_c {}".format(arg_string)
# print(arg_string)
# Run it and get output
buffer = ""
output = binary_c_python_api.run_binary_with_log(arg_string)
return output
def parse_output(output, selected_header):
"""
Function that parses output of binary_c:
This function works in two cases:
if the caught line contains output like 'example_header time=12.32 mass=0.94 ..'
or if the line contains output like 'example_header 12.32 0.94'
You can give a 'selected_header' to catch any line that starts with that.
Then the values will be put into a dictionary.
TODO: Think about exporting to numpy array or pandas instead of a defaultdict
"""
value_dicts = []
val_lists = []
# split output on newlines
for i, line in enumerate(output.split("\n")):
# Skip any blank lines
if not line == "":
split_line = line.split()
# Select parts
header = split_line[0]
values_list = split_line[1:]
# print(values_list)
# Catch line starting with selected header
if header == selected_header:
# Check if the line contains '=' symbols:
value_dict = {}
if all('=' in el for el in values_list):
for el in values_list:
key, val = el.split("=")
value_dict[key.strip()] = val.strip()
value_dicts.append(value_dict)
else:
if any('=' in el for el in values_list):
raise ValueError('Caught line contains some = symbols but not all of them do. aborting run')
else:
for i, val in enumerate(values_list):
value_dict[i] = val
value_dicts.append(value_dict)
if len(value_dicts) == 0:
print(
"Sorry, didnt find any line matching your header {}".format(selected_header)
)
return None
keys = value_dicts[0].keys()
# Construct final dict.
final_values_dict = defaultdict(list)
for value_dict in value_dicts:
for key in keys:
final_values_dict[key].append(value_dict[key])
return final_values_dict
def load_logfile(logfile):
with open(logfile, 'r') as f:
logfile_data = f.readlines()
time_list = []
m1_list = []
m2_list = []
k1_list = []
k2_list = []
sep_list = []
ecc_list = []
rel_r1_list = []
rel_r2_list = []
event_list = []
random_seed = logfile_data[0].split()[-2]
random_count = logfile_data[0].split()[-1]
probability = logfile_data[-1].split()
for line in logfile_data[1:-1]:
split_line = line.split()
time_list.append(split_line[0])
m1_list.append(split_line[1])
m2_list.append(split_line[2])
k1_list.append(split_line[3])
k2_list.append(split_line[4])
sep_list.append(split_line[5])
ecc_list.append(split_line[6])
rel_r1_list.append(split_line[7])
rel_r2_list.append(split_line[8])
event_list.append(' '.join(split_line[9:]))
print(event_list)
\ No newline at end of file
#!/usr/bin/python3
import os
import sys
import binary_c_python_api
from binarycpython.utils.functions import run_system, parse_output
from binarycpython.utils.custom_logging_functions import (
autogen_C_logging_code,
binary_c_log_code,
)
"""
Very basic scripts to run a binary system and print the output.
Use these as inspiration/base.
"""
def run_example_binary():
"""
Function to run a binary system. Very basic approach which directly adresses the run_binary(..) python-c wrapper function.
"""
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 # Myr. You need to include this argument.
#
argstring = "binary_c M_1 {m1} M_2 {m2} separation {separation} orbital_period {orbital_period} \
eccentricity {eccentricity} metallicity {metallicity} \
max_evolution_time {max_evolution_time}".format(
m1=m1,
m2=m2,
separation=separation,
orbital_period=orbital_period,
eccentricity=eccentricity,
metallicity=metallicity,
max_evolution_time=max_evolution_time,
)
output = binary_c_python_api.run_binary(argstring)
print(output)
run_example_binary()
def run_example_binary_with_run_system():
"""
This function serves as an example on the function run_system and parse_output.
There is more functionality with this method and several tasks are done behind the scene.
Requires pandas, numpy to run.
run_system: mostly just makes passing arguments to the function easier. It also loads all the necessary defaults in the background
parse_output: Takes the raw output of binary_c and selects those lines that start with the given header.
Note, if you dont use the custom_logging functionality binary_c should be configured to have output that starts with that given header
The parsing of the output only works correctly if either all of the values are described inline like `mass=<number>' or none of them are.
"""
import pandas as pd
import numpy as np
# Run system. all arguments can be given as optional arguments.
output = run_system(M_1=10, M_2=20, separation=0, orbital_period=100000000000)
# print(output)
# Catch results that start with a given header. (Mind that binary_c has to be configured to print them if your not using a custom logging function)
result_example_header_1 = parse_output(output, selected_header="example_header_1")
result_example_header_2 = parse_output(output, selected_header="example_header_2")
# print(result_example_header_1)
#### Now do whatever you want with it:
# Put it in numpy arrays
# t_res = np.asarray(result_example_header['t'], dtype=np.float64, order='C')
# m_res = np.asarray(result_example_header['mass'], dtype=np.float64, order='C')
# Or put them into a pandas array
# Cast the data into a dataframe.
# This example automatically catches the column names because the binary_c output line is constructed as 'example_header_1 time=<number>..'
df = pd.DataFrame.from_dict(result_example_header_1, dtype=np.float64)
print(df)
# This example has column headers which are numbered, but we can override that with custom headers.
df2 = pd.DataFrame.from_dict(result_example_header_2, dtype=np.float64)
df2.columns=['time', 'mass_1', 'mass_2', 'st1', 'st2', 'sep', 'ecc']
print(df2)
# print(df)
# sliced_df = df[df.t < 1000] # Cut off late parts of evolution
# print(sliced_df[["t","m1"]])
# Some routine to plot.
run_example_binary_with_run_system()
def run_example_binary_with_custom_logging():
"""
Function that will use a automatically generated piece of logging code. Compile it, load it
into memory and run a binary system. See run_system on how several things are done in the background here.
"""
import pandas as pd
import numpy as np
# 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"],}
)
# OR
# 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)'
# Generate entire shared lib code around logging lines
custom_logging_code = binary_c_log_code(logging_line)
# Run system. all arguments can be given as optional arguments. the custom_logging_code is one of them and will be processed automatically.
output = run_system(
M_1=1,
metallicity=0.002,
M_2=0.1,
separation=0,
orbital_period=100000000000,
custom_logging_code=custom_logging_code,
)
# Catch results that start with a given header. (Mind that binary_c has to be configured to print them if your not using a custom logging function)
# DOESNT WORK YET if you have the line autogenerated.
result_example_header = parse_output(output, "MY_STELLAR_DATA")
# Cast the data into a dataframe.
df = pd.DataFrame.from_dict(result_example_header, dtype=np.float64)
# Do whatever you like with the dataframe.
print(df)
run_example_binary_with_custom_logging()
def run_example_binary_with_writing_logfile():
"""
Same as above but when giving the log_filename argument the log filename will be written
"""
import pandas as pd
import numpy as np
import tempfile
# Run system. all arguments can be given as optional arguments.
output = run_system(
M_1=10,
M_2=20,
separation=0,
orbital_period=100000000000,
log_filename=tempfile.gettempdir() + "/test_log.txt",
)
run_example_binary_with_writing_logfile()
\ No newline at end of file
This diff is collapsed.
File added
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment