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
« 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
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.
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 """
14 def log_global_error(self, step, level_number, suffix=''):
15 """
16 Function to add the global error to the stats
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
23 Returns:
24 None
25 """
26 L = step.levels[level_number]
28 L.sweep.compute_end_point()
30 u_ref = L.prob.u_exact(t=L.time + L.dt)
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 )
53 def log_local_error(self, step, level_number, suffix=''):
54 """
55 Function to add the local error to the stats
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
62 Returns:
63 None
64 """
65 L = step.levels[level_number]
67 L.sweep.compute_end_point()
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)
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 )
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 )
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 """
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``.
104 Args:
105 step (pySDC.Step.step): the current step
106 level_number (int): the current level number
108 Returns:
109 None
110 """
111 super().post_step(step, level_number)
112 self.log_global_error(step, level_number, '_post_step')
115class LogGlobalErrorPostIter(LogError):
116 """
117 Log the global error after each iteration
118 """
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
126 Returns:
127 None
128 """
129 super().post_iteration(step, level_number)
131 self.log_global_error(step, level_number, suffix='_post_iteration')
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 """
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
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.
160 Args:
161 step (pySDC.Step.step): The current step
162 level_number (int): The index of the level
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)
171 def post_run(self, step, level_number):
172 """
173 Log the global error.
175 Args:
176 step (pySDC.Step.step): The current step
177 level_number (int): The index of the level
179 Returns:
180 None
181 """
182 super().post_run(step, level_number)
183 self._Hooks__num_restarts = self.num_restarts
185 if level_number == 0 and step.status.last:
186 L = step.levels[level_number]
188 u_num = L.uend
189 u_ref = L.prob.u_exact(t=self.t_last_solution)
191 self.logger.info(f'Finished with a global error of e={abs(u_num-u_ref):.2e}')
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 )
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 """
222 def post_step(self, step, level_number):
223 """
224 Record the local error after the step as ``e_local_post_step``.
226 Args:
227 step (pySDC.Step.step): the current step
228 level_number (int): the current level number
230 Returns:
231 None
232 """
233 super().post_step(step, level_number)
234 self.log_local_error(step, level_number, suffix='_post_step')
237class LogLocalErrorPostIter(LogError):
238 """
239 Log the local error after each iteration
240 """
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
248 Returns:
249 None
250 """
251 super().post_iteration(step, level_number)
253 self.log_local_error(step, level_number, suffix='_post_iteration')