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
« 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
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.
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 """
15 def log_global_error(self, step, level_number, suffix=''):
16 """
17 Function to add the global error to the stats
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
24 Returns:
25 None
26 """
27 L = step.levels[level_number]
29 L.sweep.compute_end_point()
31 u_ref = L.prob.u_exact(t=L.time + L.dt)
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 )
54 def log_local_error(self, step, level_number, suffix=''):
55 """
56 Function to add the local error to the stats
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
63 Returns:
64 None
65 """
66 L = step.levels[level_number]
68 L.sweep.compute_end_point()
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)
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 )
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 )
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')
102class LogGlobalErrorPostIter(LogError):
103 """
104 Log the global error after each iteration
105 """
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
113 Returns:
114 None
115 """
116 super().post_iteration(step, level_number)
118 self.log_global_error(step, level_number, suffix='_post_iteration')
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 """
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
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.
147 Args:
148 step (pySDC.Step.step): The current step
149 level_number (int): The index of the level
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)
158 def post_run(self, step, level_number):
159 """
160 Log the global error.
162 Args:
163 step (pySDC.Step.step): The current step
164 level_number (int): The index of the level
166 Returns:
167 None
168 """
169 super().post_run(step, level_number)
170 self._Hooks__num_restarts = self.num_restarts
172 if level_number == 0 and step.status.last:
173 L = step.levels[level_number]
175 u_num = L.uend
176 u_ref = L.prob.u_exact(t=self.t_last_solution)
178 self.logger.info(f'Finished with a global error of e={abs(u_num-u_ref):.2e}')
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 )
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 """
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')
214class LogLocalErrorPostIter(LogError):
215 """
216 Log the local error after each iteration
217 """
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
225 Returns:
226 None
227 """
228 super().post_iteration(step, level_number)
230 self.log_local_error(step, level_number, suffix='_post_iteration')