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

50 statements  

« prev     ^ index     » next       coverage.py v7.6.7, created at 2024-11-16 14:51 +0000

1import numpy as np 

2from pySDC.core.hooks import Hooks 

3 

4 

5class LogError(Hooks): 

6 """ 

7 Base class with functions to add the local and global error to the stats, which can be inherited by hooks logging 

8 these at specific places. 

9 

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

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

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

13 """ 

14 

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

16 """ 

17 Function to add the global error to the stats 

18 

19 Args: 

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

21 level_number (int): The index of the level 

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

23 

24 Returns: 

25 None 

26 """ 

27 L = step.levels[level_number] 

28 

29 L.sweep.compute_end_point() 

30 

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

32 

33 self.add_to_stats( 

34 process=step.status.slot, 

35 process_sweeper=L.sweep.rank, 

36 time=L.time + L.dt, 

37 level=L.level_index, 

38 iter=step.status.iter, 

39 sweep=L.status.sweep, 

40 type=f'e_global{suffix}', 

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

42 ) 

43 self.add_to_stats( 

44 process=step.status.slot, 

45 process_sweeper=L.sweep.rank, 

46 time=L.time + L.dt, 

47 level=L.level_index, 

48 iter=step.status.iter, 

49 sweep=L.status.sweep, 

50 type=f'e_global_rel{suffix}', 

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

52 ) 

53 

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

55 """ 

56 Function to add the local error to the stats 

57 

58 Args: 

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

60 level_number (int): The index of the level 

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

62 

63 Returns: 

64 None 

65 """ 

66 L = step.levels[level_number] 

67 

68 L.sweep.compute_end_point() 

69 

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

71 

72 self.add_to_stats( 

73 process=step.status.slot, 

74 process_sweeper=L.sweep.rank, 

75 time=L.time + L.dt, 

76 level=L.level_index, 

77 iter=step.status.iter, 

78 sweep=L.status.sweep, 

79 type=f'e_local{suffix}', 

80 value=value, 

81 ) 

82 

83 self.logger.debug( 

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

85 'local_error: %12.8e', 

86 step.status.slot, 

87 L.time, 

88 step.status.stage, 

89 L.level_index, 

90 step.status.iter, 

91 L.status.sweep, 

92 value, 

93 ) 

94 

95 

96class LogGlobalErrorPostStep(LogError): 

97 def post_step(self, step, level_number): 

98 super().post_step(step, level_number) 

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

100 

101 

102class LogGlobalErrorPostIter(LogError): 

103 """ 

104 Log the global error after each iteration 

105 """ 

106 

107 def post_iteration(self, step, level_number): 

108 """ 

109 Args: 

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

111 level_number (int): the current level number 

112 

113 Returns: 

114 None 

115 """ 

116 super().post_iteration(step, level_number) 

117 

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

119 

120 

121class LogGlobalErrorPostRun(Hooks): 

122 """ 

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

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

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

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

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

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

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

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

131 """ 

132 

133 def __init__(self): 

134 """ 

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

136 """ 

137 super().__init__() 

138 self.t_last_solution = 0 

139 self.num_restarts = 0 

140 

141 def post_step(self, step, level_number): 

142 """ 

143 Store the time at which the solution is stored. 

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

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

146 

147 Args: 

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

149 level_number (int): The index of the level 

150 

151 Returns: 

152 None 

153 """ 

154 super().post_step(step, level_number) 

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

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

157 

158 def post_run(self, step, level_number): 

159 """ 

160 Log the global error. 

161 

162 Args: 

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

164 level_number (int): The index of the level 

165 

166 Returns: 

167 None 

168 """ 

169 super().post_run(step, level_number) 

170 self._Hooks__num_restarts = self.num_restarts 

171 

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

173 L = step.levels[level_number] 

174 

175 u_num = L.uend 

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

177 

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

179 

180 self.add_to_stats( 

181 process=step.status.slot, 

182 process_sweeper=L.sweep.rank, 

183 time=self.t_last_solution, 

184 level=L.level_index, 

185 iter=step.status.iter, 

186 sweep=L.status.sweep, 

187 type='e_global_post_run', 

188 value=abs(u_num - u_ref), 

189 ) 

190 self.add_to_stats( 

191 process=step.status.slot, 

192 process_sweeper=L.sweep.rank, 

193 time=self.t_last_solution, 

194 level=L.level_index, 

195 iter=step.status.iter, 

196 sweep=L.status.sweep, 

197 type='e_global_rel_post_run', 

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

199 ) 

200 

201 

202class LogLocalErrorPostStep(LogError): 

203 """ 

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

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

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

207 """ 

208 

209 def post_step(self, step, level_number): 

210 super().post_step(step, level_number) 

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

212 

213 

214class LogLocalErrorPostIter(LogError): 

215 """ 

216 Log the local error after each iteration 

217 """ 

218 

219 def post_iteration(self, step, level_number): 

220 """ 

221 Args: 

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

223 level_number (int): the current level number 

224 

225 Returns: 

226 None 

227 """ 

228 super().post_iteration(step, level_number) 

229 

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