pyforestry.norway.simulation.orchestration package#

Submodules#

pyforestry.norway.simulation.orchestration.runbook module#

Running Norway’s scenario configurations over real stands.

Norway had a scenario configuration, rulesets and a preset, and nothing that executed any of them – the whole tier was a mirror of Sweden’s scaffolding, added in the same initial commit, before either region had a runtime. Norway’s four published models were reachable through pyforestry.project() all along; what was missing was the scenario runtime around them.

run_norway_scenario() is that. It is a thin regional entry point over pyforestry.simulation.scenario.run_scenario(): it supplies the Kuehne (2022) Scots-pine model, builds the stands, and hands everything else to the shared runner, so the manifest Norway emits has the same shape and the same guarantees as Sweden’s.

pyforestry.norway.simulation.orchestration.runbook.build_kuehne_stands(n_stands: int, *, basal_area_m2_ha: float = 20.0, stems_per_ha: float = 1400.0) → list[StandUnit][source]#

Build n_stands identical aggregate pine stands for a scenario run.

Identical on purpose: the Kuehne model is deterministic, so identical stands give identical rows, and any difference between rows in a run is the scenario’s doing rather than the inventory’s. Supply your own StandUnit list for real inventory.

Parameters:
  • n_stands – How many stands to build.

  • basal_area_m2_ha – Starting basal area.

  • stems_per_ha – Starting stem density.

Returns:

One StandUnit per stand, ids counting from 1.

Raises:

ValueError – If n_stands is not positive.

pyforestry.norway.simulation.orchestration.runbook.kuehne_mean_tree(ctx: SimulationContext, removed_fraction: float) → MeanTree[source]#

Return the stand’s representative stem, so a thinning can be bucked.

The Kuehne model steps a basal area and a stem count, so a thinning from it has no individual stems. It does have a quadratic mean diameter, which the stand derives from those two, and the stem that diameter describes is a real one: bucking it gives the assortment split a stand of that mean size yields.

The height is the harder half, because Kuehne (2022) predicts only dominant height – its height function is a dominant-height trajectory – and a mean stem is shorter than a dominant one. Substituting the dominant height, which this did, makes the representative stem too big: on the shipped baseline it bucks to about 12% more wood than the model says the thinning removed, which is impossible for a real stem and was previously absorbed unseen into a scale factor.

So the height is implied from the model instead. The model states how much volume came out and in how many stems, so the mean stem’s volume is their quotient; brantseg_1967_volume_scots_pine_norway() gives the volume of a Scots pine of a given diameter and height, and this inverts it for the height consistent with the model’s own figures at the stand’s QMD. That is not a new height relation: Brantseg (1967) is one of the three single-tree functions Kuehne’s own volume equation is built on, so the stem this returns is the one the model is already implicitly describing.

Parameters:
  • ctx – The run’s context, read before the removal is applied.

  • removed_fraction – The share of the stand being taken out.

Returns:

The representative stem and how many of it come out.

pyforestry.norway.simulation.orchestration.runbook.kuehne_stand_volume(ctx: SimulationContext) → float[source]#

Return the stand’s volume now, from Kuehne (2022) Eq. 8.

Evaluated on the context’s current basal area, dominant height and age, so a thinning or a disturbance between two growth steps is visible immediately. Reading the stand_volume_m3_per_ha the model published at its last step would not be: it goes stale the moment anything removes a stem, and the run’s summary would then report the removal as zero and quietly fold the volume back into growth.

Kuehne (2022) is fitted for thinned as well as unthinned stands, so re-evaluating it on a post-thinning basal area is within what the model was published to do.

pyforestry.norway.simulation.orchestration.runbook.run_norway_scenario(*, global_seed: int, output_dir: Path, config: ScenarioConfig | None = None, stands: Sequence[StandUnit] | None = None, n_stands: int = 8, n_steps: int = 10, step_years: float = 5.0, valuation: ValuationSettings | None = None, discount_rate: float | None = None, disturbance_rate_per_year: float = 0.0, thin_at_age: Sequence[AgeMeasurement] | None = None, thin_at_year: Sequence[float] | None = None, start_age: AgeMeasurement | None = None, time_to_breast_height: float | None = None, start_year: float = 0.0, forcings: ForcingSet | None = None) → ScenarioRunResult[source]#

Run a Norway scenario with the Kuehne (2022) pine model and write its artifacts.

Parameters:
  • global_seed – The run’s seed; each stand’s derives from it.

  • output_dir – Where the three artifacts go.

  • config – The scenario configuration. Defaults to the Kuehne baseline.

  • stands – The stands to project. Defaults to n_stands built by build_kuehne_stands().

  • n_stands – How many default stands to build, if stands is not given.

  • n_steps – Number of periods.

  • step_years – Period length in years.

  • valuation – Price list and bucking settings. Required, because Norway’s scenario declares a valuation stage. This package ships no Norwegian price list – a price list is regional market data, not science, and inventing one would put numbers under Norway’s name with nothing behind them. Supply your own ValuationSettings, and give its price list a PricelistIdentity: since the list is yours, it is the only thing that can tell the manifest what currency the summary’s money is in. The Kuehne model is a stand-level one, so a thinning from it has no individual stems – it is bucked at the stand’s mean tree instead, once, and scaled to the volume the model says came out. See kuehne_mean_tree() for what that assumes.

  • discount_rate – The annual rate the summary’s net present value is discounted at. Required, like valuation, because Norway’s scenario declares a valuation stage and a net present value in an artifact has to say what it was discounted at. 0.0 states no time preference.

  • disturbance_rate_per_year – Annual share of the stand a scenario disturbance removes, before the scenario’s disturbance_factor. Supplied by the caller; this package ships no rate, because a disturbance rate is a finding and there is no source for one here. Zero, the default, makes the stage an exact no-op.

  • thin_at_age – Stand ages at which the management stage thins, as Age.TOTAL(60). The one to reach for, and especially here: the Kuehne adapter starts its clock at the stand’s total age, so the raw clock times this replaces meant something different in Norway than in Sweden under the same argument name. Defaults to the age build_kuehne_stands() starts its stands at.

  • thin_at_year – Calendar years at which it thins instead, read against start_year. Mutually exclusive with thin_at_age.

  • start_age – How old the stands are at the start. Defaults to _BASELINE_AGE_YEARS as a total age, which is what the adapter is configured with; supply it when passing your own stands.

  • time_to_breast_height – Years to 1.3 m, needed only to schedule in one age measure stands described in the other.

  • start_year – Calendar year the projection begins in, which is the year a forcing series is read at.

  • forcings – Named values the run reads per period – a weather correction, a price index. This package ships none.

Returns:

The run result, including the written artifacts.

Module contents#

Running Norway’s scenario configurations over real stands.