diff --git a/binarycpython/utils/grid.py b/binarycpython/utils/grid.py index cb07a85198aff976e7db9031e08e1f9801089708..c30f45c33910f2abf6527ede10a9ea205cedb6a0 100644 --- a/binarycpython/utils/grid.py +++ b/binarycpython/utils/grid.py @@ -3,9 +3,6 @@ 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 - Tasks: - TODO: add functionality to 'on-init' set arguments - TODO: add functionality to return the initial_abundance_hash @@ -21,7 +18,6 @@ Tasks: - TODO: uncomment and implement the HPC functionality - TODO: think of a clean and nice way to unload and remove the custom_logging_info library from memory (and from disk) - TODO: think of a nice way to remove the loaded grid_code/ generator from memory. - - TODO: Create a designated dict for results """ import os @@ -366,16 +362,6 @@ class Population: ): return grid_variable - def clean( - self - ) -> None: - """ - Clean the contents of the population object so it can be reused. - """ - self.grid_results = AutoVivificationDict() - self.grid_ensemble_results = {} - self.process_ID = 0 - def update_grid_variable( self, name: str, @@ -394,9 +380,9 @@ class Population: grid_variable = None try: grid_variable = self.grid_options["_grid_variables"][name] - except: + except KeyError: msg = "Unknown grid variable {} - please create it with the add_grid_variable() method.".format(name) - raise ValueError(msg) + raise KeyError(msg) for key,value in kwargs.items(): grid_variable[key] = value @@ -414,14 +400,19 @@ class Population: """ Function to rename a grid variable. + note: this does NOT alter the order + of the self.grid_options["_grid_variables"] dictionary. + + The order in which the grid variables are loaded into the grid is based on their + `grid_variable_number` property + Args: oldname: old name of the grid variable newname: new name of the grid variable """ - # note: this apparently does NOT alter the order - # of the self.grid_options["_grid_variables"] dictionary. + try: self.grid_options["_grid_variables"][newname] = self.grid_options["_grid_variables"].pop(oldname) self.grid_options["_grid_variables"][newname]["name"] = newname @@ -516,8 +507,6 @@ class Population: (steps starting at lower edge + 0.5 * stepsize). """ - # TODO: Add check for the grid type input value - # Add grid_variable grid_variable = { "name": name, @@ -684,37 +673,42 @@ class Population: all_info_cleaned = copy.deepcopy(all_info) if use_datadir: - if not self.custom_options.get("base_filename", None): - base_name = "simulation_{}".format( - datetime.datetime.strftime(datetime.datetime.now(), "%Y%m%d_%H%M%S") - ) - else: - base_name = os.path.splitext(self.custom_options["base_filename"])[0] + if self.custom_options.get("data_dir", None): + if not self.custom_options.get("base_filename", None): + base_name = "simulation_{}".format( + datetime.datetime.strftime(datetime.datetime.now(), "%Y%m%d_%H%M%S") + ) + else: + base_name = os.path.splitext(self.custom_options["base_filename"])[0] - settings_name = base_name + "_settings.json" + settings_name = base_name + "_settings.json" - # Check directory, make if necessary - os.makedirs(self.custom_options["data_dir"], exist_ok=True) + # Check directory, make if necessary + os.makedirs(self.custom_options["data_dir"], exist_ok=True) - settings_fullname = os.path.join( - self.custom_options["data_dir"], settings_name - ) + settings_fullname = os.path.join( + self.custom_options["data_dir"], settings_name + ) - verbose_print( - "Writing settings to {}".format(settings_fullname), - self.grid_options["verbosity"], - 1, - ) - # if not outfile.endswith('json'): - with open(settings_fullname, "w") as file: - file.write( - json.dumps( - all_info_cleaned, - indent=4, - default=binaryc_json_serializer, - ) + verbose_print( + "Writing settings to {}".format(settings_fullname), + self.grid_options["verbosity"], + 1, ) - return settings_fullname + # if not outfile.endswith('json'): + with open(settings_fullname, "w") as file: + file.write( + json.dumps( + all_info_cleaned, + indent=4, + default=binaryc_json_serializer, + ) + ) + return settings_fullname + else: + msg = "Exporting all info without passing a value for `outfile` requires custom_options['data_dir'] to be present. That is not the cause. Either set the `data_dir` or pass a value for `outfile` " + raise ValueError + else: verbose_print( "Writing settings to {}".format(outfile), @@ -809,19 +803,40 @@ class Population: # Evolution functions ################################################### - def _pre_run_cleanup(self): + def _pre_run_cleanup(self) -> None: """ Function to clean up some stuff in the grid before a run (like results, ensemble results etc) """ # empty results self.grid_options["results"] = {} + self.grid_results = AutoVivificationDict() + self.grid_ensemble_results = {} + + # Reset the process ID (should not have a value initially, but can't hurt if it does) + self.process_ID = 0 + + # Reset population ID: + self.grid_options["_population_id"] = uuid.uuid4().hex + + def clean( + self + ) -> None: + """ + Clean the contents of the population object so it can be reused. + + Calling _pre_run_cleanup() + + TODO: decide to deprecate this function + """ + + self._pre_run_cleanup() def evolve(self) -> None: """ Entry point function of the whole object. From here, based on the settings, we set up a SLURM or CONDOR grid, or if no setting is given we go straight - to evolving the population + to evolving the population. There are no direct arguments to this function, rather it is based on the grid_options settings: grid_options['slurm']: integer Boolean whether to use a slurm_grid evolution @@ -831,7 +846,9 @@ class Population: (that doesn't mean this cannot be run on a server with many cores) Returns an dictionary containing the analytics of the run + TODO: change the way this is done. Slurm & CONDOR should probably do this different + NOTE: SLURM and CONDOR options are not working properly yet """ # Just to make sure we don't have stuff from a previous run hanging around @@ -894,9 +911,6 @@ class Population: - TODO: include options for different ways of generating a population here. (i.e. MC or source file) """ - # Reset some settings: population_id, results, ensemble_results etc - self.grid_options["_population_id"] = uuid.uuid4().hex - ## # Prepare code/initialise grid. # set custom logging, set up store_memaddr, build grid code. dry run grid code. @@ -1427,7 +1441,6 @@ class Population: if self.grid_options['verbosity'] >= _LOGGER_VERBOSITY_LEVEL: stream_logger.debug(f"Process-{self.process_ID} is finishing.") - # Handle ensemble output: is ensemble==1, then either directly write that data to a file, or combine everything into 1 file. ensemble_json = {} # Make sure it exists already if self.bse_options.get("ensemble", 0) == 1: @@ -1591,31 +1604,42 @@ class Population: ### Custom logging code: self._set_custom_logging() - # Get argument line - argline = self._return_argline(self.bse_options) - verbose_print("Running {}".format(argline), self.grid_options["verbosity"], 1) - # Run system - out = _binary_c_bindings.run_system( - argstring=argline, - custom_logging_func_memaddr=self.grid_options[ - "custom_logging_func_memaddr" - ], - store_memaddr=self.grid_options["_store_memaddr"], - population=0, - ) + # Check if there are actually arguments passed: + if self.bse_options: + + # Get argument line and + argline = self._return_argline(self.bse_options) + + verbose_print("Running {}".format(argline), self.grid_options["verbosity"], 1) + + # Run system + out = _binary_c_bindings.run_system( + argstring=argline, + custom_logging_func_memaddr=self.grid_options[ + "custom_logging_func_memaddr" + ], + store_memaddr=self.grid_options["_store_memaddr"], + population=0, + ) + + # Clean up custom logging + if clean_up_custom_logging_files: + self._clean_up_custom_logging(evol_type="single") + + # Parse output and return the result + if self.grid_options["parse_function"]: + return self.grid_options["parse_function"](self, out) + + # Otherwise just return the raw output + return out + + else: + msg = "No actual evolution options passed to the evolve call. Aborting" + raise ValueError(msg) - # TODO: add call to function that cleans up the temp custom logging 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) - return out def _setup(self): """ diff --git a/binarycpython/utils/grid_options_defaults.py b/binarycpython/utils/grid_options_defaults.py index 37585f2e426e3ae4159cf1ca11970cb77512d7ff..d01f92fcc3786eeada771de7e1a83f597b4ccfbd 100644 --- a/binarycpython/utils/grid_options_defaults.py +++ b/binarycpython/utils/grid_options_defaults.py @@ -475,7 +475,7 @@ grid_options_descriptions = { "_main_pid": "Main process ID of the master process. Used and set by the population object.", "_store_memaddr": "Memory address 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 command line 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", + "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 self.grid_results dictionary, or just output results to a file", "condor": "Int flag whether to use a condor type population evolution. Not implemented yet.", # TODO: describe this in more detail "slurm": "Int flag whether to use a Slurm type population evolution.", # TODO: describe this in more detail "weight": "Weight factor for each system. The calculated probability is multiplied 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.