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
« prev ^ index » next coverage.py v7.5.0, created at 2024-04-29 09:02 +0000
1import logging
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
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, while the convergence controllers are allowed to add more variables in a controlled fashion
24 later on using the `add_variable` function.
25 """
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()
45class step(FrozenClass):
46 """
47 Step class, referencing most of the structure needed for the time-stepping
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.
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 """
59 def __init__(self, description):
60 """
61 Initialization routine
63 Args:
64 description (dict): parameters given by the user, will be added as attributes
65 """
67 # set params and status
68 self.params = _Pars(description.get('step_params', {}))
69 self.status = _Status()
71 # set up logger
72 self.logger = logging.getLogger('step')
74 # empty attributes
75 self.__transfer_dict = {}
76 self.base_transfer = None
77 self.levels = []
78 self.__prev = None
79 self.__next = None
81 # freeze class, no further attributes allowed from this point
82 self._freeze()
84 # create hierarchy of levels
85 self.__generate_hierarchy(description)
87 def __generate_hierarchy(self, descr):
88 """
89 Routine to generate the level hierarchy for a single step
91 This makes the explicit generation of levels in the frontend obsolete and hides a few dirty hacks here and
92 there.
94 Args:
95 descr (dict): dictionary containing the description of the levels as list per key
96 """
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 )
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)
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', {})
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)
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)
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')
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 )
162 self.levels.append(L)
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 )
174 @staticmethod
175 def __dict_to_list(in_dict):
176 """
177 Straightforward helper function to convert dictionary of list to list of dictionaries
179 Args:
180 in_dict (dict): dictionary of lists
181 Returns:
182 list of dictionaries
183 """
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
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
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
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 """
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
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
234 def transfer(self, source, target):
235 """
236 Wrapper routine to ease the call of the transfer functions
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.
242 Args:
243 source (pySDC.Level.level): source level
244 target (pySDC.Level.level): target level
245 """
246 self.__transfer_dict[(source, target)]()
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()
256 def init_step(self, u0):
257 """
258 Initialization routine for a new step.
260 This routine uses initial values u0 to set up the u[0] values at the finest level
262 Args:
263 u0 (dtype_u): initial values
264 """
266 assert len(self.levels) >= 1
267 assert len(self.levels[0].u) >= 1
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)
273 @property
274 def prev(self):
275 """
276 Getter for previous step
278 Returns:
279 prev
280 """
281 return self.__prev
283 @prev.setter
284 def prev(self, p):
285 """
286 Setter for previous step
288 Args:
289 p: new previous step
290 """
291 self.__prev = p
293 @property
294 def next(self):
295 """
296 Getter for next step
298 Returns:
299 prev
300 """
301 return self.__next
303 @next.setter
304 def next(self, p):
305 """
306 Setter for next step
308 Args:
309 p: new next step
310 """
311 self.__next = p
313 @property
314 def dt(self):
315 """
316 Getter for current time-step size
318 Returns:
319 float: dt of level[0]
320 """
321 return self.levels[0].dt
323 @property
324 def time(self):
325 """
326 Getter for current time
328 Returns:
329 float: time of level[0]
330 """
331 return self.levels[0].time