pyforestry.base.helpers package#
Subpackages#
- pyforestry.base.helpers.primitives package
- Submodules
- pyforestry.base.helpers.primitives.age module
- pyforestry.base.helpers.primitives.area_aggregates module
- pyforestry.base.helpers.primitives.bawad module
- pyforestry.base.helpers.primitives.cartesian_position module
- pyforestry.base.helpers.primitives.diameter_cm module
- pyforestry.base.helpers.primitives.loreys_mean_height module
- pyforestry.base.helpers.primitives.qmd module
- pyforestry.base.helpers.primitives.sitebase module
- pyforestry.base.helpers.primitives.siteindex_value module
- pyforestry.base.helpers.primitives.topheight module
- pyforestry.base.helpers.primitives.volume module
- Module contents
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 contributeskm^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:
objectParameters 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.0means level ground (no correction). Leave it at0.0for 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.5for a 180° half-sweep at the border). The per-hectare estimates are scaled up by1 / (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 bysec θrestores the estimate to a hectare of horizontal (map) area, the usual reference for per-hectare statistics. Leaveslopeat0.0when 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
- class pyforestry.base.helpers.bitterlich_angle_count.AngleCountAggregator(records: List[AngleCount])[source]#
Bases:
objectAggregate 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:
objectSimple 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:
objectSettings 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:
MappingEncapsulates the output of a bucking algorithm.
- 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
BuckingResultwithtotal_value == 0instead 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 toQualityTypeso 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:
objectContainer 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#
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 curveh = 1.3 + d**p / (a + b*d)**p(p = 2by default), fit from measured(diameter, height)pairs by ordinary least squares after a linearising transform.HeightSourceand 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-facingheight_sourceargument (a string, a callablef(d) -> h, a fitted curve, or a readyHeightSource) into aHeightSource.
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
HeightSourceabstraction around it are pyforestry’s own.
- class pyforestry.base.helpers.height_models.CurveHeightSource(curve: Callable[[float], float | None])[source]#
Bases:
HeightSourceWrap 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.
- is_curve: bool = True#
- class pyforestry.base.helpers.height_models.HeightSource[source]#
Bases:
objectInterface 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).
- is_curve: bool = False#
- class pyforestry.base.helpers.height_models.MeasuredHeightSource(use_imputed_fallback: bool = False)[source]#
Bases:
HeightSourceUse each tree’s measured
height_m(optionally an imputed fallback).- Parameters:
use_imputed_fallback – When
Trueand a tree has no measuredheight_m, fall back to an imputed height fromTree.imputed(a modelled value). Defaults toFalse, 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:
objectNä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
aandbare 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 (seefit()withfit_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
pthe fit is ordinary least squares after Näslund’s linearising transform: withy = d / (h - 1.3)**(1/p)andx = d,h = 1.3 + d**p/(a + b*d)**pbecomesy = a + b*x. Only pairs withd > 0andh >= 1.3 + min_height_above_breast_mcontribute; seeMIN_HEIGHT_ABOVE_BREAST_M.When
fit_exponentisTruethe exponent is fit as well: for each candidatepthe coefficientsa, bare obtained as above, and thepminimising the sum of squared height residuals overexponent_boundsis chosen (a coarse grid seed refined by golden-section search). The height-space criterion is used because the transform itself depends onp, so transformed residuals are not comparable between candidates;aandbremain the linearised least-squares solution at the chosenp, which is the conventional way the curve is fitted. Identifying the exponent needs at least three usable pairs; with fewer, the fixedexponentis used.- Parameters:
diameters_cm – Diameters at breast height (cm).
heights_m – Corresponding measured heights (m).
exponent – The Näslund exponent
pto use (or fall back to). Defaults to 2.fit_exponent – When
True, also fit the exponent. Defaults toFalse.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
Noneif it could not be fit (fewer than two usable pairs, or a degenerate design).- Return type:
NaslundHeightCurve | None
- 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_sourceargument into aHeightSource.- Parameters:
source –
One of:
"measured"– measured heights only;"measured+imputed"– measured, falling back toTree.imputed;"naslund"– fit aNaslundHeightCurvefromfit_trees;a callable
f(diameter_cm) -> height_m;a
NaslundHeightCurve; ora 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
Noneif"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:
objectContains 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_mcontribute, soHLis 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_cmand a matchingn_per_ha. This is a copy: mutate it freely and hand it back throughset_diameter_classes(), which revalidates and refreshes the metrics.- Raises:
RuntimeError – If the stand is not in
diameter_classrepresentation.
- 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(). Thereferencechooses what “the top” is andheight_sourcechooses how heights are obtained; they are composed per plot and averaged over all plots. Seepyforestry.base.helpers.top_height.compute_top_height()for the full description of eachreference("mean_of_largest","garcia_u","garcia_pp","percentile","mean_plus_k_sigma") andheight_source("measured","naslund", a callable, or a fitted curve).The stand’s
top_height_definitionsuppliesnominal_n/nominal_area_ha(the reference area used by the García methods).- Returns:
The estimated top height (metres), or
Noneif 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.
metricsmaps"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_classesmaps 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
mthickest measured-height trees per plot (mbeing 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
ntallest/widest trees – useestimate_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
imputedmap 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 withvalue_of().- Parameters:
attribute – What to impute, e.g.
"height_m".imputer – A registered imputer name (
"naslund"), anImputer, a callablef(tree) -> value | None, orNonefor 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 whenimputernames 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
attributeand none was given.TypeError – If
imputer_kwargsaccompany 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
whichis 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 onrepresentation: 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 viagetattr(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 thefrom_*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
aggregaterepresentation.
- 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
aggregaterepresentation.
- 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_classrepresentation.ValueError – If a species’
bin_mids_cmandn_per_hadiffer 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
aggregaterepresentation.
- 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
uidvalues to remove.rule – Callable that returns
Truefor 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
uidsandruleareNoneall trees inside the polygon are removed.
- use_angle_count: bool = False#
- class pyforestry.base.helpers.stand.StandMetricAccessor(stand: Stand, metric_name: str)[source]#
Bases:
objectProvides 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
TreeNameor a species-name string), or a predicatecallable(TreeName) -> bool(a predicate may be passed positionally for convenience).predicate – A
callable(TreeName) -> boolselecting species to include. A species is included if it matchesspeciesorpredicate.
- Returns:
The same value type this accessor exposes (
StandBasalArea,Stems,QuadraticMeanDiameter,BasalAreaWeightedDiameterorLoreysMeanHeight),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 thenlargest 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 constantalpha = 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 thep-th diameter percentile (e.g. D90)."mean_plus_k_sigma"– the curve height atmean(d) + k * sd(d)(Petterson 1955 usesk = 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
treesandarea_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 callablef(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 countround(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.90for D90).k – For
"mean_plus_k_sigma": the sigma multiplier (Petterson uses 3).definition – The top-height definition (
nominal_npernominal_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
Noneif 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:
objectA 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 byimpute().- 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. LeftNoneunless 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 toid(), 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
Nonewhen 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"orNoneforattribute.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_idandsourceare stored alongside the value.
- Returns:
The stored entry.
- Return type:
- 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 fromimputedjust 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 (orNone).
- Returns:
The value, or
Noneif neither source has one.- Return type:
float | None
- Raises:
ValueError – If
preferis 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
ithe returned value is the sum ofbasal_areasover all elements whosesizesvalue is strictly greater thansizes[i]. Trees that tie onsizestherefore 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 tosizes[i]). The computation isO(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
sizesandbasal_areashave 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:
objectA 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:
objectExposes 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:
objectRepresents a botanical genus.
- class pyforestry.base.helpers.tree_species.TreeName(genus: TreeGenus, species_name: str, code: str)[source]#
Bases:
objectA 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:
objectNamespace 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") forgenus.
- pyforestry.base.helpers.tree_species.parse_tree_species(species_str: str | TreeName) TreeName[source]#
Return the
TreeNamecorresponding tospecies_str.species_strmay be either aTreeNameinstance or a string such as"Pinus sylvestris". Strings are normalized and looked up among the globally defined species (for example those exposed throughTreeSpecies).
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_normalizationbeside 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.