Source code for core.check_convergence

import numpy as np

from pySDC.core.convergence_controller import ConvergenceController


[docs] class CheckConvergence(ConvergenceController): """ Perform simple checks on convergence for SDC iterations. Iteration is terminated via one of two criteria: - Residual tolerance - Maximum number of iterations """
[docs] def setup(self, controller, params, description, **kwargs): """ Define default parameters here 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 = {'control_order': +200, 'use_e_tol': 'e_tol' in description['level_params'].keys()} return {**defaults, **super().setup(controller, params, description, **kwargs)}
[docs] def dependencies(self, controller, description, **kwargs): """ Load the embedded error estimator if needed. Args: controller (pySDC.Controller): The controller description (dict): The description object used to instantiate the controller Returns: None """ if self.params.useMPI: self.prepare_MPI_logical_operations() super().dependencies(controller, description) if self.params.use_e_tol: from pySDC.implementations.convergence_controller_classes.estimate_embedded_error import ( EstimateEmbeddedError, ) controller.add_convergence_controller( EstimateEmbeddedError, description=description, ) return None
[docs] @staticmethod def check_convergence(S, self=None): """ Check the convergence of a single step. Test the residual and max. number of iterations as well as allowing overrides to both stop and continue. Args: S (pySDC.Step): The current step Returns: bool: Convergence status of the step """ # do all this on the finest level L = S.levels[0] # get residual and check against prescribed tolerance (plus check number of iterations) iter_converged = S.status.iter >= S.params.maxiter res_converged = L.status.residual <= L.params.restol and (S.status.iter > 0 or L.status.sweep > 0) e_tol_converged = ( L.status.increment < L.params.e_tol if (L.params.get('e_tol') and L.status.get('increment')) else False ) converged = ( iter_converged or res_converged or e_tol_converged or S.status.force_done ) and not S.status.force_continue if converged is None: converged = False # print information for debugging if converged and self: self.debug( f'Declared convergence: maxiter reached[{"x" if iter_converged else " "}] restol reached[{"x" if res_converged else " "}] e_tol reached[{"x" if e_tol_converged else " "}]', S, ) return converged
[docs] def check_iteration_status(self, controller, S, **kwargs): """ Routine to determine whether to stop iterating (currently testing the residual + the max. number of iterations) Args: controller (pySDC.Controller.controller): The controller S (pySDC.Step.step): The current step Returns: None """ S.status.done = self.check_convergence(S, self) if "comm" in kwargs.keys(): self.communicate_convergence(controller, S, **kwargs) S.status.force_continue = False return None
[docs] def communicate_convergence(self, controller, S, comm=None, **kwargs): """ Share convergence status across the block. Two ways to stop, and the same two whether the block is spread over ranks or sitting in one process. Either every step has to agree, which is a reduction, or each step waits on its predecessor, which is a cascade and is what lets an early step finish and drop out. The only difference between the transports is how the neighbour is reached: an `allreduce` and point-to-point messages with one step per rank, reading the other steps' status directly when they are all here. Note the reduction is applied step by step in the second case rather than all at once as `allreduce` does -- both operations are monotone, so the two agree. Args: controller (pySDC.Controller): The controller S (pySDC.Step.step): The current step comm (mpi4py.MPI.Intracomm): Communicator, or None when the whole block is in one process Returns: None """ block = kwargs.get('MS', controller.steps) if controller.params.all_to_done: for hook in controller.hooks: hook.pre_comm(step=S, level_number=0) if comm is None: S.status.done = all(T.status.done for T in block) S.status.force_done = any(T.status.force_done for T in block) else: S.status.done = comm.allreduce(sendobj=S.status.done, op=self.MPI_LAND) S.status.force_done = comm.allreduce(sendobj=S.status.force_done, op=self.MPI_LOR) for hook in controller.hooks: hook.post_comm(step=S, level_number=0, add_to_stats=True) S.status.done = S.status.done or S.status.force_done else: if comm is None: if not S.status.first: for hook in controller.hooks: hook.pre_comm(step=S, level_number=0) S.status.prev_done = S.prev.status.done for hook in controller.hooks: hook.post_comm(step=S, level_number=0, add_to_stats=True) S.status.done = S.status.done and S.status.prev_done return None for hook in controller.hooks: hook.pre_comm(step=S, level_number=0) # check if an open request of the status send is pending controller.wait_for_request(request=controller.req_status) if S.status.force_done: return None # recv status if not S.status.first and not S.status.prev_done: buff = np.empty(1, dtype=bool) self.Recv(comm, source=S.status.slot - 1, buffer=[buff, self.MPI_BOOL]) S.status.prev_done = buff[0] S.status.done = S.status.done and S.status.prev_done # send status forward if not S.status.last: buff = np.empty(1, dtype=bool) buff[0] = S.status.done self.Send(comm, dest=S.status.slot + 1, buffer=[buff, self.MPI_BOOL]) for hook in controller.hooks: hook.post_comm(step=S, level_number=0, add_to_stats=True)