Coverage for pySDC / implementations / convergence_controller_classes / hotrod.py: 87%
38 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-13 09:00 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-13 09:00 +0000
1import numpy as np
3from pySDC.core.convergence_controller import ConvergenceController
6class HotRod(ConvergenceController):
7 """
8 Class that incorporates the Hot Rod detector [1] for soft faults. Based on comparing two estimates of the local
9 error.
11 Default control order is -40.
13 See for the reference:
14 [1]: Lightweight and Accurate Silent Data Corruption Detection in Ordinary Differential Equation Solvers,
15 Guhur et al. 2016, Springer. DOI: https://doi.org/10.1007/978-3-319-43659-3_47
16 """
18 def setup(self, controller, params, description, **kwargs):
19 """
20 Setup default values for crucial parameters.
22 Args:
23 controller (pySDC.Controller): The controller
24 params (dict): The params passed for this specific convergence controller
25 description (dict): The description object used to instantiate the controller
27 Returns:
28 dict: The updated params
29 """
30 default_params = {
31 "HotRod_tol": np.inf,
32 "control_order": -40,
33 "no_storage": False,
34 }
35 return {**default_params, **super().setup(controller, params, description, **kwargs)}
37 def dependencies(self, controller, description, **kwargs):
38 """
39 Load the dependencies of Hot Rod, which are the two error estimators
41 Args:
42 controller (pySDC.Controller): The controller
43 description (dict): The description object used to instantiate the controller
45 Returns:
46 None
47 """
48 from pySDC.implementations.convergence_controller_classes.estimate_embedded_error import EstimateEmbeddedError
50 controller.add_convergence_controller(
51 EstimateEmbeddedError.get_implementation(flavor='linearized', useMPI=self.params.useMPI),
52 description=description,
53 )
54 if not self.params.useMPI:
55 from pySDC.implementations.convergence_controller_classes.estimate_extrapolation_error import (
56 EstimateExtrapolationErrorNonMPI,
57 )
58 from pySDC.implementations.convergence_controller_classes.basic_restarting import BasicRestartingNonMPI
60 controller.add_convergence_controller(
61 EstimateExtrapolationErrorNonMPI, description=description, params={'no_storage': self.params.no_storage}
62 )
63 controller.add_convergence_controller(
64 BasicRestartingNonMPI,
65 description=description,
66 params={'restart_from_first_step': True},
67 )
68 else:
69 raise NotImplementedError("Don't know how to estimate extrapolated error with MPI")
71 def check_parameters(self, controller, params, description, **kwargs):
72 """
73 Check whether parameters are compatible with whatever assumptions went into the step size functions etc.
75 Args:
76 controller (pySDC.Controller): The controller
77 params (dict): The params passed for this specific convergence controller
78 description (dict): The description object used to instantiate the controller
80 Returns:
81 bool: Whether the parameters are compatible
82 str: Error message
83 """
84 if self.params.HotRod_tol == np.inf:
85 controller.logger.warning("Hot Rod needs a detection threshold, which is now set to infinity, such that a \
86restart is never triggered!")
88 if description["step_params"].get("restol", -1.0) >= 0:
89 return (
90 False,
91 "Hot Rod needs constant order in time and hence restol in the step parameters has to be \
92smaller than 0!",
93 )
95 if controller.params.mssdc_jac:
96 return (
97 False,
98 "Hot Rod needs the same order on all steps, please activate Gauss-Seidel multistep mode!",
99 )
101 return True, ""
103 def determine_restart(self, controller, S, MS, **kwargs):
104 """
105 Check if the difference between the error estimates exceeds the allowed tolerance
107 Args:
108 controller (pySDC.Controller): The controller
109 S (pySDC.Step): The current step
110 MS (list): List of steps
112 Returns:
113 None
114 """
115 # we determine whether to restart only on the last sweep
116 if S.status.iter < S.params.maxiter:
117 return None
119 for L in S.levels:
120 if None not in [
121 L.status.error_extrapolation_estimate,
122 L.status.error_embedded_estimate,
123 ]:
124 diff = abs(L.status.error_extrapolation_estimate - L.status.error_embedded_estimate)
125 if diff > self.params.HotRod_tol:
126 S.status.restart = True
127 self.log(
128 f"Triggering restart: e_em={L.status.error_embedded_estimate:.2e}, e_ex={L.status.error_extrapolation_estimate:.2e} -> delta={diff:.2e}, tol={self.params.HotRod_tol:.2e}",
129 S,
130 )
131 else:
132 self.debug(
133 f"Not triggering restart: e_em={L.status.error_embedded_estimate:.2e}, e_ex={L.status.error_extrapolation_estimate:.2e} -> delta={diff:.2e}, tol={self.params.HotRod_tol:.2e}",
134 S,
135 )
137 return None
139 def post_iteration_processing(self, controller, S, **kwargs):
140 """
141 Throw away the final sweep to match the error estimates.
143 Args:
144 controller (pySDC.Controller): The controller
145 S (pySDC.Step): The current step
147 Returns:
148 None
149 """
150 if S.status.iter == S.params.maxiter:
151 for L in S.levels:
152 L.u[:] = L.uold[:]
154 return None