def setup(self): """ Sets up the SeisFlows3 modules for the Migration """ # Set up all the requisite modules from the master job self.logger.info(msg.mnr("PERFORMING MODULE SETUP")) preprocess.setup() postprocess.setup() system.run("solver", "setup")
def clean(self): """ Cleans directories in which function and gradient evaluations were carried out """ self.logger.info(msg.mnr("CLEANING WORKDIR FOR NEXT ITERATION")) unix.rm(PATH.GRAD) unix.rm(PATH.FUNC) unix.mkdir(PATH.GRAD) unix.mkdir(PATH.FUNC)
def evaluate_gradient(self, path=None): """ Performs adjoint simulation to retrieve the gradient of the objective """ self.logger.info(msg.mnr("EVALUATING GRADIENT")) self.logger.debug( f"evaluating gradient {PAR.NTASK} times on system...") system.run("solver", "eval_grad", path=path or PATH.GRAD, export_traces=PAR.SAVETRACES)
def finalize(self): """ Saves results from current model update iteration """ self.logger.info(msg.mnr("FINALIZING MIGRATION WORKFLOW")) if PAR.SAVETRACES: self.save_traces() if PAR.SAVEKERNELS: self.save_kernels() else: self.save_kernels_sum()
def write_gradient(self): """ Writes gradient in format expected by non-linear optimization library. Calls the postprocess module, which will smooth/precondition gradient. """ self.logger.info(msg.mnr("POSTPROCESSING KERNELS")) src = os.path.join(PATH.GRAD, "gradient") dst = f"g_new" postprocess.write_gradient(PATH.GRAD) parts = solver.load(src, suffix="_kernel") optimize.save(dst, solver.merge(parts))
def clean(self): """ Determine if forward simulation from line search can be carried over. We assume clean() is the final flow() argument so that we can update the thrifty status here. """ self.update_status() if self.thrifty: self.logger.info( msg.mnr("THRIFTY CLEANING WORKDIR FOR NEXT " "ITERATION")) unix.rm(PATH.GRAD) unix.mv(PATH.FUNC, PATH.GRAD) unix.mkdir(PATH.FUNC) else: super().clean()
def main(self, return_flow=False): """ This function controls the main SeisFlows3 workflow, and is submitted to system by the call `seisflows submit` or `seisflows resume`. It proceeds to evaluate a list of functions in order until a User defined stop criteria is met. :type return_flow: bool :param return_flow: for CLI tool, simply returns the flow function rather than running the workflow. Used for print statements etc. """ # The workFLOW is a tuple of functions that can be called dynamic ally flow = (self.setup, self.initialize, self.evaluate_gradient, self.write_gradient, self.compute_direction, self.line_search, self.finalize, self.clean) if return_flow: return flow # Allow workflow resume from and stop after given flow functions start, stop = self.check_stop_resume_cond(flow) # Run the workflow until from the current iteration until PAR.END optimize.iter = PAR.BEGIN self.logger.info(msg.mjr("STARTING INVERSION WORKFLOW")) while True: self.logger.info(msg.mnr(f"ITERATION {optimize.iter} / {PAR.END}")) # Execute the functions within the flow for func in flow[start:stop]: func() # Finish. Assuming completion of all arguments in flow() self.logger.info(msg.mjr(f"FINISHED FLOW EXECUTION")) # Reset flow for subsequent iterations start, stop = None, None if optimize.iter >= PAR.END: break optimize.iter += 1 self.logger.info( msg.sub(f"INCREMENT ITERATION TO {optimize.iter}")) self.logger.info(msg.mjr("FINISHED INVERSION WORKFLOW"))
def line_search(self): """ Conducts line search in given search direction Status codes: status > 0 : finished status == 0 : not finished status < 0 : failed """ # Calculate the initial step length based on optimization algorithm if optimize.line_search.step_count == 0: self.logger.info( msg.mjr(f"CONDUCTING LINE SEARCH " f"({optimize.eval_str})")) optimize.initialize_search() # Attempt a new trial step with the given step length optimize.line_search.step_count += 1 self.logger.info(msg.mnr(f"TRIAL STEP COUNT: {optimize.eval_str}")) self.evaluate_function(path=PATH.FUNC, suffix="try") # Check the function evaluation against line search history status = optimize.update_search() # Proceed based on the outcome of the line search if status > 0: self.logger.info("trial step successful") # Save outcome of line search to disk; reset step to 0 for next iter optimize.finalize_search() return elif status == 0: self.logger.info("retrying with new trial step") # Recursively call this function to attempt another trial step self.line_search() elif status < 0: if optimize.retry_status(): self.logger.info("line search failed. restarting line search") # Reset the line search machinery; set step count to 0 optimize.restart() self.line_search() else: self.logger.info("line search failed. aborting inversion.") sys.exit(-1)
def setup(self): """ Lays groundwork for inversion by running setup() functions for the involved sub-modules, generating True model synthetic data if necessary, and generating the pre-requisite database files. .. note:: This function should only be run one time, at the start of iter 1 """ # Iter check is done inside setup() so that we can include fx in FLOW if optimize.iter == 1: # Set up all the requisite modules from the master job self.logger.info(msg.mnr("PERFORMING MODULE SETUP")) preprocess.setup() postprocess.setup() optimize.setup() # Run solver.setup() in parallel self.logger.info("setting up solver on system...") system.run("solver", "setup")
def compute_direction(self): """ Computes search direction """ self.logger.info(msg.mnr("COMPUTING SEARCH DIRECTION")) optimize.compute_direction()
def check_stop_resume_cond(self, flow): """ Chek the stop after and resume from conditions Allow the main() function to resume a workflow from a given flow argument, or stop the workflow after a given argument. In the event that a previous workflow errored, or if the User had previously stopped a workflow to look at results and they want to pick up where they left off. Late check: Exits the workflow if RESUME_FROM or STOP_AFTER arguments do not match any of the given flow arguments. :type flow: tuple of functions :param flow: an ordered list of functions that will be :rtype: tuple of int :return: (start, stop) indices of the `flow` input dictating where the list should be begun and ended. If RESUME_FROM and STOP_AFTER conditions are NOT given by the user, start and stop will be 0 and -1 respectively, meaning we should execute the ENTIRE list """ fxnames = [func.__name__ for func in flow] # Default values which dictate that flow will execute in its entirety start_idx = None stop_idx = None # Overwrite start_idx if RESUME_FROM given, exit condition if no match if PAR.RESUME_FROM: try: start_idx = fxnames.index(PAR.RESUME_FROM) fx_name = flow[start_idx].__name__ self.logger.info( msg.mnr(f"WORKFLOW WILL RESUME FROM FUNC: '{fx_name}'")) except ValueError: self.logger.info( msg.cli( f"{PAR.RESUME_FROM} does not correspond to any FLOW " f"functions. Please check that PAR.RESUME_FROM " f"matches one of the functions listed out in " f"`seisflows print flow`.", header="error", border="=")) sys.exit(-1) # Overwrite stop_idx if STOP_AFTER provided, exit condition if no match if PAR.STOP_AFTER: try: stop_idx = fxnames.index(PAR.STOP_AFTER) fx_name = flow[stop_idx].__name__ stop_idx += 1 # increment to stop AFTER, due to python indexing self.logger.info( msg.mnr(f"WORKFLOW WILL STOP AFTER FUNC: '{fx_name}'")) except ValueError: self.logger.info( msg.cli( f"{PAR.STOP_AFTER} does not correspond to any FLOW " f"functions. Please check that PAR.STOP_AFTER " f"matches one of the functions listed out in " f"`seisflows print flow`.", header="error", border="=")) sys.exit(-1) # Make sure stop after doesn't come before resume_from, otherwise none # of the flow will execute if PAR.STOP_AFTER and PAR.RESUME_FROM: if stop_idx <= start_idx: self.logger.info( msg.cli( f"PAR.STOP_AFTER=='{PAR.STOP_AFTER}' is called " f"before PAR.RESUME_FROM=='{PAR.RESUME_FROM}' in " f"the FLOW functions. Please adjust accordingly " f"and rerun.", header="error", border="=")) sys.exit(-1) return start_idx, stop_idx