Coverage for pySDC/core/Step.py: 97%

117 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-04-29 09:02 +0000

1import logging 

2 

3from pySDC.core import Level as levclass 

4from pySDC.core.BaseTransfer import base_transfer 

5from pySDC.core.Errors import ParameterError 

6from pySDC.helpers.pysdc_helper import FrozenClass 

7 

8 

9# short helper class to add params as attributes 

10class _Pars(FrozenClass): 

11 def __init__(self, params): 

12 self.maxiter = None 

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

14 setattr(self, k, v) 

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

16 self._freeze() 

17 

18 

19# short helper class to bundle all status variables 

20class _Status(FrozenClass): 

21 """ 

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

23 initialized here, while the convergence controllers are allowed to add more variables in a controlled fashion 

24 later on using the `add_variable` function. 

25 """ 

26 

27 def __init__(self): 

28 self.iter = None 

29 self.stage = None 

30 self.slot = None 

31 self.first = None 

32 self.last = None 

33 self.pred_cnt = None 

34 self.done = None 

35 self.force_done = None 

36 self.force_continue = False 

37 self.prev_done = None 

38 self.time_size = None 

39 self.diff_old_loc = None 

40 self.diff_first_loc = None 

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

42 self._freeze() 

43 

44 

45class step(FrozenClass): 

46 """ 

47 Step class, referencing most of the structure needed for the time-stepping 

48 

49 This class bundles multiple levels and the corresponding transfer operators and is used by the controller 

50 (e.g. SDC and MLSDC). Status variables like the current time are hidden via properties and setters methods. 

51 

52 Attributes: 

53 params (__Pars): parameters given by the user 

54 status (__Status): status class for the step 

55 logger: custom logger for step-related logging 

56 levels (list): list of levels 

57 """ 

58 

59 def __init__(self, description): 

60 """ 

61 Initialization routine 

62 

63 Args: 

64 description (dict): parameters given by the user, will be added as attributes 

65 """ 

66 

67 # set params and status 

68 self.params = _Pars(description.get('step_params', {})) 

69 self.status = _Status() 

70 

71 # set up logger 

72 self.logger = logging.getLogger('step') 

73 

74 # empty attributes 

75 self.__transfer_dict = {} 

76 self.base_transfer = None 

77 self.levels = [] 

78 self.__prev = None 

79 self.__next = None 

80 

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

82 self._freeze() 

83 

84 # create hierarchy of levels 

85 self.__generate_hierarchy(description) 

86 

87 def __generate_hierarchy(self, descr): 

88 """ 

89 Routine to generate the level hierarchy for a single step 

90 

91 This makes the explicit generation of levels in the frontend obsolete and hides a few dirty hacks here and 

92 there. 

93 

94 Args: 

95 descr (dict): dictionary containing the description of the levels as list per key 

96 """ 

97 

98 if 'dtype_u' in descr: 

99 raise ParameterError( 

100 'Deprecated parameter dtype_u, please remove from description dictionary and specify ' 

101 'directly in the problem class' 

102 ) 

103 if 'dtype_f' in descr: 

104 raise ParameterError( 

105 'Deprecated parameter dtype_f, please remove from description dictionary and specify ' 

106 'directly in the problem class' 

107 ) 

108 

109 # assert the existence of all the keys we need to set up at least on level 

110 essential_keys = ['problem_class', 'sweeper_class', 'sweeper_params', 'level_params'] 

111 for key in essential_keys: 

112 if key not in descr: 

113 msg = 'need %s to instantiate step, only got %s' % (key, str(descr.keys())) 

114 self.logger.error(msg) 

115 raise ParameterError(msg) 

116 

117 descr['problem_params'] = descr.get('problem_params', {}) 

118 # check if base_transfer class is specified 

119 descr['base_transfer_class'] = descr.get('base_transfer_class', base_transfer) 

120 # check if base_transfer parameters are needed 

121 descr['base_transfer_params'] = descr.get('base_transfer_params', {}) 

122 # check if space_transfer class is specified 

123 descr['space_transfer_class'] = descr.get('space_transfer_class', {}) 

124 # check if space_transfer parameters are needed 

125 descr['space_transfer_params'] = descr.get('space_transfer_params', {}) 

126 

127 # convert problem-dependent parameters consisting of dictionary of lists to a list of dictionaries with only a 

128 # single entry per key, one dict per level 

129 pparams_list = self.__dict_to_list(descr['problem_params']) 

130 lparams_list = self.__dict_to_list(descr['level_params']) 

131 swparams_list = self.__dict_to_list(descr['sweeper_params']) 

132 # put this newly generated list into the description dictionary (copy to avoid changing the original one) 

133 descr_new = descr.copy() 

134 descr_new['problem_params'] = pparams_list 

135 descr_new['level_params'] = lparams_list 

136 descr_new['sweeper_params'] = swparams_list 

137 # generate list of dictionaries out of the description 

138 descr_list = self.__dict_to_list(descr_new) 

139 

140 # sanity check: is there a base_transfer class? Is there one even if only a single level is specified? 

141 if len(descr_list) > 1 and not descr_new['space_transfer_class']: 

142 msg = 'need %s to instantiate step, only got %s' % ('space_transfer_class', str(descr_new.keys())) 

143 self.logger.error(msg) 

144 raise ParameterError(msg) 

145 

146 if len(descr_list) == 1 and ( 

147 descr_new['space_transfer_class'] or descr_new['base_transfer_class'] is not base_transfer 

148 ): 

149 self.logger.warning('you have specified transfer classes, but only a single level') 

150 

151 # generate levels, register and connect if needed 

152 for l in range(len(descr_list)): 

153 L = levclass.level( 

154 problem_class=descr_list[l]['problem_class'], 

155 problem_params=descr_list[l]['problem_params'], 

156 sweeper_class=descr_list[l]['sweeper_class'], 

157 sweeper_params=descr_list[l]['sweeper_params'], 

158 level_params=descr_list[l]['level_params'], 

159 level_index=l, 

160 ) 

161 

162 self.levels.append(L) 

163 

164 if l > 0: 

165 self.connect_levels( 

166 base_transfer_class=descr_new['base_transfer_class'], 

167 base_transfer_params=descr_list[l]['base_transfer_params'], 

168 space_transfer_class=descr_list[l]['space_transfer_class'], 

169 space_transfer_params=descr_list[l]['space_transfer_params'], 

170 fine_level=self.levels[l - 1], 

171 coarse_level=self.levels[l], 

172 ) 

173 

174 @staticmethod 

175 def __dict_to_list(in_dict): 

176 """ 

177 Straightforward helper function to convert dictionary of list to list of dictionaries 

178 

179 Args: 

180 in_dict (dict): dictionary of lists 

181 Returns: 

182 list of dictionaries 

183 """ 

184 

185 max_val = 1 

186 for _, v in in_dict.items(): 

187 if type(v) is list: 

188 max_val = max(max_val, len(v)) 

189 else: 

190 pass 

191 

192 ld = [{} for _ in range(max_val)] 

193 for d in range(len(ld)): 

194 for k, v in in_dict.items(): 

195 if type(v) is not list: 

196 ld[d][k] = v 

197 else: 

198 ld[d][k] = v[min(d, len(v) - 1)] 

199 return ld 

200 

201 def connect_levels( 

202 self, 

203 base_transfer_class, 

204 base_transfer_params, 

205 space_transfer_class, 

206 space_transfer_params, 

207 fine_level, 

208 coarse_level, 

209 ): 

210 """ 

211 Routine to couple levels with base_transfer operators 

212 

213 Args: 

214 base_transfer_class: the class which can do transfer between the two space-time levels 

215 base_transfer_params (dict): parameters for the space_transfer class 

216 space_transfer_class: the user-defined class which can do spatial transfer 

217 space_transfer_params (dict): parameters for the base_transfer class 

218 fine_level (pySDC.Level.level): the fine level 

219 coarse_level (pySDC.Level.level): the coarse level 

220 """ 

221 

222 # create new instance of the specific base_transfer class 

223 self.base_transfer = base_transfer_class( 

224 fine_level, coarse_level, base_transfer_params, space_transfer_class, space_transfer_params 

225 ) 

226 # use base_transfer dictionary twice to set restrict and prolong operator 

227 self.__transfer_dict[(fine_level, coarse_level)] = self.base_transfer.restrict 

228 

229 if self.base_transfer.params.finter: 

230 self.__transfer_dict[(coarse_level, fine_level)] = self.base_transfer.prolong_f 

231 else: 

232 self.__transfer_dict[(coarse_level, fine_level)] = self.base_transfer.prolong 

233 

234 def transfer(self, source, target): 

235 """ 

236 Wrapper routine to ease the call of the transfer functions 

237 

238 This function can be called in the multilevel stepper (e.g. MLSDC), passing a source and a target level. 

239 Using the transfer dictionary, the calling stepper does not need to specify whether to use restrict of 

240 prolong. 

241 

242 Args: 

243 source (pySDC.Level.level): source level 

244 target (pySDC.Level.level): target level 

245 """ 

246 self.__transfer_dict[(source, target)]() 

247 

248 def reset_step(self): 

249 """ 

250 Routine so clean-up step structure and the corresp. levels for further uses 

251 """ 

252 # reset all levels 

253 for l in self.levels: 

254 l.reset_level() 

255 

256 def init_step(self, u0): 

257 """ 

258 Initialization routine for a new step. 

259 

260 This routine uses initial values u0 to set up the u[0] values at the finest level 

261 

262 Args: 

263 u0 (dtype_u): initial values 

264 """ 

265 

266 assert len(self.levels) >= 1 

267 assert len(self.levels[0].u) >= 1 

268 

269 # pass u0 to u[0] on the finest level 0 

270 P = self.levels[0].prob 

271 self.levels[0].u[0] = P.dtype_u(u0) 

272 

273 @property 

274 def prev(self): 

275 """ 

276 Getter for previous step 

277 

278 Returns: 

279 prev 

280 """ 

281 return self.__prev 

282 

283 @prev.setter 

284 def prev(self, p): 

285 """ 

286 Setter for previous step 

287 

288 Args: 

289 p: new previous step 

290 """ 

291 self.__prev = p 

292 

293 @property 

294 def next(self): 

295 """ 

296 Getter for next step 

297 

298 Returns: 

299 prev 

300 """ 

301 return self.__next 

302 

303 @next.setter 

304 def next(self, p): 

305 """ 

306 Setter for next step 

307 

308 Args: 

309 p: new next step 

310 """ 

311 self.__next = p 

312 

313 @property 

314 def dt(self): 

315 """ 

316 Getter for current time-step size 

317 

318 Returns: 

319 float: dt of level[0] 

320 """ 

321 return self.levels[0].dt 

322 

323 @property 

324 def time(self): 

325 """ 

326 Getter for current time 

327 

328 Returns: 

329 float: time of level[0] 

330 """ 

331 return self.levels[0].time