pyforestry.base.helpers package#

Subpackages#

Submodules#

pyforestry.base.helpers.bitterlich_angle_count module#

Module for Bitterlich (angle count) sampling techniques.

This module provides classes to tally tree counts per species using the angle-count (relascope) method and to aggregate multiple sampling points into stand-level basal area and stem density metrics.

Source:

Bitterlich, W. (1948). Die Winkelzählprobe. Allgemeine Forst- und Holzwirtschaftliche Zeitung 59(1/2), 4-5. The angle-count principle: with a basal area factor k, every tallied tree contributes k m^2/ha of basal area regardless of its distance from the sample point.

class pyforestry.base.helpers.bitterlich_angle_count.AngleCount(ba_factor: float, value: List[float] | None = None, species: List[TreeName] | None = None, point_id: str | None = None, slope: float = 0.0, diameters_cm: List[List[float]] | None = None, occlusion: float = 0.0)[source]#

Bases: object

Parameters and tallies for angle-count sampling at a single point.

Variables:
  • ba_factor (float) – Basal area factor (m²/ha) applied per count.

  • slope (float) – Terrain slope at the point as a ratio (rise/run, i.e. tan of the inclination). A slope correction factor sqrt(1 + slope**2) (= sec of the inclination angle) is applied to the per-hectare estimates during aggregation; 0.0 means level ground (no correction). Leave it at 0.0 for instruments that already compensate for inclination, or the correction is doubled.

  • point_id (Optional[str]) – Identifier for the sampling point.

  • species (List[TreeName]) – List of tree species encountered.

  • value (List[float]) – Corresponding tallied counts for each species.

  • diameters_cm (Optional[List[List[float]]]) – Optional breast-height diameters (cm) of the tallied (“in”) trees, one inner list per species aligned with species. When supplied for every species, it enables an unbiased stems/ha estimate (sum(BAF / g_i)); a relascope tally alone cannot yield stems/ha without these diameters.

  • occlusion (float) – Portion [0, 1) of the sweep that could not be observed because the point lies on/near the stand boundary (e.g. 0.5 for a 180° half-sweep at the border). The per-hectare estimates are scaled up by 1 / (1 - occlusion) to compensate.

add_observation(sp: TreeName, count: float, diameters: List[float] | None = None)[source]#

Add or update tally for a species at this point.

Parameters:
  • sp (TreeName) – Tree species identifier.

  • count (float) – Count increment for species tallies.

  • diameters (Optional[List[float]], optional) – Diameters (cm) of the tallied trees to record for sp. Defaults to None.

property correction_factor: float#

slope (sec θ) over visible sweep.

sqrt(1 + slope**2) / (1 - occlusion).

The first term is the secant of the inclination. On sloping ground the gauge’s limiting distance is measured in the inclined sight plane, whose horizontal projection is shorter by cos θ; fewer trees are counted than the horizontal projection warrants, so multiplying by sec θ restores the estimate to a hectare of horizontal (map) area, the usual reference for per-hectare statistics. Leave slope at 0.0 when the instrument already compensates for inclination (as a Spiegelrelaskop does), otherwise the correction is applied twice.

The second term divides by the observed fraction of the sweep, scaling a boundary (half-)sweep back up to a full circle.

Type:

Combined per-hectare correction

update_series(sp: TreeName, diameter_cm: float | None = None) → None[source]#

Increment count for sp by one, adding a new entry if needed.

Parameters:
  • sp (TreeName) – Tree species identifier.

  • diameter_cm (Optional[float], optional) – Diameter (cm) of the tallied tree to record. Defaults to None.

class pyforestry.base.helpers.bitterlich_angle_count.AngleCountAggregator(records: List[AngleCount])[source]#

Bases: object

Aggregate multiple AngleCount samples into stand metrics.

Combines per-point basal area factors (BAF) and tallies, merging duplicate point records and computing mean and standard error for basal area and stems per species.

aggregate_stand_metrics() → Tuple[Dict[TreeName, StandBasalArea], Dict[TreeName, Stems]][source]#

Compute mean basal area and stems density per species across plots.

Returns:

Mapping species to basal area and stems metrics.

Return type:

Tuple[Dict[TreeName, StandBasalArea], Dict[TreeName, Stems]]

merge_by_point_id() → List[AngleCount][source]#

Merge records sharing the same point_id, ensuring consistent BAF.

Returns:

Merged samples per unique point.

Return type:

List[AngleCount]

Raises:

ValueError – If BAF differs among records with same point_id.

pyforestry.base.helpers.bucking module#

Abstract helpers shared between bucking algorithms.

class pyforestry.base.helpers.bucking.Bucker[source]#

Bases: object

Simple base class for bucking implementations.

class pyforestry.base.helpers.bucking.BuckingConfig(timber_price_factor: float = 1.0, pulp_price_factor: float = 1.0, use_downgrading: bool = False, save_sections: bool = False)[source]#

Bases: object

Settings that modify bucking algorithm behaviour.

pulp_price_factor: float = 1.0#
save_sections: bool = False#
timber_price_factor: float = 1.0#
use_downgrading: bool = False#
class pyforestry.base.helpers.bucking.BuckingResult(species_group: str, total_value: float, top_proportion: float, dead_wood_proportion: float, high_stump_volume_proportion: float, high_stump_value_proportion: float, last_cut_relative_height: float, volume_per_quality: ~typing.List[float], timber_price_by_quality: ~typing.List[float], vol_fub_5cm: float, vol_sk_ub: float, dbh_cm: float, height_m: float, stump_height_m: float, diameter_stump_cm: float, taper_diameters_cm: ~typing.List[float], taper_heights_m: ~typing.List[float], sections: ~typing.List[~pyforestry.base.helpers.bucking.CrossCutSection] | None = <factory>)[source]#

Bases: Mapping

Encapsulates the output of a bucking algorithm.

plot() → None[source]#

Plot a simple representation of the bucking result.

classmethod zero(**known: Any) → BuckingResult[source]#

Return a well-formed zero-value result for a stem with no merchantable bucking.

Every value/volume field defaults to zero and the taper arrays to empty, so a degenerate stem yields a fully-constructed BuckingResult with total_value == 0 instead of a partially-built object. Any geometry actually known at the call site (species_group, dbh_cm, height_m, taper_diameters_cm …) may be passed as keyword arguments to override the corresponding defaults. The per-quality arrays are sized to QualityType so they stay indexable by quality value.

class pyforestry.base.helpers.bucking.CrossCutSection(start_point: int, end_point: int, volume: float, top_diameter: float, value: float, species_group: str, timber_proportion: float = 0.0, pulp_proportion: float = 0.0, cull_proportion: float = 0.0, fuelwood_proportion: float = 0.0, quality: QualityType = QualityType.Undefined)[source]#

Bases: object

Container for one section in a cutting solution.

cull_proportion: float = 0.0#
fuelwood_proportion: float = 0.0#
pulp_proportion: float = 0.0#
quality: QualityType = 0#
timber_proportion: float = 0.0#
class pyforestry.base.helpers.bucking.QualityType(*values)[source]#

Bases: IntEnum

Possible quality classes for bucked logs.

ButtLog = 1#
Fuelwood = 6#
LogCull = 5#
MiddleLog = 2#
Pulp = 4#
TopLog = 3#
Undefined = 0#

pyforestry.base.helpers.height_models module#

Height-diameter curves and pluggable height sources.

This module provides the height machinery used by height imputation and by the configurable top-height estimators:

  • NaslundHeightCurve – Näslund’s two-parameter height-diameter curve h = 1.3 + d**p / (a + b*d)**p (p = 2 by default), fit from measured (diameter, height) pairs by ordinary least squares after a linearising transform.

  • HeightSource and its concretions – a small abstraction that yields a height for a tree, and (for curve-based sources) for an arbitrary diameter.

  • resolve_height_source() – turn the user-facing height_source argument (a string, a callable f(d) -> h, a fitted curve, or a ready HeightSource) into a HeightSource.

Heights produced by a curve are interpolated, never measured; callers keep the two provenances distinct (see Tree.imputed and Tree.value_of).

Source:

Näslund, M. (1936). Skogsförsöksanstaltens gallringsförsök i tallskog. Meddelanden från Statens skogsförsöksanstalt 29(1), 1-169. The height-diameter curve and its linearising transform are Näslund’s; the least-squares fitting procedure and the pluggable HeightSource abstraction around it are pyforestry’s own.

class pyforestry.base.helpers.height_models.CurveHeightSource(curve: Callable[[float], float | None])[source]#

Bases: HeightSource

Wrap a diameter-to-height callable (e.g. a fitted Näslund curve).

Parameters:

curve – A callable f(diameter_cm) -> height_m | None.

height_at_diameter(diameter_cm: float) → float | None[source]#

Return the curve height (m) at diameter_cm.

height_for(tree) → float | None[source]#

Return the curve height for the tree’s diameter.

is_curve: bool = True#
class pyforestry.base.helpers.height_models.HeightSource[source]#

Bases: object

Interface for objects that supply tree heights.

A height source answers two questions: the height of a given tree (height_for()) and, when it is curve-based (is_curve), the height at an arbitrary diameter (height_at_diameter()).

height_at_diameter(diameter_cm: float) → float | None[source]#

Return a height (m) at diameter_cm (curve sources only).

height_for(tree) → float | None[source]#

Return a height (m) for tree, or None if unavailable.

is_curve: bool = False#
class pyforestry.base.helpers.height_models.MeasuredHeightSource(use_imputed_fallback: bool = False)[source]#

Bases: HeightSource

Use each tree’s measured height_m (optionally an imputed fallback).

Parameters:

use_imputed_fallback – When True and a tree has no measured height_m, fall back to an imputed height from Tree.imputed (a modelled value). Defaults to False, i.e. measured heights only.

height_for(tree) → float | None[source]#

Return the tree’s measured height, or an imputed height if allowed.

is_curve: bool = False#
class pyforestry.base.helpers.height_models.NaslundHeightCurve(a: float, b: float, exponent: float = 2)[source]#

Bases: object

Näslund’s height-diameter curve h = 1.3 + d**p / (a + b*d)**p.

After Näslund, M. (1936), Skogsförsöksanstaltens gallringsförsök i tallskog, Meddelanden från Statens skogsförsöksanstalt 29(1).

The two coefficients a and b are positive; p (the exponent) is 2 for Näslund’s common “minor” function and is occasionally 3, but it may also be fit from the data (see fit() with fit_exponent=True). Heights and the breast-height offset are in metres, diameters in centimetres.

Parameters:
  • a – Curve coefficients.

  • b – Curve coefficients.

  • exponent – The exponent p. Defaults to 2.

classmethod fit(diameters_cm: Sequence[float], heights_m: Sequence[float], exponent: float = 2, *, fit_exponent: bool = False, exponent_bounds: Tuple[float, float] = (1.0, 5.0), min_height_above_breast_m: float = 0.5) → NaslundHeightCurve | None[source]#

Fit the curve from measured (diameter, height) pairs.

For a fixed exponent p the fit is ordinary least squares after Näslund’s linearising transform: with y = d / (h - 1.3)**(1/p) and x = d, h = 1.3 + d**p/(a + b*d)**p becomes y = a + b*x. Only pairs with d > 0 and h >= 1.3 + min_height_above_breast_m contribute; see MIN_HEIGHT_ABOVE_BREAST_M.

When fit_exponent is True the exponent is fit as well: for each candidate p the coefficients a, b are obtained as above, and the p minimising the sum of squared height residuals over exponent_bounds is chosen (a coarse grid seed refined by golden-section search). The height-space criterion is used because the transform itself depends on p, so transformed residuals are not comparable between candidates; a and b remain the linearised least-squares solution at the chosen p, which is the conventional way the curve is fitted. Identifying the exponent needs at least three usable pairs; with fewer, the fixed exponent is used.

Parameters:
  • diameters_cm – Diameters at breast height (cm).

  • heights_m – Corresponding measured heights (m).

  • exponent – The Näslund exponent p to use (or fall back to). Defaults to 2.

  • fit_exponent – When True, also fit the exponent. Defaults to False.

  • exponent_bounds – Inclusive search range for a fitted exponent. Defaults to (1.0, 5.0).

  • min_height_above_breast_m – Least height above breast height a pair must have to be used. Defaults to MIN_HEIGHT_ABOVE_BREAST_M.

Returns:

The fitted curve, or None if it could not be fit (fewer than two usable pairs, or a degenerate design).

Return type:

NaslundHeightCurve | None

predict(diameter_cm: float) → float | None[source]#

Return the curve height (m) at diameter_cm, or None if undefined.

None is returned for a non-positive diameter or if the denominator a + b*d is non-positive (outside the curve’s valid range).

pyforestry.base.helpers.height_models.resolve_height_source(source: str | Callable[[float], float | None] | HeightSource | NaslundHeightCurve, fit_trees: Sequence | None = None, *, naslund_exponent: int | float | str = 2) → HeightSource | None[source]#

Turn a height_source argument into a HeightSource.

Parameters:
  • source –

    One of:

    • "measured" – measured heights only;

    • "measured+imputed" – measured, falling back to Tree.imputed;

    • "naslund" – fit a NaslundHeightCurve from fit_trees;

    • a callable f(diameter_cm) -> height_m;

    • a NaslundHeightCurve; or

    • a ready HeightSource.

  • fit_trees – Trees whose measured (diameter, height) pairs fit the "naslund" curve. Ignored otherwise.

  • naslund_exponent – Exponent for the "naslund" fit: a number to fix it, or "auto" to fit the exponent from the data as well. Defaults to 2.

Returns:

The resolved source, or None if "naslund" could not be fit.

Return type:

HeightSource | None

pyforestry.base.helpers.plot module#

Objects describing a collection of trees recorded on a plot.

class pyforestry.base.helpers.plot.CircularPlot(id: int | str, occlusion: float = 0.0, position: Position | None = None, radius_m: float | None = None, area_m2: float | None = None, site: SiteBase | None = None, AngleCount: List[AngleCount] | None = None, trees: List[Tree] | None = None)[source]#

Bases: object

Contains information from a (usually) circular plot in a stand.

Attributes:#

idint | str

An identifier for this plot.

positionPosition | None

The location of the plot center (if known).

radius_mfloat | None

The radius of the circular plot in meters (if known).

occlusionfloat

Portion [0, 1) of the plot that falls outside the stand and so was never observed. Per-hectare figures are computed against the visible area, area_ha * (1 - occlusion), which scales the observed trees up to the whole plot.

area_m2float | None

The area of the plot in m² (if known). Must supply either radius_m or area_m2.

siteSiteBase | None

Reference to a site object, if any.

treeslist[Tree]

The trees recorded on this plot (each possibly representing multiple stems).

property area_ha: float#

Returns the plot area in hectares.

pyforestry.base.helpers.stand module#

Utilities for working with forest stands.

This module defines Stand, representing a collection of sample plots and an optional boundary polygon, along with the StandMetricAccessor helper used to access aggregated stand metrics such as basal area or stem count.

class pyforestry.base.helpers.stand.Stand(site: SiteBase | None = None, area_ha: float | None = None, plots: List[CircularPlot] = <factory>, polygon: Polygon | None = None, crs: CRS | None = None, top_height_definition: TopHeightDefinition = <factory>)[source]#

Bases: object

Represents a forest stand, which may have:
  • A polygon boundary

  • A list of sample plots

  • A site reference

  • Additional attributes (attrs dict)

  • A user-defined definition of “top height”

If a polygon is provided, the area_ha is computed from the polygon geometry (reprojected to a suitable UTM if the original CRS is geographic).

property BAWAD: StandMetricAccessor#

Access the stand’s basal-area weighted mean diameter.

property BasalArea: StandMetricAccessor#

Access the stand’s basal-area aggregator. .. rubric:: Example

stand.BasalArea.TOTAL -> StandBasalArea for total stand.BasalArea(TreeName(…))-> species-level StandBasalArea float(stand.BasalArea) -> numeric total

property HL: StandMetricAccessor#

Access the stand’s Lorey’s (basal-area weighted) mean height, in metres.

Only trees carrying a measured height_m contribute, so HL is the basal-area weighted mean of the measured heights:

stand.HL.TOTAL               -> LoreysMeanHeight for the whole stand
stand.HL(TreeSpecies(...))   -> species-level Lorey's mean height
float(stand.HL)              -> numeric total value (metres)
property QMD: StandMetricAccessor#

Access the stand’s quadratic mean diameter (QMD) aggregator.

Usage:

Stand.QMD.TOTAL               -> Total QMD (QuadraticMeanDiameter)
Stand.QMD(TreeSpecies(...))   -> Species-level QMD estimate
float(Stand.QMD)              -> Numeric total QMD value (in cm)
property Stems: StandMetricAccessor#

Access the stand’s stems aggregator. .. rubric:: Example

stand.Stems.TOTAL -> Stems object stand.Stems(TreeName(…)) -> species-level Stems float(stand.Stems) -> numeric total

append_plot(plot: CircularPlot) → None[source]#

Append a new plot to the stand and recalculate the stand-level metrics. If any plot in the updated stand has AngleCount data, those estimates take precedence.

area_ha: float | None = None#
crs: CRS | None = None#
property diameter_classes: Dict[Any, Dict[str, List[float]]]#

The diameter-class inventory, keyed by species.

Each entry holds bin_mids_cm and a matching n_per_ha. This is a copy: mutate it freely and hand it back through set_diameter_classes(), which revalidates and refreshes the metrics.

Raises:

RuntimeError – If the stand is not in diameter_class representation.

estimate_top_height(reference: str = 'garcia_u', height_source: str | Callable[[float], float | None] | HeightSource | NaslundHeightCurve = 'measured', *, n: int | None = None, by: str = 'diameter', percentile: float = 90.0, k: float = 3.0, naslund_exponent: int | float | str = 2) → TopHeightMeasurement | None[source]#

Estimate stand top height with a selectable estimator, over all plots.

This is the flexible counterpart to get_dominant_height(). The reference chooses what “the top” is and height_source chooses how heights are obtained; they are composed per plot and averaged over all plots. See pyforestry.base.helpers.top_height.compute_top_height() for the full description of each reference ("mean_of_largest", "garcia_u", "garcia_pp", "percentile", "mean_plus_k_sigma") and height_source ("measured", "naslund", a callable, or a fitted curve).

The stand’s top_height_definition supplies nominal_n / nominal_area_ha (the reference area used by the García methods).

Returns:

The estimated top height (metres), or None if no plot could contribute an estimate.

Return type:

TopHeightMeasurement | None

classmethod from_aggregate_metrics(metrics: Mapping[str, Mapping[Any, Any]], *, site: SiteBase | None = None, area_ha: float | None = None, top_height_definition: TopHeightDefinition | None = None) → Stand[source]#

Build a stand from per-hectare totals rather than from plots.

metrics maps "BasalArea" and "Stems" to species -> value mappings, each optionally carrying a "TOTAL"; a missing total is summed from the species entries. QMD is derived, never supplied.

classmethod from_diameter_classes(diameter_classes: Mapping[Any, Mapping[str, List[float]]], *, site: SiteBase | None = None, area_ha: float | None = None, top_height_definition: TopHeightDefinition | None = None) → Stand[source]#

Build a stand from a binned diameter distribution.

diameter_classes maps species to {"bin_mids_cm": [...], "n_per_ha": [...]}; the two arrays must be the same length.

get_dominant_height() → TopHeightMeasurement | None[source]#

Compute a simple stand-level ‘dominant height’ (aka top height).

This is the straightforward estimator: within the most common plot area, take the m thickest measured-height trees per plot (m being the smallest such count across those plots), average their heights, and average across plots. No small-area bias correction is applied.

For a selectable estimator – García’s (1998) selection-corrected order-statistic estimators for variable plot sizes, or a plain mean of the n tallest/widest trees – use estimate_top_height().

Returns:

The best estimate of top height in meters, along with metadata. If insufficient data, returns None.

Return type:

TopHeightMeasurement | None

impute(attribute: str, imputer: ImputerSpec = None, *, which: str = 'missing', overwrite: bool = False, **imputer_kwargs: object) → int[source]#

Fill in a tree attribute the inventory does not carry.

Modelled values are written to each tree’s imputed map together with the imputer that produced them and its citation. Measured attributes are never modified, so a modelled value cannot be mistaken for a measurement; read the two together with value_of().

Parameters:
  • attribute – What to impute, e.g. "height_m".

  • imputer – A registered imputer name ("naslund"), an Imputer, a callable f(tree) -> value | None, or None for the attribute’s default. A callable is recorded as uncited, which is what it is.

  • which – "missing" (default) imputes only trees lacking a measured value; "all" imputes every tree the imputer can produce a value for.

  • overwrite – When True, replace an existing imputed value; otherwise keep any already present.

  • **imputer_kwargs – Passed to a registered imputer’s constructor, e.g. naslund_exponent="auto". Only meaningful when imputer names a registered imputer (or is left to the default); an already-built imputer or a callable is used as given, so passing both is an error rather than a silently discarded argument.

Returns:

The number of trees assigned a value.

Return type:

int

Raises:
  • KeyError – If no imputer is registered for attribute and none was given.

  • TypeError – If imputer_kwargs accompany an already-built imputer or a callable, which cannot be reconfigured from them.

  • ValueError – If the imputer cannot be fitted from the available trees, or which is not recognised.

metric_estimates() → Dict[str, Dict[Any, Stems | StandBasalArea]][source]#

Return this stand’s stored per-metric, per-species estimates.

Keys are metric names ("Stems", "BasalArea", "QMD" where it has been derived); each maps species – and "TOTAL" – to the estimate. Which of them are present depends on representation: an angle-count stand whose tallies carry no diameters has no "Stems" at all, so callers must handle absence rather than assume a zero.

The returned dict is the live store, not a copy: the simulation runtime writes results back through it. Read it through this method rather than through stand._metric_estimates, which three modules outside this file reached for directly, two of them defensively via getattr(stand, "_metric_estimates", {}) and one straight through [...] – so the same access had two failure modes depending on where it was written.

polygon: Polygon | None = None#
refresh_metrics() → None[source]#

Rebuild the metric estimates from whichever representation is active.

A no-op for aggregate, where the metrics are the state; for the others they are a view of an inventory that a step may have changed.

representation: Literal['tree_list', 'angle_count', 'diameter_class', 'aggregate'] = 'tree_list'#

Which representation currently holds this stand’s state. Set by __post_init__ for measured stands and by the from_* constructors and mutators for the reduced ones.

scale_stems(factor: float) → None[source]#

Scale the stand totals by factor, clamping at zero.

Raises:

RuntimeError – If the stand is not in aggregate representation.

set_aggregate_metrics(*, ba_total: float, stems_total: float) → None[source]#

Replace the stand totals, dropping any species detail.

QMD is re-derived from the two. Use set_species_metrics() when the per-species breakdown is known.

Raises:

RuntimeError – If the stand is not in aggregate representation.

set_diameter_classes(diameter_classes: Mapping[Any, Mapping[str, List[float]]]) → None[source]#

Replace the diameter-class inventory and recompute the metrics.

Raises:
  • RuntimeError – If the stand is not in diameter_class representation.

  • ValueError – If a species’ bin_mids_cm and n_per_ha differ in length.

set_species_metrics(*, basal_area: Mapping[Any, Any], stems: Mapping[Any, Any]) → None[source]#

Replace the aggregate metrics with a per-species breakdown.

A supplied "TOTAL" is taken as given; it is summed from the species entries only when absent. The breakdown a stand carries is often partial – basal area known per species from an angle count, stems only for the species that were tallied – so forcing the total to equal the sum would silently shrink such a stand. The caller knows whether its breakdown is complete; this method does not.

QMD is derived here, per species and for the total, because that derivation is the same arithmetic everywhere and having each model repeat it is how the two copies came to disagree. A species with no basal area or no stems gets no QMD, so the metric is never reported for a species it cannot be defined for.

Raises:

RuntimeError – If the stand is not in aggregate representation.

site: SiteBase | None = None#
thin_trees(uids: List[Any] | None = None, rule: Callable[[Tree], bool] | None = None, polygon: Polygon | None = None) → None[source]#

Remove trees from the stand based on various criteria.

Parameters:
  • uids – List of tree uid values to remove.

  • rule – Callable that returns True for trees that should be removed.

  • polygon – When provided, the rule and/or UIDs are applied only to trees whose coordinates fall inside this polygon. If both uids and rule are None all trees inside the polygon are removed.

use_angle_count: bool = False#
class pyforestry.base.helpers.stand.StandMetricAccessor(stand: Stand, metric_name: str)[source]#

Bases: object

Provides access to stand-level metric data (e.g. BasalArea or Stems).

Usage:

stand.BasalArea.TOTAL
stand.BasalArea(TreeName(...))
float(stand.BasalArea) -> numeric total
stand.BasalArea.precision -> total's precision
group(species=None, *, predicate=None)[source]#

Aggregate this metric over a group of species.

Parameters:
  • species – Either an iterable of species to include (each a TreeName or a species-name string), or a predicate callable(TreeName) -> bool (a predicate may be passed positionally for convenience).

  • predicate – A callable(TreeName) -> bool selecting species to include. A species is included if it matches species or predicate.

Returns:

  • The same value type this accessor exposes (StandBasalArea,

  • Stems, QuadraticMeanDiameter,

  • BasalAreaWeightedDiameter or LoreysMeanHeight),

  • aggregated over the selected species. Additive metrics (basal area,

  • stems) are summed with standard errors combined in quadrature; ratio

  • metrics (QMD, BAWAD, HL) are recomputed from the selected species’

  • component sums.

Examples

Conifers only:

stand.BasalArea.group(lambda s: s.tree_type == "Coniferous")

An explicit species list:

stand.Stems.group(["pinus sylvestris", "picea abies"])
property value: float#

Shortcut to the total aggregator’s numeric value.

pyforestry.base.helpers.top_height module#

Configurable stand top-height (dominant height) estimators.

Top height is estimated per plot and then averaged over all plots. Two families of reference (what “the top” is) are supported, each composed with a height source (measured heights, a fitted Näslund curve, or a user callable):

Tree-selection references (use the heights of selected trees):

  • "mean_of_largest" – mean height of the n largest trees, by diameter (widest) or by height (tallest).

  • "garcia_u" – García’s (1998) distribution-free linear-unbiased U-statistic (his eqs 4-7), for plots that differ in size from the reference area.

  • "garcia_pp" – García’s plotting-position estimator (his eq 3), a single interpolated order statistic with the Hosking Gumbel constant alpha = 0.65.

Diameter-reference references (read a height off a curve at a reference diameter; these require a curve height source):

  • "percentile" – the curve height at the p-th diameter percentile (e.g. D90).

  • "mean_plus_k_sigma" – the curve height at mean(d) + k * sd(d) (Petterson 1955 uses k = 3).

Reference#

García, O. (1998). Estimating top height with variable plot sizes. Canadian Journal of Forest Research 28(10): 1509-1517.

pyforestry.base.helpers.top_height.compute_top_height(plots: Sequence, *, reference: str = 'garcia_u', height_source: str | Callable[[float], float | None] | HeightSource | NaslundHeightCurve = 'measured', n: int | None = None, by: str = 'diameter', percentile: float = 90.0, k: float = 3.0, definition: TopHeightDefinition | None = None, naslund_exponent: int | float | str = 2) → TopHeightMeasurement | None[source]#

Estimate stand top height, averaging a per-plot estimate over all plots.

Parameters:
  • plots – The sample plots (each exposing trees and area_ha).

  • reference – Which top-height reference to use: "mean_of_largest", "garcia_u", "garcia_pp", "percentile" or "mean_plus_k_sigma".

  • height_source – "measured", "naslund" (fit stand-wide), a callable f(diameter_cm) -> height_m, or a ready height source/curve. The diameter-reference methods require a curve source. With "measured" only trees carrying a measured height contribute, so if the thickest trees lack heights, impute them first (Stand.impute("height_m")) or use a curve source instead.

  • n – For "mean_of_largest": number of largest trees per plot. Defaults to the definition-consistent count round(area_ha * nominal_n / nominal_area_ha).

  • by – For "mean_of_largest": "diameter" (widest) or "height" (tallest).

  • percentile – For "percentile": the diameter percentile (e.g. 90 for D90).

  • k – For "mean_plus_k_sigma": the sigma multiplier (Petterson uses 3).

  • definition – The top-height definition (nominal_n per nominal_area_ha). Defaults to 100 per hectare.

  • naslund_exponent – Exponent for a "naslund" fit: a number, or "auto" to fit the exponent from the data too. Defaults to 2.

Returns:

The stand top height (metres), or None if no plot could contribute.

Return type:

TopHeightMeasurement | None

Raises:

ValueError – For an unknown reference, or a diameter-reference method with a non-curve height source.

pyforestry.base.helpers.tree module#

Classes for representing individual and aggregated tree records.

class pyforestry.base.helpers.tree.Tree(position: Position | tuple | None = None, species: TreeName | str | None = None, age: Age | float | None = None, diameter_cm: Diameter_cm | float | None = None, height_m: float | None = None, weight_n: float | None = 1.0, is_overstorey: bool | None = None, mortality: float | None = None, uid: int | str | None = None, crown_radius_m: float | None = None, imputed: Dict[str, ImputedValue] | None = None)[source]#

Bases: object

A spatially explicit single tree: (x, y, [z]) in a coordinate system + attributes.

Attributes:#

positionPosition | tuple[float,float] | tuple[float,float,float] | None

The location of the tree in some coordinate system.

speciesTreeName | str | None

The species of the tree (or a string name to be parsed).

ageAge | float | None

The age of the tree. If an Age enum is used, it wraps the value in AgeMeasurement.

diameter_cmDiameter_cm | float | None

The diameter (cm) if known. If a float is passed, it can be coerced to a Diameter_cm.

height_mfloat | None

The measured height (m) of the tree if known. This is authoritative and is never overwritten by a modelled value.

imputeddict[str, ImputedValue]

Modelled stand-ins for attributes that were never measured, keyed by attribute name. Plain attributes are measurements; a modelled value lives here with the imputer and citation that produced it. Read both together with value_of(). Written by impute().

weight_nfloat

Number of trees represented by Tree object. Defaults to 1.0.

is_overstoreybool | None

Optional flag indicating overstorey status. If None, overstorey classification is left to downstream models.

mortalityfloat | None

Optional mortality fraction (0-1) applied to the tree record.

crown_radius_mfloat | None

Optional horizontal crown (or influence-zone) radius in metres. Used by the influence-zone overlap competition indices; see pyforestry.base.competition. Left None unless measured or supplied by a crown model.

uidint | str

Stable identifier for this record. Supply your own to carry an inventory’s keys through; otherwise one is assigned automatically ("t1", "t2", …), so every tree can always be tracked across a simulation step, a checkpoint or a serialisation round trip without falling back to id(), which means nothing outside one process. A copied tree keeps its uid: it is the same tree, in a later state.

imputed_source(attribute: str) → SourceReference | None[source]#

Return the citation behind an imputed attribute, if it has one.

Returns None when the attribute was measured or is absent. For a caller-supplied callable the reference is the "(none)"/year-0 sentinel rather than a publication.

provenance(attribute: str) → str | None[source]#

Return "measured", "imputed" or None for attribute.

A measurement always takes precedence over a modelled value.

set_imputed(attribute: str, value: float, imputer: Any) → ImputedValue[source]#

Record a modelled value for attribute.

Parameters:
  • attribute – Attribute name the value stands in for.

  • value – The modelled value, in the attribute’s own units.

  • imputer – The imputer that produced it; its component_id and source are stored alongside the value.

Returns:

The stored entry.

Return type:

ImputedValue

value_of(attribute: str, prefer: str = 'measured') → float | None[source]#

Return a usable value for attribute, measured or imputed.

Plain attributes on a tree are measurements; modelled stand-ins live in imputed. This reads both, so callers do not have to.

Parameters:
  • attribute – Attribute name, e.g. "height_m". Need not be one the class declares – "crown_base_height_m" resolves from imputed just as well.

  • prefer – "measured" (default) returns the measurement, falling back to an imputed value; "imputed" reverses the order; "measured_only" / "imputed_only" return just that source (or None).

Returns:

The value, or None if neither source has one.

Return type:

float | None

Raises:

ValueError – If prefer is not one of the four accepted values.

pyforestry.base.helpers.tree_metrics module#

Reusable tree-list metrics that operate on ordered collections of trees.

Currently this module exposes basal_area_larger(), the “basal area of larger trees” (BAL) competition index used by many individual-tree growth models.

pyforestry.base.helpers.tree_metrics.basal_area_larger(sizes: Sequence[float], basal_areas: Sequence[float]) → List[float][source]#

Basal area of all trees larger than each tree (the BAL competition index).

For every element i the returned value is the sum of basal_areas over all elements whose sizes value is strictly greater than sizes[i]. Trees that tie on sizes therefore all receive the same BAL – the sum of the strictly larger trees only – which is the conventional definition.

The result is aligned to the input order (result[i] corresponds to sizes[i]). The computation is O(n log n) (one sort plus a single linear cumulative pass), so it stays efficient for large tree lists.

Parameters:
  • sizes – The ordering key for each tree, typically diameter at breast height in centimetres. Larger means “more dominant”.

  • basal_areas – The basal area contributed by each tree, in whatever unit the caller wants BAL expressed in (e.g. m²/ha after applying per-hectare expansion factors). Must be the same length as sizes.

Returns:

BAL for each input tree, in the original input order.

Return type:

list[float]

Raises:

ValueError – If sizes and basal_areas have different lengths.

Examples

>>> basal_area_larger([30.0, 20.0, 20.0, 10.0], [3.0, 2.0, 2.0, 1.0])
[0.0, 3.0, 3.0, 7.0]

pyforestry.base.helpers.tree_species module#

Utility classes and helpers for defining tree species.

This module exposes two related concepts: TreeName represents a single biological species (genus + species name), while TreeSpecies is a namespace that groups TreeName instances for a given region. For example TreeSpecies.Sweden.pinus_sylvestris returns the TreeName object for Scots pine.

class pyforestry.base.helpers.tree_species.RegionalGenusGroup(genus: str, species: List[TreeName])[source]#

Bases: object

A container for a group of TreeName objects sharing the same genus. It is iterable and supports membership testing.

property full_name: str#

Return the genus name in lower case.

class pyforestry.base.helpers.tree_species.RegionalTreeSpecies(region: str, allowed_species: List[TreeName])[source]#

Bases: object

Exposes a subset of globally defined TreeName objects as attributes. Also computes regional genus groups (e.g. ‘alnus’) that contain only the allowed species.

class pyforestry.base.helpers.tree_species.TreeGenus(name: str, code: str)[source]#

Bases: object

Represents a botanical genus.

class pyforestry.base.helpers.tree_species.TreeName(genus: TreeGenus, species_name: str, code: str)[source]#

Bases: object

A specific tree species within a genus.

property full_name: str#

Return the lowercase "genus species" representation.

property tree_type: str#

Return "Coniferous" or "Deciduous" based on the genus.

class pyforestry.base.helpers.tree_species.TreeSpecies[source]#

Bases: object

Namespace exposing regional groups of tree species.

Regional namespaces (TreeSpecies.Sweden, TreeSpecies.Norway) resolve on first access, so they are available without importing the regional package yourself.

Norway: ClassVar[RegionalTreeSpecies] = <pyforestry.base.helpers.tree_species.RegionalTreeSpecies object>#
Sweden: ClassVar[RegionalTreeSpecies] = <pyforestry.base.helpers.tree_species.RegionalTreeSpecies object>#
pyforestry.base.helpers.tree_species.get_tree_type_by_genus(genus: str) → str[source]#

Return the tree type ("Coniferous" or "Deciduous") for genus.

pyforestry.base.helpers.tree_species.parse_tree_species(species_str: str | TreeName) → TreeName[source]#

Return the TreeName corresponding to species_str.

species_str may be either a TreeName instance or a string such as "Pinus sylvestris". Strings are normalized and looked up among the globally defined species (for example those exposed through TreeSpecies).

pyforestry.base.helpers.utils module#

Miscellaneous helper utilities.

pyforestry.base.helpers.utils.enum_code(value: int | float | bool | str) → int | float | bool | str[source]#

Return numeric or label from an enum member or dataclass.

pyforestry.base.helpers.utils.warn_proportion(name: str, value: float) → None[source]#

Warn when a proportion is outside [0, 1].

Nothing about this is regional – a species proportion is a proportion in any country – but it lived in pyforestry.sweden._model_input_normalization beside the genuinely Swedish normalisers. Norway had no way to reach it that did not import from Sweden, and this package has no cross-region imports.

Parameters:
  • name – The argument being checked, for the message.

  • value – The proportion, expected in [0, 1].

Module contents#

Convenience imports for common helper types.