diff --git a/pySDC/core/ConvergenceController.py b/pySDC/core/ConvergenceController.py index 2a98d1c771..f1cf2eda02 100644 --- a/pySDC/core/ConvergenceController.py +++ b/pySDC/core/ConvergenceController.py @@ -263,26 +263,7 @@ def prepare_next_block(self, controller, S, size, time, Tend, **kwargs): controller (pySDC.Controller): The controller S (pySDC.Step): The current step size (int): The number of ranks - time (float): The current time - Tend (float): The final time - - Returns: - None - """ - pass - - def prepare_next_block_nonMPI(self, controller, MS, active_slots, time, Tend): - """ - This is an extension to the function `prepare_next_block`, which is only called in the non MPI controller and - is needed because there is no chance to communicate backwards otherwise. While you should not do this in the - first place, the first step in the new block comes after the last step in the last block, such that it is still - in fact forwards communication, even though it looks backwards. - - Args: - controller (pySDC.Controller): The controller - MS (list): All steps of the controller - active_slots (list): Index list of active steps - time (float): The current time + time (float): The current time will be list in nonMPI controller implementation Tend (float): The final time Returns: diff --git a/pySDC/implementations/controller_classes/controller_MPI.py b/pySDC/implementations/controller_classes/controller_MPI.py index fc49884f1f..b3731f23c4 100644 --- a/pySDC/implementations/controller_classes/controller_MPI.py +++ b/pySDC/implementations/controller_classes/controller_MPI.py @@ -62,7 +62,7 @@ def __init__(self, controller_params, description, comm): if num_levels == 1 and self.params.predict_type is not None: self.logger.warning( - 'you have specified a predictor type but only a single level.. ' 'predictor will be ignored' + 'you have specified a predictor type but only a single level.. predictor will be ignored' ) for C in [self.convergence_controllers[i] for i in self.convergence_controller_order]: @@ -632,7 +632,7 @@ def it_check(self, comm, num_procs): C.pre_iteration_processing(self, self.S, comm=comm) if self.params.use_iteration_estimator: - # store pervious iterate to compute difference later on + # store previous iterate to compute difference later on self.S.levels[0].uold[1:] = self.S.levels[0].u[1:] if len(self.S.levels) > 1: # MLSDC or PFASST diff --git a/pySDC/implementations/controller_classes/controller_nonMPI.py b/pySDC/implementations/controller_classes/controller_nonMPI.py index 8720c0251e..c48cf74776 100644 --- a/pySDC/implementations/controller_classes/controller_nonMPI.py +++ b/pySDC/implementations/controller_classes/controller_nonMPI.py @@ -75,12 +75,12 @@ def __init__(self, num_procs, controller_params, description): if self.nlevels == 1 and self.params.predict_type is not None: self.logger.warning( - 'you have specified a predictor type but only a single level.. ' 'predictor will be ignored' + 'you have specified a predictor type but only a single level.. predictor will be ignored' ) for C in [self.convergence_controllers[i] for i in self.convergence_controller_order]: C.reset_buffers_nonMPI(self) - C.setup_status_variables(self) + C.setup_status_variables(self, MS=self.MS) def run(self, u0, t0, Tend): """ @@ -153,8 +153,7 @@ def run(self, u0, t0, Tend): C.post_step_processing(self, S) for C in [self.convergence_controllers[i] for i in self.convergence_controller_order]: - [C.prepare_next_block(self, S, len(active_slots), time, Tend) for S in self.MS] - C.prepare_next_block_nonMPI(self, self.MS, active_slots, time, Tend) + [C.prepare_next_block(self, S, len(active_slots), time, Tend, MS=MS_active) for S in self.MS] # setup the times of the steps for the next block for i in range(1, len(active_slots)): @@ -351,7 +350,7 @@ def spread(self, local_MS_running): S.status.stage = 'IT_CHECK' for C in [self.convergence_controllers[i] for i in self.convergence_controller_order]: - C.post_spread_processing(self, S) + C.post_spread_processing(self, S, MS=local_MS_running) def predict(self, local_MS_running): """ @@ -496,8 +495,8 @@ def it_check(self, local_MS_running): # decide if the step is done, needs to be restarted and other things convergence related for C in [self.convergence_controllers[i] for i in self.convergence_controller_order]: - C.post_iteration_processing(self, S) - C.convergence_control(self, S) + C.post_iteration_processing(self, S, MS=local_MS_running) + C.convergence_control(self, S, MS=local_MS_running) for S in local_MS_running: if not S.status.first: @@ -521,7 +520,7 @@ def it_check(self, local_MS_running): for hook in self.hooks: hook.pre_iteration(step=S, level_number=0) for C in [self.convergence_controllers[i] for i in self.convergence_controller_order]: - C.pre_iteration_processing(self, S) + C.pre_iteration_processing(self, S, MS=local_MS_running) if len(S.levels) > 1: # MLSDC or PFASST S.status.stage = 'IT_DOWN' diff --git a/pySDC/implementations/convergence_controller_classes/basic_restarting.py b/pySDC/implementations/convergence_controller_classes/basic_restarting.py index 0f86d34060..596e093168 100644 --- a/pySDC/implementations/convergence_controller_classes/basic_restarting.py +++ b/pySDC/implementations/convergence_controller_classes/basic_restarting.py @@ -1,7 +1,6 @@ from pySDC.core.ConvergenceController import ConvergenceController, Pars from pySDC.implementations.convergence_controller_classes.spread_step_sizes import ( - SpreadStepSizesBlockwiseNonMPI, - SpreadStepSizesBlockwiseMPI, + SpreadStepSizesBlockwise, ) from pySDC.core.Errors import ConvergenceError @@ -42,7 +41,7 @@ def __init__(self, controller, params, description, **kwargs): params (dict): Parameters for the convergence controller description (dict): The description object used to instantiate the controller """ - super(BasicRestarting, self).__init__(controller, params, description) + super().__init__(controller, params, description) self.buffers = Pars({"restart": False, "max_restart_reached": False}) def setup(self, controller, params, description, **kwargs): @@ -68,6 +67,7 @@ def setup(self, controller, params, description, **kwargs): "control_order": 95, "max_restarts": 10, "crash_after_max_restarts": True, + "step_size_spreader": SpreadStepSizesBlockwise.get_implementation(useMPI=params['useMPI']), } from pySDC.implementations.hooks.log_restarts import LogRestarts @@ -141,8 +141,8 @@ def prepare_next_block(self, controller, S, size, time, Tend, **kwargs): Args: controller (pySDC.Controller): The controller - MS (list): List of the steps of the controller - active_slots (list): Index list of active steps + S (pySDC.Step): The current step + size (int): The number of ranks time (list): List containing the time of all the steps Tend (float): Final time of the simulation @@ -174,33 +174,6 @@ def reset_buffers_nonMPI(self, controller, **kwargs): return None - def setup(self, controller, params, description, **kwargs): - """ - Define parameters here. - - Default parameters are: - - control_order (int): The order relative to other convergence controllers - - max_restarts (int): Maximum number of restarts we allow each step before we just move on with whatever we - have - - step_size_spreader (pySDC.ConvergenceController): A convergence controller that takes care of distributing - the steps sizes between blocks - - Args: - controller (pySDC.Controller): The controller - params (dict): The params passed for this specific convergence controller - description (dict): The description object used to instantiate the controller - - Returns: - (dict): The updated params dictionary - """ - defaults = { - "step_size_spreader": SpreadStepSizesBlockwiseNonMPI, - } - return { - **defaults, - **super(BasicRestartingNonMPI, self).setup(controller, params, description), - } - def determine_restart(self, controller, S, **kwargs): """ Restart all steps after the first one which wants to be restarted as well, but also check if we lost patience @@ -245,37 +218,10 @@ def __init__(self, controller, params, description, **kwargs): params (dict): Parameters for the convergence controller description (dict): The description object used to instantiate the controller """ - super(BasicRestartingMPI, self).__init__(controller, params, description) + super().__init__(controller, params, description) self.buffers = Pars({"restart": False, "max_restart_reached": False, 'restart_earlier': False}) - def setup(self, controller, params, description, **kwargs): - """ - Define parameters here. - - Default parameters are: - - control_order (int): The order relative to other convergence controllers - - max_restarts (int): Maximum number of restarts we allow each step before we just move on with whatever we - have - - step_size_spreader (pySDC.ConvergenceController): A convergence controller that takes care of distributing - the steps sizes between blocks - - Args: - controller (pySDC.Controller): The controller - params (dict): The params passed for this specific convergence controller - description (dict): The description object used to instantiate the controller - - Returns: - (dict): The updated params dictionary - """ - defaults = { - "step_size_spreader": SpreadStepSizesBlockwiseMPI, - } - return { - **defaults, - **super(BasicRestartingMPI, self).setup(controller, params, description), - } - - def determine_restart(self, controller, S, **kwargs): + def determine_restart(self, controller, S, comm, **kwargs): """ Restart all steps after the first one which wants to be restarted as well, but also check if we lost patience with the restarts and want to move on anyways. @@ -283,11 +229,11 @@ def determine_restart(self, controller, S, **kwargs): Args: controller (pySDC.Controller): The controller S (pySDC.Step): The current step + comm (mpi4py.MPI.Intracomm): Communicator Returns: None """ - comm = kwargs['comm'] assert S.status.slot == comm.rank if S.status.first: diff --git a/pySDC/implementations/convergence_controller_classes/estimate_extrapolation_error.py b/pySDC/implementations/convergence_controller_classes/estimate_extrapolation_error.py index ab72c392bc..d62dc43ab5 100644 --- a/pySDC/implementations/convergence_controller_classes/estimate_extrapolation_error.py +++ b/pySDC/implementations/convergence_controller_classes/estimate_extrapolation_error.py @@ -27,7 +27,7 @@ def __init__(self, controller, params, description, **kwargs): """ self.prev = Status(["t", "u", "f", "dt"]) # store solutions etc. of previous steps here self.coeff = Status(["u", "f", "prefactor"]) # store coefficients for extrapolation here - super(EstimateExtrapolationErrorBase, self).__init__(controller, params, description) + super().__init__(controller, params, description) controller.add_hook(LogExtrapolationErrorEstimate) def setup(self, controller, params, description, **kwargs): @@ -265,7 +265,7 @@ def setup(self, controller, params, description, **kwargs): Returns: dict: Updated parameters with default values """ - default_params = super(EstimateExtrapolationErrorNonMPI, self).setup(controller, params, description) + default_params = super().setup(controller, params, description) non_mpi_defaults = { "no_storage": False, @@ -283,7 +283,7 @@ def setup_status_variables(self, controller, **kwargs): Returns: None """ - super(EstimateExtrapolationErrorNonMPI, self).setup_status_variables(controller, **kwargs) + super().setup_status_variables(controller, **kwargs) self.prev.t = np.array([None] * self.params.n) self.prev.dt = np.array([None] * self.params.n) @@ -327,21 +327,23 @@ def post_iteration_processing(self, controller, S, **kwargs): return None - def prepare_next_block_nonMPI(self, controller, MS, active_slots, time, Tend, **kwargs): + def prepare_next_block(self, controller, S, size, time, Tend, MS, **kwargs): """ If the no-memory-overhead version is used, we need to delete stuff that shouldn't be available. Otherwise, we need to store all the stuff that we can. Args: controller (pySDC.Controller): The controller - MS (list): All steps of the controller - active_slots (list): Index list of active steps + S (pySDC.step): The current step + size (int): Number of ranks time (float): The current time Tend (float): The final time + MS (list): Active steps Returns: None """ + # delete values that should not be available in the next step if self.params.no_storage: self.prev.t = np.array([None] * self.params.n) @@ -351,13 +353,12 @@ def prepare_next_block_nonMPI(self, controller, MS, active_slots, time, Tend, ** else: # decide where we need to restart to store everything up to that point - MS_active = [MS[i] for i in range(len(MS)) if i in active_slots] - restarts = [S.status.restart for S in MS_active] - restart_at = np.where(restarts)[0][0] if True in restarts else len(MS_active) + restarts = [S.status.restart for S in MS] + restart_at = np.where(restarts)[0][0] if True in restarts else len(MS) # store values in the current block that don't need restarting - if restart_at > 0: - [self.store_values(S) for S in MS_active[:restart_at]] + if restart_at > S.status.slot: + self.store_values(S) return None diff --git a/pySDC/implementations/convergence_controller_classes/spread_step_sizes.py b/pySDC/implementations/convergence_controller_classes/spread_step_sizes.py index 907ac18c6a..5a5ce6eabd 100644 --- a/pySDC/implementations/convergence_controller_classes/spread_step_sizes.py +++ b/pySDC/implementations/convergence_controller_classes/spread_step_sizes.py @@ -2,7 +2,7 @@ from pySDC.core.ConvergenceController import ConvergenceController -class SpreadStepSizesBlockwiseBase(ConvergenceController): +class SpreadStepSizesBlockwise(ConvergenceController): """ Take the step size from the last step in the block and spread it to all steps in the next block such that every step in a block always has the same step size. @@ -30,56 +30,80 @@ def setup(self, controller, params, description, **kwargs): return {**defaults, **super().setup(controller, params, description, **kwargs)} + @classmethod + def get_implementation(cls, useMPI, **kwargs): + """ + Get MPI or non-MPI version + + Args: + useMPI (bool): The implementation that you want + + Returns: + cls: The child class implementing the desired flavor + """ + if useMPI: + return SpreadStepSizesBlockwiseMPI + else: + return SpreadStepSizesBlockwiseNonMPI + -class SpreadStepSizesBlockwiseNonMPI(SpreadStepSizesBlockwiseBase): +class SpreadStepSizesBlockwiseNonMPI(SpreadStepSizesBlockwise): """ Non-MPI version """ - def prepare_next_block_nonMPI(self, controller, MS, active_slots, time, Tend, **kwargs): + def prepare_next_block(self, controller, S, size, time, Tend, MS, **kwargs): """ Spread the step size of the last step with no restarted predecessors to all steps and limit the step size based on Tend Args: controller (pySDC.Controller): The controller - MS (list): List of the steps of the controller - active_slots (list): Index list of active steps - time (list): List containing the time of all the steps + S (pySDC.step): The current step + size (int): The number of ranks + time (list): List containing the time of all the steps handled by the controller (or float in MPI implementation) Tend (float): Final time of the simulation + MS (list): Active steps Returns: None """ # figure out where the block is restarted - restarts = [MS[p].status.restart for p in active_slots] + restarts = [me.status.restart for me in MS] if True in restarts: restart_at = np.where(restarts)[0][0] else: restart_at = len(restarts) - 1 # Compute the maximum allowed step size based on Tend. - dt_max = (Tend - time[0]) / len(active_slots) + dt_max = (Tend - time[0]) / size # record the step sizes to restart with from all the levels of the step - new_steps = [None] * len(MS[restart_at].levels) + new_steps = [None] * len(S.levels) for i in range(len(MS[restart_at].levels)): l = MS[restart_at].levels[i] # overrule the step size control to reach Tend if needed new_steps[i] = min( - [l.status.dt_new if l.status.dt_new is not None else l.params.dt, max([dt_max, l.params.dt_initial])] + [ + l.status.dt_new if l.status.dt_new is not None else l.params.dt, + max([dt_max, l.params.dt_initial]), + ] ) - for p in active_slots: - # spread the step sizes to all levels - for i in range(len(MS[p].levels)): - MS[p].levels[i].params.dt = new_steps[i] + if new_steps[i] < (l.status.dt_new if l.status.dt_new is not None else l.params.dt) and i == 0: + self.log( + f"Overwriting stepsize control to reach Tend: {Tend:.2e}! New step size: {new_steps[i]:.2e}", S + ) + + # spread the step sizes to all levels + for i in range(len(S.levels)): + S.levels[i].params.dt = new_steps[i] return None -class SpreadStepSizesBlockwiseMPI(SpreadStepSizesBlockwiseBase): - def prepare_next_block(self, controller, S, size, time, Tend, **kwargs): +class SpreadStepSizesBlockwiseMPI(SpreadStepSizesBlockwise): + def prepare_next_block(self, controller, S, size, time, Tend, comm, **kwargs): """ Spread the step size of the last step with no restarted predecessors to all steps and limit the step size based on Tend @@ -88,13 +112,13 @@ def prepare_next_block(self, controller, S, size, time, Tend, **kwargs): controller (pySDC.Controller): The controller S (pySDC.step): The current step size (int): The number of ranks - time (list): List containing the time of all the steps + time (list): List containing the time of all the steps handled by the controller (or float in MPI implementation) Tend (float): Final time of the simulation + comm (mpi4py.MPI.Intracomm): Communicator Returns: None """ - comm = kwargs['comm'] # figure out where the block is restarted restarts = comm.allgather(S.status.restart) diff --git a/pySDC/projects/PinTSimE/switch_estimator.py b/pySDC/projects/PinTSimE/switch_estimator.py index d04038dbb2..7cb4e4674d 100644 --- a/pySDC/projects/PinTSimE/switch_estimator.py +++ b/pySDC/projects/PinTSimE/switch_estimator.py @@ -59,7 +59,7 @@ def reset_status_variables(self, controller, **kwargs): self.setup_status_variables(controller, **kwargs) - def get_new_step_size(self, controller, S): + def get_new_step_size(self, controller, S, **kwargs): """ Determine a new step size when a switch is found such that the switch happens at the time step. @@ -124,7 +124,7 @@ def get_new_step_size(self, controller, S): else: self.status.switch_detected = False - def determine_restart(self, controller, S): + def determine_restart(self, controller, S, **kwargs): """ Check if the step needs to be restarted due to a predicting switch. @@ -140,9 +140,9 @@ def determine_restart(self, controller, S): S.status.restart = True S.status.force_done = True - super(SwitchEstimator, self).determine_restart(controller, S) + super().determine_restart(controller, S, **kwargs) - def post_step_processing(self, controller, S): + def post_step_processing(self, controller, S, **kwargs): """ After a step is done, some variables will be prepared for predicting a possibly new switch. If no Adaptivity is used, the next time step will be set as the default one from the front end. @@ -160,7 +160,7 @@ def post_step_processing(self, controller, S): if self.status.t_switch is None: L.status.dt_new = L.status.dt_new if L.status.dt_new is not None else L.params.dt_initial - super(SwitchEstimator, self).post_step_processing(controller, S) + super().post_step_processing(controller, S, **kwargs) @staticmethod def get_switch(t_interp, vC_switch, m_guess):