From 95335032ae7af7fb11204a5eb1af13110ecbbdb3 Mon Sep 17 00:00:00 2001 From: David Hendriks <davidhendriks93@gmail.com> Date: Sat, 2 Jan 2021 21:22:50 +0000 Subject: [PATCH] Updating the docstrings to contain all the necessary types, arguments and returns --- .../utils/custom_logging_functions.py | 128 ++++++++--- binarycpython/utils/functions.py | 69 +++++- binarycpython/utils/grid.py | 176 ++++++++------- binarycpython/utils/grid_options_defaults.py | 209 +++++++++++++++--- .../utils/grid_options_descriptions.py | 65 ------ 5 files changed, 434 insertions(+), 213 deletions(-) delete mode 100644 binarycpython/utils/grid_options_descriptions.py diff --git a/binarycpython/utils/custom_logging_functions.py b/binarycpython/utils/custom_logging_functions.py index 826f07ad7..6a31405d4 100644 --- a/binarycpython/utils/custom_logging_functions.py +++ b/binarycpython/utils/custom_logging_functions.py @@ -9,26 +9,33 @@ import subprocess import socket import ctypes import uuid - +from typing import Union, Tuple, Optional from binarycpython.utils.functions import temp_dir, remove_file - -def autogen_C_logging_code(logging_dict, verbose=0): +def autogen_C_logging_code(logging_dict: dict, verbose: int=0) -> Optional[str]: """ 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' - ] - } + Example: + input dictionary should look like this:: + + {'MY_STELLAR_DATA': + [ + 'model.time', + 'star[0].mass', + 'model.probability', + 'model.dt' + ] + } + + Args: + logging_dict: Dictionary containing lists of parameters that binary_c has to output. The keys are used by binary_c as start of the sentence. + verbose: Level of verbosity. Defaults to zero if not set explicilty. + Returns: + string containing C printf statement built to output the parameters given as input. """ # Check if the input is of the correct form @@ -37,6 +44,7 @@ def autogen_C_logging_code(logging_dict, verbose=0): return None code = "" + # Loop over dict keys for key in logging_dict: if verbose > 0: @@ -71,9 +79,43 @@ def autogen_C_logging_code(logging_dict, verbose=0): #################################################################################### -def binary_c_log_code(code, verbose=0): +def binary_c_log_code(code: str, verbose: int=0) -> str: """ Function to construct the code to construct the custom logging function + + Example: + Code to log and terminate evolution when the primary star becomes a NS + + :: + if(stardata->star[0].stellar_type>=NS) + { + if (stardata->model.time < stardata->model.max_evolution_time) + { + Printf("EXAMPLE_LOG_CO %30.12e %g %g %g %g %d %d\\n", + // + stardata->model.time, // 1 + + stardata->star[0].mass, //2 + stardata->previous_stardata->star[0].mass, //3 + + stardata->star[0].radius, //4 + stardata->previous_stardata->star[0].radius, //5 + + stardata->star[0].stellar_type, //6 + stardata->previous_stardata->star[0].stellar_type //7 + ); + }; + /* Kill the simulation to save time */ + stardata->model.max_evolution_time = stardata->model.time - stardata->model.dtm; + }; + + Args: + code: Exact c-statement to output information in binary_c. Can be wrapped in logical statements. + verbose: Level of verbosity. Defaults to zero if not set explicilty. + + Returns: + string containing the custom logging code. This includes all the includes and other definitions. This code will be used as the shared library + """ if verbose > 0: @@ -108,11 +150,17 @@ void binary_c_API_function custom_output_function(struct stardata_t * stardata) return textwrap.dedent(custom_logging_function_string) -def binary_c_write_log_code(code, filename, verbose=0): +def binary_c_write_log_code(code: str, filename: str, verbose: int=0) -> None: """ Function to write the generated logging code to a file + + Args: + code: string containing the custom logging code to write to a file. + filename: target filename. + verbose: Level of verbosity. Defaults to zero if not set explicilty. """ + # TODO: change this. I don't like the cwd cwd = os.getcwd() filePath = os.path.join(cwd, filename) @@ -127,9 +175,16 @@ def binary_c_write_log_code(code, filename, verbose=0): file.write(code) -def from_binary_c_config(config_file, flag): +def from_binary_c_config(config_file: str, flag: str) -> str: """ Function to run the binaryc_config command with flags + + Args: + config_file: binary_c-config filepath TODO: change the name of this + flag: flag used in the binary_c-config call. + + Returns: + returns the result of <binary_c-config> <flag> """ res = subprocess.check_output( @@ -139,22 +194,25 @@ def from_binary_c_config(config_file, flag): ) # convert and chop off newline - res = res.decode("utf").rstrip() + res = res.decode("utf-8").rstrip() + return res -def return_compilation_dict(verbose=0): +def return_compilation_dict(verbose: int=0) -> dict: """ Function to build the compile command for the shared library - inspired by binary_c_inline_config command in perl + 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/ + Args: + verbose: Level of verbosity. Defaults to zero if not set explicilty. - returns: - - string containing the command to build the shared library + Returns: + string containing the command to build the shared library """ if verbose > 0: @@ -166,7 +224,6 @@ def return_compilation_dict(verbose=0): 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") @@ -240,10 +297,18 @@ def return_compilation_dict(verbose=0): return {"cc": cc, "ld": ld, "ccflags": ccflags, "libs": libs, "inc": inc} -def compile_shared_lib(code, sourcefile_name, outfile_name, verbose=0): +def compile_shared_lib(code: str, sourcefile_name: str, outfile_name: str, verbose: int=0) -> None: """ Function to write the custom logging code to a file and then compile it. + TODO: nicely put in the -fPIC + TODO: consider returning a status + + Args: + code: string containing the custom logging code + sourcefile_name: name of the file that will contain the code + outfile_name: name of the file that will be the shared library + verbose: Level of verbosity. Defaults to zero if not set explicilty. """ # Write code to file @@ -272,8 +337,6 @@ def compile_shared_lib(code, sourcefile_name, outfile_name, verbose=0): # Execute compilation and create the library if verbose > 0: - # BINARY_C_DIR = os.getenv("BINARY_C") - # BINARY_C_SRC_DIR = os.path.join(BINARY_C_DIR, "src") print( "Building shared library for custom logging with (binary_c.h) on {}\n".format( socket.gethostname() @@ -291,12 +354,19 @@ def compile_shared_lib(code, sourcefile_name, outfile_name, verbose=0): print("Output of compilation command:\n{}".format(res)) -def create_and_load_logging_function(custom_logging_code, verbose=0): +def create_and_load_logging_function(custom_logging_code: str, verbose: int=0) -> Tuple[int, str]: """ Function to automatically compile the shared library with the given - custom logging code and load it with ctypes + custom logging code and load it with ctypes. - returns: + This function is more or less the main function of this module and unless you know what you're doing with the other functions + I recommend using this in function in combination with a function that generates the exact code (like :meth:`~binarycpython.utils.custom_logging_code.binary_c_log_code`) + + Args: + custom_logging_code: string containing the custom logging code + verbose: Level of verbosity. Defaults to zero if not set explicilty. + + Returns: memory adress of the custom logging function in a int type. """ @@ -331,6 +401,10 @@ def create_and_load_logging_function(custom_logging_code, verbose=0): libcustom_logging.custom_output_function, ctypes.c_void_p ).value + if not isinstance(func_memaddr, int): + print("Something went wrong. The memory adress returned by the ctypes.cast is not an integer. It has the value {}".format(func_memaddr)) + raise ValueError + if verbose > 0: print( "loaded shared library for custom logging. \ diff --git a/binarycpython/utils/functions.py b/binarycpython/utils/functions.py index 312523fde..57cc3b22d 100644 --- a/binarycpython/utils/functions.py +++ b/binarycpython/utils/functions.py @@ -19,6 +19,7 @@ import numpy as np from binarycpython import _binary_c_bindings + ######################################################## # utility functions ######################################################## @@ -36,12 +37,22 @@ def verbose_print(message, verbosity, minimal_verbosity): print(message) -def remove_file(file, verbosity=0): +def remove_file(file: str, verbosity: int=0) -> None: """ Function to remove files but with verbosity + + Args: + file: full filepath to the file that will be removed. + + Returns: + the path of a subdirectory called binary_c_python in the TMP of the filesystem + """ if os.path.exists(file): + if not os.path.isfile(file): + verbose_print("This path ({}) is a directory, not a file".format(file), verbosity, 0) + try: verbose_print("Removed {}".format(file), verbosity, 1) os.remove(file) @@ -50,12 +61,15 @@ def remove_file(file, verbosity=0): print("Error while deleting file {}: {}".format(file, inst)) -def temp_dir(): +def temp_dir()-> str: """ 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+ + + Returns: + the path of a subdirectory called binary_c_python in the TMP of the filesystem """ tmp_dir = tempfile.gettempdir() @@ -732,9 +746,10 @@ def get_help_super(print_help=False, fail_silently=True): help_all_dict = get_help_all(print_help=False) for section_name in help_all_dict: section = help_all_dict[section_name] - print(section_name) - for parameter_name in section["parameters"].keys(): - print("\t", parameter_name) + + # print(section_name) + # for parameter_name in section["parameters"].keys(): + # print("\t", parameter_name) help_all_super_dict = help_all_dict.copy() @@ -781,11 +796,52 @@ def get_help_super(print_help=False, fail_silently=True): return help_all_super_dict +def write_binary_c_parameter_descriptions_to_rst_file(output_file): + """ + Function that calls the binary_c api to get the help text/descriptions for all the paramateres available in that build. + Writes the results to a .rst file that can be included in the docs. + + TODO: add the specific version to this document + """ + + # Get the whole arguments dictionary + arguments_dict = get_help_super() + + if not output_file.endswith(".rst"): + print("Filename doesn't end with .rst, please provide a proper filename") + return None + + with open(output_file, 'w') as f: + + print("Binary\\_c parameters", file=f) + print("{}".format("="*len("Binary\\_c parameters")), file=f) + print("The following chapter contains all the parameters that the current version of binary\\_c can handle, along with their descriptions and other properties.", file=f) + print("\n", file=f) + + for el in arguments_dict.keys(): + print("Section: {}".format(el), file=f) + print("{}\n".format("-"*len("Section: {}".format(el))), file=f) + # print(arguments_dict[el]['parameters'].keys()) + + for arg in arguments_dict[el]['parameters'].keys(): + argdict = arguments_dict[el]['parameters'][arg] + + print("| **Parameter**: {}".format(argdict["param_name"]), file=f) + print("| **Description**: {}".format(argdict["description"]), file=f) + if "parameter_value_input_type" in argdict: + print("| **Parameter input type**: {}".format(argdict["parameter_value_input_type"]), file=f) + if "default" in argdict: + print("| **Default value**: {}".format(argdict["default"]), file=f) + if "macros" in argdict: + print("| **Macros**: {}".format(argdict['macros']), file=f) + if not argdict["rest"] == "(null)": + print("| **Extra**: {}".format(argdict["rest"]), file=f) + print("", file=f) + ######################################################## # logfile functions ######################################################## - def load_logfile(logfile): """ Experimental function that parses the generated logfile of binary_c. @@ -1014,3 +1070,4 @@ def handle_ensemble_string_to_json(raw_output): # return json.loads(json.dumps(ast.literal_eval(raw_output)), cls=binarycDecoder) return json.loads(raw_output, cls=binarycDecoder) + diff --git a/binarycpython/utils/grid.py b/binarycpython/utils/grid.py index eec2d188c..5fc3fdaff 100644 --- a/binarycpython/utils/grid.py +++ b/binarycpython/utils/grid.py @@ -94,7 +94,7 @@ class Population: self.argline_dict = {} # Set main process id - self.grid_options["main_pid"] = os.getpid() + self.grid_options["_main_pid"] = os.getpid() # Set some memory dicts self.persistent_data_memory_dict = {} @@ -220,7 +220,7 @@ class Population: # Grab the input and split them up, while accepting only non-empty entries cmdline_args = args.cmdline - self.grid_options["commandline_input"] = cmdline_args + self.grid_options["_commandline_input"] = cmdline_args split_args = [ cmdline_arg for cmdline_arg in cmdline_args.split(" ") @@ -320,11 +320,11 @@ class Population: "dphasevol": dphasevol, "parameter_name": parameter_name, "condition": condition, - "grid_variable_number": len(self.grid_options["grid_variables"]), + "grid_variable_number": len(self.grid_options["_grid_variables"]), } # Load it into the grid_options - self.grid_options["grid_variables"][grid_variable["name"]] = grid_variable + self.grid_options["_grid_variables"][grid_variable["name"]] = grid_variable verbose_print( "Added grid variable: {}".format(json.dumps(grid_variable, indent=4)), self.grid_options["verbosity"], @@ -509,7 +509,7 @@ class Population: # Load memory adress ( self.grid_options["custom_logging_func_memaddr"], - self.grid_options["custom_logging_shared_library_file"], + self.grid_options["_custom_logging_shared_library_file"], ) = create_and_load_logging_function( custom_logging_code, verbose=self.grid_options["verbosity"] ) @@ -529,7 +529,7 @@ class Population: # Load memory adress ( self.grid_options["custom_logging_func_memaddr"], - self.grid_options["custom_logging_shared_library_file"], + self.grid_options["_custom_logging_shared_library_file"], ) = create_and_load_logging_function( custom_logging_code, verbose=self.grid_options["verbosity"] ) @@ -641,7 +641,7 @@ class Population: """ # TODO: set a unique population_name here - self.grid_options["population_id"] = uuid.uuid4().hex + self.grid_options["_population_id"] = uuid.uuid4().hex ## # Prepare code/initialise grid. @@ -653,7 +653,7 @@ class Population: # multiprocessing method. if ( self.grid_options["evolution_type"] - in self.grid_options["evolution_type_options"] + in self.grid_options["_evolution_type_options"] ): if self.grid_options["evolution_type"] == "mp": self._evolve_population_mp() @@ -663,26 +663,29 @@ class Population: print( "Warning. you chose a wrong option for the grid evolution types.\ Please choose from the following: {}.".format( - self.grid_options["evolution_type_options"] + self.grid_options["_evolution_type_options"] ) ) + self.grid_options["_end_time_evolution"] = time.time() + + # Log and print some information verbose_print( - "Population-{} finished!".format(self.grid_options['population_id']), + "Population-{} finished!".format(self.grid_options['_population_id']), self.grid_options["verbosity"], 0 ) - if self.grid_options['errors_found']: + if self.grid_options['_errors_found']: # Some information afterwards verbose_print( - "During the run {} failed systems were found, with a total probability of {} and with the following unique error codes: {} ".format(self.grid_options['failed_count'], self.grid_options['failed_prob'], self.grid_options['failed_systems_error_codes']), + "During the run {} failed systems were found, with a total probability of {} and with the following unique error codes: {} ".format(self.grid_options['_failed_count'], self.grid_options['_failed_prob'], self.grid_options['_failed_systems_error_codes']), self.grid_options["verbosity"], 0 ) # Some information afterwards verbose_print( - "The full argline commands for {} these systems have been written to {}".format("ALL" if not self.grid_options['errors_exceeded'] else "SOME (only the first ones, as there were too many to log all of them)", os.path.join(self.grid_options['tmp_dir'], 'failed_systemsX.txt')), + "The full argline commands for {} these systems have been written to {}".format("ALL" if not self.grid_options['_errors_exceeded'] else "SOME (only the first ones, as there were too many to log all of them)", os.path.join(self.grid_options['tmp_dir'], 'failed_systemsX.txt')), self.grid_options["verbosity"], 0 ) @@ -706,7 +709,7 @@ class Population: self._load_grid_function() # Set up generator - generator = self.grid_options["system_generator"](self) + generator = self.grid_options["_system_generator"](self) # Set up local variables running = True @@ -728,7 +731,7 @@ class Population: full_system_dict.update(system) # self._print_info( - # i + 1, self.grid_options["total_starcount"], full_system_dict + # i + 1, self.grid_options["_total_starcount"], full_system_dict # ) # @@ -746,11 +749,11 @@ class Population: # Return a set of results and errors output_dict = { "results": self.grid_options["results"], - "failed_count": self.grid_options['failed_count'], - "failed_prob": self.grid_options['failed_prob'], - "failed_systems_error_codes": self.grid_options['failed_systems_error_codes'], - "errors_exceeded": self.grid_options['errors_exceeded'], - "errors_found": self.grid_options['errors_found'], + "_failed_count": self.grid_options['_failed_count'], + "_failed_prob": self.grid_options['_failed_prob'], + "_failed_systems_error_codes": self.grid_options['_failed_systems_error_codes'], + "_errors_exceeded": self.grid_options['_errors_exceeded'], + "_errors_found": self.grid_options['_errors_found'], } return output_dict @@ -806,11 +809,11 @@ class Population: print(combined_output_dict) self.grid_options["results"] = combined_output_dict["results"] - self.grid_options["failed_count"] = combined_output_dict["failed_count"] - self.grid_options["failed_prob"] = combined_output_dict["failed_prob"] - self.grid_options["failed_systems_error_codes"] = list(set(combined_output_dict["failed_systems_error_codes"])) - self.grid_options["errors_exceeded"] = combined_output_dict["errors_exceeded"] - self.grid_options["errors_found"] = combined_output_dict["errors_found"] + self.grid_options["_failed_count"] = combined_output_dict["_failed_count"] + self.grid_options["_failed_prob"] = combined_output_dict["_failed_prob"] + self.grid_options["_failed_systems_error_codes"] = list(set(combined_output_dict["_failed_systems_error_codes"])) + self.grid_options["_errors_exceeded"] = combined_output_dict["_errors_exceeded"] + self.grid_options["_errors_found"] = combined_output_dict["_errors_found"] def _evolve_population_lin(self): @@ -818,7 +821,7 @@ class Population: Function to evolve the population linearly (i.e. 1 core, no multiprocessing methods) """ - for i, system in enumerate(self.grid_options["system_generator"](self)): + for i, system in enumerate(self.grid_options["_system_generator"](self)): full_system_dict = self.bse_options.copy() full_system_dict.update(system) binary_cmdline_string = self._return_argline(full_system_dict) @@ -827,11 +830,11 @@ class Population: custom_logging_func_memaddr=self.grid_options[ "custom_logging_func_memaddr" ], - store_memaddr=self.grid_options["store_memaddr"], + store_memaddr=self.grid_options["_store_memaddr"], population=1, ) self._print_info( - i + 1, self.grid_options["total_starcount"], full_system_dict + i + 1, self.grid_options["_total_starcount"], full_system_dict ) if self.grid_options["parse_function"]: @@ -853,7 +856,7 @@ class Population: custom_logging_func_memaddr=self.grid_options[ "custom_logging_func_memaddr" ], - store_memaddr=self.grid_options["store_memaddr"], + store_memaddr=self.grid_options["_store_memaddr"], population=1, ) @@ -885,7 +888,7 @@ class Population: custom_logging_func_memaddr=self.grid_options[ "custom_logging_func_memaddr" ], - store_memaddr=self.grid_options["store_memaddr"], + store_memaddr=self.grid_options["_store_memaddr"], population=0, ) @@ -925,7 +928,7 @@ class Population: self._set_custom_logging() ### Load store - self.grid_options["store_memaddr"] = _binary_c_bindings.return_store_memaddr() + self.grid_options["_store_memaddr"] = _binary_c_bindings.return_store_memaddr() ### ensemble: ## check the settings: @@ -945,10 +948,10 @@ class Population: if self.grid_options["population_type"] == "grid": ####################### # Dry run and getting starcount - self.grid_options["probtot"] = 0 + self.grid_options["_probtot"] = 0 # Put in check - if len(self.grid_options["grid_variables"]) == 0: + if len(self.grid_options["_grid_variables"]) == 0: print("Error: you havent defined any grid variables! Aborting") raise ValueError @@ -963,20 +966,19 @@ class Population: print( "Total starcount for this run will be: {}".format( - self.grid_options["total_starcount"] + self.grid_options["_total_starcount"] ) ) ####################### # Reset values and prepare the grid function self.grid_options[ - "probtot" + "_probtot" ] = 0 # To make sure that the values are reset. TODO: fix this in a cleaner way self.grid_options[ - "start_time_evolution" + "_start_time_evolution" ] = time.time() # Setting start time of grid - # self._generate_grid_code(dry_run=False) @@ -987,7 +989,7 @@ class Population: elif self.grid_options["population_type"] == "source_file": ####################### # Dry run and getting starcount - self.grid_options["probtot"] = 0 + self.grid_options["_probtot"] = 0 # Load the grid code # TODO: fix this function @@ -998,17 +1000,17 @@ class Population: print( "Total starcount for this run will be: {}".format( - self.grid_options["total_starcount"] + self.grid_options["_total_starcount"] ) ) ####################### # Reset values and prepare the grid function self.grid_options[ - "probtot" + "_probtot" ] = 0 # To make sure that the values are reset. TODO: fix this in a cleaner way self.grid_options[ - "start_time_evolution" + "_start_time_evolution" ] = time.time() # Setting start time of grid # @@ -1036,19 +1038,23 @@ class Population: self._free_persistent_data_memory_and_combine_results_and_output() # Reset values - self.grid_options["count"] = 0 - self.grid_options["probtot"] = 0 - self.grid_options["system_generator"] = None + self.grid_options["_count"] = 0 + self.grid_options["_probtot"] = 0 + self.grid_options["_system_generator"] = None self.grid_options["error"] = 0 - self.grid_options["failed_count"] = 0 - self.grid_options["failed_prob"] = 0 + self.grid_options["_failed_count"] = 0 + self.grid_options["_failed_prob"] = 0 + self.grid_options["_errors_found"] = False + self.grid_options["_errors_exceeded"] = False # Remove files + # TODO: remove files # Unload functions + # TODO: unload functions # Unload store - _binary_c_bindings.free_store_memaddr(self.grid_options["store_memaddr"]) + _binary_c_bindings.free_store_memaddr(self.grid_options["_store_memaddr"]) ################################################### # Gridcode functions @@ -1082,7 +1088,7 @@ class Population: code_string = "" depth = 0 indent = " " - total_grid_variables = len(self.grid_options["grid_variables"]) + total_grid_variables = len(self.grid_options["_grid_variables"]) # Import packages code_string += "import math\n" @@ -1110,7 +1116,7 @@ class Population: # Set some values in the generated code: code_string += indent * depth + "# Setting initial values\n" - code_string += indent * depth + "total_starcount = 0\n" + code_string += indent * depth + "_total_starcount = 0\n" code_string += indent * depth + "starcounts = [0 for i in range({})]\n".format( total_grid_variables ) @@ -1134,7 +1140,7 @@ class Population: code_string += indent * depth + "# setting probability lists\n" # Prepare the probability for grid_variable_el in sorted( - self.grid_options["grid_variables"].items(), + self.grid_options["_grid_variables"].items(), key=lambda x: x[1]["grid_variable_number"], ): # Make probabilities dict @@ -1151,7 +1157,7 @@ class Population: print("Generating grid code") for loopnr, grid_variable_el in enumerate( sorted( - self.grid_options["grid_variables"].items(), + self.grid_options["_grid_variables"].items(), key=lambda x: x[1]["grid_variable_number"], ) ): @@ -1326,7 +1332,7 @@ class Population: # 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. - if loopnr == len(self.grid_options["grid_variables"]) - 1: + if loopnr == len(self.grid_options["_grid_variables"]) - 1: ################################################################################# # Here are the calls to the queuing or other solution. this part is for every system @@ -1340,17 +1346,17 @@ class Population: # Calculate value code_string += ( indent * (depth + 1) - + 'probability = self.grid_options["weight"] * probabilities_list[{}]'.format( + + 'probability = self.grid_options["_weight"] * probabilities_list[{}]'.format( grid_variable["grid_variable_number"] ) + "\n" ) code_string += ( indent * (depth + 1) - + 'repeat_probability = probability / self.grid_options["repeat"]' + + 'repeat_probability = probability / self.grid_options["_repeat"]' + "\n" ) - code_string += indent * (depth + 1) + "total_starcount += 1\n" + code_string += indent * (depth + 1) + "_total_starcount += 1\n" # set probability and phasevol values code_string += ( @@ -1367,7 +1373,7 @@ class Population: # Some prints. will be removed # code_string += indent * (depth + 1) + "print(probabilities)\n" # code_string += ( - # indent * (depth + 1) + 'print("total_starcount: ", total_starcount)\n' + # indent * (depth + 1) + 'print("_total_starcount: ", _total_starcount)\n' # ) # code_string += indent * (depth + 1) + "print(probability)\n" @@ -1402,7 +1408,7 @@ class Population: # Here comes the stuff that is put after the deepest nested part that calls returns stuff. for loopnr, grid_variable_el in enumerate( sorted( - self.grid_options["grid_variables"].items(), + self.grid_options["_grid_variables"].items(), key=lambda x: x[1]["grid_variable_number"], reverse=True, ) @@ -1430,7 +1436,7 @@ class Population: code_string += indent * (depth + 1) + "#" * 40 + "\n" code_string += ( indent * (depth + 1) - + "print('Grid has handled {} stars'.format(total_starcount))\n" + + "print('Grid has handled {} stars'.format(_total_starcount))\n" ) code_string += ( indent * (depth + 1) @@ -1438,7 +1444,7 @@ class Population: ) if dry_run: - code_string += indent * (depth + 1) + "return total_starcount\n" + code_string += indent * (depth + 1) + "return _total_starcount\n" ################################################################################# # Stop of code generation. Here the code is saved and written @@ -1490,7 +1496,7 @@ class Population: spec.loader.exec_module(grid_file) generator = grid_file.grid_code - self.grid_options["system_generator"] = generator + self.grid_options["_system_generator"] = generator verbose_print("Grid code loaded", self.grid_options["verbosity"], 1) @@ -1501,9 +1507,9 @@ class Population: Requires the grid to be built as a dry run grid """ - system_generator = self.grid_options["system_generator"] + system_generator = self.grid_options["_system_generator"] total_starcount = system_generator(self) - self.grid_options["total_starcount"] = total_starcount + self.grid_options["_total_starcount"] = total_starcount def _print_info(self, run_number, total_systems, full_system_dict): """ @@ -1522,7 +1528,7 @@ class Population: # 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) @@ -1557,7 +1563,7 @@ class Population: Function to go through the source_file and count the amount of lines and the total probability """ - system_generator = self.grid_options["system_generator"] + system_generator = self.grid_options["_system_generator"] total_starcount = 0 total_probability = 0 @@ -1568,7 +1574,7 @@ class Population: total_starcount += 1 total_starcount = system_generator(self) - self.grid_options["total_starcount"] = total_starcount + self.grid_options["_total_starcount"] = total_starcount def _load_source_file(self, check=False): """ @@ -1605,7 +1611,7 @@ class Population: source_file_filehandle = open(self.grid_options["source_file_filename"], "r") - self.grid_options["system_generator"] = source_file_filehandle + self.grid_options["_system_generator"] = source_file_filehandle verbose_print("Source file loaded", self.grid_options["verbosity"], 1) @@ -1711,7 +1717,7 @@ class Population: command += ' --cmdline "{}"'.format( " ".join( [ - "{}".format(self.grid_options["commandline_input"]), + "{}".format(self.grid_options["_commandline_input"]), "offset=$jobarrayindex", "modulo={}".format(self.grid_options["slurm_njobs"]), "vb={}".format(self.grid_options["verbosity"]), @@ -2132,7 +2138,7 @@ class Population: """ # Check if there is no compiled grid yet. If not, lets try to build it first. - if not self.grid_options["system_generator"]: + if not self.grid_options["_system_generator"]: ## check the settings: if self.bse_options.get("ensemble", None): @@ -2146,7 +2152,7 @@ class Population: raise ValueError # Put in check - if len(self.grid_options["grid_variables"]) == 0: + if len(self.grid_options["_grid_variables"]) == 0: print("Error: you havent defined any grid variables! Aborting") raise ValueError @@ -2156,7 +2162,7 @@ class Population: # self._load_grid_function() - if self.grid_options["system_generator"]: + if self.grid_options["_system_generator"]: # Check if there is an output dir configured if self.custom_options.get("data_dir", None): binary_c_calls_output_dir = self.custom_options["data_dir"] @@ -2192,7 +2198,7 @@ class Population: else: full_system_dict = self.bse_options.copy() - for system in self.grid_options["system_generator"](self): + for system in self.grid_options["_system_generator"](self): # update values with current system values full_system_dict.update(system) @@ -2227,7 +2233,7 @@ class Population: Has two types: 'single': - removes the compiled shared library - (which name is stored in grid_options['custom_logging_shared_library_file']) + (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 @@ -2248,9 +2254,9 @@ class Population: # print(self.grid_options["custom_logging_func_memaddr"]) # remove shared library files - if self.grid_options["custom_logging_shared_library_file"]: + if self.grid_options["_custom_logging_shared_library_file"]: remove_file( - self.grid_options["custom_logging_shared_library_file"], + self.grid_options["_custom_logging_shared_library_file"], self.grid_options["verbosity"], ) @@ -2272,13 +2278,13 @@ class Population: Function to add to the total probability """ - self.grid_options["probtot"] += prob + self.grid_options["_probtot"] += prob def _increment_count(self): """ Function to add to the total amount of stars """ - self.grid_options["count"] += 1 + self.grid_options["_count"] += 1 def _set_loggers(self): """ @@ -2336,16 +2342,16 @@ class Population: ) # Keep track of the amount of failed systems and their error codes - self.grid_options['failed_prob'] += system_dict['probability'] - self.grid_options['failed_count'] += 1 - self.grid_options['errors_found'] = True + self.grid_options['_failed_prob'] += system_dict['probability'] + self.grid_options['_failed_count'] += 1 + self.grid_options['_errors_found'] = True # Try catching the error code and keep track of the unique ones. try: error_code = int(binary_c_output.splitlines()[0].split("with error code")[-1].split(":")[0].strip()) - if not error_code in self.grid_options['failed_systems_error_codes']: - self.grid_options['failed_systems_error_codes'].append(error_code) + if not error_code in self.grid_options['_failed_systems_error_codes']: + self.grid_options['_failed_systems_error_codes'].append(error_code) except ValueError: verbose_print( "Failed to extract the error-code", @@ -2354,20 +2360,20 @@ class Population: ) # Check if we have exceeded the amount of errors - if self.grid_options['failed_count'] > self.grid_options['failed_systems_threshold']: - if not self.grid_options['errors_exceeded']: + if self.grid_options['_failed_count'] > self.grid_options['failed_systems_threshold']: + if not self.grid_options['_errors_exceeded']: verbose_print( "Process {} exceeded the maximum ({}) amount of failing systems. Stopped logging them to files now".format(self.process_ID, self.grid_options['failed_systems_threshold']), self.grid_options["verbosity"], 1, ) - self.grid_options['errors_exceeded'] = True + self.grid_options['_errors_exceeded'] = True # If not, write the failing systems to files unique to each process else: # Write arglines to file argstring = self._return_argline(system_dict) - with open(os.path.join(self.grid_options['tmp_dir'], 'failed_systems_{}_process_{}.txt'.format(self.grid_options['population_id'], self.process_ID)), 'a+') as f: + with open(os.path.join(self.grid_options['tmp_dir'], 'failed_systems_{}_process_{}.txt'.format(self.grid_options['_population_id'], self.process_ID)), 'a+') as f: f.write(argstring+"\n") ################################################################################################ diff --git a/binarycpython/utils/grid_options_defaults.py b/binarycpython/utils/grid_options_defaults.py index 6e2a01d58..90df8297e 100644 --- a/binarycpython/utils/grid_options_defaults.py +++ b/binarycpython/utils/grid_options_defaults.py @@ -1,11 +1,20 @@ """ -Module that contains the default options for the population gric code. +Module that contains the default options for the population grid code, + +along with a dictionary containing descriptions for each of the parameters, and some utility functions: +- grid_options_help: interactive function for the user to get descriptions for options +- grid_options_description_checker: function that checks that checks which options have a description. + +With this its also possible to automatically generate a pdf file containing all the setting names + descriptions. + +All the options starting with _ should not be changed by the user except when you really know what you're doing (which is probably hacking the code :P) """ import os from binarycpython.utils.custom_logging_functions import temp_dir +# Options dict grid_options_defaults_dict = { ########################## # general (or unordered..) @@ -14,9 +23,9 @@ grid_options_defaults_dict = { "binary": 0, # FLag on whether the systems are binary systems or single systems. "parse_function": None, # FUnction to parse the output with. "tmp_dir": temp_dir(), # Setting the temp dir of the program - "main_pid": -1, # Placeholder for the main process id of the run. + "_main_pid": -1, # Placeholder for the main process id of the run. # "output_dir": - "commandline_input": "", + "_commandline_input": "", ########################## # Execution log: ########################## @@ -27,16 +36,16 @@ grid_options_defaults_dict = { ########################## # binary_c files ########################## - "binary_c_executable": os.path.join( + "_binary_c_executable": os.path.join( os.environ["BINARY_C"], "binary_c" ), # TODO: make this more robust - "binary_c_shared_library": os.path.join( + "_binary_c_shared_library": os.path.join( os.environ["BINARY_C"], "src", "libbinary_c.so" ), # TODO: make this more robust - "binary_c_config_executable": os.path.join( + "_binary_c_config_executable": os.path.join( os.environ["BINARY_C"], "binary_c-config" ), # TODO: make this more robust - "binary_c_dir": os.environ["BINARYC_DIR"], + "_binary_c_dir": os.environ["BINARYC_DIR"], ########################## # Custom logging ########################## @@ -45,52 +54,52 @@ grid_options_defaults_dict = { # 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, + "_custom_logging_shared_library_file": None, # file containing the .so file ########################## # Store pre-loading: ########################## - "store_memaddr": -1, # Contains the store object memory adress, useful for preloading. + "_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 ########################## - "log_args": 0, # - "log_args_dir": "/tmp/", + "log_args": 0, # unused + "log_args_dir": "/tmp/", # unused ########################## # Population evolution ########################## ## General "evolution_type": "mp", # Flag for type of population evolution - "evolution_type_options": [ + "_evolution_type_options": [ "mp", "linear", ], # available choices for type of population evolution - "system_generator": None, # value that holds the function that generates the system + "_system_generator": None, # value that holds the function that generates the system # (result of building the grid script) "population_type": "grid", # - "population_type_options": [ + "_population_type_options": [ "grid", "source_file", - ], # Available choices for type of population generation # TODO: fill later with monte carlo etc + ], # Available choices for type of population generation. Unused for now. # TODO: fill later with monte carlo etc "source_file_filename": None, # filename for the source - "count": 0, # total count of systems - "probtot": 0, # total probability - "weight": 1.0, # weighting for the probability - "repeat": 1.0, # number of times to repeat each system (probability is adjusted to be 1/repeat) + "_count": 0, # count of systems + "_total_starcount": 0, # Total count of systems in this generator + "_probtot": 0, # total probability + "_weight": 1.0, # weighting for the probability + "_repeat": 1.0, # number of times to repeat each system (probability is adjusted to be 1/repeat) "results": {}, # dict to store the results. Every process fills this on its own and then it will be joined later - "start_time_evolution": 0, # Start time of the grid - "end_time_evolution": 0, # end time of the grid - "errors_found": False, # Flag whether there are any errors from binary_c - "errors_exceeded": False, # Flag whether the amt of errors have exceeded the limit - "failed_count": 0, # amt of failed systems - "failed_prob": 0, # Summed probability of failed systems + "_start_time_evolution": 0, # Start time of the grid + "_end_time_evolution": 0, # end time of the grid + "_errors_found": False, # Flag whether there are any errors from binary_c + "_errors_exceeded": False, # Flag whether the amt of errors have exceeded the limit + "_failed_count": 0, # amt of failed systems + "_failed_prob": 0, # Summed probability of failed systems "failed_systems_threshold": 20, # Maximum failed systems per process allowed to fail before the process stops logging the failing systems. - "failed_systems_error_codes": [], # List to store the unique error codes - "population_id": 0, # Random id of this grid/population run, Unique code for the population. Should be set only once by the controller process. - "modulo": 1, # run modulo n of the grid. + "_failed_systems_error_codes": [], # List to store the unique error codes + "_population_id": 0, # Random id of this grid/population run, Unique code for the population. Should be set only once by the controller process. + "modulo": 1, # run modulo n of the grid. #TODO: fix this ## Grid type evolution - "grid_variables": {}, # grid variables - "grid_code": None, # literal grid code: contains the whole script that'll be written to a file + "_grid_variables": {}, # grid variables "gridcode_filename": None, # filename of gridcode ## Monte carlo type evolution # TODO: make MC options @@ -410,3 +419,143 @@ grid_options_defaults_dict = { # # stop and start a grid # starting_snapshot_file=>undef, } + +# Grid containing the descriptions of the options # TODO: add input types for all of them +grid_options_descriptions = { + 'tmp_dir': "Directory where certain types of output are stored. The grid code is stored in that directory, as well as the custom logging libraries. Log files and other diagnostics will usually be written to this location, unless specified otherwise", # TODO: improve this + '_binary_c_dir': 'Director where binary_c is stored. This options are not really used', + '_binary_c_config_executable': 'Full path of the binary_c-config executable. This options is not used in the population object.', + '_binary_c_executable': 'Full path to the binary_c executable. This options is not used in the population object.', + '_binary_c_shared_library': "Full path to the libbinary_c file. This options is not used in the population object", + 'verbosity': 'Verbosity of the population code. Default is 0, by which only errors will be printed. Higher values will show more output, which is good for debugging.', + 'binary': "Set this to 1 if the population contains binaries. Input: int", # TODO: write what effect this has. + 'amt_cores': "The amount of cores that the population grid will use. The multiprocessing is useful but make sure to figure out how many logical cores the machine has. The core is multiprocessed, not multithreaded, and will gain no extra speed when amt_cores exceeds the amount of logical cores. Input: int", + '_start_time_evolution': "Variable storing the start timestamp of the population evolution. Set by the object itself.", # TODO: make sure this is logged to a file + '_end_time_evolution': "Variable storing the end timestamp of the population evolution. Set by the object itself", # TODO: make sure this is logged to a file + '_total_starcount': "Variable storing the total amount of systems in the generator. Used and set by the population object.", + '_custom_logging_shared_library_file': "filename for the custom_logging shared library. Used and set by the population object", + '_errors_found': "Variable storing a boolean flag whether errors by binary_c are encountered.", + '_errors_exceeded': "Variable storing a boolean flag whether the amount of errors was higher than the set threshold (failed_systems_threshold). If True, then the commandline arguments of the failing systems will not be stored in the failed_system_log files.", + 'source_file_filename': "Variable containing the source file containing lines of binary_c commandline calls. These all have to start with binary_c.", # TODO: Expand + 'results': "Dictionary in which the user can place their results. This dictionary gets merged at the end of a mulitprocessing simulation.", + 'C_auto_logging': "Dictionary containing parameters to be logged by binary_c. The structure of this dictionary is as follows: the key is used as the headline which the user can then catch. The value at that key is a list of binary_c system parameters (like star[0].mass)", + 'C_logging_code': "Variable to store the exact code that is used for the custom_logging. In this way the user can do more complex logging, as well as putting these logging strings in files.", + '_failed_count': "Variable storing the amount of failed systems.", + '_evolution_type_options': "List containing the evolution type options.", + '_failed_prob': "Variable storing the total probability of all the failed systems", + '_failed_systems_error_codes': "List storing the unique error codes raised by binary_c of the failed systems", + '_grid_variables': "Dictionary storing the grid_variables. These contain properties which are accessed by the _generate_grid_code function", + '_population_id': "Variable storing a unique 32-char hex string.", + '_commandline_input': "String containing the arguments passed to the population object via the command line. Set and used by the population object.", + '_system_generator': "Function object that contains the system generator function. This can be from a grid, or a source file, or a montecarlo grid.", + 'gridcode_filename': "Filename for the grid code. Set and used by the population object. TODO: allow the user to provide their own function, rather than only a generated function.", + 'log_args': "Boolean to log the arguments. Unused ", # TODO: fix the functionality for this and describe it properly + 'log_args_dir': "Directory to log the arguments to. Unused", # TODO: fix the functionality for this and describe it properly + 'log_file': "Log file for the population object. Unused", # TODO: fix the functionality for this and describe it properly + 'custom_logging_func_memaddr': "Memory adress where the custom_logging_function is stored. Input: int", + '_count': "Counter tracking which system the generator is on.", + '_probtot': "Total probability of the population.", # TODO: check whether this is used properly throughout + '_main_pid': "Main process ID of the master process. Used and set by the population object.", + '_store_memaddr': "Memory adress of the store object for binary_c.", + 'failed_systems_threshold': "Variable storing the maximum amount of systems that are allowed to fail before logging their commandline arguments to failed_systems log files", + 'parse_function': "Function that the user can provide to handle the output the binary_c. This function has to take the arguments (self, output). Its best not to return anything in this function, and just store stuff in the grid_options['results'] dictionary, or just output results to a file", + 'condor': "Int flag whether to use a condor type population evolution.", # TODO: describe this in more detail + 'slurm': "Int flag whether to use a slurm type population evolution.", # TODO: describe this in more detail + 'population_type': "variable storing what kind of population type should be evolved. See population_type_options for the options.", # TODO: make this functionality work properly + '_population_type_options': "List storing the population_type options.", + '_weight': "Weight factor for each system. The calculated probability is mulitplied by this. If the user wants each system to be repeated several times, then this variable should not be changed, rather change the _repeat variable instead, as that handles the reduction in probability per system. This is useful for systems that have a process with some random element in it.", # TODO: add more info here, regarding the evolution splitting. + '_repeat': "Factor of how many times a system should be repeated. Consider the evolution splitting binary_c argument for supernovae kick repeating.", # TODO: make sure this is used. + 'evolution_type': "Variable containing the type of evolution used of the grid. Multiprocessing or linear processing", +} + +################################# +# Grid options functions + +# Utility functions +def grid_options_help(option): + """ + Function that returns the description of a grid option + """ + + option_keys = grid_options_defaults_dict.keys() + description_keys = grid_options_descriptions.keys() + + if not option in option_keys: + print("Error: This is an invalid entry. Option does not exist, please choose from the following options:\n\t{}".format(', '.join(option_keys))) + else: + if not option in description_keys: + print("This option has not been described properly yet. Please contact on of the authors") + + else: + print(grid_options_descriptions[option]) + +def grid_options_description_checker(print_info=True): + """ + Function that checks which descriptions are missing + """ + + option_keys = grid_options_defaults_dict.keys() + description_keys = grid_options_descriptions.keys() + + undescribed_keys = list(set(option_keys)-set(description_keys)) + + if undescribed_keys: + if print_info: + print("Warning: the following keys have no description yet:\n\t{}".format(", ".join(sorted(undescribed_keys)))) + print("Total description progress: {:.2f}%%".format(100 * len(description_keys)/len(option_keys))) + return len(undescribed_keys) + +def write_grid_options_to_rst_file(output_file): + """ + Function that writes the descriptions of the grid options to a rst file + + TODO: seperate things into private and public options + """ + + # Get the options and the description + options = grid_options_defaults_dict + descriptions = grid_options_descriptions + + # Get those that do not have a description + not_described_yet = list(set(options) - set(descriptions)) + + # separate public and private options + public_options = [key for key in options if not key.startswith("_")] + private_options = [key for key in options if key.startswith("_")] + + # Check input + if not output_file.endswith(".rst"): + print("Filename doesn't end with .rst, please provide a proper filename") + return None + + with open(output_file, 'w') as f: + print("Population grid code options", file=f) + print("{}".format("="*len("Population grid code options")), file=f) + print("The following chapter contains all grid code options, along with their descriptions", file=f) + print("There are {} options that are not described yet.".format(len(not_described_yet)), file=f) + print("\n", file=f) + + # Start public options part + print("Public options", file=f) + print("{}".format("-"*len("Public options")), file=f) + print("The following options are meant to be changed by the user.", file=f) + print("\n", file=f) + + for public_option in sorted(public_options): + if public_option in descriptions: + print("| **{}**: {}".format(public_option, descriptions[public_option]), file=f) + else: + print("| **{}**: No description available yet".format(public_option), file=f) + print("", file=f) + + # Start private options part + print("Private options", file=f) + print("{}".format("-"*len("Private options")), file=f) + print("The following options are not meant to be changed by the user, as these options are used and set internally by the object itself. The description still is provided, but just for documentation purposes.", file=f) + + for private_option in sorted(private_options): + if private_option in descriptions: + print("| **{}**: {}".format(private_option, descriptions[private_option]), file=f) + else: + print("| **{}**: No description available yet".format(private_option), file=f) + print("", file=f) diff --git a/binarycpython/utils/grid_options_descriptions.py b/binarycpython/utils/grid_options_descriptions.py deleted file mode 100644 index eb4bb60a7..000000000 --- a/binarycpython/utils/grid_options_descriptions.py +++ /dev/null @@ -1,65 +0,0 @@ -""" -File containing a dictionary which holds the description for all the grid_options settings. - -Using this, together with the accompanied function, the user can interactively check what the settings result in. - -With this its also possible to automatically generate a pdf file containing all the setting names + descriptions. - -TODO: change the options that one should not use (i.e. the things that are set by the grid itself) to start with an underscore - -""" - -from binarycpython.utils.grid_options_defaults import grid_options_defaults_dict - -# Grid containing the descriptions of the options -grid_options_descriptions = { - 'tmp_dir': "Directory where certain types of output are stored. The grid code is stored in that directory, as well as the custom logging libraries. Log files and other diagnostics will usually be written to this location, unless specified otherwise", # TODO: improve this - 'binary_c_dir': 'Director where binary_c is stored. This options are not really used', - 'binary_c_config_executable': 'Full path of the binary_c-config executable. This option is not really used.', - 'binary_c_executable': 'Full path to the binary_c executable. This options is not really used.', - 'binary_c_shared_library': "Full path to the libbinary_c file. This option is not really used", - 'verbosity': 'Verbosity of the population code. Default is 0, by which only errors will be printed. Higher values will show more output, which is good for debugging.', - 'binary': "Set this to 1 if the population contains binaries. Input: int", # TODO: write what effect this has. - 'amt_cores': "The amount of cores that the population grid will use. The multiprocessing is useful but make sure to figure out how many logical cores the machine has. The core is multiprocessed, not multithreaded, and will gain no extra speed when amt_cores exceeds the amount of logical cores. Input: int" -} - - -def grid_options_help(option): - """ - Function that returns the description of a grid option - """ - - option_keys = grid_options_defaults_dict.keys() - description_keys = grid_options_descriptions.keys() - - if not option in option_keys: - print("Error: This is an invalid entry. Option does not exist, please choose from the following options:\n\t{}".format(', '.join(option_keys))) - else: - if not option in description_keys: - print("This option has not been described properly yet. Please contact on of the authors") - - else: - print(grid_options_descriptions[option]) - -def grid_options_description_checker(print_info=True): - """ - Function that checks which descriptions are missing - """ - - option_keys = grid_options_defaults_dict.keys() - description_keys = grid_options_descriptions.keys() - - undescribed_keys = list(set(option_keys)-set(description_keys)) - - if undescribed_keys: - if print_info: - print("Warning: the following keys have no description yet:\n\t{}".format(", ".join(sorted(undescribed_keys)))) - print("Total description progress: {:.2f}%%".format(100 * len(description_keys)/len(option_keys))) - return len(undescribed_keys) - - -# grid_options_help('amt_cores') - -# grid_options_description_checker() - - -- GitLab