Coverage for pySDC/implementations/convergence_controller_classes/hotrod.py: 92%

38 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-04-29 09:02 +0000

1import numpy as np 

2 

3from pySDC.core.ConvergenceController import ConvergenceController 

4 

5 

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. 

10 

11 Default control order is -40. 

12 

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 """ 

17 

18 def setup(self, controller, params, description, **kwargs): 

19 """ 

20 Setup default values for crucial parameters. 

21 

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 

26 

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)} 

36 

37 def dependencies(self, controller, description, **kwargs): 

38 """ 

39 Load the dependencies of Hot Rod, which are the two error estimators 

40 

41 Args: 

42 controller (pySDC.Controller): The controller 

43 description (dict): The description object used to instantiate the controller 

44 

45 Returns: 

46 None 

47 """ 

48 from pySDC.implementations.convergence_controller_classes.estimate_embedded_error import EstimateEmbeddedError 

49 

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 

59 

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") 

70 

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. 

74 

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 

79 

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( 

86 "Hot Rod needs a detection threshold, which is now set to infinity, such that a \ 

87restart is never triggered!" 

88 ) 

89 

90 if description["step_params"].get("restol", -1.0) >= 0: 

91 return ( 

92 False, 

93 "Hot Rod needs constant order in time and hence restol in the step parameters has to be \ 

94smaller than 0!", 

95 ) 

96 

97 if controller.params.mssdc_jac: 

98 return ( 

99 False, 

100 "Hot Rod needs the same order on all steps, please activate Gauss-Seidel multistep mode!", 

101 ) 

102 

103 return True, "" 

104 

105 def determine_restart(self, controller, S, MS, **kwargs): 

106 """ 

107 Check if the difference between the error estimates exceeds the allowed tolerance 

108 

109 Args: 

110 controller (pySDC.Controller): The controller 

111 S (pySDC.Step): The current step 

112 MS (list): List of steps 

113 

114 Returns: 

115 None 

116 """ 

117 # we determine whether to restart only on the last sweep 

118 if S.status.iter < S.params.maxiter: 

119 return None 

120 

121 for L in S.levels: 

122 if None not in [ 

123 L.status.error_extrapolation_estimate, 

124 L.status.error_embedded_estimate, 

125 ]: 

126 diff = abs(L.status.error_extrapolation_estimate - L.status.error_embedded_estimate) 

127 if diff > self.params.HotRod_tol: 

128 S.status.restart = True 

129 self.log( 

130 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}", 

131 S, 

132 ) 

133 else: 

134 self.debug( 

135 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}", 

136 S, 

137 ) 

138 

139 return None 

140 

141 def post_iteration_processing(self, controller, S, **kwargs): 

142 """ 

143 Throw away the final sweep to match the error estimates. 

144 

145 Args: 

146 controller (pySDC.Controller): The controller 

147 S (pySDC.Step): The current step 

148 

149 Returns: 

150 None 

151 """ 

152 if S.status.iter == S.params.maxiter: 

153 for L in S.levels: 

154 L.u[:] = L.uold[:] 

155 

156 return None