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
« prev ^ index » next coverage.py v7.6.7, created at 2024-11-16 14:51 +0000
1import logging
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
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()
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 """
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()
44class Step(FrozenClass):
45 """
46 Step class, referencing most of the structure needed for the time-stepping
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.
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 """
58 def __init__(self, description):
59 """
60 Initialization routine
62 Args:
63 description (dict): parameters given by the user, will be added as attributes
64 """
66 # set params and status
67 self.params = _Pars(description.get('step_params', {}))
68 self.status = _Status()
70 # set up logger
71 self.logger = logging.getLogger('step')
73 # empty attributes
74 self.__transfer_dict = {}
75 self.base_transfer = None
76 self.levels = []
77 self.__prev = None
78 self.__next = None
80 # freeze class, no further attributes allowed from this point
81 self._freeze()
83 # create hierarchy of levels
84 self.__generate_hierarchy(description)
86 def __generate_hierarchy(self, descr):
87 """
88 Routine to generate the level hierarchy for a single step
90 This makes the explicit generation of levels in the frontend obsolete and hides a few dirty hacks here and
91 there.
93 Args:
94 descr (dict): dictionary containing the description of the levels as list per key
95 """
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 )
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)
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', {})
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)
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)
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')
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 )
161 self.levels.append(L)
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 )
173 @staticmethod
174 def __dict_to_list(in_dict):
175 """
176 Straightforward helper function to convert dictionary of list to list of dictionaries
178 Args:
179 in_dict (dict): dictionary of lists
180 Returns:
181 list of dictionaries
182 """
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
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
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
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 """
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
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
233 def transfer(self, source, target):
234 """
235 Wrapper routine to ease the call of the transfer functions
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.
241 Args:
242 source (pySDC.Level.level): source level
243 target (pySDC.Level.level): target level
244 """
245 self.__transfer_dict[(source, target)]()
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()
255 def init_step(self, u0):
256 """
257 Initialization routine for a new step.
259 This routine uses initial values u0 to set up the u[0] values at the finest level
261 Args:
262 u0 (dtype_u): initial values
263 """
265 assert len(self.levels) >= 1
266 assert len(self.levels[0].u) >= 1
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)
272 @property
273 def prev(self):
274 """
275 Getter for previous step
277 Returns:
278 prev
279 """
280 return self.__prev
282 @prev.setter
283 def prev(self, p):
284 """
285 Setter for previous step
287 Args:
288 p: new previous step
289 """
290 self.__prev = p
292 @property
293 def next(self):
294 """
295 Getter for next step
297 Returns:
298 prev
299 """
300 return self.__next
302 @next.setter
303 def next(self, p):
304 """
305 Setter for next step
307 Args:
308 p: new next step
309 """
310 self.__next = p
312 @property
313 def dt(self):
314 """
315 Getter for current time-step size
317 Returns:
318 float: dt of level[0]
319 """
320 return self.levels[0].dt
322 @property
323 def time(self):
324 """
325 Getter for current time
327 Returns:
328 float: time of level[0]
329 """
330 return self.levels[0].time