diff --git a/binarycpython/utils/custom_logging_functions.py b/binarycpython/utils/custom_logging_functions.py index 763b4f143732ab161643c0d6b5a012d596d3513b..18cad734f9385de3ba0addaa97bc04b2471704eb 100644 --- a/binarycpython/utils/custom_logging_functions.py +++ b/binarycpython/utils/custom_logging_functions.py @@ -1,9 +1,13 @@ +""" +Module containing functions for the custom logging functionality. +The functions here make it possible for the user to define binaryc output logs on runtime +""" + import os import textwrap import subprocess import socket import ctypes -import random import uuid from binarycpython.utils.functions import temp_dir, remove_file @@ -11,24 +15,23 @@ from binarycpython.utils.functions import temp_dir, remove_file def autogen_C_logging_code(logging_dict, verbose=0): """ - Function that autogenerates PRINTF statements for binaryc. - Input 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 + Function that autogenerates PRINTF statements for binaryc. + Input 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' - ] - } + {'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: + if not isinstance(logging_dict, dict): print("Error: please use a dictionary as input") return None @@ -44,7 +47,7 @@ def autogen_C_logging_code(logging_dict, verbose=0): logging_dict_entry = logging_dict[key] # Check if item is of correct type: - if type(logging_dict_entry) == list: + if isinstance(logging_dict_entry, list): # Construct print statement code += 'Printf("{}'.format(key) @@ -119,8 +122,8 @@ def binary_c_write_log_code(code, filename, verbose=0): print("Writing the custom logging code to {}".format(filePath)) # Write again - with open(filePath, "w") as f: - f.write(code) + with open(filePath, "w") as file: + file.write(code) def from_binary_c_config(config_file, flag): @@ -165,7 +168,6 @@ def return_compilation_dict(verbose=0): # 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") @@ -195,14 +197,15 @@ def return_compilation_dict(verbose=0): "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 + # 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"] + # debug = defaults["debug"] inc = defaults[ "inc" ] # = ($ENV{BINARY_GRID2_INC} // $defaults{inc}).' '.($ENV{BINARY_GRID2_EXTRAINC} // ''); @@ -211,7 +214,8 @@ def return_compilation_dict(verbose=0): ] # = ($ENV{BINARY_GRID2_LIBS} // $defaults{libs}).' '.($ENV{BINARY_GRID2_EXTRALIBS}//''); ccflags = defaults[ "ccflags" - ] # = $ENV{BINARY_GRID2_CCFLAGS} // ($defaults{ccflags}) . ($ENV{BINARY_GRID2_EXTRACCFLAGS} // ''); + ] # = $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" @@ -240,7 +244,6 @@ def return_compilation_dict(verbose=0): def compile_shared_lib(code, sourcefile_name, outfile_name, verbose=0): """ Function to write the custom logging code to a file and then compile it. - TODO: nicely put in the -fPIC """ @@ -289,7 +292,7 @@ def compile_shared_lib(code, sourcefile_name, outfile_name, verbose=0): def create_and_load_logging_function(custom_logging_code, verbose=0): """ - Function to automatically compile the shared library with the given + Function to automatically compile the shared library with the given custom logging code and load it with ctypes returns: @@ -328,7 +331,8 @@ def create_and_load_logging_function(custom_logging_code, verbose=0): if verbose > 0: print( - "loaded shared library for custom logging. custom_output_function is loaded in memory at {}".format( + "loaded shared library for custom logging. \ + custom_output_function is loaded in memory at {}".format( func_memaddr ) ) diff --git a/binarycpython/utils/distribution_functions.py b/binarycpython/utils/distribution_functions.py index f5f73e04e5360837a5d041e17cf66e2ff6e60b74..409c651fd08af96471e5a87e3fc8acb4926393c4 100644 --- a/binarycpython/utils/distribution_functions.py +++ b/binarycpython/utils/distribution_functions.py @@ -1,5 +1,16 @@ +""" +Module containing the predefined distribution functions + +The user can use any of these distribution functions to +generate probability distributions for sampling populations +""" + + import math -import binarycpython.utils.useful_funcs + +from binarycpython.utils.useful_funcs import ( + calc_period_from_sep, +) ### # File containing probability distributions @@ -11,12 +22,11 @@ import binarycpython.utils.useful_funcs # TODO: make global constants stuff # TODO: make description of module submodule -log_ln_converter = 1.0 / math.log(10.0) +LOG_LN_CONVERTER = 1.0 / math.log(10.0) - -def flat(parameter): +def flat(): """ - Dummt distribution function that returns 1 + Dummy distribution function that returns 1 """ return 1 @@ -26,6 +36,7 @@ def number(value): """ Dummy distribution function that returns the input """ + return value @@ -59,21 +70,25 @@ def powerlaw(min_val, max_val, k, x): print("input value is out of bounds!") return 0 - else: - const = powerlaw_constant(min_val, max_val, k) + powerlaw_const = powerlaw_constant(min_val, max_val, k) - # powerlaw - y = const * (x ** k) - # print( - # "Power law from {} to {}: const = {}, y = {}".format( - # min_val, max_val, const, y - # ) - # ) - return y + # powerlaw + prob = powerlaw_const * (x ** k) + # print( + # "Power law from {} to {}: const = {}, y = {}".format( + # min_val, max_val, const, y + # ) + # ) + return prob def calculate_constants_three_part_powerlaw(m0, m1, m2, m_max, p1, p2, p3): - # print("Initialising constants for the three-part powerlaw: m0={} m1={} m2={} m_max={} p1={} p2={} p3={}\n".format(m0, m1, m2, m_max, p1, p2, p3)) + """ + Function to calculate the constants for a three-part powerlaw + """ + + # print("Initialising constants for the three-part powerlaw: m0={} m1={} m2={}\ + # m_max={} p1={} p2={} p3={}\n".format(m0, m1, m2, m_max, p1, p2, p3)) array_constants_three_part_powerlaw = [0, 0, 0] @@ -130,17 +145,17 @@ def three_part_powerlaw(M, M0, M1, M2, M_MAX, P1, P2, P3): # if M < M0: - p = 0 # Below lower bound + prob = 0 # Below lower bound elif M0 < M <= M1: - p = three_part_powerlaw_constants[0] * (M ** P1) # Between M0 and M1 + prob = three_part_powerlaw_constants[0] * (M ** P1) # Between M0 and M1 elif M1 < M <= M2: - p = three_part_powerlaw_constants[1] * (M ** P2) # Between M1 and M2 + prob = three_part_powerlaw_constants[1] * (M ** P2) # Between M1 and M2 elif M2 < M <= M_MAX: - p = three_part_powerlaw_constants[2] * (M ** P3) # Between M2 and M_MAX + prob = three_part_powerlaw_constants[2] * (M ** P3) # Between M2 and M_MAX else: - p = 0 # Above M_MAX + prob = 0 # Above M_MAX - return p + return prob def const(min_bound, max_bound, val=None): @@ -149,12 +164,12 @@ def const(min_bound, max_bound, val=None): """ if val: - if not (min_bound < val <= max_bound): + if not min_bound < val <= max_bound: print("out of bounds") - p = 0 - return p - p = 1.0 / (min_bound - max_bound) - return p + prob = 0 + return prob + prob = 1.0 / (min_bound - max_bound) + return prob def set_opts(opts, newopts): @@ -168,29 +183,29 @@ def set_opts(opts, newopts): if newopts: for opt in newopts.keys(): if opt in opts.keys(): - opts[key] = newopts[key] + opts[opt] = newopts[opt] - return opt + return opts def gaussian(x, mean, sigma, gmin, gmax): """ Gaussian distribution function. used for e..g Duquennoy + Mayor 1991 - + Input: location, mean, sigma, min and max: """ # # location (X value), mean and sigma, min and max range # my ($x,$mean,$sigma,$gmin,$gmax) = @_; if (x < gmin) or (x > gmax): - p = 0 + prob = 0 else: # normalize over given range # TODO: add loading into global var normalisation = gaussian_normalizing_const(mean, sigma, gmin, gmax) - p = normalisation * gaussian_func(x, mean, sigma) + prob = normalisation * gaussian_func(x, mean, sigma) - return p + return prob def gaussian_normalizing_const(mean, sigma, gmin, gmax): @@ -215,11 +230,11 @@ def gaussian_func(x, mean, sigma): """ Function to evaluate a gaussian at a given point """ - GAUSSIAN_PREFACTOR = 1.0 / math.sqrt(2.0 * math.pi) + gaussian_prefactor = 1.0 / math.sqrt(2.0 * math.pi) r = 1.0 / (sigma) y = (x - mean) * r - return GAUSSIAN_PREFACTOR * r * math.exp(-0.5 * y ** 2) + return gaussian_prefactor * r * math.exp(-0.5 * y ** 2) ##### @@ -229,9 +244,9 @@ def gaussian_func(x, mean, sigma): def Kroupa2001(m, newopts=None): """ - Probability distribution function for kroupa 2001 IMF + Probability distribution function for kroupa 2001 IMF - Input: Mass, (and optional: dict of new options. Input the + Input: Mass, (and optional: dict of new options. Input the default = {'m0':0.1, 'm1':0.5, 'm2':1, 'mmax':100, 'p1':-1.3, 'p2':-2.3, 'p3':-2.3} """ @@ -245,9 +260,12 @@ def Kroupa2001(m, newopts=None): "p2": -2.3, "p3": -2.3, } + value_dict = default.copy() + if newopts: value_dict.update(newopts) + return three_part_powerlaw( m, value_dict["m0"], @@ -291,7 +309,9 @@ def ktg93(m, newopts): # } # set options - # opts = set_opts({'m0':0.1, 'm1':0.5, 'm2':1.0, 'mmax':80, 'p1':-1.3, 'p2':-2.2, 'p3':-2.7}, newopts) + # opts = set_opts({'m0':0.1, 'm1':0.5, 'm2':1.0, 'mmax':80, 'p1':-1.3, 'p2':-2.2, 'p3':-2.7}, + # newopts) + defaults = { "m0": 0.1, "m1": 0.5, @@ -301,9 +321,11 @@ def ktg93(m, newopts): "p2": -2.2, "p3": -2.7, } - value_dict = default.copy() + value_dict = defaults.copy() + if newopts: value_dict.update(newopts) + return three_part_powerlaw( m, value_dict["m0"], @@ -348,34 +370,36 @@ def imf_scalo1998(m): def imf_chabrier2003(m): """ - # IMF of Chabrier 2003 PASP 115:763-795 + IMF of Chabrier 2003 PASP 115:763-795 """ - Chabrier_logmc = math.log10(0.079) - Chabrier_sigma2 = 0.69 * 0.69 - Chabrier_A1 = 0.158 - Chabrier_A2 = 4.43e-2 - Chabrier_x = -1.3 + chabrier_logmc = math.log10(0.079) + chabrier_sigma2 = 0.69 * 0.69 + chabrier_a1 = 0.158 + chabrier_a2 = 4.43e-2 + chabrier_x = -1.3 if m < 0: print("below bounds") raise ValueError if 0 < m < 1.0: A = 0.158 - dm = math.log10(m) - Chabrier_logmc - p = Chabrier_A1 * math.exp(-(dm ** 2) / (2.0 * Chabrier_sigma2)) + dm = math.log10(m) - chabrier_logmc + prob = chabrier_a1 * math.exp(-(dm ** 2) / (2.0 * chabrier_sigma2)) else: - p = Chabrier_A2 * (m ** Chabrier_x) - p = p / (0.1202462 * m * math.log(10)) - return p + prob = chabrier_a2 * (m ** chabrier_x) + prob = prob / (0.1202462 * m * math.log(10)) + return prob ######################################################################## # Binary fractions ######################################################################## def Arenou2010_binary_fraction(m): - # Arenou 2010 function for the binary fraction as f(M1) - # - # GAIA-C2-SP-OPM-FA-054 - # www.rssd.esa.int/doc_fetch.php?id=2969346 + """ + Arenou 2010 function for the binary fraction as f(M1) + + GAIA-C2-SP-OPM-FA-054 + www.rssd.esa.int/doc_fetch.php?id=2969346 + """ return 0.8388 * math.tanh(0.688 * m + 0.079) @@ -385,14 +409,14 @@ def Arenou2010_binary_fraction(m): def raghavan2010_binary_fraction(m): """ - Fit to the Raghavan 2010 binary fraction as a function of + Fit to the Raghavan 2010 binary fraction as a function of spectral type (Fig 12). Valid for local stars (Z=Zsolar). - - The spectral type is converted mass by use of the ZAMS + + The spectral type is converted mass by use of the ZAMS effective temperatures from binary_c/BSE (at Z=0.02) and the new "long_spectral_type" function of binary_c (based on Jaschek+Jaschek's Teff-spectral type table). - + Rob then fitted the result """ @@ -422,10 +446,10 @@ def duquennoy1991(x): return gaussian(x, 4.8, 2.3, -2, 12) -def sana12(M1, M2, a, P, amin, amax, x0, x1, p): # TODO: ? wtf. vague input +def sana12(M1, M2, a, P, amin, amax, x0, x1, p): """ distribution of initial orbital periods as found by Sana et al. (2012) - which is a flat distribution in ln(a) and ln(P) respectively for stars + which is a flat distribution in ln(a) and ln(P) respectively for stars * less massive than 15Msun (no O-stars) * mass ratio q=M2/M1<0.1 * log(P)<0.15=x0 and log(P)>3.5=x1 @@ -434,6 +458,9 @@ def sana12(M1, M2, a, P, amin, amax, x0, x1, p): # TODO: ? wtf. vague input arguments are M1, M2, a, Period P, amin, amax, x0=log P0, x1=log P1, p example args: 10, 5, ?, P, ?, ?, -2, 12, -0.55 + + # TODO: ? wtf. vague input + # TODO: Fix this function! """ res = 0 @@ -443,11 +470,12 @@ def sana12(M1, M2, a, P, amin, amax, x0, x1, p): # TODO: ? wtf. vague input else: p1 = 1.0 + p - # For more details see the LyX document of binary_c for this distribution where the variables and normalizations are given + # For more details see the LyX document of binary_c for this distribution + # where the variables and normalizations are given # we use the notation x=log(P), xmin=log(Pmin), x0=log(P0), ... to determine the - x = log_ln_converter * math.log(p) - xmin = log_ln_converter * math.log(calc_period_from_sep(m1, m2, amin)) - xmin = log_ln_converter * math.log(calc_period_from_sep(m1, m2, amax)) + x = LOG_LN_CONVERTER * math.log(p) + xmin = LOG_LN_CONVERTER * math.log(calc_period_from_sep(M1, M2, amin)) + xmax = LOG_LN_CONVERTER * math.log(calc_period_from_sep(M1, M2, amax)) # my $x0 = 0.15; # my $x1 = 3.5; @@ -459,11 +487,11 @@ def sana12(M1, M2, a, P, amin, amax, x0, x1, p): # TODO: ? wtf. vague input A2 = A1 * x1 ** p if x < x0: - res = 3.0 / 2.0 * log_ln_converter * A0 + res = 3.0 / 2.0 * LOG_LN_CONVERTER * A0 elif x > x1: - res = 3.0 / 2.0 * log_ln_converter * A2 + res = 3.0 / 2.0 * LOG_LN_CONVERTER * A2 else: - res = 3.0 / 2.0 * log_ln_converter * A1 * x ** p + res = 3.0 / 2.0 * LOG_LN_CONVERTER * A1 * x ** p return res @@ -477,9 +505,11 @@ def sana12(M1, M2, a, P, amin, amax, x0, x1, p): # TODO: ? wtf. vague input def flatsections(x, opts): """ - - opts = list of dicts with settings for the flat sections + Function to generate flat distributions, possibly in multiple sections + + opts: list of dicts with settings for the flat sections x: location to calculate the y value + TODO: figure out why it has to be a list of dict. why not just 1 """ @@ -488,7 +518,8 @@ def flatsections(x, opts): for opt in opts: dc = (opt["max"] - opt["min"]) * opt["height"] - # print("added flatsection ({}-{})*{} = {}\n".format(opt['max'], opt['min'], opt['height'], dc)) + # print("added flatsection ({}-{})*{} = {}\n".format( + # opt['max'], opt['min'], opt['height'], dc)) c += dc if opt["min"] <= x <= opt["max"]: y = opt["height"] diff --git a/binarycpython/utils/functions.py b/binarycpython/utils/functions.py index 516235312a79cffe5d4a9255af09056a2214c230..1d06b1660d62780d66b4c579d83ccb5682b634b5 100644 --- a/binarycpython/utils/functions.py +++ b/binarycpython/utils/functions.py @@ -1,12 +1,18 @@ -import copy +""" +Module containing most of the utility functions for the binarycpython package + +Functions here are mostly functions used in other classes/functions, or +useful functions for the user +""" + import json import os -import h5py import tempfile - from collections import defaultdict +import h5py import numpy as np + import binary_c_python_api @@ -29,7 +35,8 @@ def remove_file(file, verbose=0): def temp_dir(): """ - Function to return the path the custom logging library shared object and script will be written to. + 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+ """ @@ -45,104 +52,108 @@ def temp_dir(): def output_lines(output): """ - Function that outputs the lines that were recieved from the binary_c run. + Function that outputs the lines that were recieved from the binary_c run. """ return output.splitlines() def parse_binary_c_version_info(version_info_string): + """ + Function that parses the binary_c version info. Length function with a lot of branches + """ + version_info_dict = {} - for el in version_info_string.splitlines(): - el = el.strip() - if el == "": + for line in version_info_string.splitlines(): + line = line.strip() + if line == "": continue - if " is " in el: - split = el.split(" is ") + if " is " in line: + split = line.split(" is ") version_info_dict[split[0].strip()] = split[1].strip() else: - if el.startswith("Binary_c/nucsyn"): - version_info_dict["intro"] = el - elif el.startswith("Email"): - emails = el.split("Email ")[1].split(",") + if line.startswith("Binary_c/nucsyn"): + version_info_dict["intro"] = line + elif line.startswith("Email"): + emails = line.split("Email ")[1].split(",") cleaned_emails = [email.strip() for email in emails] version_info_dict["emails"] = cleaned_emails - elif el.startswith("DTlimit"): - split = el.split(" : ") + elif line.startswith("DTlimit"): + split = line.split(" : ") version_info_dict[split[0]] = ": ".join(split[1:]) - elif el.startswith("Version"): - split = el.split("Version ") + elif line.startswith("Version"): + split = line.split("Version ") version_number = split[1] version_info_dict["version_number"] = version_number - elif el.startswith("git URL"): - split = el.split("git URL ") + elif line.startswith("git URL"): + split = line.split("git URL ") git_url = split[1] version_info_dict["git_url"] = git_url - elif el.startswith("Build: "): - split = el.split("Build: ") + elif line.startswith("Build: "): + split = line.split("Build: ") build = split[1] version_info_dict["build"] = build - elif el.startswith("Compiled for "): - split = el.split("Compiled for ") + elif line.startswith("Compiled for "): + split = line.split("Compiled for ") compiled_for = split[1] version_info_dict["compiled_for"] = compiled_for - elif el.startswith("Stack limit "): - split = el.split("Stack limit ") + elif line.startswith("Stack limit "): + split = line.split("Stack limit ") stack_limit = split[1] version_info_dict["stack_limit"] = stack_limit - elif el.startswith("SVN URL "): - split = el.split("SVN URL ") + elif line.startswith("SVN URL "): + split = line.split("SVN URL ") svn_url = split[1] version_info_dict["svn_url"] = svn_url - elif el.startswith("git branch "): - split = el.split("git branch ") + elif line.startswith("git branch "): + split = line.split("git branch ") git_branch = split[1] version_info_dict["git_branch"] = git_branch - elif el.startswith("_SC_CLK_TCK"): - split = el.split(" = ") + elif line.startswith("_SC_CLK_TCK"): + split = line.split(" = ") _SC_CLK_TCK = split[1] version_info_dict["_SC_CLK_TCK"] = _SC_CLK_TCK - elif el.startswith("Random number mean "): - split = el.split("Random number mean ") + elif line.startswith("Random number mean "): + split = line.split("Random number mean ") random_number_mean = split[1] version_info_dict["Random number mean"] = random_number_mean - elif el.startswith("SVN revision "): - split = el.split("SVN revision ") + elif line.startswith("SVN revision "): + split = line.split("SVN revision ") svn_revision = split[1] version_info_dict["svn_revision"] = svn_revision - elif el.startswith("Size of :"): - split = el.split("Size of :") + elif line.startswith("Size of :"): + split = line.split("Size of :") data_type_sizes = split[1] version_info_dict["data_type_sizes"] = data_type_sizes - elif el.startswith("git revision "): - split = el.split("git revision ") + elif line.startswith("git revision "): + split = line.split("git revision ") git_revision = split[1] version_info_dict["git_revision"] = git_revision - elif el.startswith("BINARY_C_PRE_VERSION "): - split = el.split("BINARY_C_PRE_VERSION ") + elif line.startswith("BINARY_C_PRE_VERSION "): + split = line.split("BINARY_C_PRE_VERSION ") binary_c_pre_version = split[1] version_info_dict["binary_c_pre_version"] = binary_c_pre_version - elif el.startswith("Comenv accretion:"): - split = el.split("Comenv accretion:") + elif line.startswith("Comenv accretion:"): + split = line.split("Comenv accretion:") comenv_accretion = split[1] version_info_dict["comenv_accretion"] = comenv_accretion - elif el.startswith("Compiled in parameters:"): - split = el.split("Compiled in parameters:") + elif line.startswith("Compiled in parameters:"): + split = line.split("Compiled in parameters:") compiled_in_parameters = split[1] version_info_dict["compiled_in_parameters"] = compiled_in_parameters - elif el.startswith("__short__ is"): - split = el.split("__short__ is") + elif line.startswith("__short__ is"): + split = line.split("__short__ is") short_type = split[1] version_info_dict["short_type"] = short_type else: - print("Still found unmatched items!:\n{}".format(repr(el))) + print("Still found unmatched items!:\n{}".format(repr(line))) return version_info_dict def create_hdf5(data_dir, name): """ - Function to create an hdf5 file from the contents of a directory: + Function to create an hdf5 file from the contents of a directory: - settings file is selected by checking on files ending on settings - data files are selected by checking on files ending with .dat @@ -153,7 +164,7 @@ def create_hdf5(data_dir, name): # Create the file hdf5_filename = os.path.join(data_dir, "{}".format(name)) print("Creating {}".format(hdf5_filename)) - f = h5py.File(hdf5_filename, "w") + hdf5_file = h5py.File(hdf5_filename, "w") # Get content of data_dir content_data_dir = os.listdir(data_dir) @@ -170,7 +181,7 @@ def create_hdf5(data_dir, name): settings_json = json.load(settings_file) # Create settings group - settings_grp = f.create_group("settings") + settings_grp = hdf5_file.create_group("settings") # Write version_string to settings_group settings_grp.create_dataset("used_settings", data=json.dumps(settings_json)) @@ -181,7 +192,7 @@ def create_hdf5(data_dir, name): print("Adding data to HDF5 file") # Create the data group - data_grp = f.create_group("data") + data_grp = hdf5_file.create_group("data") # Write the data to the file: # Make sure: @@ -201,12 +212,13 @@ def create_hdf5(data_dir, name): data = np.loadtxt(full_path, skiprows=1) data_grp.create_dataset(base_name, data=data) - f.close() + hdf5_file.close() -def get_help_super(print_help=False, return_dict=True, fail_silently=True): +def get_help_super(print_help=False, fail_silently=True): """ - Function that first runs get_help_all, and then per argument also run the help function to get as much information as possible. + Function that first runs get_help_all, and then per argument also run + the help function to get as much information as possible. """ # Get help_all information @@ -215,7 +227,7 @@ def get_help_super(print_help=False, return_dict=True, fail_silently=True): help_all_super_dict = help_all_dict.copy() # Loop over all sections and stuff - for section_name in help_all_dict.keys(): + for section_name in help_all_dict: section = help_all_dict[section_name] for parameter_name in section["parameters"].keys(): @@ -251,17 +263,15 @@ def get_help_super(print_help=False, return_dict=True, fail_silently=True): if print_help: # TODO: make a pretty print print(json.dumps(help_all_super_dict, indent=4)) - pass - if return_dict: - return help_all_super_dict + return help_all_super_dict -def get_help_all(print_help=True, return_dict=False): +def get_help_all(print_help=True): """ Function that reads out the output of the help_all api call to binary_c - prints all the parameters and their descriptions. + print_help: bool, prints all the parameters and their descriptions. return_dict: returns a dictionary """ @@ -303,10 +313,11 @@ def get_help_all(print_help=True, return_dict=False): split_params = params[0].strip().replace("\n ", " ").split("\n") # Process params and descriptions per section - for el in split_params: - split_param_info = el.split(" : ") + for split_param in split_params: + split_param_info = split_param.split(" : ") if not len(split_param_info) == 3: - # there are ocassions where the semicolon is used in the description text itself. + # there are ocassions where the semicolon + # is used in the description text itself. if len(split_param_info) == 4: split_param_info = [ split_param_info[0], @@ -353,16 +364,14 @@ def get_help_all(print_help=True, return_dict=False): ) ) - # Loop over all the parameters an call the help() function on it. Takes a long time but this is for testing + # # Loop over all the parameters an call the help() function on it. + # # Takes a long time but this is for testing # for section in help_all_dict.keys(): # section_dict = help_all_dict[section] # for param in section_dict['parameters'].keys(): # get_help(param) - if return_dict: - return help_all_dict - else: - return None + return help_all_dict def filter_arg_dict(arg_dict): @@ -383,13 +392,13 @@ def filter_arg_dict(arg_dict): def create_arg_string(arg_dict, sort=False, filter_values=False): """ - Function that creates the arg string + Function that creates the arg string for binary_c. Options: - sort: sort the order of the keys - filter_values: filters the input dict on keys that have NULL or `function` as value - + sort: sort the order of the keys. + filter_values: filters the input dict on keys that have NULL or `function` as value. """ + arg_string = "" if filter_values: @@ -404,9 +413,10 @@ def create_arg_string(arg_dict, sort=False, filter_values=False): def get_defaults(filter_values=False): """ - Function that calls the binaryc get args function and cast it into a dictionary + Function that calls the binaryc get args function and cast it into a dictionary. + All the values are strings - + filter_values: whether to filter out NULL and Function defaults. """ @@ -432,18 +442,18 @@ def get_arg_keys(): return get_defaults().keys() -def get_help(param_name="", print_help=True, return_dict=False, fail_silently=False): +def get_help(param_name="", print_help=True, fail_silently=False): """ - Function that returns the help info for a given parameter. + Function that returns the help info for a given parameter. Binary_c will output things in the following order; - Did you mean? - binary_c help for variable - - default + - default - available macros This function reads out that structure and catches the different components of this output - + Will print a dict return_dict: wether to return the help info dictionary @@ -459,76 +469,76 @@ def get_help(param_name="", print_help=True, return_dict=False, fail_silently=Fa ) ) return None - else: - if param_name in available_arg_keys: - help_info = binary_c_python_api.return_help(param_name) - cleaned = [el for el in help_info.split("\n") if not el == ""] - - # Get line numbers - did_you_mean_nr = [ - i for i, el in enumerate(cleaned) if el.startswith("Did you mean") - ] - parameter_line_nr = [ - i for i, el in enumerate(cleaned) if el.startswith("binary_c help") - ] - default_line_nr = [ - i for i, el in enumerate(cleaned) if el.startswith("Default") - ] - macros_line_nr = [ - i for i, el in enumerate(cleaned) if el.startswith("Available") - ] - - help_info_dict = {} - - # Get alternatives - if did_you_mean_nr: - alternatives = cleaned[did_you_mean_nr[0] + 1 : parameter_line_nr[0]] - alternatives = [el.strip() for el in alternatives] - help_info_dict["alternatives"] = alternatives - - # Information about the parameter - parameter_line = cleaned[parameter_line_nr[0]] - parameter_name = parameter_line.split(":")[1].strip().split(" ")[0] - parameter_value_input_type = ( - " ".join(parameter_line.split(":")[1].strip().split(" ")[1:]) - .replace("<", "") - .replace(">", "") - ) - help_info_dict["parameter_name"] = parameter_name - help_info_dict["parameter_value_input_type"] = parameter_value_input_type + if param_name in available_arg_keys: + help_info = binary_c_python_api.return_help(param_name) + cleaned = [el for el in help_info.split("\n") if not el == ""] + + # Get line numbers + did_you_mean_nr = [ + i for i, el in enumerate(cleaned) if el.startswith("Did you mean") + ] + parameter_line_nr = [ + i for i, el in enumerate(cleaned) if el.startswith("binary_c help") + ] + default_line_nr = [ + i for i, el in enumerate(cleaned) if el.startswith("Default") + ] + macros_line_nr = [ + i for i, el in enumerate(cleaned) if el.startswith("Available") + ] + + help_info_dict = {} + + # Get alternatives + if did_you_mean_nr: + alternatives = cleaned[did_you_mean_nr[0] + 1 : parameter_line_nr[0]] + alternatives = [el.strip() for el in alternatives] + help_info_dict["alternatives"] = alternatives + + # Information about the parameter + parameter_line = cleaned[parameter_line_nr[0]] + parameter_name = parameter_line.split(":")[1].strip().split(" ")[0] + parameter_value_input_type = ( + " ".join(parameter_line.split(":")[1].strip().split(" ")[1:]) + .replace("<", "") + .replace(">", "") + ) - description_line = " ".join( - cleaned[parameter_line_nr[0] + 1 : default_line_nr[0]] - ) - help_info_dict["description"] = description_line + help_info_dict["parameter_name"] = parameter_name + help_info_dict["parameter_value_input_type"] = parameter_value_input_type - # Default: - default_line = cleaned[default_line_nr[0]] - default_value = default_line.split(":")[-1].strip() + description_line = " ".join( + cleaned[parameter_line_nr[0] + 1 : default_line_nr[0]] + ) + help_info_dict["description"] = description_line - help_info_dict["default"] = default_value + # Default: + default_line = cleaned[default_line_nr[0]] + default_value = default_line.split(":")[-1].strip() - # Get Macros: - if macros_line_nr: - macros = cleaned[macros_line_nr[0] + 1 :] - help_info_dict["macros"] = macros + help_info_dict["default"] = default_value - if print_help: - for key in help_info_dict.keys(): - print("{}:\n\t{}".format(key, help_info_dict[key])) + # Get Macros: + if macros_line_nr: + macros = cleaned[macros_line_nr[0] + 1 :] + help_info_dict["macros"] = macros - if return_dict: - return help_info_dict + if print_help: + for key in help_info_dict: + print("{}:\n\t{}".format(key, help_info_dict[key])) - else: - if not fail_silently: - print( - "{} is not a valid parameter name. Please choose from the following parameters:\n\t{}".format( - param_name, list(available_arg_keys) - ) + return help_info_dict + + else: + if not fail_silently: + print( + "{} is not a valid parameter name. Please choose from the \ + following parameters:\n\t{}".format( + param_name, list(available_arg_keys) ) - return None + ) + return None def parse_output(output, selected_header): @@ -539,17 +549,16 @@ def parse_output(output, selected_header): 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. + 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")): + for line in output.split("\n"): # Skip any blank lines if not line == "": split_line = line.split() @@ -563,20 +572,21 @@ def parse_output(output, 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("=") + if all("=" in value for value in values_list): + for value in values_list: + key, val = value.split("=") value_dict[key.strip()] = val.strip() value_dicts.append(value_dict) else: - if any("=" in el for el in values_list): + if any("=" in value for value in values_list): raise ValueError( - "Caught line contains some = symbols but not all of them do. aborting run" + "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) + + for j, val in enumerate(values_list): + value_dict[j] = val + value_dicts.append(value_dict) if len(value_dicts) == 0: print( @@ -597,11 +607,11 @@ def parse_output(output, selected_header): def load_logfile(logfile): """ - Function that parses the generated logfile of binary_c + Experimental function that parses the generated logfile of binary_c. """ - with open(logfile, "r") as f: - logfile_data = f.readlines() + with open(logfile, "r") as file: + logfile_data = file.readlines() time_list = [] m1_list = [] @@ -614,9 +624,9 @@ def load_logfile(logfile): rel_r2_list = [] event_list = [] - random_seed = logfile_data[0].split()[-2] - random_count = logfile_data[0].split()[-1] - probability = logfile_data[-1].split() + # 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() diff --git a/binarycpython/utils/grid.py b/binarycpython/utils/grid.py index a7c75e57a655bacff09d0e59631911e3fa2a37af..190ff83d421817ea4bddfe9ad576fce3d85e429c 100644 --- a/binarycpython/utils/grid.py +++ b/binarycpython/utils/grid.py @@ -1,39 +1,44 @@ +""" +Module containing the Population grid class object. + +Here all the functionality of a Population object is defined. + +Useful for the user to understand the functionality, +but copying functionality isn't recommended except if you know what you are doing +""" + import os import copy import json -import sys import datetime import time -import random -import inspect +import argparse +import importlib.util -import numpy as np -import multiprocessing as mp +from pathos.helpers import mp as pathos_multiprocess +# from pathos.multiprocessing import ProcessingPool as Pool +from pathos.pools import _ProcessPool as Pool -from pathos.multiprocessing import ProcessingPool as Pool - -import binary_c_python_api -import binarycpython from binarycpython.utils.grid_options_defaults import grid_options_defaults_dict from binarycpython.utils.custom_logging_functions import ( autogen_C_logging_code, binary_c_log_code, create_and_load_logging_function, - temp_dir, ) from binarycpython.utils.functions import ( get_defaults, parse_binary_c_version_info, - output_lines, remove_file, filter_arg_dict, + get_help_all, ) +import binary_c_python_api + # Todo-list # TODO: add functionality to 'on-init' set arguments - # TODO: add functionality to return the initial_abundance_hash # TODO: add functionality to return the isotope_hash # TODO: add functionality to return the isotope_list @@ -44,21 +49,29 @@ from binarycpython.utils.functions import ( # TODO: change the grid_options dict structure so that there is room for descriptions # TODO: consider spreading the functions over more files. -# Make this function also an API call. Doest seem to get written to a buffer that is stored into a python object. rather its just written to stdout +# Make this function also an API call. Doest seem to get written to a buffer +# that is stored into a python object. rather its just written to stdout + +class Population(): + """ + Population Object. Contains all the necessary functions to set up, run and process a + population of systems + """ -class Population(object): def __init__(self): """ Initialisation function of the population class """ self.defaults = get_defaults() + self.cleaned_up_defaults = self.cleanup_defaults() # Different sections of options self.bse_options = ( {} - ) # bse_options is just empty. Setting stuff will check against the defaults to see if the input is correct. + ) # bse_options is just empty. + # Setting stuff will check against the defaults to see if the input is correct. self.grid_options = grid_options_defaults_dict.copy() self.custom_options = {} @@ -74,33 +87,40 @@ class Population(object): # General flow of generating the arguments for the binary_c call: # - user provides parameter and value via set (or manually but that is risky) - # - The parameter names of these input get compared to the parameter names in the self.defaults; with this, we know that its a valid - # parameter to give to binary_c. + # - The parameter names of these input get compared to the parameter names in the self.defaults; + # with this, we know that its a valid parameter to give to binary_c. # - For a single system, the bse_options will be written as a arg line - # - For a population the bse_options will get copied to a temp_bse_options dict and updated with all the parameters generated by the grid + # - For a population the bse_options will get copied to a temp_bse_options dict and updated with + # all the parameters generated by the grid - # I will NOT create the argument line by fully writing ALL the defaults and overriding user input, that seems not necessary - # because by using the get_defaults() function we already know for sure which parameter names are valid for the binary_c version + # I will NOT create the argument line by fully writing ALL the defaults and overriding user + # input, that seems not necessary because by using the get_defaults() function we already + # know for sure which parameter names are valid for the binary_c version # And because binary_c uses internal defaults, its not necessary to explicitly pass them. - # I do however suggest everyone to export the binary_c defaults to a file, so that you know exactly which values were the defaults. - - # TODO: maybe make a set_bse option. + # I do however suggest everyone to export the binary_c defaults to a file, so that you know + # exactly which values were the defaults. def set_bse_option(self, key, arg): + """ + Setter for the BSE options. + """ self.bse_options[key] = arg def set(self, **kwargs): """ - Function to set the values of the population. This is the preferred method to set values of functions, as it - provides checks on the input. + Function to set the values of the population. This is the preferred method to set values + of functions, as it provides checks on the input. + + the bse_options will get populated with all the those that have a key that is present + in the self.defaults + + the grid_options will get updated with all the those that have a key that is present + in the self.grid_options - the bse_options will get populated with all the those that have a key that is present in the self.defaults - the grid_options will get updated with all the those that have a key that is present in the self.grid_options - If neither of above is met; the key and the value get stored in a custom_options dict. """ - for key in kwargs.keys(): + for key in kwargs: # Filter out keys for the bse_options if key in self.defaults.keys(): if self.grid_options["verbose"] > 0: @@ -115,20 +135,18 @@ class Population(object): # The of the keys go into a custom_options dict else: print( - "!! Key doesnt match previously known parameter: adding: {}={} to custom_options".format( - key, kwargs[key] - ) + "!! Key doesnt match previously known parameter: \ + adding: {}={} to custom_options".format(key, kwargs[key]) ) self.custom_options[key] = kwargs[key] def parse_cmdline(self): """ Function to handle settings values via the command line: + TODO: remove the need for --cmdline """ - import argparse - parser = argparse.ArgumentParser() parser.add_argument( "--cmdline", @@ -179,57 +197,62 @@ class Population(object): def generate_population_arglines_file(self, output_file): """ - Function to generate a file that contains all the argument lines that would be given to binary_c if the population had been run + Function to generate a file that contains all the argument lines that would be given to + binary_c if the population had been run + + TODO: Fix this function """ pass def add_grid_variable( - self, - name, - longname, - valuerange, - resolution, - spacingfunc, - probdist, - dphasevol, - parameter_name, - precode=None, - condition=None, + self, + name, + longname, + valuerange, + resolution, + spacingfunc, + probdist, + dphasevol, + parameter_name, + precode=None, + condition=None, ): """spec Function to add grid variables to the grid_options. TODO: Fix this complex function. + TODO: update the descriptiontext - The execution of the grid generation will be through a nested forloop, - and will rely heavily on the eval() functionality of python. Which, in terms of safety is very bad, but in terms of flexibility is very good. + The execution of the grid generation will be through a nested forloop, + and will rely heavily on the eval() functionality of python. Which, in terms of safety is + very bad, but in terms of flexibility is very good. - name: + name: name of parameter - example: name = 'lnm1' - longname: + example: name = 'lnm1' + longname: Long name of parameter example: longname = 'Primary mass' - range: + range: Range of values to take example: range = [log($mmin),log($mmax)] - resolution: + resolution: Resolution of the sampled range (amount of samples) example: resolution = $resolution->{m1} - spacingfunction: + spacingfunction: Function determining how the range is sampled example: spacingfunction = "const(log($mmin),log($mmax),$resolution->{m1})" - precode: + precode: # TODO: think of good description. example: precode = '$m1=exp($lnm1);' - probdist: + probdist: FUnction determining the probability that gets asigned to the sampled parameter example: probdist = 'Kroupa2001($m1)*$m1' - dphasevol: + dphasevol: part of the parameter space that the total probability is calculated with example: dphasevol = '$dlnm1' - condition: + condition: condition that has to be met in order for the grid generation to continue example: condition = '$self->{_grid_options}{binary}==1' """ @@ -262,7 +285,7 @@ class Population(object): """ Function that returns all the options that have been set. - Can be combined with json to make a nice file. + Can be combined with json to make a nice file. """ options = { @@ -293,18 +316,16 @@ class Population(object): return self.defaults def return_all_info( - self, - include_population_settings=True, - include_binary_c_defaults=True, - include_binary_c_version_info=True, - include_binary_c_help_all=True, + self, + include_population_settings=True, + include_binary_c_defaults=True, + include_binary_c_version_info=True, + include_binary_c_help_all=True, ): """ Function that returns all the information about the population and binary_c """ - from binarycpython.utils.functions import get_help_all - # all_info = {} @@ -323,27 +344,29 @@ class Population(object): all_info["binary_c_version_info"] = binary_c_version_info if include_binary_c_help_all: - binary_c_help_all_info = get_help_all(print_help=False, return_dict=True) + binary_c_help_all_info = get_help_all(print_help=False) all_info["binary_c_help_all"] = binary_c_help_all_info return all_info def export_all_info( - self, - use_datadir=True, - outfile=None, - include_population_settings=True, - include_binary_c_defaults=True, - include_binary_c_version_info=True, - include_binary_c_help_all=True, + self, + use_datadir=True, + outfile=None, + include_population_settings=True, + include_binary_c_defaults=True, + include_binary_c_version_info=True, + include_binary_c_help_all=True, ): """ Function that exports the all_info to a json file - TODO: if any of the values in the dicts here is of a not-serializable form, then we need to change that to a string or something - so, use a recursive function that goes over the all_info dict and finds those that fit + TODO: if any of the values in the dicts here is of a not-serializable form, then we need + to change that to a string or something so, use a recursive function that goes over the + all_info dict and finds those that fit TODO: Fix to write things to the directory. which options do which etc + TODO: theres flawed logic here. rewrite this part pls """ @@ -360,7 +383,7 @@ class Population(object): # Clean the all_info_dict: (i.e. transform the function objects to strings) if all_info_cleaned.get("population_settings", None): if all_info_cleaned["population_settings"]["grid_options"][ - "parse_function" + "parse_function" ]: all_info_cleaned["population_settings"]["grid_options"][ "parse_function" @@ -390,19 +413,20 @@ class Population(object): if self.grid_options["verbose"] > 0: print("Writing settings to {}".format(settings_fullname)) # if not outfile.endswith('json'): - with open(settings_fullname, "w") as f: - f.write(json.dumps(all_info_cleaned, indent=4)) + with open(settings_fullname, "w") as file: + file.write(json.dumps(all_info_cleaned, indent=4)) else: if self.grid_options["verbose"] > 0: print("Writing settings to {}".format(outfile)) # if not outfile.endswith('json'): - with open(outfile, "w") as f: - f.write(json.dumps(all_info_cleaned, indent=4)) + with open(outfile, "w") as file: + file.write(json.dumps(all_info_cleaned, indent=4)) def set_custom_logging(self): """ - Function/routine to set all the custom logging so that the function memory pointer is known to the grid. + Function/routine to set all the custom logging so that the function memory pointer + is known to the grid. """ # C_logging_code gets priority of C_autogen_code @@ -451,12 +475,18 @@ class Population(object): def setup(self): """ Function to set up the necessary stuff for the population evolution: - # TODO: Make other kinds of populations possible. i.e, read out type of grid, and set up accordingly - # TODO: make this function more general. Have it explicitly set the system_generator function + # TODO: Make other kinds of populations possible. i.e, read out type of grid, + and set up accordingly + # TODO: make this function more general. Have it explicitly set the system_generator + function """ + if not self.grid_options["parse_function"]: + print("Error: No parse function set. Aborting run") + raise ValueError + ####################### ### Custom logging code: self.set_custom_logging() @@ -505,7 +535,7 @@ class Population(object): def cleanup(self): """ Function that handles all the cleaning up after the grid has been generated and/or run - + - reset values to 0 - remove grid file - unload grid function/module @@ -559,8 +589,8 @@ class Population(object): def evolve_single(self, clean_up_custom_logging_files=True): """ Function to run a single system - - The output of the run gets returned, unless a parse function is given to this function. + + The output of the run gets returned, unless a parse function is given to this function. """ ### Custom logging code: @@ -579,48 +609,51 @@ class Population(object): population=0, ) - # TODO: add call to function that cleans up the temp customlogging dir, and unloads the loaded libraries. + # TODO: add call to function that cleans up the temp customlogging dir, + # and unloads the loaded libraries. # TODO: make a switch to turn this off + if clean_up_custom_logging_files: self.clean_up_custom_logging(evol_type="single") # Parse if self.grid_options["parse_function"]: return self.grid_options["parse_function"](self, out) - else: - return out + return out def evolve_population_mp(self): """ - Function to evolve the population with multiprocessing approach. Using pathos to be able to include class-owned functions. + Function to evolve the population with multiprocessing approach. + Using pathos to be able to include class-owned functions. """ - import multiprocessing as mp - from pathos.pools import _ProcessPool as Pool - from pathos.helpers import mp as pathos_multiprocess - - # TODO: make further use of a queue to handle jobs or at least get information on the process ids etc + # TODO: make further use of a queue to handle jobs or at least + # get information on the process ids etc # https://stackoverflow.com/questions/10190981/get-a-unique-id-for-worker-in-python-multiprocessing-pool - # https://stackoverflow.com/questions/8640367/python-manager-dict-in-multiprocessing/9536888 for muting values through dicts + # https://stackoverflow.com/questions/8640367/python-manager-dict-in-multiprocessing/9536888 + # for muting values through dicts # https://python-forum.io/Thread-Dynamic-updating-of-a-nested-dictionary-in-multiprocessing-pool # https://stackoverflow.com/questions/28740955/working-with-pathos-multiprocessing-tool-in-python-and + + # TODO: make good example of how to deal with a result_dict manager = pathos_multiprocess.Manager() self.grid_options["result_dict"] = manager.dict() # Create pool - p = Pool(processes=self.grid_options["amt_cores"]) + pool = Pool(processes=self.grid_options["amt_cores"]) # Execute # TODO: calculate the chunksize value based on: total starcount and cores used. - r = list( - p.imap_unordered( + _ = list( + pool.imap_unordered( self.evolve_system_mp, self.yield_system_mp(), chunksize=20 ) ) - # Handle clean termination of the whole multiprocessing (making sure there are no zombie processes (https://en.wikipedia.org/wiki/Zombie_process)) - p.close() - p.join() + # Handle clean termination of the whole multiprocessing (making sure there are no zombie + # processes (https://en.wikipedia.org/wiki/Zombie_process)) + pool.close() + pool.join() def evolve_population_lin(self): """ @@ -644,9 +677,14 @@ class Population(object): i + 1, self.grid_options["total_starcount"], full_system_dict ) + if self.grid_options["parse_function"]: + self.grid_options["parse_function"](self, out) + + def evolve_population(self): """ - Function to evolve populations. This is the main function. Handles the setting up, evolving and cleaning up of a population of stars. + Function to evolve populations. This is the main function. Handles the setting up, evolving + and cleaning up of a population of stars. """ ## @@ -655,10 +693,11 @@ class Population(object): self.setup() ## - # Evolve systems: via grid_options one can choose to do this linearly, or multiprocessing method. + # Evolve systems: via grid_options one can choose to do this linearly, or + # multiprocessing method. if ( - self.grid_options["evolution_type"] - in self.grid_options["evolution_type_options"] + self.grid_options["evolution_type"] + in self.grid_options["evolution_type_options"] ): if self.grid_options["evolution_type"] == "mp": self.evolve_population_mp() @@ -666,7 +705,8 @@ class Population(object): self.evolve_population_lin() else: print( - "Warning. you chose a wrong option for the grid evolution types. Please choose from the following: {}.".format( + "Warning. you chose a wrong option for the grid evolution types.\ + Please choose from the following: {}.".format( self.grid_options["evolution_type_options"] ) ) @@ -694,7 +734,8 @@ class Population(object): 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( + 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, @@ -719,21 +760,22 @@ class Population(object): def generate_grid_code(self, dry_run=False): """ Function that generates the code from which the population will be made. - - dry_run: when True, it will return the starcount at the end so that we know what the total amount of systems is. + + dry_run: when True, it will return the starcount at the end so that we know + what the total amount of systems is. The phasevol values are handled by generating a second array - # DONE: make a generator for this. + # DONE: make a generator for this. # TODO: Add correct logging everywhere - # TODO: add part to handle separation if orbital_period is added. Idea. use default values for orbital parameters and possibly overwrite those or something. - # TODO: add centering center left right for the spacing + # TODO: add part to handle separation if orbital_period is added. Idea. use default values + # for orbital parameters and possibly overwrite those or something. + # TODO: add centering center left right for the spacing. # TODO: add sensible description to this function. # TODO: Check whether all the probability and phasevol values are correct. Results in a generated file that contains a system_generator function. - """ if self.grid_options["verbose"] > 0: @@ -794,12 +836,12 @@ class Population(object): code_string += indent * depth + "# setting probability lists\n" # Prepare the probability - for el in sorted( - self.grid_options["grid_variables"].items(), - key=lambda x: x[1]["grid_variable_number"], + for grid_variable_el in sorted( + self.grid_options["grid_variables"].items(), + key=lambda x: x[1]["grid_variable_number"], ): # Make probabilities dict - grid_variable = el[1] + grid_variable = grid_variable_el[1] code_string += indent * depth + 'probabilities["{}"] = 0\n'.format( grid_variable["parameter_name"] ) @@ -810,14 +852,14 @@ class Population(object): code_string += indent * depth + "\n" # Generate code print("Generating grid code") - for loopnr, el in enumerate( - sorted( - self.grid_options["grid_variables"].items(), - key=lambda x: x[1]["grid_variable_number"], - ) + for loopnr, grid_variable_el in enumerate( + sorted( + self.grid_options["grid_variables"].items(), + key=lambda x: x[1]["grid_variable_number"], + ) ): - print("Constructing/adding: {}".format(el[0])) - grid_variable = el[1] + print("Constructing/adding: {}".format(grid_variable_el[0])) + grid_variable = grid_variable_el[1] ################################################################################# # Check condition and generate forloop @@ -868,7 +910,8 @@ class Population(object): + "\n" ) - # TODO: Make clear that the phasevol only works good if you sample linearly in that thing. + # TODO: Make clear that the phasevol only works good + # if you sample linearly in that thing. code_string += ( indent * depth + "phasevol_{} = sampled_values_{}[1]-sampled_values_{}[0]".format( @@ -880,7 +923,8 @@ class Population(object): # # Some print statement # code_string += ( # indent * depth - # + "print('phasevol_{}:', phasevol_{})".format(grid_variable["name"], grid_variable["name"]) + # + "print('phasevol_{}:', phasevol_{})".format(grid_variable["name"], + # grid_variable["name"]) # + "\n" # ) @@ -978,7 +1022,8 @@ class Population(object): # The final parts of the code, where things are returned, are within the deepest loop, # but in some cases code from a higher loop needs to go under it again - # SO I think its better to put an ifstatement here that checks whether this is the last loop. + # SO I think its better to put an ifstatement here that checks + # whether this is the last loop. if loopnr == len(self.grid_options["grid_variables"]) - 1: ################################################################################# @@ -1050,16 +1095,17 @@ class Population(object): depth -= 1 code_string += "\n" - # Write parts to write below the part that yield the results. this has to go in a reverse order: + # Write parts to write below the part that yield the results. + # this has to go in a reverse order: # Here comes the stuff that is put after the deepest nested part that calls returns stuff. - for loopnr, el in enumerate( - sorted( - self.grid_options["grid_variables"].items(), - key=lambda x: x[1]["grid_variable_number"], - reverse=True, - ) + for loopnr, grid_variable_el in enumerate( + sorted( + self.grid_options["grid_variables"].items(), + key=lambda x: x[1]["grid_variable_number"], + reverse=True, + ) ): - grid_variable = el[1] + grid_variable = grid_variable_el[1] code_string += indent * (depth + 1) + "#" * 40 + "\n" code_string += ( indent * (depth + 1) @@ -1110,16 +1156,16 @@ class Population(object): if self.grid_options["verbose"] > 0: print("Writing grid code to {}".format(gridcode_filename)) - with open(gridcode_filename, "w") as f: - f.write(code_string) + with open(gridcode_filename, "w") as file: + file.write(code_string) def load_grid_function(self): """ + TODO: Update this description Test function to run grid stuff. mostly to test the import """ # Code to load the - import importlib.util if self.grid_options["verbose"] > 0: print( @@ -1144,7 +1190,7 @@ class Population(object): def dry_run(self): """ Function to dry run the grid and know how many stars it will run - + Requires the grid to be built as a dry run grid """ @@ -1169,11 +1215,13 @@ class Population(object): # Calculate amount of time left # calculate amount of time passed - time_passed = time.time() - self.grid_options["start_time_evolution"] + # time_passed = time.time() - self.grid_options["start_time_evolution"] if run_number % print_freq == 0: binary_cmdline_string = self.return_argline(full_system_dict) - info_string = "{color_part_1} {text_part_1}{end_part_1}{color_part_2} {text_part_2}{end_part_2}".format( + info_string = "{color_part_1} \ + {text_part_1}{end_part_1}{color_part_2} \ + {text_part_2}{end_part_2}".format( color_part_1="\033[1;32;41m", text_part_1="{}/{}".format(run_number, total_systems), end_part_1="\033[0m", @@ -1204,18 +1252,23 @@ class Population(object): ################################################### def write_binary_c_calls_to_file( - self, output_dir=None, output_filename=None, include_defaults=False + self, + output_dir=None, + output_filename=None, + include_defaults=False ): """ - Function that loops over the gridcode and writes the generated parameters to a file. In the form of a commandline call + Function that loops over the gridcode and writes the generated parameters to a file. + In the form of a commandline call Only useful when you have a variable grid as system_generator. MC wouldnt be that useful - Also, make sure that in this export there are the basic parameters like m1,m2,sep, orb-per, ecc, probability etc. + Also, make sure that in this export there are the basic parameters + like m1,m2,sep, orb-per, ecc, probability etc. On default this will write to the datadir, if it exists - # warning; dont use yet. not fully tested. + # warning; dont use yet. not fully tested. """ if self.grid_options["system_generator"]: @@ -1229,8 +1282,7 @@ class Population(object): "Error. No data_dir configured and you gave no output_dir. Aborting" ) raise ValueError - else: - binary_c_calls_output_dir = output_dir + binary_c_calls_output_dir = output_dir # check if theres a filename passed to the function if output_filename: @@ -1245,11 +1297,11 @@ class Population(object): print("Writing binary_c calls to {}".format(binary_c_calls_full_filename)) # Write to file - with open(binary_c_calls_full_filename, "w") as f: + with open(binary_c_calls_full_filename, "w") as file: # Get defaults and clean them, then overwrite them with the set values. if include_defaults: # TODO: make sure that the defaults here are cleaned up properly - cleaned_up_defaults = self.cleaned_up_defaults() + cleaned_up_defaults = self.cleaned_up_defaults full_system_dict = cleaned_up_defaults.copy() full_system_dict.update(self.bse_options.copy()) else: @@ -1260,21 +1312,23 @@ class Population(object): full_system_dict.update(system) binary_cmdline_string = self.return_argline(full_system_dict) - f.write(binary_cmdline_string + "\n") + file.write(binary_cmdline_string + "\n") else: print("Error. No grid function found!") raise KeyError def cleanup_defaults(self): """ - Function to clean up the default values: - + Function to clean up the default values: + from a dictionary, removes the entries that have the following values: - - "NULL" + - "NULL" - "" - "Function" - - Uses the function from utils.functions + + Uses the function from utils.functions + + TODO: Rethink this functionality. seems a bit double, could also be just outside of the class """ binary_c_defaults = self.return_binary_c_defaults().copy() @@ -1284,13 +1338,15 @@ class Population(object): def clean_up_custom_logging(self, evol_type): """ - Function to clean up the custom logging. - Has two types: - 'single': - - removes the compiled shared library (which name is stored in grid_options['custom_logging_shared_library_file']) - - TODO: unloads/frees the memory allocated to that shared library (which is stored in grid_options['custom_logging_func_memaddr']) + Function to clean up the custom logging. + Has two types: + 'single': + - removes the compiled shared library + (which name is stored in grid_options['custom_logging_shared_library_file']) + - TODO: unloads/frees the memory allocated to that shared library + (which is stored in grid_options['custom_logging_func_memaddr']) - sets both to None - 'multiple': + 'multiple': - TODO: make this and design this """ @@ -1312,7 +1368,8 @@ class Population(object): if evol_type == "population": if self.grid_options["verbose"] > 0: print("Cleaning up the custom logging stuffs. type: population") - # TODO: make sure that these also work. not fully sure if necessary tho. whether its a single file, or a dict of files/memaddresses + # TODO: make sure that these also work. not fully sure if necessary tho. + # whether its a single file, or a dict of files/memaddresses def increment_probtot(self, prob): """ @@ -1332,5 +1389,4 @@ class Population(object): # Function to join the result dictionaries # """ - ################################################################################################ diff --git a/binarycpython/utils/grid_options_defaults.py b/binarycpython/utils/grid_options_defaults.py index 6e212d3997a377f0626a63a78363c1190c45b3d3..3719d0ffede5649ae2ec7889c8b3c89895ac6e39 100644 --- a/binarycpython/utils/grid_options_defaults.py +++ b/binarycpython/utils/grid_options_defaults.py @@ -1,5 +1,8 @@ +""" +Module that contains the default options for the population gric code. +""" + import os -import sys from binarycpython.utils.custom_logging_functions import temp_dir @@ -30,14 +33,17 @@ grid_options_defaults_dict = { ########################## # Custom logging ########################## - "C_auto_logging": None, # Should contain a dictionary where the kes are they headers and the values are lists of parameters that should be logged. This will get parsed by autogen_C_logging_code in custom_loggion_functions.py + "C_auto_logging": None, # Should contain a dictionary where the kes are they headers + # and the values are lists of parameters that should be logged. + # This will get parsed by autogen_C_logging_code in custom_loggion_functions.py "C_logging_code": None, # Should contain a string which holds the logging code. "custom_logging_func_memaddr": -1, # Contains the custom_logging functions memory address "custom_logging_shared_library_file": None, ########################## # Store pre-loading: ########################## - "store_memaddr": -1, # Contains the store object memory adress, useful for preloading. defaults to -1 and isnt used if thats the default then. + "store_memaddr": -1, # Contains the store object memory adress, useful for preloading. + # defaults to -1 and isnt used if thats the default then. ########################## # Log args: logging of arguments ########################## @@ -52,7 +58,8 @@ grid_options_defaults_dict = { "mp", "linear", ], # available choices for type of population evolution - "system_generator": None, # value that holds the function that generates the system (result of building the grid script) + "system_generator": None, # value that holds the function that generates the system + # (result of building the grid script) "population_type": "grid", # "population_type_options": [ "grid", @@ -136,7 +143,8 @@ grid_options_defaults_dict = { # condor_jobid=>'', # condor job id # condor_postpone_join=>0, # if 1, data is not joined, e.g. if you # # want to do it off the condor grid (e.g. with more RAM) - # condor_join_machine=>undef, # if defined then this is the machine on which the join command should be launched (must be sshable and not postponed) + # condor_join_machine=>undef, # if defined then this is the machine on which the join command + #should be launched (must be sshable and not postponed) # condor_join_pwd=>undef, # directory the join should be in # # (defaults to $ENV{PWD} if undef) # condor_memory=>1024, # in MB, the memory use (ImageSize) of the job @@ -144,9 +152,11 @@ grid_options_defaults_dict = { # condor_snapshot_on_kill=>0, # if 1 snapshot on SIGKILL before exit # condor_load_from_snapshot=>0, # if 1 check for snapshot .sv file and load it if found # condor_checkpoint_interval=>0, # checkpoint interval (seconds) - # condor_checkpoint_stamp_times=>0, # if 1 then files are given timestamped names (warning: lots of files!), otherwise just store the lates + # condor_checkpoint_stamp_times=>0, # if 1 then files are given timestamped names + # (warning: lots of files!), otherwise just store the lates # condor_streams=>0, # stream stderr/stdout by default (warning: might cause heavy network load) - # condor_save_joined_file=>0, # if 1 then results/joined contains the results (useful for debugging, otherwise a lot of work) + # condor_save_joined_file=>0, # if 1 then results/joined contains the results + # (useful for debugging, otherwise a lot of work) # condor_requirements=>'', # used? # # resubmit options : if the status of a condor script is # # either 'finished','submitted','running' or 'crashed', @@ -330,7 +340,8 @@ grid_options_defaults_dict = { # sort_args=>0, # do not sort args # save_args=>0, # do not save args in a string # log_args_dir=>$tmp, # where to output the args files - # always_reopen_arg_files=>0, # if 1 then arg files are always closed and reopened (may cause a lot of disk I/O) + # always_reopen_arg_files=>0, # if 1 then arg files are always closed and reopened + # (may cause a lot of disk I/O) # lazy_arg_sending=>1, # if 1, the previous args are remembered and # # only args that changed are sent (except M1, M2 etc. which always # # need sending) diff --git a/binarycpython/utils/plot_functions.py b/binarycpython/utils/plot_functions.py index 4d7a88b03db7d0f74eb7b218c1acd909cc7c286f..af5649f306f0c37dd252494dad9ff062979db4e9 100644 --- a/binarycpython/utils/plot_functions.py +++ b/binarycpython/utils/plot_functions.py @@ -1,32 +1,40 @@ """ Module that contains plotting routines for single systems. -The idea to do this is to provide the user with some quick +The idea to do this is to provide the user with some quick commands to plot the evolution of a system There is no preloaded matplotlib rc, you should do that yourself + +TODO: This module is not finished yet. """ import pandas as pd import numpy as np -import matplotlib.pyplot as plt +# import matplotlib.pyplot as plt + +from binarycpython.utils.functions import ( + output_lines, +) +from binarycpython.utils.run_system_wrapper import run_system +from binarycpython.utils.custom_logging_functions import binary_c_log_code + from david_phd_functions.plotting.plot_functions import ( plot_orbit, plot_masses, plot_HR_diagram, ) -from binarycpython.utils.functions import get_arg_keys, output_lines -from binarycpython.utils.run_system_wrapper import run_system -from binarycpython.utils.custom_logging_functions import binary_c_log_code -# Define the custom_logging_strings. These are kept to the minimum necessary for each plotting routine. +# Define the custom_logging_strings. +# These are kept to the minimum necessary for each plotting routine. -custom_logging_string_masses = "" -custom_logging_string_orbit = "" +CUSTOM_LOGGING_STRING_MASSES = "" -custom_logging_string_HR_diagram = """ +CUSTOM_LOGGING_STRING_ORBIT = "" + +CUSTOM_LOGGING_STRING_HR_DIAGRAM = """ Printf("HR_PLOTTING %30.12e %d %d %g %g %g %g %g %g\\n", // stardata->model.time, // 1 @@ -50,9 +58,9 @@ Printf("HR_PLOTTING %30.12e %d %d %g %g %g %g %g %g\\n", # Define the parse functions for the plotting routines def dummy(): + """Placeholder""" pass - def parse_function_hr_diagram(output): """ Parsing function for the HR plotting routine @@ -75,12 +83,12 @@ def parse_function_hr_diagram(output): ] # Go over the output. - for el in output_lines(output): - headerline = el.split()[0] + for line in output_lines(output): + headerline = line.split()[0] # Check the header and act accordingly if headerline == "HR_PLOTTING": - values = el.split()[1:] + values = line.split()[1:] values_list.append(values) df = pd.DataFrame(values_list) @@ -98,12 +106,13 @@ def plot_system(plot_type, **kwargs): TODO: Complex Function! TODO: make sure this way of passing args works correctly. TODO: make the plotting specific keywords available via the inspect stuff - Function to plot the evolution of the system. + Function to plot the evolution of the system. This goes (in general) via the following steps: - a preset custom logging for a specific plotting routine is loaded. - This is used for the run_system call - - The output of this run_system is loaded into a dataframe by parsing it with a corresponding parsing function + - The output of this run_system is loaded into a dataframe by + parsing it with a corresponding parsing function - The dataframe is passed to the plotting routine - plot is shown or returned. @@ -111,11 +120,16 @@ def plot_system(plot_type, **kwargs): All keywords are considered kwargs, except for plot_type input: - plot_type: string input should be one of the following types: ['mass_evolution', 'orbit_evolution', 'hr_diagram']. - Input will be matched against this, and then go through a dictionary to pick the correct plotting function. - return_fig: boolean whether to return the fig object instead of plotting the plot (makes so that you can customize it) - show_stellar_types: whether to plot the stellar type evolution on a second pane. This is not included in all the plotting routines. - Other input: other kwargs that are passed to run_system (inspect the docstring of run_system for more info) + plot_type: string input should be one of the following types: + ['mass_evolution', 'orbit_evolution', 'hr_diagram']. + Input will be matched against this, + and then go through a dictionary to pick the correct plotting function. + return_fig: boolean whether to return the fig object instead of plotting the plot + (makes so that you can customize it) + show_stellar_types: whether to plot the stellar type evolution on a second pane. + This is not included in all the plotting routines. + Other input: other kwargs that are passed to run_system + (inspect the docstring of run_system for more info) """ # set defaults and options @@ -125,32 +139,35 @@ def plot_system(plot_type, **kwargs): plot_types_dict = { "mass_evolution": { "plot_function": plot_masses, - "custom_logging_string": custom_logging_string_masses, + "custom_logging_string": CUSTOM_LOGGING_STRING_MASSES, "parse_function": dummy, }, "orbit_evolution": { "plot_function": plot_orbit, - "custom_logging_string": custom_logging_string_orbit, + "custom_logging_string": CUSTOM_LOGGING_STRING_ORBIT, "parse_function": dummy, }, "hr_diagram": { "plot_function": plot_HR_diagram, - "custom_logging_string": custom_logging_string_HR_diagram, + "custom_logging_string": CUSTOM_LOGGING_STRING_HR_DIAGRAM, "parse_function": parse_function_hr_diagram, }, } plot_system_specific_keywords = ["plot_type", "show_plot", "show_stellar_types"] # First check on the plot_type input - if not plot_type in plot_types_dict.keys(): + if not plot_type in plot_types_dict: print( - "Warning, the provided plot type is not known. Please choose one from the following:\n\t{}".format( + "Warning, the provided plot type is not known. \ + Please choose one from the following:\n\t{}".format( plot_types_dict.keys() ) ) raise ValueError - # First: check all the arguments. Chosen to not check all the keywords for run_system and binary_c specifically, but just to pick out the ones needed for this routine. run_system will handle the rest + # First: check all the arguments. Chosen to not check all the keywords for run_system + # and binary_c specifically, but just to pick out the ones needed for this routine. + # run_system will handle the rest run_system_arg_dict = {} for key in kwargs.keys(): @@ -163,7 +180,8 @@ def plot_system(plot_type, **kwargs): else: run_system_arg_dict[key] = kwargs[key] - # TODO: When a list of plot_types is passed, make it so that the strings are chained, and that the output of the binary_c call is handled by multiple parsers + # TODO: When a list of plot_types is passed, make it so that the strings are chained, + # and that the output of the binary_c call is handled by multiple parsers custom_logging_code = binary_c_log_code( plot_types_dict[plot_type]["custom_logging_string"] ) diff --git a/binarycpython/utils/run_system_wrapper.py b/binarycpython/utils/run_system_wrapper.py index 4237548dc3cb232d461a189074144508e5ca55c4..7b726892ee794ccb0ab5d70974d7947379d22f11 100644 --- a/binarycpython/utils/run_system_wrapper.py +++ b/binarycpython/utils/run_system_wrapper.py @@ -1,7 +1,10 @@ -import binary_c_python_api +""" +Module containing the utility function run_system, +which handles a lot of things by analysing the passed kwargs +""" + from binarycpython.utils.functions import ( - get_defaults, create_arg_string, get_arg_keys, remove_file, @@ -10,29 +13,37 @@ from binarycpython.utils.functions import ( from binarycpython.utils.custom_logging_functions import ( create_and_load_logging_function, ) +import binary_c_python_api def run_system(**kwargs): """ - Function that runs a system. Mostly as a useful utility function that handles all the setup of argument lists etc. + Function that runs a system. + Mostly as a useful utility function that handles all the setup of argument lists etc. All~ the arguments known to binary_c can be passed to this function as kwargs. Several extra arguments can be passed through the kwargs: - * custom_logging_code (string): Should contain a string containing the c-code for the shared library. + * custom_logging_code (string): + Should contain a string containing the c-code for the shared library. If this is provided binary_c will use that custom logging code to output its data - * log_filename (string): Should contain name of the binary_c system logfile. - Passing this will make sure that the filename gets written for a run (its default behaviour is NOT to write a logfile for a system) - * parse_function (function): should contain a function that parses the output. The parse function should take 1 required parameter: the output of the binaryc run - Passing this will call the parse_function by passing it the output of the binary_c call and returns what the parse_function returns - + * log_filename (string): + Should contain name of the binary_c system logfile. + Passing this will make sure that the filename gets written for a run + (its default behaviour is NOT to write a logfile for a system) + * parse_function (function): + should contain a function that parses the output. + The parse function should take 1 required parameter: the output of the binaryc run + Passing this will call the parse_function by passing it the output of the binary_c call + and returns what the parse_function returns + examples: * run_system(M_1=10): will run a system with ZAMS mass 1 = 10 - * run_system(M_1=10, log_filename="~/example_log.txt"): Will run a system and write the logfile to + * run_system(M_1=10, log_filename="~/example_log.txt"): Will run a system + and write the logfile too * run_system(M_1=10, parse_function=fancy_parsing_function) - Todo: - * Expand functionality. - * Notify user when an unknown keyword is passed + TODO: Expand functionality. + TODO: Notify user when an unknown keyword is passed. """ # Load available arg keywords @@ -48,7 +59,7 @@ def run_system(**kwargs): binary_c_args = {} # Check which binary_c arguments have been passed and put them into a dict - for key in kwargs.keys(): + for key in kwargs: if key in available_binary_c_arg_keywords: binary_c_args[key] = kwargs[key] @@ -88,5 +99,4 @@ def run_system(**kwargs): if "parse_function" in kwargs: return kwargs["parse_function"](output) - else: - return output + return output diff --git a/binarycpython/utils/spacing_functions.py b/binarycpython/utils/spacing_functions.py index 84aeecdf0d8018cc74f8961f436954dd784b8ac1..28fe7ebe27b2ff2562941d92d19e82d58a67644b 100644 --- a/binarycpython/utils/spacing_functions.py +++ b/binarycpython/utils/spacing_functions.py @@ -1,5 +1,14 @@ +""" +Module containing the spacing functions for the binarycpython package +""" + + import numpy as np -def const(min, max, steps): - return np.linspace(min, max, steps) +def const(min_bound, max_bound, steps): + """ + Samples a range linearly. Uses numpy linspace. + """ + + return np.linspace(min_bound, max_bound, steps) diff --git a/binarycpython/utils/stellar_types.py b/binarycpython/utils/stellar_types.py index d18a63cf207f22823f1d00ac6dfaeb61191aa4c8..d7b2e3b788509e8faef65317e1e724832e18cdcb 100644 --- a/binarycpython/utils/stellar_types.py +++ b/binarycpython/utils/stellar_types.py @@ -1,4 +1,8 @@ -stellar_type_dict = { +""" +Module containing stellar type dicts +""" + +STELLAR_TYPE_DICT = { 0: "low mass main sequence", 1: "Main Sequence", 2: "Hertzsprung Gap", @@ -17,7 +21,7 @@ stellar_type_dict = { 15: "MASSLESS REMNANT", } -stellar_type_dict_short = { +STELLAR_TYPE_DICT_SHORT = { 0: "LMMS", 1: "MS", 2: "HG", diff --git a/binarycpython/utils/useful_funcs.py b/binarycpython/utils/useful_funcs.py index cf1b5b444224bc2adad9d1e53da7e78f96d248ac..bda0da21239a098874acf74141b5bc3cbe5bf8d7 100644 --- a/binarycpython/utils/useful_funcs.py +++ b/binarycpython/utils/useful_funcs.py @@ -1,17 +1,19 @@ -############################################################ -# Collection of useful functions. - -# Part of this is copied/inspired by Robs -# Rob's binary_stars module - -# Has functions to convert period to separation and vice versa. -# calc_period_from_sep($m1,$m2,$sep) calculate the period given the separation. -# calc_sep_from_period($m1,$m2,per) does the inverse. -# M1,M2,separation are in solar units, period in days. -# rzams($m,$z) gives you the ZAMS radius of a star -# ZAMS_collision($m1,$m2,$e,$sep,$z) returns 1 if stars collide on the ZAMS -### +""" +Collection of useful functions. + +Part of this is copied/inspired by Robs +Rob's binary_stars module + +Has functions to convert period to separation and vice versa. +calc_period_from_sep($m1,$m2,$sep) calculate the period given the separation. +calc_sep_from_period($m1,$m2,per) does the inverse. +M1,M2,separation are in solar units, period in days. +rzams($m,$z) gives you the ZAMS radius of a star +ZAMS_collision($m1,$m2,$e,$sep,$z) returns 1 if stars collide on the ZAMS + # TODO: check whether these are correct +""" +import math AURSUN = 2.150445198804013386961742071435e02 YEARDY = 3.651995478818308811241877265275e02 @@ -23,7 +25,7 @@ def calc_period_from_sep(M1, M2, sep): args : M1, M2, separation (Rsun) """ - return YEARDY * (sep / AURSUN) * sqrt(sep / (AURSUN * (M1 + M2))) + return YEARDY * (sep / AURSUN) * math.sqrt(sep / (AURSUN * (M1 + M2))) def calc_sep_from_period(M1, M2, period): @@ -41,7 +43,7 @@ def roche_lobe(q): """ p = q ** (1.0 / 3.0) - return 0.49 * p * p / (0.6 * p * p + log(1.0 + p)) + return 0.49 * p * p / (0.6 * p * p + math.log(1.0 + p)) def ragb(m, z): @@ -55,9 +57,10 @@ def ragb(m, z): def zams_collission(m1, m2, sep, e, z): """ - # given m1,m2, separation and eccentricity (and metallicity) - # determine if two stars collide on the ZAMS + given m1,m2, separation and eccentricity (and metallicity) + determine if two stars collide on the ZAMS """ + # calculate periastron distance peri_distance = (1.0 - e) * sep @@ -67,14 +70,14 @@ def zams_collission(m1, m2, sep, e, z): if r1 + r2 > peri_distance: return 1 - else: - return 0 + return 0 def rzams(m, z): """ Function to determine the radius of a ZAMS star as a function of m and z: """ + lzs = math.log10(z / 0.02) # diff --git a/setup.py b/setup.py index 086900e4bbdce83840ef56beafaf9a5681dfc834..9b2e77cac374002a061461364c90ea9819b6d60f 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,7 @@ +""" +Setup script for binarycpython +""" + from distutils.core import setup, Extension # from setuptools import find_packages @@ -5,12 +9,12 @@ from distutils.core import setup, Extension import os import subprocess import re -import sys GSL_DIR = os.getenv("GSL_DIR", None) if not GSL_DIR: print( - "Warning: GSL_DIR is not set, this might lead to errors along the installation if there is no other version of GSL in the include dirs" + "Warning: GSL_DIR is not set, this might lead to errors along the installation if\ + there is no other version of GSL in the include dirs" ) # TODO: write code to know exact parent directory of this file. @@ -21,90 +25,90 @@ CWD = os.getcwd() ############################################################ # binary_c must be installed. -binary_c_config = os.environ["BINARY_C"] + "/binary_c-config" +BINARY_C_CONFIG = os.environ["BINARY_C"] + "/binary_c-config" -binary_c_incdirs = ( - subprocess.run([binary_c_config, "incdirs_list"], stdout=subprocess.PIPE) +BINARY_C_INCDIRS = ( + subprocess.run([BINARY_C_CONFIG, "incdirs_list"], stdout=subprocess.PIPE, check=True) .stdout.decode("utf-8") .split() ) -binary_c_libdirs = ( - subprocess.run([binary_c_config, "libdirs_list"], stdout=subprocess.PIPE) +BINARY_C_LIBDIRS = ( + subprocess.run([BINARY_C_CONFIG, "libdirs_list"], stdout=subprocess.PIPE, check=True) .stdout.decode("utf-8") .split() ) -binary_c_cflags = ( - subprocess.run([binary_c_config, "cflags"], stdout=subprocess.PIPE) +BINARY_C_CFLAGS = ( + subprocess.run([BINARY_C_CONFIG, "cflags"], stdout=subprocess.PIPE, check=True) .stdout.decode("utf-8") .split() ) -# binary_c_cflags.remove('-fvisibility=hidden') -binary_c_libs = ( - subprocess.run([binary_c_config, "libs_list"], stdout=subprocess.PIPE) +# BINARY_C_CFLAGS.remove('-fvisibility=hidden') +BINARY_C_LIBS = ( + subprocess.run([BINARY_C_CONFIG, "libs_list"], stdout=subprocess.PIPE, check=True) .stdout.decode("utf-8") .split() ) # create list of tuples of defined macros -binary_c_define_macros = [] -defines = ( - subprocess.run([binary_c_config, "define_macros"], stdout=subprocess.PIPE) +BINARY_C_DEFINE_MACROS = [] +DEFINES = ( + subprocess.run([BINARY_C_CONFIG, "define_macros"], stdout=subprocess.PIPE, check=True) .stdout.decode("utf-8") .split() ) -lone = re.compile("^-D(.+)$") -partner = re.compile("^-D(.+)=(.+)$") +LONE = re.compile("^-D(.+)$") +PARTNER = re.compile("^-D(.+)=(.+)$") -for x in defines: - y = partner.match(x) +for x in DEFINES: + y = PARTNER.match(x) if y: - binary_c_define_macros.extend([(y.group(1), y.group(2))]) + BINARY_C_DEFINE_MACROS.extend([(y.group(1), y.group(2))]) else: - y = lone.match(x) + y = LONE.match(x) if y: - binary_c_define_macros.extend([(y.group(1), None)]) + BINARY_C_DEFINE_MACROS.extend([(y.group(1), None)]) # add API header file API_h = os.environ["BINARY_C"] + "/src/API/binary_c_API.h" -binary_c_define_macros.extend([("BINARY_C_API_H", API_h)]) +BINARY_C_DEFINE_MACROS.extend([("BINARY_C_API_H", API_h)]) ############################################################ -# Setting all directories and libraries to their final values +# Setting all directories and LIBRARIES to their final values ############################################################ -include_dirs = ( +INCLUDE_DIRS = ( [os.environ["BINARY_C"] + "/src", os.environ["BINARY_C"] + "/src/API", "include",] - + binary_c_incdirs + + BINARY_C_INCDIRS + [os.path.join(GSL_DIR, "include")] if GSL_DIR else [] ) -libraries = ["binary_c"] + binary_c_libs + ["binary_c_api"] +LIBRARIES = ["binary_c"] + BINARY_C_LIBS + ["binary_c_api"] -library_dirs = [ +LIBRARY_DIRS = [ os.environ["BINARY_C"] + "/src", "./", os.path.join(CWD, "lib/"), # os.path.join(CWD, "binarycpython/core/"), -] + binary_c_libdirs +] + BINARY_C_LIBDIRS -runtime_library_dirs = [ +RUNTIME_LIBRARY_DIRS = [ os.environ["BINARY_C"] + "/src", "./", os.path.join(CWD, "lib/"), # os.path.join(CWD, "binarycpython/core/"), -] + binary_c_libdirs +] + BINARY_C_LIBDIRS # print('\n') -# print("binary_c_config: ", str(binary_c_config) + "\n") -# print("incdirs: ", str(include_dirs) + "\n") -# print("binary_c_libs: ", str(binary_c_libs) + "\n") -# print("libraries: ", str(libraries) + "\n") -# print("library_dirs: ", str(library_dirs) + "\n") -# print("runtime_library_dirs: ", str(runtime_library_dirs) + "\n") -# print("binary_c_cflags: ", str(binary_c_cflags) + "\n") +# print("BINARY_C_CONFIG: ", str(BINARY_C_CONFIG) + "\n") +# print("incdirs: ", str(INCLUDE_DIRS) + "\n") +# print("BINARY_C_LIBS: ", str(BINARY_C_LIBS) + "\n") +# print("LIBRARIES: ", str(LIBRARIES) + "\n") +# print("LIBRARY_DIRS: ", str(LIBRARY_DIRS) + "\n") +# print("RUNTIME_LIBRARY_DIRS: ", str(RUNTIME_LIBRARY_DIRS) + "\n") +# print("BINARY_C_CFLAGS: ", str(BINARY_C_CFLAGS) + "\n") # print("API_h: ", str(API_h) + "\n") -# print("macros: ", str(binary_c_define_macros) + "\n") +# print("macros: ", str(BINARY_C_DEFINE_MACROS) + "\n") # print('\n') ############################################################ @@ -112,15 +116,15 @@ runtime_library_dirs = [ ############################################################ # TODO: fix that this one also compiles the code itself -binary_c_python_api_module = Extension( +BINARY_C_PYTHON_API_MODULE = Extension( # name="binarycpython.core.binary_c", name="binary_c_python_api", sources=["src/binary_c_python.c"], - include_dirs=include_dirs, - libraries=libraries, - library_dirs=library_dirs, - runtime_library_dirs=runtime_library_dirs, - define_macros=[] + binary_c_define_macros, + INCLUDE_DIRS=INCLUDE_DIRS, + LIBRARIES=LIBRARIES, + LIBRARY_DIRS=LIBRARY_DIRS, + RUNTIME_LIBRARY_DIRS=RUNTIME_LIBRARY_DIRS, + define_macros=[] + BINARY_C_DEFINE_MACROS, extra_objects=[], extra_compile_args=[], language="C", @@ -128,8 +132,9 @@ binary_c_python_api_module = Extension( def readme(): - with open("README.md") as f: - return f.read() + """Opens readme file and returns content""" + with open("README.md") as file: + return file.read() ############################################################ @@ -139,9 +144,11 @@ def readme(): setup( name="binarycpython", version="0.2", - description="This is a python API for binary_c by David Hendriks, Rob Izzard and collaborators. Based on the initial set up by Jeff andrews", + description="This is a python API for binary_c by David Hendriks, Rob Izzard and collaborators.\ + Based on the initial set up by Jeff andrews", author=" David Hendriks, Robert Izzard and Jeff Andrews", - author_email="davidhendriks93@gmail.com/d.hendriks@surrey.ac.uk, r.izzard@surrey.ac.uk/rob.izzard@gmail.com andrews@physics.uoc.gr", + author_email="davidhendriks93@gmail.com/d.hendriks@surrey.ac.uk,\ + r.izzard@surrey.ac.uk/rob.izzard@gmail.com andrews@physics.uoc.gr", long_description=readme(), url="https://gitlab.eps.surrey.ac.uk/ri0005/binary_c-python", license="", @@ -158,5 +165,5 @@ setup( # package_data={ # 'binarycpython.core': ['libbinary_c_api.so'], # }, - ext_modules=[binary_c_python_api_module], # binary_c must be loaded + ext_modules=[BINARY_C_PYTHON_API_MODULE], # binary_c must be loaded )