pyforestry.simulation.services package#
Submodules#
pyforestry.simulation.services.checkpoint module#
Capture a running simulation’s state, and put it back.
A checkpoint is what a run would need to be resumed somewhere else: in another process, after a crash, or at the head of a branching scenario that explores two managements from one common history. It is not a copy of the object – a pipeline holds its model, its price list and its steps, all of which are rebuilt from configuration and none of which change while the run advances.
That distinction is the whole design. The evolving state is named by the object
that owns it, through Checkpointable, rather than discovered here by
walking __dict__. A composite pipeline’s phases are frozen dataclasses each
holding a back-reference to the pipeline, so a blanket deepcopy of its
attributes would drag the model, the mortality engine and every step into the
snapshot, and restore a pipeline whose steps point at the previous one. Asking
the object what its state is keeps the answer where the answer is known.
This module stays region-neutral for the same reason the rest of
simulation/services does: it is imported by the Sweden and Norway runtimes,
and must not import either.
- class pyforestry.simulation.services.checkpoint.Checkpoint(component_id: str, state: Mapping[str, Any])[source]#
Bases:
objectOne subject’s evolving state, detached from the subject.
- Variables:
component_id (str) – What the checkpoint was taken from. :meth:` CheckpointSerializer.restore` refuses a checkpoint whose id does not match its target, because the failure it prevents is silent: two pipelines expose the same state names, so restoring a Sweden checkpoint into a Norway run would succeed and mean nothing.
state (Mapping[str, Any]) – The state mapping, deep-copied away from the live objects so that advancing the run does not edit the checkpoint underneath it.
- class pyforestry.simulation.services.checkpoint.CheckpointSerializer[source]#
Bases:
objectMove state between a live subject and a
Checkpoint.- capture(subject: Checkpointable) Checkpoint[source]#
Return a checkpoint of
subjectas it stands now.- Parameters:
subject – Any
Checkpointable– in practice a composite pipeline part-way through a projection.- Returns:
A
Checkpointholding a deep copy of the subject’s state.- Raises:
TypeError – If
subjectdoes not implementCheckpointable.
- restore(subject: Checkpointable, checkpoint: Checkpoint) None[source]#
Put
checkpointback intosubject, in place.The state is deep-copied on the way in as well as on the way out, so one checkpoint can seed any number of runs – which is the point of taking one before a branching decision – without those runs sharing tree objects.
- Parameters:
subject – The object to restore into.
checkpoint – A checkpoint taken from a subject with the same
component_id.
- Raises:
TypeError – If
subjectdoes not implementCheckpointable.ValueError – If the checkpoint was taken from a different component.
- class pyforestry.simulation.services.checkpoint.Checkpointable(*args, **kwargs)[source]#
Bases:
ProtocolSomething that can name its evolving state and take it back.
Two methods, and a contract between them: whatever
checkpoint_statereturns,restore_checkpoint_statemust accept, and a subject restored from a checkpoint must advance exactly as the subject it was captured from would have. Derived values – caches, anything rebuilt from configuration – belong outside the mapping, and anything rebuilt from restored state (a mortality engine drawing on the run’s generator) should be rebuilt byrestore_checkpoint_stateitself.- restore_checkpoint_state(state: Mapping[str, Any]) None[source]#
Adopt a state mapping previously returned by
checkpoint_state().
pyforestry.simulation.services.keyed_rng module#
Deterministic random number generators keyed by hierarchical identifiers.
- class pyforestry.simulation.services.keyed_rng.KeyedRNG(_bundle: RandomBundle, path: Tuple[str, ...], seed: int)[source]#
Bases:
objectOne reproducible random stream, identified by its path from the root seed.
Two generators live behind this: a
random.Randomfor the scalar draws most kernels make, and anumpy.random.Generatorfor the vectorised ones, reachable asnumpy. Both are seeded from the same derived seed, and both are captured bystate, so a checkpoint restores either.Keying is what makes that safe. Two generators seeded from one scalar and drawn from in the same code – which is what the Elfving composite carried – make the interleaving of draws across the two an unwritten part of every result: reorder two calls that touch different streams and the numbers move. Streams reached by different paths (
rng.child("mortality")versusrng.child("ingrowth")) are independent, so the order in which their owners happen to run cannot change what either produces.- child(*keys: Iterable[str] | str) KeyedRNG[source]#
Return a derived generator scoped by
keysrelative topath.
- property numpy: Generator#
This stream’s NumPy generator, for vectorised draws.
Created on first use and seeded from the same derived seed as the scalar generator, so a stream that only ever draws scalars costs nothing.
- property state: object#
Return the serialisable state of both underlying generators.
The NumPy state is included only once its generator exists, so a stream that has never drawn a vector does not force one into being just to be snapshotted – and a checkpoint written before the first vectorised draw restores correctly into a run that makes one.
pyforestry.simulation.services.parallel_runner module#
Multiprocessing runner for stand simulations with checkpoint hand-off.
- pyforestry.simulation.services.parallel_runner.run_parallel(target: ContextEnsemble | Sequence[SimulationContext], *, dt: float, steps: int = 1, processes: int | None = None, write_back: bool = True, include_history: bool = False, history_tail: int | None = None, dispatcher: Callable[[int, SimulationContext], int] | None = None, max_processes: int = 8, telemetry_sink: Callable[[int, List[Any]], None] | None = None) List[SimulationContext][source]#
Execute steps of dt across contexts in parallel using checkpoints.
Growth only. Management is a per-context policy run through
run_pipeline(), and a policy is a live callable that cannot cross a process boundary in a checkpoint.
pyforestry.simulation.services.rng_bundle module#
RNG bundle providing keyed deterministic streams for simulations.
- class pyforestry.simulation.services.rng_bundle.RandomBundle(seed: int)[source]#
Bases:
objectManage keyed random number generators derived from a root seed.
- restore(states: Dict[Tuple[str, ...], object]) None[source]#
Restore generator states from
statesreturned bysnapshot().
pyforestry.simulation.services.telemetry module#
Telemetry publishing helpers for simulation events.
- class pyforestry.simulation.services.telemetry.TelemetryEvent(type: str, payload: Mapping[str, object])[source]#
Bases:
objectRecord emitted by
TelemetryPublisher.
- class pyforestry.simulation.services.telemetry.TelemetryPublisher(*, model_id: str | None, seed: int, sink: Callable[[TelemetryEvent], None] | None = None)[source]#
Bases:
objectPublish telemetry events, enriching payloads with provenance metadata.
- property events: List[TelemetryEvent]#
Return a copy of the recorded events.
Module contents#
Support services shared across simulation modules.