Coverage for pySDC/implementations/convergence_controller_classes/crash.py: 100%
45 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 12:08 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-12 12:08 +0000
1from pySDC.core.convergence_controller import ConvergenceController
2from pySDC.core.errors import ConvergenceError
3import numpy as np
4import time
7class CrashBase(ConvergenceController):
8 """
9 Base class for convergence controllers that raise a `ConvergenceError` on all ranks as soon as one rank crashes.
10 """
12 comm = None #: communicator across the sweeper, which subclasses set up in `setup`
14 def __init__(self, controller, params, description, **kwargs):
15 """
16 Initialization routine, which additionally prepares the logical or for communicating crashes with MPI
18 Args:
19 controller (pySDC.Controller): The controller
20 params (dict): The params passed for this specific convergence controller
21 description (dict): The description object used to instantiate the controller
22 """
23 super().__init__(controller, params, description, **kwargs)
24 if self.comm or self.params.useMPI:
25 from mpi4py import MPI
27 self.MPI_OR = MPI.LOR
29 def communicate_crash(self, crash, msg='', comm=None, **kwargs):
30 """
31 Communicate a crash across all ranks and raise an error if so.
33 Args:
34 crash (bool): If this rank wants to crash
35 msg (str): Message of the `ConvergenceError` raised on a crash
36 comm (mpi4py.MPI.Intracomm or None): Communicator of the controller, if applicable:
37 """
39 # communicate across the sweeper
40 if self.comm:
41 crash = self.comm.allreduce(crash, op=self.MPI_OR)
43 # communicate across the steps
44 if comm:
45 crash = comm.allreduce(crash, op=self.MPI_OR)
47 if crash:
48 raise ConvergenceError(msg)
51class StopAtNan(CrashBase):
52 """
53 Crash all ranks when the solution contains nan or inf, or when its norm reaches the optional threshold `thresh`.
54 This class is useful when running with MPI in the sweeper or controller.
55 """
57 def setup(self, controller, params, description, **kwargs):
58 """
59 Define parameters here.
61 Default parameters are:
62 - thresh (float): Crash the code when the norm of the solution exceeds this threshold
64 Args:
65 controller (pySDC.Controller): The controller
66 params (dict): The params passed for this specific convergence controller
67 description (dict): The description object used to instantiate the controller
69 Returns:
70 (dict): The updated params dictionary
71 """
72 self.comm = description['sweeper_params'].get('comm', None)
73 defaults = {
74 "control_order": 94,
75 "thresh": np.inf,
76 }
78 return {**defaults, **super().setup(controller, params, description, **kwargs)}
80 def prepare_next_block(self, controller, S, *args, **kwargs):
81 """
82 Check if we need to crash the code.
84 Args:
85 controller (pySDC.Controller.controller): Controller
86 S (pySDC.Step.step): Step
87 comm (mpi4py.MPI.Intracomm or None): Communicator of the controller, if applicable
89 Raises:
90 ConvergenceError: If the solution does not fall within the allowed space
91 """
92 isfinite, below_limit = True, True
93 crash = False
95 for lvl in S.levels:
96 for u in lvl.u:
97 if u is None:
98 break
99 isfinite = np.all(np.isfinite(u))
101 below_limit = abs(u) < self.params.thresh
103 crash = not (isfinite and below_limit)
105 if crash:
106 break
107 if crash:
108 break
110 self.communicate_crash(crash, msg=f'Solution exceeds bounds! Crashing code at {S.time}!', **kwargs)
113class StopAtMaxRuntime(CrashBase):
114 """
115 Abort the code when the problem has exceeded a maximum runtime.
116 """
118 def setup(self, controller, params, description, **kwargs):
119 """
120 Define parameters here.
122 Default parameters are:
123 - max_runtime (float): Crash the code when the wall time since this setup exceeds this many seconds
125 Args:
126 controller (pySDC.Controller): The controller
127 params (dict): The params passed for this specific convergence controller
128 description (dict): The description object used to instantiate the controller
130 Returns:
131 (dict): The updated params dictionary
132 """
133 self.comm = description['sweeper_params'].get('comm', None)
134 defaults = {
135 "control_order": 94,
136 "max_runtime": np.inf,
137 }
138 self.t0 = time.perf_counter()
140 return {**defaults, **super().setup(controller, params, description, **kwargs)}
142 def prepare_next_block(self, controller, S, *args, **kwargs):
143 """
144 Check if we need to crash the code.
146 Args:
147 controller (pySDC.Controller.controller): Controller
148 S (pySDC.Step.step): Step
149 comm (mpi4py.MPI.Intracomm or None): Communicator of the controller, if applicable
151 Raises:
152 ConvergenceError: If the solution does not fall within the allowed space
153 """
154 self.communicate_crash(
155 crash=abs(self.t0 - time.perf_counter()) > self.params.max_runtime,
156 msg=f'Exceeding max. runtime of {self.params.max_runtime}s! Crashing code at {S.time}!',
157 **kwargs,
158 )