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

117 statements  

« prev     ^ index     » next       coverage.py v7.6.7, created at 2024-11-16 14:51 +0000

1import logging 

2 

3from pySDC.core import level as levclass 

4from pySDC.core.base_transfer import BaseTransfer 

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. 

24 """ 

25 

26 def __init__(self): 

27 self.iter = None 

28 self.stage = None 

29 self.slot = None 

30 self.first = None 

31 self.last = None 

32 self.pred_cnt = None 

33 self.done = None 

34 self.force_done = None 

35 self.force_continue = False 

36 self.prev_done = None 

37 self.time_size = None 

38 self.diff_old_loc = None 

39 self.diff_first_loc = None 

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

41 self._freeze() 

42 

43 

44class Step(FrozenClass): 

45 """ 

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

47 

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

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

50 

51 Attributes: 

52 params (__Pars): parameters given by the user 

53 status (__Status): status class for the step 

54 logger: custom logger for step-related logging 

55 levels (list): list of levels 

56 """ 

57 

58 def __init__(self, description): 

59 """ 

60 Initialization routine 

61 

62 Args: 

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

64 """ 

65 

66 # set params and status 

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

68 self.status = _Status() 

69 

70 # set up logger 

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

72 

73 # empty attributes 

74 self.__transfer_dict = {} 

75 self.base_transfer = None 

76 self.levels = [] 

77 self.__prev = None 

78 self.__next = None 

79 

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

81 self._freeze() 

82 

83 # create hierarchy of levels 

84 self.__generate_hierarchy(description) 

85 

86 def __generate_hierarchy(self, descr): 

87 """ 

88 Routine to generate the level hierarchy for a single step 

89 

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

91 there. 

92 

93 Args: 

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

95 """ 

96 

97 if 'dtype_u' in descr: 

98 raise ParameterError( 

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

100 'directly in the problem class' 

101 ) 

102 if 'dtype_f' in descr: 

103 raise ParameterError( 

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

105 'directly in the problem class' 

106 ) 

107 

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

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

110 for key in essential_keys: 

111 if key not in descr: 

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

113 self.logger.error(msg) 

114 raise ParameterError(msg) 

115 

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

117 # check if base_transfer class is specified 

118 descr['base_transfer_class'] = descr.get('base_transfer_class', BaseTransfer) 

119 # check if base_transfer parameters are needed 

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

121 # check if space_transfer class is specified 

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

123 # check if space_transfer parameters are needed 

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

125 

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

127 # single entry per key, one dict per level 

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

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

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

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

132 descr_new = descr.copy() 

133 descr_new['problem_params'] = pparams_list 

134 descr_new['level_params'] = lparams_list 

135 descr_new['sweeper_params'] = swparams_list 

136 # generate list of dictionaries out of the description 

137 descr_list = self.__dict_to_list(descr_new) 

138 

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

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

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

142 self.logger.error(msg) 

143 raise ParameterError(msg) 

144 

145 if len(descr_list) == 1 and ( 

146 descr_new['space_transfer_class'] or descr_new['base_transfer_class'] is not BaseTransfer 

147 ): 

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

149 

150 # generate levels, register and connect if needed 

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

152 L = levclass.Level( 

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

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

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

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

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

158 level_index=l, 

159 ) 

160 

161 self.levels.append(L) 

162 

163 if l > 0: 

164 self.connect_levels( 

165 base_transfer_class=descr_new['base_transfer_class'], 

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

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

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

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

170 coarse_level=self.levels[l], 

171 ) 

172 

173 @staticmethod 

174 def __dict_to_list(in_dict): 

175 """ 

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

177 

178 Args: 

179 in_dict (dict): dictionary of lists 

180 Returns: 

181 list of dictionaries 

182 """ 

183 

184 max_val = 1 

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

186 if type(v) is list: 

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

188 else: 

189 pass 

190 

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

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

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

194 if type(v) is not list: 

195 ld[d][k] = v 

196 else: 

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

198 return ld 

199 

200 def connect_levels( 

201 self, 

202 base_transfer_class, 

203 base_transfer_params, 

204 space_transfer_class, 

205 space_transfer_params, 

206 fine_level, 

207 coarse_level, 

208 ): 

209 """ 

210 Routine to couple levels with base_transfer operators 

211 

212 Args: 

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

214 base_transfer_params (dict): parameters for the space_transfer class 

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

216 space_transfer_params (dict): parameters for the base_transfer class 

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

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

219 """ 

220 

221 # create new instance of the specific base_transfer class 

222 self.base_transfer = base_transfer_class( 

223 fine_level, coarse_level, base_transfer_params, space_transfer_class, space_transfer_params 

224 ) 

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

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

227 

228 if self.base_transfer.params.finter: 

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

230 else: 

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

232 

233 def transfer(self, source, target): 

234 """ 

235 Wrapper routine to ease the call of the transfer functions 

236 

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

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

239 prolong. 

240 

241 Args: 

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

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

244 """ 

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

246 

247 def reset_step(self): 

248 """ 

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

250 """ 

251 # reset all levels 

252 for l in self.levels: 

253 l.reset_level() 

254 

255 def init_step(self, u0): 

256 """ 

257 Initialization routine for a new step. 

258 

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

260 

261 Args: 

262 u0 (dtype_u): initial values 

263 """ 

264 

265 assert len(self.levels) >= 1 

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

267 

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

269 P = self.levels[0].prob 

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

271 

272 @property 

273 def prev(self): 

274 """ 

275 Getter for previous step 

276 

277 Returns: 

278 prev 

279 """ 

280 return self.__prev 

281 

282 @prev.setter 

283 def prev(self, p): 

284 """ 

285 Setter for previous step 

286 

287 Args: 

288 p: new previous step 

289 """ 

290 self.__prev = p 

291 

292 @property 

293 def next(self): 

294 """ 

295 Getter for next step 

296 

297 Returns: 

298 prev 

299 """ 

300 return self.__next 

301 

302 @next.setter 

303 def next(self, p): 

304 """ 

305 Setter for next step 

306 

307 Args: 

308 p: new next step 

309 """ 

310 self.__next = p 

311 

312 @property 

313 def dt(self): 

314 """ 

315 Getter for current time-step size 

316 

317 Returns: 

318 float: dt of level[0] 

319 """ 

320 return self.levels[0].dt 

321 

322 @property 

323 def time(self): 

324 """ 

325 Getter for current time 

326 

327 Returns: 

328 float: time of level[0] 

329 """ 

330 return self.levels[0].time