Coverage for pySDC/core/check_convergence.py: 97%
70 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-15 06:23 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-15 06:23 +0000
1import numpy as np
3from pySDC.core.convergence_controller import ConvergenceController
6class CheckConvergence(ConvergenceController):
7 """
8 Perform simple checks on convergence for SDC iterations.
10 Iteration is terminated via one of two criteria:
11 - Residual tolerance
12 - Maximum number of iterations
13 """
15 def setup(self, controller, params, description, **kwargs):
16 """
17 Define default parameters here
19 Args:
20 controller (pySDC.Controller): The controller
21 params (dict): The params passed for this specific convergence controller
22 description (dict): The description object used to instantiate the controller
24 Returns:
25 (dict): The updated params dictionary
26 """
27 defaults = {'control_order': +200, 'use_e_tol': 'e_tol' in description['level_params'].keys()}
29 return {**defaults, **super().setup(controller, params, description, **kwargs)}
31 def dependencies(self, controller, description, **kwargs):
32 """
33 Load the embedded error estimator if needed.
35 Args:
36 controller (pySDC.Controller): The controller
37 description (dict): The description object used to instantiate the controller
39 Returns:
40 None
41 """
42 if self.params.useMPI:
43 self.prepare_MPI_logical_operations()
45 super().dependencies(controller, description)
47 if self.params.use_e_tol:
48 from pySDC.implementations.convergence_controller_classes.estimate_embedded_error import (
49 EstimateEmbeddedError,
50 )
52 controller.add_convergence_controller(
53 EstimateEmbeddedError,
54 description=description,
55 )
57 return None
59 @staticmethod
60 def check_convergence(S, self=None):
61 """
62 Check the convergence of a single step.
63 Test the residual and max. number of iterations as well as allowing overrides to both stop and continue.
65 Args:
66 S (pySDC.Step): The current step
68 Returns:
69 bool: Convergence status of the step
70 """
71 # do all this on the finest level
72 L = S.levels[0]
74 # get residual and check against prescribed tolerance (plus check number of iterations)
75 iter_converged = S.status.iter >= S.params.maxiter
76 res_converged = L.status.residual <= L.params.restol and (S.status.iter > 0 or L.status.sweep > 0)
77 e_tol_converged = (
78 L.status.increment < L.params.e_tol if (L.params.get('e_tol') and L.status.get('increment')) else False
79 )
80 converged = (
81 iter_converged or res_converged or e_tol_converged or S.status.force_done
82 ) and not S.status.force_continue
83 if converged is None:
84 converged = False
86 # print information for debugging
87 if converged and self:
88 self.debug(
89 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 " "}]',
90 S,
91 )
92 return converged
94 def check_iteration_status(self, controller, S, **kwargs):
95 """
96 Routine to determine whether to stop iterating (currently testing the residual + the max. number of iterations)
98 Args:
99 controller (pySDC.Controller.controller): The controller
100 S (pySDC.Step.step): The current step
102 Returns:
103 None
104 """
105 S.status.done = self.check_convergence(S, self)
107 if "comm" in kwargs.keys():
108 self.communicate_convergence(controller, S, **kwargs)
110 S.status.force_continue = False
112 return None
114 def communicate_convergence(self, controller, S, comm=None, **kwargs):
115 """
116 Share convergence status across the block.
118 Two ways to stop, and the same two whether the block is spread over ranks or sitting in one
119 process. Either every step has to agree, which is a reduction, or each step waits on its
120 predecessor, which is a cascade and is what lets an early step finish and drop out.
122 The only difference between the transports is how the neighbour is reached: an `allreduce`
123 and point-to-point messages with one step per rank, reading the other steps' status directly
124 when they are all here. Note the reduction is applied step by step in the second case rather
125 than all at once as `allreduce` does -- both operations are monotone, so the two agree.
127 Args:
128 controller (pySDC.Controller): The controller
129 S (pySDC.Step.step): The current step
130 comm (mpi4py.MPI.Intracomm): Communicator, or None when the whole block is in one process
132 Returns:
133 None
134 """
135 block = kwargs.get('MS', controller.steps)
137 if controller.params.all_to_done:
138 for hook in controller.hooks:
139 hook.pre_comm(step=S, level_number=0)
141 if comm is None:
142 S.status.done = all(T.status.done for T in block)
143 S.status.force_done = any(T.status.force_done for T in block)
144 else:
145 S.status.done = comm.allreduce(sendobj=S.status.done, op=self.MPI_LAND)
146 S.status.force_done = comm.allreduce(sendobj=S.status.force_done, op=self.MPI_LOR)
148 for hook in controller.hooks:
149 hook.post_comm(step=S, level_number=0, add_to_stats=True)
151 S.status.done = S.status.done or S.status.force_done
153 else:
154 if comm is None:
155 if not S.status.first:
156 for hook in controller.hooks:
157 hook.pre_comm(step=S, level_number=0)
158 S.status.prev_done = S.prev.status.done
159 for hook in controller.hooks:
160 hook.post_comm(step=S, level_number=0, add_to_stats=True)
161 S.status.done = S.status.done and S.status.prev_done
162 return None
164 for hook in controller.hooks:
165 hook.pre_comm(step=S, level_number=0)
167 # check if an open request of the status send is pending
168 controller.wait_for_request(request=controller.req_status)
169 if S.status.force_done:
170 return None
172 # recv status
173 if not S.status.first and not S.status.prev_done:
174 buff = np.empty(1, dtype=bool)
175 self.Recv(comm, source=S.status.slot - 1, buffer=[buff, self.MPI_BOOL])
176 S.status.prev_done = buff[0]
177 S.status.done = S.status.done and S.status.prev_done
179 # send status forward
180 if not S.status.last:
181 buff = np.empty(1, dtype=bool)
182 buff[0] = S.status.done
183 self.Send(comm, dest=S.status.slot + 1, buffer=[buff, self.MPI_BOOL])
185 for hook in controller.hooks:
186 hook.post_comm(step=S, level_number=0, add_to_stats=True)