Coverage for pySDC/core/level.py: 100%

71 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-17 13:41 +0000

1from typing import Any, Dict, List, Optional, Type 

2from pySDC.core.sweeper import Sweeper 

3from pySDC.core.problem import Problem 

4 

5from pySDC.helpers.pysdc_helper import FrozenClass 

6 

7 

8# short helper class to add params as attributes 

9class _Pars(FrozenClass): 

10 def __init__(self, params: Dict[str, Any]) -> None: 

11 self.dt: Optional[float] = None 

12 self.dt_initial: Optional[float] = None 

13 self.restol: float = -1.0 

14 self.nsweeps: int = 1 

15 self.residual_type: str = 'full_abs' 

16 for k, v in params.items(): 

17 setattr(self, k, v) 

18 # freeze class, no further attributes allowed from this point 

19 self._freeze() 

20 

21 self.dt_initial = self.dt * 1.0 if self.dt is not None else None 

22 

23 

24# short helper class to bundle all status variables 

25class _Status(FrozenClass): 

26 """ 

27 This class carries the status of the level. All variables that the core SDC / PFASST functionality depend on are 

28 initialized here. 

29 """ 

30 

31 def __init__(self) -> None: 

32 self.residual: Optional[float] = None 

33 self.unlocked: bool = False 

34 self.updated: bool = False 

35 self.time: Optional[float] = None 

36 self.dt_new: Optional[float] = None 

37 self.sweep: Optional[int] = None 

38 # freeze class, no further attributes allowed from this point 

39 self._freeze() 

40 

41 

42class Level(FrozenClass): 

43 """ 

44 Level class containing all management functionality for a single level 

45 

46 A level contains all data structures, types and objects to perform sweeps on this particular level. It does not 

47 know about other levels. 

48 

49 Attributes: 

50 params (__Pars): parameter object containing the custom parameters passed by the user 

51 status (__Status): status object 

52 level_index (int): custom string naming this level 

53 uend: dof values at the right end point of the interval 

54 u (list of dtype_u): dof values at the nodes 

55 uold (list of dtype_u): copy of dof values for saving data during restriction) 

56 f (list of dtype_f): RHS values at the nodes 

57 fold (list of dtype_f): copy of RHS values for saving data during restriction 

58 tau (list of dtype_u): FAS correction, allocated via step class if necessary 

59 u0_reference: copy of ``u[0]`` taken when a residual was handed down to this level 

60 """ 

61 

62 def __init__( 

63 self, 

64 problem_class: Type[Problem], 

65 problem_params: Dict[str, Any], 

66 sweeper_class: Type[Sweeper], 

67 sweeper_params: Dict[str, Any], 

68 level_params: Dict[str, Any], 

69 level_index: int, 

70 ) -> None: 

71 """ 

72 Initialization routine 

73 

74 Args: 

75 problem_class: problem class 

76 problem_params (dict): parameters for the problem to be initialized 

77 sweeper_class: sweeper class 

78 sweeper_params (dict): parameters for the sweeper (contains collocation) 

79 level_params (dict): parameters given by the user, will be added as attributes 

80 level_index (int): custom name for this level 

81 """ 

82 

83 # set level parameters and status 

84 self.params: _Pars = _Pars(level_params) 

85 self.status: _Status = _Status() 

86 

87 # instantiate sweeper, problem and hooks 

88 self.__sweep: Sweeper = sweeper_class(sweeper_params, self) 

89 self.__prob: Problem = problem_class(**problem_params) 

90 

91 # set name 

92 self.level_index: int = level_index 

93 

94 # empty data at the nodes, the right end point and tau 

95 self.uend: Optional[Any] = None 

96 self.u: List[Optional[Any]] = [None] * (self.sweep.coll.num_nodes + 1) 

97 self.uold: List[Optional[Any]] = [None] * (self.sweep.coll.num_nodes + 1) 

98 self.u_avg: List[Optional[Any]] = [None] * self.sweep.coll.num_nodes 

99 self.residual: List[Optional[Any]] = [None] * self.sweep.coll.num_nodes 

100 self.increment: List[Optional[Any]] = [None] * self.sweep.coll.num_nodes 

101 self.f: List[Optional[Any]] = [None] * (self.sweep.coll.num_nodes + 1) 

102 self.fold: List[Optional[Any]] = [None] * (self.sweep.coll.num_nodes + 1) 

103 

104 self.tau: List[Optional[Any]] = [None] * self.sweep.coll.num_nodes 

105 

106 # Set by a transfer that hands this level a residual instead of letting it rebuild one, and 

107 # read by the sweeper that carries that residual. PFASST overwrites `u[0]` with the 

108 # predecessor's end value *after* the restriction, and the residual depends on `u[0]` 

109 # additively, so a level carrying one has to be told what `u[0]` was when it arrived. It 

110 # lives here, next to `tau`, because it is a copy of this level's own data and has to be 

111 # dropped when the level is reset -- a stale one belongs to the previous step. 

112 # 

113 # Temporary. Putting the step-to-step exchange itself in delta form, so the predecessor 

114 # sends an increment rather than a state, removes the need for it entirely. 

115 self.u0_reference: Optional[Any] = None 

116 

117 self.__tag: Optional[Any] = None 

118 

119 # freeze class, no further attributes allowed from this point 

120 self._freeze() 

121 

122 def reset_level(self, reset_status: bool = True) -> None: 

123 """ 

124 Routine to clean-up the level for the next time step 

125 

126 Args: 

127 reset_status (bool): Reset the status or only the solution 

128 

129 Returns: 

130 None 

131 """ 

132 

133 # reset status 

134 if reset_status: 

135 self.status = _Status() 

136 

137 # all data back to None 

138 self.uend = None 

139 self.u = [None] * (self.sweep.coll.num_nodes + 1) 

140 self.uold = [None] * (self.sweep.coll.num_nodes + 1) 

141 self.f = [None] * (self.sweep.coll.num_nodes + 1) 

142 self.fold = [None] * (self.sweep.coll.num_nodes + 1) 

143 self.tau = [None] * self.sweep.coll.num_nodes 

144 self.u0_reference = None 

145 

146 @property 

147 def sweep(self) -> Sweeper: 

148 """ 

149 Getter for the sweeper 

150 

151 Returns: 

152 pySDC.Sweeper.sweeper: the sweeper associated to this level 

153 """ 

154 return self.__sweep 

155 

156 @property 

157 def prob(self) -> Problem: 

158 """ 

159 Getter for the problem 

160 

161 Returns: 

162 pySDC.Problem.ptype: the problem associated to this level 

163 """ 

164 return self.__prob 

165 

166 @property 

167 def time(self) -> Optional[float]: 

168 """ 

169 Meta-getter for the current time 

170 

171 Returns: 

172 float: referencing status time for convenience 

173 """ 

174 return self.status.time 

175 

176 @property 

177 def dt(self) -> Optional[float]: 

178 """ 

179 Meta-getter for the time-step size 

180 

181 Returns: 

182 float: referencing dt from parameters for convenience 

183 """ 

184 return self.params.dt 

185 

186 @property 

187 def tag(self) -> Optional[Any]: 

188 """ 

189 Getter for tag 

190 

191 Returns: 

192 tag for sending/receiving 

193 """ 

194 return self.__tag 

195 

196 @tag.setter 

197 def tag(self, t: Any) -> None: 

198 """ 

199 Setter for tag 

200 

201 Args: 

202 t: new tag for sending/receiving 

203 """ 

204 self.__tag = t