Source code for pyforestry.sweden.biomass.marklund_1988

import inspect
from typing import Optional

import numpy as np

from pyforestry.base.timber import Timber


[docs] def Marklund_1988_T1(diameter_cm): return np.exp(-2.3388 + 11.3264 * (diameter_cm / (diameter_cm + 13)))
[docs] def Marklund_1988_T2(diameter_cm, height_m): return np.exp( -2.6768 + 7.5939 * (diameter_cm / (diameter_cm + 13)) + 0.0151 * height_m + 0.8799 * np.log(height_m) )
[docs] def Marklund_1988_T3(diameter_cm, height_m, double_bark_mm, age): return np.exp( -2.6232 + 7.7318 * (diameter_cm / (diameter_cm + 13)) + 0.0139 * height_m + 0.8625 * np.log(height_m) - 0.0704 * np.log(double_bark_mm) + 0.00185 * age )
[docs] def Marklund_1988_T4( diameter_cm, height_m, double_bark_mm, age, form_quotient5=None, form_quotient3=None, altitude_m=None, ): if form_quotient5 is not None and form_quotient3 is not None: print("Either form_quotient5 or form_quotient3 can be supplied. Not both.") print("form_quotient5 taking precedence.") form_quotient3 = None return np.exp( -2.4826 + 7.9039 * (diameter_cm / (diameter_cm + 13)) + 0.0184 * height_m + 0.6939 * np.log(height_m) - 0.0731 * np.log(double_bark_mm) + 0.00182 * age + 0.2382 * (form_quotient5 or 0) + 0.2217 * (form_quotient3 or 0) - 0.1596 * (altitude_m or 0) )
MarklundPineStem = [Marklund_1988_T1, Marklund_1988_T2, Marklund_1988_T3, Marklund_1988_T4] MarklundPineStem = sorted(MarklundPineStem, key=lambda f: f.__code__.co_argcount, reverse=True) # Stem wood functions
[docs] def Marklund_1988_T5(diameter_cm): return np.exp(-2.2184 + 11.4219 * (diameter_cm / (diameter_cm + 14)))
[docs] def Marklund_1988_T6(diameter_cm, height_m): return np.exp( -2.6864 + 7.6066 * (diameter_cm / (diameter_cm + 14)) + 0.0200 * height_m + 0.8658 * np.log(height_m) )
[docs] def Marklund_1988_T7(diameter_cm, height_m, double_bark_mm, age): return np.exp( -2.5325 + 7.8936 * (diameter_cm / (diameter_cm + 14)) + 0.0231 * height_m + 0.7887 * np.log(height_m) - 0.1065 * np.log(double_bark_mm) + 0.00201 * age )
[docs] def Marklund_1988_T8( diameter_cm, height_m, double_bark_mm, age, form_quotient5=None, form_quotient3=None, altitude_m=None, ): if form_quotient5 is not None and form_quotient3 is not None: print("Either form_quotient5 or form_quotient3 can be supplied. Not both.") print("form_quotient5 taking precedence.") form_quotient3 = None return np.exp( -2.0028 + 7.9455 * (diameter_cm / (diameter_cm + 14)) + 0.0439 * height_m + 0.2437 * np.log(height_m) - 0.0875 * np.log(double_bark_mm) + 0.00172 * age + 0.7778 * (form_quotient5 or 0) + 0.4855 * (form_quotient3 or 0) - 0.1557 * (altitude_m or 0) )
MarklundPineStemWood = [Marklund_1988_T5, Marklund_1988_T6, Marklund_1988_T7, Marklund_1988_T8] MarklundPineStemWood = sorted( MarklundPineStemWood, key=lambda f: f.__code__.co_argcount, reverse=True ) # Stem bark functions
[docs] def Marklund_1988_T9(diameter_cm): return np.exp(-2.9748 + 8.8489 * (diameter_cm / (diameter_cm + 16)))
[docs] def Marklund_1988_T10(diameter_cm, height_m): return np.exp( -3.2765 + 7.2482 * (diameter_cm / (diameter_cm + 16)) + 0.4487 * np.log(height_m) )
[docs] def Marklund_1988_T11(diameter_cm, height_m, relative_bark_thickness): return np.exp( -3.6065 + 7.0834 * (diameter_cm / (diameter_cm + 16)) + 0.5086 * np.log(height_m) + 0.0255 * relative_bark_thickness )
[docs] def Marklund_1988_T12(diameter_cm, height_m, crown_base_height_m, relative_bark_thickness): return np.exp( -3.5076 + 7.5295 * (diameter_cm / (diameter_cm + 16)) + 0.5629 * np.log(height_m) - 0.2271 * np.log(height_m - crown_base_height_m) + 0.0222 * relative_bark_thickness )
MarklundPineBark = [Marklund_1988_T9, Marklund_1988_T10, Marklund_1988_T11, Marklund_1988_T12] MarklundPineBark = sorted(MarklundPineBark, key=lambda f: f.__code__.co_argcount, reverse=True) # Living branches functions
[docs] def Marklund_1988_T13(diameter_cm): return np.exp(-2.9580 + 7.7428 * (diameter_cm / (diameter_cm + 18)))
[docs] def Marklund_1988_T14(diameter_cm, height_m): return np.exp( -3.0331 + 6.7610 * (diameter_cm / (diameter_cm + 18)) + 0.6565 * np.log(height_m) )
[docs] def Marklund_1988_T15(diameter_cm, height_m, crown_base_height_m): return np.exp( -3.1398 + 6.6698 * (diameter_cm / (diameter_cm + 18)) + 0.6797 * np.log(height_m) - 0.2763 * np.log(height_m - crown_base_height_m) )
MarklundPineLivingBranches = [Marklund_1988_T13, Marklund_1988_T14, Marklund_1988_T15] MarklundPineLivingBranches = sorted( MarklundPineLivingBranches, key=lambda f: f.__code__.co_argcount, reverse=True ) # Needles functions
[docs] def Marklund_1988_T16(diameter_cm): return np.exp(-3.5285 + 6.5301 * (diameter_cm / (diameter_cm + 20)))
[docs] def Marklund_1988_T17(diameter_cm, height_m): return np.exp( -3.6531 + 5.9425 * (diameter_cm / (diameter_cm + 20)) + 0.8165 * np.log(height_m) )
[docs] def Marklund_1988_T18(diameter_cm, height_m, crown_base_height_m): return np.exp( -3.7941 + 5.8956 * (diameter_cm / (diameter_cm + 20)) + 0.8482 * np.log(height_m) - 0.2436 * np.log(height_m - crown_base_height_m) )
MarklundPineNeedles = [Marklund_1988_T16, Marklund_1988_T17, Marklund_1988_T18] MarklundPineNeedles = sorted( MarklundPineNeedles, key=lambda f: f.__code__.co_argcount, reverse=True ) # Dead branches functions
[docs] def Marklund_1988_T19(diameter_cm): return np.exp(-4.3352 + 5.8210 * (diameter_cm / (diameter_cm + 23)))
[docs] def Marklund_1988_T20(diameter_cm, height_m): return np.exp( -4.4668 + 5.3836 * (diameter_cm / (diameter_cm + 23)) + 0.9584 * np.log(height_m) )
[docs] def Marklund_1988_T21(diameter_cm, height_m, crown_base_height_m): return np.exp( -4.5368 + 5.4689 * (diameter_cm / (diameter_cm + 23)) + 0.8945 * np.log(height_m) - 0.3322 * np.log(height_m - crown_base_height_m) )
MarklundPineDeadBranches = [Marklund_1988_T19, Marklund_1988_T20, Marklund_1988_T21] MarklundPineDeadBranches = sorted( MarklundPineDeadBranches, key=lambda f: f.__code__.co_argcount, reverse=True ) # Stump-root system functions
[docs] def Marklund_1988_T22(diameter_cm): return np.exp(-2.2114 + 7.5246 * (diameter_cm / (diameter_cm + 19)))
[docs] def Marklund_1988_T23(diameter_cm, height_m): return np.exp( -2.3323 + 6.4935 * (diameter_cm / (diameter_cm + 19)) + 0.6055 * np.log(height_m) )
[docs] def Marklund_1988_T24(diameter_cm, height_m, crown_base_height_m): return np.exp( -2.4008 + 6.5480 * (diameter_cm / (diameter_cm + 19)) + 0.6187 * np.log(height_m) - 0.2052 * np.log(height_m - crown_base_height_m) )
MarklundPineStumpRootSystem = [Marklund_1988_T22, Marklund_1988_T23, Marklund_1988_T24] MarklundPineStumpRootSystem = sorted( MarklundPineStumpRootSystem, key=lambda f: f.__code__.co_argcount, reverse=True ) # Stump functions
[docs] def Marklund_1988_T25(diameter_cm): return np.exp(-3.1444 + 8.4027 * (diameter_cm / (diameter_cm + 15)))
[docs] def Marklund_1988_T26(diameter_cm, height_m): return np.exp( -3.2571 + 7.3144 * (diameter_cm / (diameter_cm + 15)) + 0.7078 * np.log(height_m) )
[docs] def Marklund_1988_T27(diameter_cm, height_m, relative_bark_thickness): return np.exp( -3.3738 + 7.1416 * (diameter_cm / (diameter_cm + 15)) + 0.7956 * np.log(height_m) + 0.0119 * relative_bark_thickness )
MarklundPineStump = [Marklund_1988_T25, Marklund_1988_T26, Marklund_1988_T27] MarklundPineStump = sorted(MarklundPineStump, key=lambda f: f.__code__.co_argcount, reverse=True) # Norway Spruce Stem functions
[docs] def Marklund_1988_S1(diameter_cm): return np.exp(-2.3450 + 11.1111 * (diameter_cm / (diameter_cm + 12)))
[docs] def Marklund_1988_S2(diameter_cm, height_m): return np.exp( -2.7190 + 7.8497 * (diameter_cm / (diameter_cm + 12)) + 0.0095 * height_m + 0.8693 * np.log(height_m) )
[docs] def Marklund_1988_S3(diameter_cm, height_m, double_bark_mm, age): return np.exp( -2.5490 + 7.7334 * (diameter_cm / (diameter_cm + 12)) + 0.0125 * height_m + 0.8854 * np.log(height_m) - 0.0588 * np.log(double_bark_mm) + 0.00191 * age )
MarklundSpruceStem = [Marklund_1988_S1, Marklund_1988_S2, Marklund_1988_S3] MarklundSpruceStem = sorted(MarklundSpruceStem, key=lambda f: f.__code__.co_argcount, reverse=True) # Norway Spruce Living branches functions
[docs] def Marklund_1988_S4(diameter_cm): return np.exp(-2.8470 + 8.6120 * (diameter_cm / (diameter_cm + 12)))
[docs] def Marklund_1988_S5(diameter_cm, height_m): return np.exp( -2.9892 + 7.1881 * (diameter_cm / (diameter_cm + 12)) + 0.7534 * np.log(height_m) )
[docs] def Marklund_1988_S6(diameter_cm, height_m, crown_base_height_m): return np.exp( -3.1020 + 7.0834 * (diameter_cm / (diameter_cm + 12)) + 0.7707 * np.log(height_m) - 0.2426 * np.log(height_m - crown_base_height_m) )
MarklundSpruceLivingBranches = [Marklund_1988_S4, Marklund_1988_S5, Marklund_1988_S6] MarklundSpruceLivingBranches = sorted( MarklundSpruceLivingBranches, key=lambda f: f.__code__.co_argcount, reverse=True ) # Norway Spruce Dead branches functions
[docs] def Marklund_1988_S7(diameter_cm): return np.exp(-4.3352 + 5.8210 * (diameter_cm / (diameter_cm + 23)))
[docs] def Marklund_1988_S8(diameter_cm, height_m): return np.exp( -4.4668 + 5.3836 * (diameter_cm / (diameter_cm + 23)) + 0.9584 * np.log(height_m) )
[docs] def Marklund_1988_S9(diameter_cm, height_m, crown_base_height_m): return np.exp( -4.5368 + 5.4689 * (diameter_cm / (diameter_cm + 23)) + 0.8945 * np.log(height_m) - 0.3322 * np.log(height_m - crown_base_height_m) )
MarklundSpruceDeadBranches = [Marklund_1988_S7, Marklund_1988_S8, Marklund_1988_S9] MarklundSpruceDeadBranches = sorted( MarklundSpruceDeadBranches, key=lambda f: f.__code__.co_argcount, reverse=True ) # Norway Spruce Stump-root system functions
[docs] def Marklund_1988_S10(diameter_cm): return np.exp(-2.2114 + 7.5246 * (diameter_cm / (diameter_cm + 19)))
[docs] def Marklund_1988_S11(diameter_cm, height_m): return np.exp( -2.3323 + 6.4935 * (diameter_cm / (diameter_cm + 19)) + 0.6055 * np.log(height_m) )
[docs] def Marklund_1988_S12(diameter_cm, height_m, crown_base_height_m): return np.exp( -2.4008 + 6.5480 * (diameter_cm / (diameter_cm + 19)) + 0.6187 * np.log(height_m) - 0.2052 * np.log(height_m - crown_base_height_m) )
MarklundSpruceStumpRootSystem = [Marklund_1988_S10, Marklund_1988_S11, Marklund_1988_S12] MarklundSpruceStumpRootSystem = sorted( MarklundSpruceStumpRootSystem, key=lambda f: f.__code__.co_argcount, reverse=True ) # Norway Spruce Stump functions
[docs] def Marklund_1988_S13(diameter_cm): return np.exp(-3.1444 + 8.4027 * (diameter_cm / (diameter_cm + 15)))
[docs] def Marklund_1988_S14(diameter_cm, height_m): return np.exp( -3.2571 + 7.3144 * (diameter_cm / (diameter_cm + 15)) + 0.7078 * np.log(height_m) )
[docs] def Marklund_1988_S15(diameter_cm, height_m, relative_bark_thickness): return np.exp( -3.3738 + 7.1416 * (diameter_cm / (diameter_cm + 15)) + 0.7956 * np.log(height_m) + 0.0119 * relative_bark_thickness )
MarklundSpruceStump = [Marklund_1988_S13, Marklund_1988_S14, Marklund_1988_S15] MarklundSpruceStump = sorted( MarklundSpruceStump, key=lambda f: f.__code__.co_argcount, reverse=True ) # Birch Stem functions
[docs] def Marklund_1988_B1(diameter_cm): return np.exp(-2.3450 + 11.1111 * (diameter_cm / (diameter_cm + 12)))
[docs] def Marklund_1988_B2(diameter_cm, height_m): return np.exp( -2.7190 + 7.8497 * (diameter_cm / (diameter_cm + 12)) + 0.0095 * height_m + 0.8693 * np.log(height_m) )
[docs] def Marklund_1988_B3(diameter_cm, height_m, double_bark_mm, age): return np.exp( -2.5490 + 7.7334 * (diameter_cm / (diameter_cm + 12)) + 0.0125 * height_m + 0.8854 * np.log(height_m) - 0.0588 * np.log(double_bark_mm) + 0.00191 * age )
MarklundBirchStem = [Marklund_1988_B1, Marklund_1988_B2, Marklund_1988_B3] MarklundBirchStem = sorted(MarklundBirchStem, key=lambda f: f.__code__.co_argcount, reverse=True) # Birch Living branches functions
[docs] def Marklund_1988_B4(diameter_cm): return np.exp(-2.8470 + 8.6120 * (diameter_cm / (diameter_cm + 12)))
[docs] def Marklund_1988_B5(diameter_cm, height_m): return np.exp( -2.9892 + 7.1881 * (diameter_cm / (diameter_cm + 12)) + 0.7534 * np.log(height_m) )
[docs] def Marklund_1988_B6(diameter_cm, height_m, crown_base_height_m): return np.exp( -3.1020 + 7.0834 * (diameter_cm / (diameter_cm + 12)) + 0.7707 * np.log(height_m) - 0.2426 * np.log(height_m - crown_base_height_m) )
MarklundBirchLivingBranches = [Marklund_1988_B4, Marklund_1988_B5, Marklund_1988_B6] MarklundBirchLivingBranches = sorted( MarklundBirchLivingBranches, key=lambda f: f.__code__.co_argcount, reverse=True ) # Birch Dead branches functions
[docs] def Marklund_1988_B7(diameter_cm): return np.exp(-4.3352 + 5.8210 * (diameter_cm / (diameter_cm + 23)))
[docs] def Marklund_1988_B8(diameter_cm, height_m): return np.exp( -4.4668 + 5.3836 * (diameter_cm / (diameter_cm + 23)) + 0.9584 * np.log(height_m) )
[docs] def Marklund_1988_B9(diameter_cm, height_m, crown_base_height_m): return np.exp( -4.5368 + 5.4689 * (diameter_cm / (diameter_cm + 23)) + 0.8945 * np.log(height_m) - 0.3322 * np.log(height_m - crown_base_height_m) )
MarklundBirchDeadBranches = [Marklund_1988_B7, Marklund_1988_B8, Marklund_1988_B9] MarklundBirchDeadBranches = sorted( MarklundBirchDeadBranches, key=lambda f: f.__code__.co_argcount, reverse=True ) # Birch Stump-root system functions
[docs] def Marklund_1988_B10(diameter_cm): return np.exp(-2.2114 + 7.5246 * (diameter_cm / (diameter_cm + 19)))
[docs] def Marklund_1988_B11(diameter_cm, height_m): return np.exp( -2.3323 + 6.4935 * (diameter_cm / (diameter_cm + 19)) + 0.6055 * np.log(height_m) )
[docs] def Marklund_1988_B12(diameter_cm, height_m, crown_base_height_m): return np.exp( -2.4008 + 6.5480 * (diameter_cm / (diameter_cm + 19)) + 0.6187 * np.log(height_m) - 0.2052 * np.log(height_m - crown_base_height_m) )
MarklundBirchStumpRootSystem = [Marklund_1988_B10, Marklund_1988_B11, Marklund_1988_B12] MarklundBirchStumpRootSystem = sorted( MarklundBirchStumpRootSystem, key=lambda f: f.__code__.co_argcount, reverse=True ) # Birch Stump functions
[docs] def Marklund_1988_B13(diameter_cm): return np.exp(-3.1444 + 8.4027 * (diameter_cm / (diameter_cm + 15)))
[docs] def Marklund_1988_B14(diameter_cm, height_m): return np.exp( -3.2571 + 7.3144 * (diameter_cm / (diameter_cm + 15)) + 0.7078 * np.log(height_m) )
[docs] def Marklund_1988_B15(diameter_cm, height_m, relative_bark_thickness): return np.exp( -3.3738 + 7.1416 * (diameter_cm / (diameter_cm + 15)) + 0.7956 * np.log(height_m) + 0.0119 * relative_bark_thickness )
MarklundBirchStump = [Marklund_1988_B13, Marklund_1988_B14, Marklund_1988_B15] MarklundBirchStump = sorted(MarklundBirchStump, key=lambda f: f.__code__.co_argcount, reverse=True) species_map = { "pinus sylvestris": { "stem": MarklundPineStem, "stem_wood": MarklundPineStemWood, "stem_bark": MarklundPineBark, "living_branches": MarklundPineLivingBranches, "needles": MarklundPineNeedles, "dead_branches": MarklundPineDeadBranches, "stump_root_system": MarklundPineStumpRootSystem, "stump": MarklundPineStump, }, "picea abies": { "stem": MarklundSpruceStem, "living_branches": MarklundSpruceLivingBranches, "dead_branches": MarklundSpruceDeadBranches, "stump_root_system": MarklundSpruceStumpRootSystem, "stump": MarklundSpruceStump, }, "betula pendula": { "stem": MarklundBirchStem, "living_branches": MarklundBirchLivingBranches, "dead_branches": MarklundBirchDeadBranches, "stump_root_system": MarklundBirchStumpRootSystem, "stump": MarklundBirchStump, }, "betula pubescens": { "stem": MarklundBirchStem, "living_branches": MarklundBirchLivingBranches, "dead_branches": MarklundBirchDeadBranches, "stump_root_system": MarklundBirchStumpRootSystem, "stump": MarklundBirchStump, }, } # Wrapper function
[docs] def Marklund_1988( species: Optional[str] = None, component: Optional[str] = None, *args, timber: Optional[Timber] = None, **kwargs, ): """ Calculates the dry-weight biomass for individual trees in Sweden. This function serves as a wrapper for the various biomass component functions developed by Marklund (1988). It selects the appropriate formula based on the tree species and the provided arguments. Args: species (str): The tree species. Must be one of 'Picea abies' (Norway spruce), 'Pinus sylvestris' (Scots pine), or 'Betula' (Birch). DBH (float): Diameter at breast height (1.3m), in centimeters. height_m (Optional[float]): Total tree height, in meters. Required for more accurate models for stem, bark, and branches. age_at_breast_height (Optional[float]): The age of the tree at breast height (1.3m). Used for some pine foliage models. crown_base_height_m (Optional[float]): Height from the ground to the lowest green branch, in meters. Used for some foliage and branch models. is_southern_sweden (Optional[bool]): Set to True if the tree is in southern Sweden, False if in northern Sweden. Affects some spruce component calculations. Returns: Dict[str, float]: A dictionary where keys are the biomass components (e.g., 'stem', 'bark', 'living_branches', 'dead_branches', 'stump', 'roots') and values are their calculated dry weight in kilograms (kg). Components that cannot be calculated with the provided inputs will be omitted. Raises: ValueError: If the species is not one of the recognized types or if required arguments are missing for a selected calculation. Source: Marklund, Lars-Gunnar. (1988). Biomassafunktioner för Tall, Gran och Björk i Sverige [Biomass functions for pine, spruce and birch in Sweden]. Report 45. Dept. of Forest Survey. Swedish University of Agricultural Sciences. Umeå. 73 pp. ISSN 0348-0496. ISBN 91-576-3524-2. Examples: >>> # Calculate biomass for a Norway spruce with only DBH >>> Marklund_1988_biomass_Sweden(species='Picea abies', DBH=20) {'stem': 148.8, 'stump': 34.6, 'roots_1mm': 30.0} >>> # Calculate biomass for a Scots pine with DBH and height >>> Marklund_1988_biomass_Sweden( ... species='Pinus sylvestris', DBH=25, height_m=18 ... ) { 'stem': 234.1, 'bark': 30.5, 'living_branches': 45.2, 'dead_branches': 5.1, 'stump': 55.7, 'roots_1mm': 49.8, } """ # Handle Timber object as the first argument if provided if isinstance(species, Timber): timber = species species = None if timber: timber.validate() species = timber.species component_args = { "diameter_cm": timber.diameter_cm, "height_m": timber.height_m, "double_bark_mm": timber.double_bark_mm, "crown_base_height_m": timber.crown_base_height_m, } kwargs.update({k: v for k, v in component_args.items() if v is not None}) if not species: raise ValueError("Species must be specified either directly or through a Timber object.") if species not in species_map: raise ValueError(f"Unknown species: {species}") if component: if component not in species_map[species]: raise ValueError(f"Unknown component for species {species}: {component}") functions = species_map[species][component] for func in functions: try: params = inspect.signature(func).parameters call_kwargs = {k: v for k, v in kwargs.items() if k in params} return func(**call_kwargs) except TypeError: continue raise ValueError("No function matched the provided arguments.") else: # Return a dictionary of all components results = {} for comp, functions in species_map[species].items(): for func in functions: try: params = inspect.signature(func).parameters call_kwargs = {k: v for k, v in kwargs.items() if k in params} results[comp] = func(**call_kwargs) break except TypeError: continue return results