90 def __new__(mcls, name, bases, namespace):
92 cls = super().__new__(mcls, name, bases, namespace)
98 verbose = namespace.get(
"__debug_class_creation__",
False)
101 print(
"\n==============================")
102 print(f
"[metaclass] Creating class: {name}")
103 print(
"==============================\n")
105 print(
"=== RAW NAMESPACE ===")
106 for k, v
in namespace.items():
107 print(f
"{k:25} {type(v)}")
111 if name ==
"SimulationRunner":
118 print(
"=== INSPECTION ===")
120 for name, obj
in namespace.items():
121 if isinstance(obj, (types.FunctionType, classmethod, staticmethod)):
122 if isinstance(obj, (classmethod, staticmethod)):
127 cb = getattr(func,
"__simulation_callback__",
None)
128 setup = getattr(func,
"__simulation_setup__",
None)
132 print(f
"[decorator callback] applying to: {name} | value: {cb}")
133 if setup
is not None:
134 print(f
"[decorator setup] applying to: {name} | value: {setup}")
137 callbacks.append((name, cb))
140 if setup_func
is not None:
141 raise ValueError(
"Multiple setup functions")
143 setup_func = (name, func)
146 print(
"\n=== Metaclass result ===")
147 print(
"callbacks:", callbacks)
148 print(
"setup_func:", setup_func)
150 if setup_func
is None:
151 raise ValueError(
"No simulation setup function found")
179 def __init__(self, info: CallbackInfo, tsim_start: float):
184 if info.tsim_interval
is not None:
185 candidates.append(tsim_start)
186 if info.at_tsim
is not None:
187 future = [t
for t
in info.at_tsim
if t >= tsim_start]
189 candidates.append(min(future))
190 self.
next_tsim = min(candidates)
if candidates
else None
192 self.
next_iter_count = 0
if info.iter_count_interval
is not None else None
193 self.
next_walltime = 0.0
if info.walltime_interval
is not None else None
195 def advance(self, t_model: float, iter_count: int, walltime: float):
199 if self.
info.tsim_interval
is not None:
200 candidates.append(t_model + self.
info.tsim_interval)
201 if self.
info.at_tsim
is not None:
202 future = [t
for t
in self.
info.at_tsim
if t > t_model]
204 candidates.append(min(future))
205 self.
next_tsim = min(candidates)
if candidates
else None
207 if self.
info.iter_count_interval
is not None:
209 if self.
info.walltime_interval
is not None:
212 rank_0_print(f
'[Simulation] Advancing callback "{self.info.name}"')
214 rank_0_print(f
" -> t = {t_model} -> {self.next_tsim}")
215 if self.
info.iter_count_interval
is not None:
216 rank_0_print(f
" -> iter = {iter_count} -> {self.next_iter_count}")
217 if self.
info.walltime_interval
is not None:
218 rank_0_print(f
" -> walltime = {walltime} -> {self.next_walltime}")
220 def should_trigger(self, t_model: float, iter_count: int, walltime: float) -> bool:
228 log.append(f
" -> t = {t_model} >= {self.next_tsim}")
229 if self.
info.iter_count_interval
is not None:
232 log.append(f
" -> iter = {iter_count} >= {self.next_iter_count}")
233 if self.
info.walltime_interval
is not None:
236 log.append(f
" -> walltime = {walltime} >= {self.next_walltime}")
240 f
'[Simulation] Triggering callback "{self.info.name}" (counter = {self.counter}):\n'
254 def from_dict(self, data: dict):
263 SimulationRunner is a base class to declare a simulation with setup & callbacks.
265 A derived class must define:
266 - t_end: float = <end time of the simulation>
267 - a setup (any function decorated with @simulation_setup)
269 And can define callbacks (any function decorated with @callback):
271 < call every tsim = i * time_step >
272 - @callback(tsim_interval=1.0)
273 def analysis(self, icallback):
274 rank_0_print("analysis")
276 < call at exact simulation times >
277 - @callback(at_tsim=[1.0, 5.0, 10.0])
278 def snapshot(self, icallback):
279 rank_0_print("snapshot")
281 < call when tsim = dt_stop, niter_max is reached or walltime_step is reached >
282 - @callback(tsim_interval=dt_stop, iter_count_interval=1000, walltime_interval=30*60)
283 def do_checkpoint(self, icheckpoint):
284 self.dump_helper.dump(icheckpoint)
286 Note that for the last one that this reset the counters until next callback.
287 The trigger conditions are inclusive and reset the counters for all triggers of that callback.
290 t_end: float |
None =
None
291 dump_prefix: str |
None =
None
294 cur_iter_count: int = 0
296 _declared_callbacks: list
297 _setup: tuple[str, Callable]
299 def __init__(self, model):
306 func=getattr(self, name),
308 tsim_interval=info[
"tsim_interval"],
309 iter_count_interval=info[
"iter_count_interval"],
310 walltime_interval=info[
"walltime_interval"],
311 at_tsim=info[
"at_tsim"],
323 if self.
t_end is None:
324 raise ValueError(f
"{type(self).__name__}.t_end must be defined")
327 raise ValueError(f
"{type(self).__name__}._declared_callbacks must be defined")
330 raise ValueError(f
"{type(self).__name__}._setup must be defined")
332 def do_checkpoint(self, icheckpoint: int, **kwargs):
335 raise ValueError(f
"{type(self).__name__}.dump_prefix must be defined")
345 rank_0_print(
"[Simulation] Doing checkpoint")
346 self.
dump_helper.write_dump(icheckpoint, metadata=metadata, **kwargs)
347 rank_0_print(
"[Simulation] Checkpoint done")
353 rank_0_print(f
"[Simulation] Running setup function: {name}")
361 rank_0_print(
"[Simulation] Setting up callbacks states")
364 rank_0_print(
"[Simulation] Setup done")
366 def restore_from_checkpoint(self, metadata: dict):
367 self.
cur_t = metadata[
"cur_t"]
370 rank_0_print(
"[Simulation] Setting up callbacks states")
372 rank_0_print(
"[Simulation] Restoring callbacks states")
374 wtime = shamrock.get_wtime_sync()
382 if c.walltime_interval
is not None:
389 self, next_time: float, next_iter_count: int |
None, next_walltime: float |
None
392 if next_time < self.
cur_t:
393 raise ValueError(f
"Next callback time {next_time} is in the past")
395 if next_iter_count
is not None:
397 raise ValueError(f
"Next callback iter count {next_iter_count} is in the past")
399 if next_iter_count
is None:
404 if next_walltime
is None:
407 result = self.
model.evolve_until(
408 next_time, niter_max=next_iter_count, max_walltime=next_walltime
413 def trigger_and_advance_callbacks(self):
414 callback_to_advance = []
416 wtime = shamrock.get_wtime_sync()
421 rank_0_print(
"--------------------------------")
423 rank_0_print(
"--------------------------------")
424 callback_to_advance.append(ic)
429 for ic
in callback_to_advance:
432 def goto_run_next_callback(self):
434 next_time = self.
t_end
435 next_iter_count =
None
441 if state.next_tsim
is not None:
442 next_time = min(next_time, state.next_tsim)
444 if state.next_iter_count
is not None:
445 if next_iter_count
is None:
446 next_iter_count = state.next_iter_count
448 next_iter_count = min(next_iter_count, state.next_iter_count)
450 if state.next_walltime
is not None:
451 if next_walltime
is None:
452 next_walltime = state.next_walltime
454 next_walltime = min(next_walltime, state.next_walltime)
458 f
"[Simulation] Evolve until next trigger(s) :\n"
459 f
" -> t = {next_time} (current = {self.cur_t})\n"
460 f
" -> iter = {next_iter_count} (current = {self.cur_iter_count})\n"
461 f
" -> walltime = {next_walltime} (current = {shamrock.get_wtime_sync()})"
465 self.
evolve_until(next_time, next_iter_count, next_walltime)
473 if metadata
is not None:
474 rank_0_print(
"[Simulation] Restoring Simulation handle from checkpoint")