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

1import numpy as np 

2from pySDC.core.convergence_controller import ConvergenceController, Status 

3from pySDC.implementations.convergence_controller_classes.store_uold import StoreUOld 

4 

5 

6class CheckIterationEstimatorNonMPI(ConvergenceController): 

7 """ 

8 Stop iterating once the contraction of the increments predicts that `errtol` is reached, for the non-MPI controller. 

9 """ 

10 

11 def __init__(self, controller, params, description, **kwargs): 

12 """ 

13 Initialization routine 

14 

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"]) 

23 

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. 

28 

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 

33 

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 ) 

43 

44 return True, "" 

45 

46 def setup(self, controller, params, description, **kwargs): 

47 """ 

48 Setup parameters. Here we only give a default value for the control order. 

49 

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 

54 

55 Returns: 

56 dict: The updated parameters 

57 """ 

58 return {"control_order": -50, **super().setup(controller, params, description, **kwargs)} 

59 

60 def dependencies(self, controller, description, **kwargs): 

61 """ 

62 Need to store the solution of previous iterations. 

63 

64 Args: 

65 controller (pySDC.Controller): The controller 

66 description (dict): The description object used to instantiate the controller 

67 

68 Returns: 

69 None 

70 """ 

71 controller.add_convergence_controller(StoreUOld, description=description) 

72 return None 

73 

74 def reset_buffers_nonMPI(self, controller, **kwargs): 

75 """ 

76 Reset buffers used to immitate communication in non MPI version. 

77 

78 Args: 

79 controller (pySDC.controller): The controller 

80 

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 

87 

88 def setup_status_variables(self, controller, **kwargs): 

89 """ 

90 Setup storage variables for the differences between sweeps for all steps. 

91 

92 Args: 

93 controller (pySDC.Controller): The controller 

94 

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 

101 

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 

107 

108 Returns: 

109 None 

110 """ 

111 L = S.levels[0] 

112 slot = S.status.slot 

113 

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])) 

117 

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) 

131 

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 ) 

140 

141 self.status.diff_old_loc[slot] = self.buffers.diff_new 

142 

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