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: object

One 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: object

Move state between a live subject and a Checkpoint.

capture(subject: Checkpointable) → Checkpoint[source]#

Return a checkpoint of subject as it stands now.

Parameters:

subject – Any Checkpointable – in practice a composite pipeline part-way through a projection.

Returns:

A Checkpoint holding a deep copy of the subject’s state.

Raises:

TypeError – If subject does not implement Checkpointable.

restore(subject: Checkpointable, checkpoint: Checkpoint) → None[source]#

Put checkpoint back into subject, 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 subject does not implement Checkpointable.

  • ValueError – If the checkpoint was taken from a different component.

class pyforestry.simulation.services.checkpoint.Checkpointable(*args, **kwargs)[source]#

Bases: Protocol

Something that can name its evolving state and take it back.

Two methods, and a contract between them: whatever checkpoint_state returns, restore_checkpoint_state must 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 by restore_checkpoint_state itself.

checkpoint_state() → Mapping[str, Any][source]#

Return the evolving state of this object, by name.

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: object

One reproducible random stream, identified by its path from the root seed.

Two generators live behind this: a random.Random for the scalar draws most kernels make, and a numpy.random.Generator for the vectorised ones, reachable as numpy. Both are seeded from the same derived seed, and both are captured by state, 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") versus rng.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 keys relative to path.

jumpahead(steps: int) → None[source]#

Advance the generator steps positions without yielding values.

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.

randint(a: int, b: int) → int[source]#

Return a random integer N such that a <= N <= b.

random() → float[source]#

Return the next random floating point number in the range [0.0, 1.0).

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.

uniform(a: float, b: float) → float[source]#

Return a random floating point number N such that a <= N <= b.

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: object

Manage keyed random number generators derived from a root seed.

restore(states: Dict[Tuple[str, ...], object]) → None[source]#

Restore generator states from states returned by snapshot().

rng_for(*keys: object) → KeyedRNG[source]#

Return a generator scoped by keys relative to the root seed.

snapshot() → Dict[Tuple[str, ...], object][source]#

Return the states for all instantiated generators.

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: object

Record emitted by TelemetryPublisher.

class pyforestry.simulation.services.telemetry.TelemetryPublisher(*, model_id: str | None, seed: int, sink: Callable[[TelemetryEvent], None] | None = None)[source]#

Bases: object

Publish telemetry events, enriching payloads with provenance metadata.

clear() → None[source]#

Discard buffered events.

property events: List[TelemetryEvent]#

Return a copy of the recorded events.

publish(event_type: str, payload: Mapping[str, object]) → None[source]#

Emit a telemetry event augmented with provenance metadata.

Module contents#

Support services shared across simulation modules.