Coverage for pySDC/implementations/convergence_controller_classes/check_iteration_estimator.py: 98%
46 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-15 10:50 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-15 10:50 +0000
1import numpy as np
2from pySDC.core.convergence_controller import ConvergenceController, Status
3from pySDC.implementations.convergence_controller_classes.store_uold import StoreUOld
6class CheckIterationEstimatorNonMPI(ConvergenceController):
7 """
8 Stop iterating once the contraction of the increments predicts that `errtol` is reached, for the non-MPI controller.
9 """
11 def __init__(self, controller, params, description, **kwargs):
12 """
13 Initialization routine
15 Args:
16 controller (pySDC.Controller): The controller
17 params (dict): Parameters for the convergence controller
18 description (dict): The description object used to instantiate the controller
19 """
20 super(CheckIterationEstimatorNonMPI, self).__init__(controller, params, description)
21 self.buffers = Status(["Kest_loc", "diff_new", "Ltilde_loc"])
22 self.status = Status(["diff_old_loc", "diff_first_loc"])
24 def check_parameters(self, controller, params, description, **kwargs):
25 """
26 Check whether parameters are compatible with whatever assumptions went into the step size functions etc.
27 In this case, we need the user to supply a tolerance.
29 Args:
30 controller (pySDC.Controller): The controller
31 params (dict): The params passed for this specific convergence controller
32 description (dict): The description object used to instantiate the controller
34 Returns:
35 bool: Whether the parameters are compatible
36 str: The error message
37 """
38 if "errtol" not in params.keys():
39 return (
40 False,
41 "Please give the iteration estimator a tolerance in the form of `errtol`. Thanks!",
42 )
44 return True, ""
46 def setup(self, controller, params, description, **kwargs):
47 """
48 Setup parameters. Here we only give a default value for the control order.
50 Args:
51 controller (pySDC.Controller): The controller
52 params (dict): Parameters for the convergence controller
53 description (dict): The description object used to instantiate the controller
55 Returns:
56 dict: The updated parameters
57 """
58 return {"control_order": -50, **super().setup(controller, params, description, **kwargs)}
60 def dependencies(self, controller, description, **kwargs):
61 """
62 Need to store the solution of previous iterations.
64 Args:
65 controller (pySDC.Controller): The controller
66 description (dict): The description object used to instantiate the controller
68 Returns:
69 None
70 """
71 controller.add_convergence_controller(StoreUOld, description=description)
72 return None
74 def reset_buffers_nonMPI(self, controller, **kwargs):
75 """
76 Reset buffers used to immitate communication in non MPI version.
78 Args:
79 controller (pySDC.controller): The controller
81 Returns:
82 None
83 """
84 self.buffers.Kest_loc = [99] * len(controller.MS)
85 self.buffers.diff_new = 0.0
86 self.buffers.Ltilde_loc = 0.0
88 def setup_status_variables(self, controller, **kwargs):
89 """
90 Setup storage variables for the differences between sweeps for all steps.
92 Args:
93 controller (pySDC.Controller): The controller
95 Returns:
96 None
97 """
98 self.status.diff_old_loc = [0.0] * len(controller.MS)
99 self.status.diff_first_loc = [0.0] * len(controller.MS)
100 return None
102 def check_iteration_status(self, controller, S, **kwargs):
103 """
104 Args:
105 controller (pySDC.Controller): The controller
106 S (pySDC.step): The current step
108 Returns:
109 None
110 """
111 L = S.levels[0]
112 slot = S.status.slot
114 # find the global maximum difference between iterations
115 for m in range(1, L.sweep.coll.num_nodes + 1):
116 self.buffers.diff_new = max(self.buffers.diff_new, abs(L.uold[m] - L.u[m]))
118 if S.status.iter == 1:
119 self.status.diff_old_loc[slot] = self.buffers.diff_new
120 self.status.diff_first_loc[slot] = self.buffers.diff_new
121 elif S.status.iter > 1:
122 if 0.0 in (self.buffers.diff_new, self.status.diff_old_loc[slot], self.status.diff_first_loc[slot]):
123 # an update of exactly zero means the iteration has reached its fixed point: nothing is left to
124 # contract, so no more iterations are needed (this happens to steps that keep iterating when
125 # `all_to_done` is set)
126 self.buffers.Ltilde_loc = 0.0
127 self.buffers.Kest_loc = 0.0
128 else:
129 # approximate contraction factor
130 self.buffers.Ltilde_loc = min(self.buffers.diff_new / self.status.diff_old_loc[slot], 0.9)
132 # estimate how many more iterations we need for this step to converge to the desired tolerance
133 alpha = 1 / (1 - self.buffers.Ltilde_loc) * self.status.diff_first_loc[slot]
134 self.buffers.Kest_loc = np.log(self.params.errtol / alpha) / np.log(self.buffers.Ltilde_loc) * 1.05
135 self.logger.debug(
136 f'LOCAL: {L.time:8.4f}, {S.status.iter}: {int(np.ceil(self.buffers.Kest_loc))}, '
137 f'{self.buffers.Ltilde_loc:8.6e}, {self.buffers.Kest_loc:8.6e}, \
138{self.buffers.Ltilde_loc ** S.status.iter * alpha:8.6e}'
139 )
141 self.status.diff_old_loc[slot] = self.buffers.diff_new
143 # set global Kest as last local one, force stop if done
144 if S.status.last:
145 Kest_glob = self.buffers.Kest_loc
146 if np.ceil(Kest_glob) <= S.status.iter:
147 S.status.force_done = True