Coverage for pySDC/implementations/hooks/log_errors.py: 100%

50 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-11-02 12:30 +0000

1import numpy as np 

2from pySDC.core.hooks import Hooks 

3 

4 

5class LogError(Hooks): 

6 """ 

7 Base class for hooks that log the local or global error with respect to the `u_exact` of the problem. 

8 

9 Errors are computed with respect to `u_exact` defined in the problem class. 

10 Be aware that this requires the problems to be compatible with this. We need some kind of "exact" solution for this 

11 to work, be it a reference solution or something analytical. 

12 """ 

13 

14 def log_global_error(self, step, level_number, suffix=''): 

15 """ 

16 Function to add the global error to the stats 

17 

18 Args: 

19 step (pySDC.Step.step): The current step 

20 level_number (int): The index of the level 

21 suffix (str): Suffix for naming the variable in stats 

22 

23 Returns: 

24 None 

25 """ 

26 L = step.levels[level_number] 

27 

28 L.sweep.compute_end_point() 

29 

30 u_ref = L.prob.u_exact(t=L.time + L.dt) 

31 

32 self.add_to_stats( 

33 process=step.status.slot, 

34 process_sweeper=L.sweep.rank, 

35 time=L.time + L.dt, 

36 level=L.level_index, 

37 iter=step.status.iter, 

38 sweep=L.status.sweep, 

39 type=f'e_global{suffix}', 

40 value=abs(u_ref - L.uend), 

41 ) 

42 self.add_to_stats( 

43 process=step.status.slot, 

44 process_sweeper=L.sweep.rank, 

45 time=L.time + L.dt, 

46 level=L.level_index, 

47 iter=step.status.iter, 

48 sweep=L.status.sweep, 

49 type=f'e_global_rel{suffix}', 

50 value=abs(u_ref - L.uend) / abs(u_ref), 

51 ) 

52 

53 def log_local_error(self, step, level_number, suffix=''): 

54 """ 

55 Function to add the local error to the stats 

56 

57 Args: 

58 step (pySDC.Step.step): The current step 

59 level_number (int): The index of the level 

60 suffix (str): Suffix for naming the variable in stats 

61 

62 Returns: 

63 None 

64 """ 

65 L = step.levels[level_number] 

66 

67 L.sweep.compute_end_point() 

68 

69 value = abs(L.prob.u_exact(t=L.time + L.dt, u_init=L.u[0] * 1.0, t_init=L.time) - L.uend) 

70 

71 self.add_to_stats( 

72 process=step.status.slot, 

73 process_sweeper=L.sweep.rank, 

74 time=L.time + L.dt, 

75 level=L.level_index, 

76 iter=step.status.iter, 

77 sweep=L.status.sweep, 

78 type=f'e_local{suffix}', 

79 value=value, 

80 ) 

81 

82 self.logger.debug( 

83 'Process %2i on time %8.6f at stage %15s: Level: %s -- Iteration: %2i -- Sweep: %2i -- ' 

84 'local_error: %12.8e', 

85 step.status.slot, 

86 L.time, 

87 step.status.stage, 

88 L.level_index, 

89 step.status.iter, 

90 L.status.sweep, 

91 value, 

92 ) 

93 

94 

95class LogGlobalErrorPostStep(LogError): 

96 """ 

97 Log the global error with respect to `u_exact` at the end of each step as "e_global_post_step". 

98 """ 

99 

100 def post_step(self, step, level_number): 

101 """ 

102 Record the global error after the step as ``e_global_post_step`` and ``e_global_rel_post_step``. 

103 

104 Args: 

105 step (pySDC.Step.step): the current step 

106 level_number (int): the current level number 

107 

108 Returns: 

109 None 

110 """ 

111 super().post_step(step, level_number) 

112 self.log_global_error(step, level_number, '_post_step') 

113 

114 

115class LogGlobalErrorPostIter(LogError): 

116 """ 

117 Log the global error after each iteration 

118 """ 

119 

120 def post_iteration(self, step, level_number): 

121 """ 

122 Args: 

123 step (pySDC.Step.step): the current step 

124 level_number (int): the current level number 

125 

126 Returns: 

127 None 

128 """ 

129 super().post_iteration(step, level_number) 

130 

131 self.log_global_error(step, level_number, suffix='_post_iteration') 

132 

133 

134class LogGlobalErrorPostRun(Hooks): 

135 """ 

136 Compute the global error once after the run is finished. 

137 Because of some timing issues, we cannot inherit from the `LogError` class here. 

138 The issue is that the convergence controllers can change the step size after the final iteration but before the 

139 `post_run` functions of the hooks are called, which results in a mismatch of `L.time + L.dt` as corresponding to 

140 when the solution is computed and when the error is computed. The issue is resolved by recording the time at which 

141 the solution is computed in an attribute of this class. 

142 Additionally, the number of restarts is reset, which we need to filter recomputed values in post processing. 

143 For this reason, we need to mess with the private `__num_restarts` of the core Hooks class. 

144 """ 

145 

146 def __init__(self): 

147 """ 

148 Add an attribute for when the last solution was added. 

149 """ 

150 super().__init__() 

151 self.t_last_solution = 0 

152 self.num_restarts = 0 

153 

154 def post_step(self, step, level_number): 

155 """ 

156 Store the time at which the solution is stored. 

157 This is required because between the `post_step` hook where the solution is stored and the `post_run` hook 

158 where the error is stored, the step size can change. 

159 

160 Args: 

161 step (pySDC.Step.step): The current step 

162 level_number (int): The index of the level 

163 

164 Returns: 

165 None 

166 """ 

167 super().post_step(step, level_number) 

168 self.t_last_solution = step.levels[0].time + step.levels[0].dt 

169 self.num_restarts = step.status.get('restarts_in_a_row', 0) 

170 

171 def post_run(self, step, level_number): 

172 """ 

173 Log the global error. 

174 

175 Args: 

176 step (pySDC.Step.step): The current step 

177 level_number (int): The index of the level 

178 

179 Returns: 

180 None 

181 """ 

182 super().post_run(step, level_number) 

183 self._Hooks__num_restarts = self.num_restarts 

184 

185 if level_number == 0 and step.status.last: 

186 L = step.levels[level_number] 

187 

188 u_num = L.uend 

189 u_ref = L.prob.u_exact(t=self.t_last_solution) 

190 

191 self.logger.info(f'Finished with a global error of e={abs(u_num-u_ref):.2e}') 

192 

193 self.add_to_stats( 

194 process=step.status.slot, 

195 process_sweeper=L.sweep.rank, 

196 time=self.t_last_solution, 

197 level=L.level_index, 

198 iter=step.status.iter, 

199 sweep=L.status.sweep, 

200 type='e_global_post_run', 

201 value=abs(u_num - u_ref), 

202 ) 

203 self.add_to_stats( 

204 process=step.status.slot, 

205 process_sweeper=L.sweep.rank, 

206 time=self.t_last_solution, 

207 level=L.level_index, 

208 iter=step.status.iter, 

209 sweep=L.status.sweep, 

210 type='e_global_rel_post_run', 

211 value=abs(u_num - u_ref) / abs(u_ref), 

212 ) 

213 

214 

215class LogLocalErrorPostStep(LogError): 

216 """ 

217 Log the local error with respect to `u_exact` defined in the problem class as "e_local_post_step". 

218 Be aware that this requires the problems to be compatible with this. In particular, a reference solution needs to 

219 be made available from the initial conditions of the step, not of the run. Otherwise you compute the global error. 

220 """ 

221 

222 def post_step(self, step, level_number): 

223 """ 

224 Record the local error after the step as ``e_local_post_step``. 

225 

226 Args: 

227 step (pySDC.Step.step): the current step 

228 level_number (int): the current level number 

229 

230 Returns: 

231 None 

232 """ 

233 super().post_step(step, level_number) 

234 self.log_local_error(step, level_number, suffix='_post_step') 

235 

236 

237class LogLocalErrorPostIter(LogError): 

238 """ 

239 Log the local error after each iteration 

240 """ 

241 

242 def post_iteration(self, step, level_number): 

243 """ 

244 Args: 

245 step (pySDC.Step.step): the current step 

246 level_number (int): the current level number 

247 

248 Returns: 

249 None 

250 """ 

251 super().post_iteration(step, level_number) 

252 

253 self.log_local_error(step, level_number, suffix='_post_iteration')