pyforestry.sweden.simulation.presets package#

Submodules#

pyforestry.sweden.simulation.presets.baseline module#

Baseline Sweden scenario configuration.

pyforestry.sweden.simulation.presets.baseline.build_baseline_scenario_config() → ScenarioConfig[source]#

Build the baseline scenario configuration.

pyforestry.sweden.simulation.presets.elfving_2010_pipeline module#

The composite Sweden pipeline driven by Elfving (2010) mature growth.

Everything around the mature-growth phase – the NYSKOG reconstruction, Nyström (2000) young-stand growth, Elfving (2013) mortality, Wikberg (2004) ingrowth, Söderberg (1992) height and bark, and the valuation – is CompositePipeline. What this module adds is the model that steps the mature trees, its typed inputs, and its provenance.

class pyforestry.sweden.simulation.presets.elfving_2010_pipeline.Elfving2010Pipeline(config: Elfving2010PipelineConfig | None = None)[source]#

Bases: CompositePipeline

Composite Sweden stand simulation using Elfving (2010) mature growth.

property component_id: str#

Stable identifier for the Elfving 2010 composite pipeline.

property components: Sequence[Describable]#

The growth model, plus everything the composite workflow composes.

property source: SourceReference#

Bibliographic provenance for the growth model this pipeline projects with.

class pyforestry.sweden.simulation.presets.elfving_2010_pipeline.Elfving2010PipelineConfig(regeneration_type: RegenerationType = RegenerationType.NATURAL, species_to_plant: TreeName = TreeName(genus=TreeGenus(name='Pinus', code='PINUS'), species_name='sylvestris', code='PSYL'), nfi_region: NfiRegion = NfiRegion.REG3, site_index_pine_m: float = 20.0, site_index_spruce_m: float = 22.0, initial_age_years: float = 12.0, sample_trees: int = 120, random_seed: int = 42, deterministic: bool = True, handover_dbh_cm: float = 10.0, handover_mean_height_m: float = 7.0, handover_smoothing_width_m: float = 1.0, dt_years: float = 5.0, regeneration_seed_trees_per_ha: float = 150.0, regeneration_plant_count_per_ha: float | None = None, regeneration_ground_prepared: bool = True, regeneration_burnt: bool = False, regeneration_area_ha: float = 1.0, use_naslund_damage_index: bool = True, use_naslund_damage_mortality: bool = True, naslund_damage_mortality_scale: float = 1.0, naslund_moose_factor: float = 1.0, naslund_vole_factor: float = 1.0, naslund_snow_break_factor: float = 1.0, naslund_whip_factor: float = 1.0, naslund_frost_factor: float = 1.0, naslund_snow_blight_factor: float = 1.0, naslund_other_factor: float = 1.0, apply_ingrowth: bool = False, ingrowth_deterministic: bool = True, ingrowth_min_mean_age_years: float = 50.0, apply_mortality: bool = True, mortality_tree_model: MortalityTreeModel = MortalityTreeModel.ELFVING_2013, mortality_config: MortalityConfig | None = None, temperature_sum_dd: float | None = None, valuation_region: str | None = None, valuation_use_solution_cube: bool = True, valuation_solution_cube_path: str | None = None, valuation_solution_cube_autogenerate_if_missing: bool = False, valuation_solution_cube_generate_workers: int = -1, valuation_solution_cube_generate_dbh_range_cm: tuple[float, float] = (10.0, 40.0), valuation_solution_cube_generate_height_range_m: tuple[float, float] = (8.0, 30.0), valuation_solution_cube_generate_dbh_step_cm: int = 5, valuation_solution_cube_generate_height_step_m: float = 2.0, valuation_cube_dbh_step_cm: float = 1.0, valuation_cube_height_step_m: float = 0.5, valuation_cube_bark_step_mm: float = 1.0)[source]#

Bases: CompositePipelineConfig

Configuration for the Elfving 2010 composite pipeline.

Adds nothing to the composite’s own knobs: Elfving (2010) needs no switch the shared workflow does not already have. It exists so that the two pipelines’ configs are siblings rather than one being the other’s base – the Söderberg config used to inherit from this one and pick up Elfving-only fields with it.

pyforestry.sweden.simulation.presets.elfving_2010_pipeline.build_elfving_2010_pipeline(config: Elfving2010PipelineConfig | None = None) → Elfving2010Pipeline[source]#

Build the Elfving 2010 composite Sweden pipeline for hybrid projection.

pyforestry.sweden.simulation.presets.soderberg_1986_pipeline module#

Composite Sweden stand-simulation preset using Söderberg (1986) mature growth.

This preset runs the same period as the Elfving pipeline – the same eleven phases in the same order, both inheriting them from CompositePipeline – and changes what the mature-growth phase steps with. Söderberg (1986) has no equivalent of Elfving’s stand-level basal-area correction, so mature growth comes straight from the tree equations.

Everything else it declares is a consequence of that swap:

  • the growth model reads ctx.attrs rather than a typed Inputs, so Soderberg1986Pipeline._model_attrs() supplies the canonical set and Soderberg1986Pipeline._model_inputs() is left at the composite’s None;

  • use_soderberg_form_height_volume swaps the reported volume for the Söderberg form height, which is over bark and so has to be converted before this package’s under-bark price lists can touch it – see Soderberg1986Pipeline.value_standing_forest().

That the eleven phases carry over unchanged is the check that the decomposition is an abstraction rather than one model’s method list; a test asserts the two pipelines publish the same phases. Until recently the check was weaker than it looked, because this class inherited them from Elfving2010Pipeline itself.

class pyforestry.sweden.simulation.presets.soderberg_1986_pipeline.Soderberg1986Pipeline(config: Soderberg1986PipelineConfig | None = None)[source]#

Bases: CompositePipeline

Stateful composite stand simulation using Söderberg (1986) mature growth.

property component_id: str#

Stable identifier for the Söderberg 1986 composite preset.

property components: Sequence[Describable]#

The growth model, plus everything the composite workflow composes.

property source: SourceReference#

Bibliographic provenance for the Söderberg 1986 composite preset.

value_standing_forest(tree_list: list[Tree] | None = None) → dict[str, float][source]#

Estimate standing value and volume for the living trees.

With use_soderberg_form_height_volume unset – the default – this is the inherited valuation: Näsberg (1985) bucking against the Mellanskog 2013 price list, with a Brandel volume for stems too small to buck.

Setting it swaps the stem-volume function: the m3sk column then comes from the Söderberg (1986) form-height equations rather than from Brandel. The form height multiplies the basal area implied by breast-height diameter over bark, so its output is over bark already, which is what m3sk means, and it is reported as it comes.

Pricing is a separate matter, and this route has no taper. It cannot buck, so no timber volume and no timber-valued stems are reported; and it cannot find where a stem narrows past the price list’s minimum log diameter, so every cubic metre it values is valued as pulpwood. Its pulpwood volume is therefore the whole stem, an upper bound rather than a merchantable volume – the bucking route it inherits from bounds this properly, and the difference is the unmerchantable top.

Since every price list here is under bark (m3to for timber, m³fub for pulpwood), the volume that is priced is converted to under bark first, with the Söderberg (1992) double bark this pipeline already carries on each tree – see _form_height_volume_under_bark_m3(), which is where the approximation that conversion involves is written down. Pricing the over-bark figure directly would overstate value by the bark fraction.

class pyforestry.sweden.simulation.presets.soderberg_1986_pipeline.Soderberg1986PipelineConfig(regeneration_type: RegenerationType = RegenerationType.NATURAL, species_to_plant: TreeName = TreeName(genus=TreeGenus(name='Pinus', code='PINUS'), species_name='sylvestris', code='PSYL'), nfi_region: NfiRegion = NfiRegion.REG3, site_index_pine_m: float = 20.0, site_index_spruce_m: float = 22.0, initial_age_years: float = 12.0, sample_trees: int = 120, random_seed: int = 42, deterministic: bool = True, handover_dbh_cm: float = 10.0, handover_mean_height_m: float = 7.0, handover_smoothing_width_m: float = 1.0, dt_years: float = 5.0, regeneration_seed_trees_per_ha: float = 150.0, regeneration_plant_count_per_ha: float | None = None, regeneration_ground_prepared: bool = True, regeneration_burnt: bool = False, regeneration_area_ha: float = 1.0, use_naslund_damage_index: bool = True, use_naslund_damage_mortality: bool = True, naslund_damage_mortality_scale: float = 1.0, naslund_moose_factor: float = 1.0, naslund_vole_factor: float = 1.0, naslund_snow_break_factor: float = 1.0, naslund_whip_factor: float = 1.0, naslund_frost_factor: float = 1.0, naslund_snow_blight_factor: float = 1.0, naslund_other_factor: float = 1.0, apply_ingrowth: bool = False, ingrowth_deterministic: bool = True, ingrowth_min_mean_age_years: float = 50.0, apply_mortality: bool = True, mortality_tree_model: MortalityTreeModel = MortalityTreeModel.ELFVING_2013, mortality_config: MortalityConfig | None = None, temperature_sum_dd: float | None = None, valuation_region: str | None = None, valuation_use_solution_cube: bool = True, valuation_solution_cube_path: str | None = None, valuation_solution_cube_autogenerate_if_missing: bool = False, valuation_solution_cube_generate_workers: int = -1, valuation_solution_cube_generate_dbh_range_cm: tuple[float, float] = (10.0, 40.0), valuation_solution_cube_generate_height_range_m: tuple[float, float] = (8.0, 30.0), valuation_solution_cube_generate_dbh_step_cm: int = 5, valuation_solution_cube_generate_height_step_m: float = 2.0, valuation_cube_dbh_step_cm: float = 1.0, valuation_cube_height_step_m: float = 0.5, valuation_cube_bark_step_mm: float = 1.0, soderberg_include_thinning_effect: bool = True, use_soderberg_form_height_volume: bool = False)[source]#

Bases: CompositePipelineConfig

Configuration for the Söderberg 1986 composite preset.

Extends the composite workflow’s config, not the Elfving pipeline’s – which it used to, and so carried whatever knobs Elfving added along with it.

soderberg_include_thinning_effect: bool = True#
use_soderberg_form_height_volume: bool = False#

Report volume from the Söderberg (1986) form-height equations instead of bucking each tree. Nothing is bucked, so everything is priced as pulpwood; and because a form-height volume is over bark while the price lists are under bark, it is converted first, by an approximation spelled out in Soderberg1986Pipeline._form_height_volume_under_bark_m3().

pyforestry.sweden.simulation.presets.soderberg_1986_pipeline.build_soderberg_1986_pipeline(config: Soderberg1986PipelineConfig | None = None) → Soderberg1986Pipeline[source]#

Build the Söderberg 1986 composite Sweden preset for hybrid projection.

pyforestry.sweden.simulation.presets.valuation_cube module#

Building and loading the Mellanskog-2013 valuation solution cube.

A solution cube is a precomputed bucking result for a grid of species, diameters and heights: generating one takes minutes and writes a file, loading one is milliseconds. Both are filesystem work, and both used to be classmethods and methods on the Elfving 2010 pipeline – so a class whose job is to step a stand also owned a multi-minute build, a mkdir, and a file-existence policy.

They are module-level functions here because none of them needs a pipeline. What they need is a path and a grid, and the pipeline passes those in.

pyforestry.sweden.simulation.presets.valuation_cube.RECOMMENDED_SPECIES: Tuple[str, ...] = ('pinus sylvestris', 'picea abies')#

The species the Swedish pipelines value through a cube. A cube covering only these is far smaller than one covering every species in the pricelist, and trees outside it fall back to direct bucking, which is correct but slower.

pyforestry.sweden.simulation.presets.valuation_cube.ensure_cube_file(*, path: str, overwrite: bool = False, workers: int = -1, dbh_range_cm: Tuple[float, float] = (10.0, 40.0), height_range_m: Tuple[float, float] = (8.0, 30.0), dbh_step_cm: int = 5, height_step_m: float = 2.0) → str[source]#

Write a cube to path unless one is already there.

Returns the path either way, so a caller can use the result without checking whether it built anything.

pyforestry.sweden.simulation.presets.valuation_cube.generate_cube(*, workers: int = -1, dbh_range_cm: Tuple[float, float] = (10.0, 40.0), height_range_m: Tuple[float, float] = (8.0, 30.0), dbh_step_cm: int = 5, height_step_m: float = 2.0) → SolutionCube[source]#

Build a Mellanskog-2013 cube over the recommended species and grid.

This is the expensive one: it buckets every (species, diameter, height) cell on the grid. Narrow the ranges or widen the steps to trade accuracy for time.

pyforestry.sweden.simulation.presets.valuation_cube.load_cube(path: str) → SolutionCube[source]#

Load a cube, checking it was built against the Mellanskog 2013 pricelist.

The check matters: a cube holds values, so one built from a different pricelist would price this pipeline’s stands at another year’s prices without anything in the numbers looking wrong.

Module contents#

Sweden’s runnable pipelines and its scenario configuration.

This package holds two unrelated kinds of thing, and they used to share the word “preset”, which meant the name told you nothing:

  • Pipelines – Elfving2010Pipeline and Soderberg1986Pipeline are stateful simulators. You initialize one on a site, step it, and read a projection out of it.

  • Scenario configuration – ScenarioConfig is a frozen dataclass of seeds, stage names, rulesets and required artifacts. It declares a run rather than being one; run_scenario() executes it and writes the artifacts.

get_scenario_config reaches the second, get_pipeline the first. There is deliberately no single lookup that returns “a preset”: the two have no common interface, and the one that used to exist was typed as returning the configuration, so it could not reach either simulator.

pyforestry.sweden.simulation.presets.PIPELINE_BUILDERS: dict[str, Callable[[...], CompositePipeline]] = {'elfving_2010_composite': <function build_elfving_2010_pipeline>, 'soderberg_1986_composite': <function build_soderberg_1986_pipeline>}#

Every runnable pipeline, by the name a caller would type – each pipeline’s own component_id.

The keys used to be "elfving_2010" and "soderberg_1986", which are also the names pyforestry.project(model=...) accepts, where they mean the bare single-tree growth adapters. One string named two very different things through two entry points: project(model="elfving_2010") steps trees you supply with Elfving (2010) alone, while get_pipeline("elfving_2010") reconstructed a stand with NYSKOG and ran nine more models around it. Naming the composites after themselves removes the collision.

pyforestry.sweden.simulation.presets.available_pipelines() → list[str][source]#

Return every name get_pipeline() accepts, sorted.

These are the composite pipelines, and they are not everything this package can run end to end. A composite is a composition pyforestry assembled: a dozen publications that were never fitted to each other, driven through one period. The whole published systems – Eriksson (1976), Ekö (1985), Persson (1992), Petterson (1955) – each project a stand just as completely, but the composition is their own author’s, so they live in pyforestry.sweden.systems and ship their own runner. Ask available_systems() for those.

The split is by whose composition it is, not by what can be run, and reading this list as the answer to “what can I project?” understates the package by four systems.

pyforestry.sweden.simulation.presets.get_pipeline(name: str, config: CompositePipelineConfig | None = None) → CompositePipeline[source]#

Build a runnable composite pipeline by name.

Parameters:
  • name – One of available_pipelines().

  • config – The pipeline’s own config dataclass, or None for its defaults.

Returns:

initialize(site=...), then step() or run_projection(...). Unlike a GrowthModel it builds its own stand from a site, which is why pyforestry.project(stand, ...) cannot drive one.

Return type:

A stateful simulator

Raises:

ValueError – If name is not a known pipeline.

pyforestry.sweden.simulation.presets.get_scenario_config(scenario_id: str) → ScenarioConfig[source]#

Build the scenario configuration for a supported scenario id.

Raises:

ValueError – If scenario_id is not one this package defines.