import numpy as np
from pySDC.core.convergence_controller import ConvergenceController
[docs]
class HotRod(ConvergenceController):
"""
Class that incorporates the Hot Rod detector [1] for soft faults. Based on comparing two estimates of the local
error.
Default control order is -40.
See for the reference:
[1]: Lightweight and Accurate Silent Data Corruption Detection in Ordinary Differential Equation Solvers,
Guhur et al. 2016, Springer. DOI: https://doi.org/10.1007/978-3-319-43659-3_47
"""
[docs]
def setup(self, controller, params, description, **kwargs):
"""
Setup default values for crucial parameters.
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
"""
default_params = {
"HotRod_tol": np.inf,
"control_order": -40,
"no_storage": False,
}
return {**default_params, **super().setup(controller, params, description, **kwargs)}
[docs]
def dependencies(self, controller, description, **kwargs):
"""
Load the dependencies of Hot Rod, which are the two error estimators
Args:
controller (pySDC.Controller): The controller
description (dict): The description object used to instantiate the controller
Returns:
None
"""
from pySDC.implementations.convergence_controller_classes.estimate_embedded_error import EstimateEmbeddedError
controller.add_convergence_controller(
EstimateEmbeddedError.get_implementation(flavor='linearized', useMPI=self.params.useMPI),
description=description,
)
if not self.params.useMPI:
from pySDC.implementations.convergence_controller_classes.estimate_extrapolation_error import (
EstimateExtrapolationErrorNonMPI,
)
from pySDC.implementations.convergence_controller_classes.basic_restarting import BasicRestartingNonMPI
controller.add_convergence_controller(
EstimateExtrapolationErrorNonMPI, description=description, params={'no_storage': self.params.no_storage}
)
controller.add_convergence_controller(
BasicRestartingNonMPI,
description=description,
params={'restart_from_first_step': True},
)
else:
raise NotImplementedError("Don't know how to estimate extrapolated error with MPI")
[docs]
def check_parameters(self, controller, params, description, **kwargs):
"""
Check whether parameters are compatible with whatever assumptions went into the step size functions etc.
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:
bool: Whether the parameters are compatible
str: Error message
"""
if self.params.HotRod_tol == np.inf:
controller.logger.warning(
"Hot Rod needs a detection threshold, which is now set to infinity, such that a \
restart is never triggered!"
)
if description["step_params"].get("restol", -1.0) >= 0:
return (
False,
"Hot Rod needs constant order in time and hence restol in the step parameters has to be \
smaller than 0!",
)
if controller.params.mssdc_jac:
return (
False,
"Hot Rod needs the same order on all steps, please activate Gauss-Seidel multistep mode!",
)
return True, ""
[docs]
def determine_restart(self, controller, S, MS, **kwargs):
"""
Check if the difference between the error estimates exceeds the allowed tolerance
Args:
controller (pySDC.Controller): The controller
S (pySDC.Step): The current step
MS (list): List of steps
Returns:
None
"""
# we determine whether to restart only on the last sweep
if S.status.iter < S.params.maxiter:
return None
for L in S.levels:
if None not in [
L.status.error_extrapolation_estimate,
L.status.error_embedded_estimate,
]:
diff = abs(L.status.error_extrapolation_estimate - L.status.error_embedded_estimate)
if diff > self.params.HotRod_tol:
S.status.restart = True
self.log(
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}",
S,
)
else:
self.debug(
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}",
S,
)
return None
[docs]
def post_iteration_processing(self, controller, S, **kwargs):
"""
Throw away the final sweep to match the error estimates.
Args:
controller (pySDC.Controller): The controller
S (pySDC.Step): The current step
Returns:
None
"""
if S.status.iter == S.params.maxiter:
for L in S.levels:
L.u[:] = L.uold[:]
return None