Source code for virtual_ecosystem.models.animal.model_config

"""The `models.animal.constants` module contains a set of dataclasses containing
constants" (fitting relationships taken from the literature) required by the broader
:mod:`~virtual_ecosystem.models.animal` module

"""  # noqa: D205, D415

import operator
from functools import reduce
from typing import Annotated, Literal

from pydantic import (
    Field,
    PlainSerializer,
    PlainValidator,
)

from virtual_ecosystem.core.configuration import (
    FILEPATH_PLACEHOLDER,
    Configuration,
    ModelConfigurationRoot,
)
from virtual_ecosystem.models.animal.animal_traits import (
    DietType,
    MetabolicType,
    TaxaType,
)

BOLTZMANN_CONSTANT: float = 8.617333262145e-5  # Boltzmann constant [eV/K]

TEMPERATURE: float = 37.0  # Toy temperature for setting up metabolism [C].

DENSITY_SCALING_METHODS = Literal["damuth", "madingley"]


[docs] def deserialise_diet_type(diet: str) -> DietType: """Deserialise string diet terms to DietType. For standard ``Enum`` types, pydantic correctly serialises and deserialise a string representation, but DietType is a ``Flag``, which has underlying numeric values. If these are seriliased and deserialised as with an ``Enum``, then users would need to use numeric diet codes in the configuration. This function takes a string such as "CARNIVORE" or "FRUIT|SEEDS" and returns the appropriate DietType. """ return reduce(operator.or_, [DietType[d] for d in diet.split("|")])
[docs] def serialise_diet_type(diet: DietType) -> str: """Serialise DietType Flag value to string. This shim simply exports the ``DietType._name_`` attribute, which is a string representation of the Flag value. """ # The _name_ value _can_ be None, but I don't think will be in this use case. return diet._name_ # type: ignore[return-value]
DietTypeSer = Annotated[ DietType, PlainSerializer(serialise_diet_type), PlainValidator(deserialise_diet_type), ] """Custom data type to serialise and deserialise DietType Flag values."""
[docs] class AnimalConstants(Configuration): """Dataclass to store all constants related to animals. TODO: Remove unused constants. """ density_scaling_method: DENSITY_SCALING_METHODS = "madingley" """The density scaling method to use within a simulation.""" total_heterotroph_biomass_density_kg_m2: float = 0.151 # Madingley global median """Total heterotroph biomass density in the system, used for normalizing density.""" _ELEMENTAL_MASS_NOISE_TOLERANCE = 1e-10 """The value used for clamping negative mass movement created by fp errors."""
[docs] def get_population_density_terms( self, taxa: TaxaType, diet: DietType ) -> tuple[float, ...]: """Return scaling terms for the specified density scaling method. Args: taxa: The TaxaType of the functional group (used for damuth). diet: The DietType of the functional group (used for damuth). Returns: A tuple (exponent, scalar) for the scaling law. """ return ( self.damuths_law_terms[taxa][diet] if self.density_scaling_method == "damuth" else self.madingley_biomass_scaling_terms )
damuths_law_terms: dict[TaxaType, dict[DietTypeSer, tuple[float, float]]] = Field( default_factory=lambda: { TaxaType.MAMMAL: { DietType.HERBIVORE: (-0.75, 4.23), DietType.CARNIVORE: (-0.75, 1.00), DietType.OMNIVORE: (-0.75, 3.00), }, TaxaType.BIRD: { DietType.HERBIVORE: (-0.75, 5.00), DietType.CARNIVORE: (-0.75, 2.00), DietType.OMNIVORE: (-0.75, 3.00), }, TaxaType.INVERTEBRATE: { DietType.HERBIVORE: (-0.75, 5.00), DietType.CARNIVORE: (-0.75, 2.00), DietType.OMNIVORE: (-0.75, 3.00), }, TaxaType.AMPHIBIAN: { DietType.HERBIVORE: (-0.75, 5.00), DietType.CARNIVORE: (-0.75, 2.00), DietType.OMNIVORE: (-0.75, 3.00), }, TaxaType.REPTILE: { DietType.HERBIVORE: (-0.75, 5.00), DietType.CARNIVORE: (-0.75, 2.00), DietType.OMNIVORE: (-0.75, 3.00), }, } ) """Damuth Law terms, structured by taxonomic type and broad diet category.""" madingley_biomass_scaling_terms: tuple[float, float] = (0.6, 300000.0) """Biomass scaling terms from the Madingley model.""" metabolic_scaling_coefficients: tuple[float, float] = ( 0.037, # Es 0.69, # Ea ) r"""Metabolic rate scaling coefficients. These are the coefficients of Madingley style scaling of metabolic rate with mass and temperature, assuming a power-law relationship with mass and an exponential relationship with temperature. The three values are: * :math:`E_s` - energy to mass conversion constant (g/kJ) * :math:`E_a` - aggregate activation energy of metabolic reactions """ metabolic_rate_terms: dict[MetabolicType, dict[str, tuple[float, float]]] = Field( default_factory=lambda: { MetabolicType.ENDOTHERMIC: { "basal": (4.19e10, 0.69), "field": (9.08e11, 0.7), }, MetabolicType.ECTOTHERMIC: { "basal": (4.19e10, 0.69), "field": (1.49e11, 0.88), }, } ) """Parameters from Madingley, mass-based metabolic rates""" energy_density: dict[str, float] = Field( default_factory=lambda: { "meat": 7000.0, "plant": 18200000.0, } ) """Energy densities of different food sources [J/g]""" # TODO: rework these efficiencies to be interaction-specific, not trait based conversion_efficiency: dict[DietTypeSer, float] = Field( default_factory=lambda: { DietType.HERBIVORE: 0.1, # Toy value DietType.CARNIVORE: 0.25, # Toy value DietType.OMNIVORE: 0.175, # Toy value } ) """Conversion efficiencies by broad diet categories.""" mechanical_efficiency: dict[DietTypeSer, float] = Field( default_factory=lambda: { DietType.HERBIVORE: 0.9, # Toy value DietType.CARNIVORE: 0.8, # Toy value DietType.OMNIVORE: 0.85, # Toy value } ) """Mechanical efficiencies by broad diet categories.""" prey_mass_scaling_terms: dict[ MetabolicType, dict[TaxaType, tuple[float, float]] ] = Field( default_factory=lambda: { MetabolicType.ENDOTHERMIC: { TaxaType.MAMMAL: (1.0, 1.0), # Toy values TaxaType.BIRD: (1.0, 1.0), # Toy values }, MetabolicType.ECTOTHERMIC: { TaxaType.INVERTEBRATE: (1.0, 1.0), TaxaType.AMPHIBIAN: (1.0, 1.0), TaxaType.REPTILE: (1.0, 1.0), }, # Toy values } ) """Prey mass scaling terms by metabolic type.""" cnp_proportion_terms: dict[TaxaType, dict[str, float]] = Field( default_factory=lambda: { TaxaType.MAMMAL: {"C": 0.5, "N": 0.3, "P": 0.2}, TaxaType.BIRD: {"C": 0.4, "N": 0.3, "P": 0.3}, TaxaType.INVERTEBRATE: {"C": 0.4, "N": 0.2, "P": 0.4}, TaxaType.AMPHIBIAN: {"C": 0.4, "N": 0.2, "P": 0.4}, TaxaType.REPTILE: {"C": 0.4, "N": 0.2, "P": 0.4}, } ) """Stoichiometric proportions structured by taxon type.""" birth_mass_threshold: float = 1.5 """Mass threshold for reproduction""" flow_to_reproductive_mass_threshold: float = 1.0 """Threshold of trophic flow to reproductive mass.""" dispersal_mass_threshold: float = 0.8 """Mass threshold for dispersal.""" energy_percentile_threshold: float = 0.5 """Energy threshold for initiating migration.""" # Madingley Foraging Parameters tau_f: float = 0.5 # tau_f """Proportion of time for which functional group is active.""" # Trophic parameters alpha_0_herb: float = 1.0e-7 # ha/(day*g) [Madingley] converted to m2/(day*g) """Effective rate per unit mass at which a herbivore searches its environment.""" alpha_0_pred: float = 1.0e-2 # ha/(day*g) [Madingley] converted to m2/(day*g) """Effective rate per unit mass at which a predator searches its environment.""" b_herb: float = 0.7 # ( ),b_herb) """Herbivore exponent of the power-law function relating the handling time of autotroph matter to herbivore mass.""" b_pred: float = 0.05 # Toy Values """Carnivore exponent of the power-law relationship between the handling time of prey and the ratio of prey to predator body mass.""" M_herb_ref: float = 1.0 # M_herb_ref [Madingley] g """Reference mass for herbivore handling time.""" h_herb_0: float = 0.7 # h_pred_0 [Madingley] """Time that it would take a herbivore of body mass equal to the reference mass, to handle one gram of autotroph biomass""" M_pred_ref: float = 1.0 # toy value TODO: find real value """The reference value for predator mass.""" sigma_opt_pred_prey: float = 0.7 # sigma_opt_pred-prey [Madingley] """Standard deviation of the normal distribution describing realized attack rates around the optimal predator-prey body mass ratio.""" theta_opt_min_f: float = 0.01 # theta_opt_min_f [Madingley] """The minimum optimal prey-predator body mass ratio.""" theta_opt_f: float = 0.1 # theta_opt_f [Madingley] """The mean optimal prey-predator body mass ratio, from which actual cohort optima are drawn.""" sigma_opt_f: float = 0.02 # sigma_opt_f [Madingley] """The standard deviation of optimal predator-prey mass ratios among cohorts.""" N_sigma_opt_pred_prey: float = 3.0 # N_sigma_opt_pred-prey [Madingley] """The standard deviations of the realized attack rates around the optimal predator-prey body mass ratio for which to calculate predator specific cumulative prey densities.""" h_pred_0: float = 0.5 # h_pred_0 [Madingley] """Time that it would take a predator of body mass equal to the reference mass, to handle a prey individual of body mass equal to one gram.""" # Activity parameters m_tol: float = 1.6 # m_tol_terrestrial [Madingley] """Slope of the relationship between monthly temperature variability and the upper critical temperature limit relative to annual mean temperature, for terrestrial ectothermic functional groups.""" c_tol: float = 6.61 # c_tol_terrestrial [Madingley] (degrees C) """Intercept of the relationship between monthly temperature variability and the upper critical temperature limit relative to annual mean temperature, for terrestrial ectothermic functional groups.""" m_tsm: float = 1.53 # m_tsm [Madingley] """Slope of the relationship between monthly temperature variability and the optimal temperature relative to annual mean temperature, for terrestrial ectothermic functional groups.""" c_tsm: float = 1.51 # c_tsm [Madingley] (degrees C) """Intercept of the relationship between monthly temperature variability and the optimal temperature relative to annual mean temperature, for terrestrial ectothermic functional groups.""" # Placeholder climate inputs for activity window computation. # TODO: replace with dynamic per-cell values from the abiotic model once the # required variables ( annual mean temperature, annual # temperature SD) are exposed via the data object. placeholder_annual_mean_temp: float = 20.0 """Annual mean temperature used as a toy stand-in for $T_{Annual}^C$ [°C]. Replace once abiotic model exposes this.""" placeholder_annual_temp_sd: float = 5.0 """Standard deviation of monthly temperatures across the climatological year, used as a toy stand-in for $\\sigma_{T_{Annual}^C}$ [°C]. Replace once abiotic model exposes this.""" # Madingley dispersal parameters M_disp_ref: float = 1.0 # M_disp_ref [Madingley] [g] """The reference mass for calculating diffusive juvenile dispersal in grams.""" V_disp: float = 0.0278 # V_disp [Madingley] [km/month] """Diffusive dispersal speed on an individual of body-mass equal to M_disp_ref in km/month.""" o_disp: float = 0.48 # o_disp [Madingley] [unitless] """Power law exponent for the scaling relationship between body-mass and dispersal distance as mediated by a reference mass, M_disp_ref.""" beta_responsive_bodymass: float = 0.8 # Beta_responsive_bodymass [unitless] """Ratio of current body-mass to adult body-mass at which starvation-response dispersal is attempted.""" # Madingley reproductive parameters semelparity_mass_loss: float = 0.5 # chi [Madingley] [unitless] """The proportion of non-reproductive mass lost in semelparous reproduction.""" # Madingley mortality parameters u_bg: float = 10.0**-3.0 # u_bg [Madingley] [day^-1] """The constant background mortality faced by all animal.""" lambda_se: float = 3.0 * 10.0**-3.0 # lambda_se [Madingley] [day^-1] """The instantaneous rate of senescence mortality at the point of maturity.""" lambda_max: float = 1.0 # lambda_max [Madingley] [day^-1] """The maximum possible instantaneous fractional starvation mortality rate.""" J_st: float = 0.6 # J_st [Madingley] [unitless] """Determines the inflection point of the logistic function describing ratio of the realised mortality rate to the maximum rate.""" zeta_st: float = 0.05 # zeta_st [Madingley] [unitless] """The scaling parameter of the logistic function describing the ratio of the realised starvation mortality rate to the maximum starvation mortality rate.""" metamorph_mortality: float = 0.1 # toy [unitless] """The mortality proportion inflicted on a larval cohort undergoing metamorphosis. """ carbon_excreta_proportion: float = 0.9 # toy [unitless] """The proportion of metabolic wastes that are carbonaceous. This is a temporary fix to facilitate building the machinery and will be updated with stoichiometry.""" nitrogen_excreta_proportion: float = 0.1 # toy [unitless] """The proportion of metabolic wastes that are nitrogenous. This is a temporary fix to facilitate building the machinery and will be updated with stoichiometry.""" decay_rate_excrement: float = 0.25 """Rate at which excrement decays due to microbial activity [day^-1]. In reality this should not be constant, but as a simplifying assumption it is. """ scavenging_rate_excrement: float = 0.25 """Rate at which excrement is scavenged by animals [day^-1]. Used along with :attr:`AnimalConstants.decay_rate_excrement` to calculate the split of excrement between scavengable excrement and flow into the soil. In reality this should be a constant, but as a simplifying assumption it is. """ decay_rate_carcasses: float = 0.0625 """Rate at which carcasses decay due to microbial activity [day^-1]. In reality this should not be constant, but as a simplifying assumption it is. """ scavenging_rate_carcasses: float = 0.25 """Rate at which carcasses are scavenged by animals [day^-1]. Used along with :attr:`AnimalConstants.decay_rate_carcasses` to calculate the split of carcass biomass between scavengable carcass biomass and flow into the soil. In reality this should be a constant, but as a simplifying assumption it is. """ migration_mortality: float = 0.1 # toy """Proportion of mortality that occurs on return from a migration [unitless].""" aquatic_mortality: float = 0.1 # toy """Proportion of mortality that occurs on return from aquatic status [unitless].""" aquatic_residence_time: float = 60.0 # toy """Amount of time a new cohort spends living in aquatic environment [days].""" migration_residence_time: float = 60.0 # toy """Amount of time a migrated cohort spends away [days].""" seasonal_migration_probability: float = 0.083 # approx 1 seasonal migration per yr. """The probability a seasonal migration event occurs per time step (month).""" territory_size_terms: dict[MetabolicType, dict[TaxaType, tuple[float, float]]] = ( Field( default_factory=lambda: { MetabolicType.ENDOTHERMIC: { TaxaType.MAMMAL: ( -6.09, 1.13, ), # Ofstad et al. (2016), ungulates, closed habitat TaxaType.BIRD: ( -6.09, 1.13, ), # Ofstad et al. (2016), ungulates, closed habitat }, MetabolicType.ECTOTHERMIC: { TaxaType.INVERTEBRATE: ( -6.09, 1.13, ), # Ofstad et al. (2016), ungulates, closed habitat TaxaType.AMPHIBIAN: ( -6.09, 1.13, ), # Ofstad et al. (2016), ungulates, closed habitat TaxaType.REPTILE: ( -6.09, 1.13, ), # Ofstad et al. (2016), ungulates, closed habitat }, } ) ) """Territory size scaling terms (intercept, exponent) by metabolic and taxa type. Tuple entries are (intercept, exponent) for the log-log relationship: ln(territory_ha) = intercept + exponent * ln(BM_g) All entries currently use the Ofstad et al. (2016) closed-habitat ungulate parameters as a placeholder. The data team should replace these with taxon-specific fits as they become available. Reference: Ofstad EG et al. 2016 Proc. R. Soc. B 283: 20161234. https://doi.org/10.1098/rspb.2016.1234 """
[docs] class AnimalExportConfig(Configuration): """Configuration for animal cohort data export. This lightweight configuration is intended to be embedded inside :class:`AnimalConfiguration` and mirrors the pattern used for the plants exporter configuration. Attributes: enabled: Whether animal cohort export is active. cohort_attributes: Optional subset of cohort attributes to export. If empty, all available attributes are written. float_format: Float format string used when writing numeric data. """ enabled: bool = False cohort_attributes: tuple[str, ...] = () float_format: str = "%0.5f"
[docs] class ResourcePoolExportConfig(Configuration): """Configuration for resource pool data export. Attributes: enabled: Whether resource pool export is active. float_format: Float format string used when writing numeric data. """ enabled: bool = False float_format: str = "%0.5f"
[docs] class AnimalConfiguration(ModelConfigurationRoot): """Root configuration class for the animal model.""" functional_group_definitions_path: FILEPATH_PLACEHOLDER "A file path to a data file of animal functional group definitions" constants: AnimalConstants = AnimalConstants() """The constants class for the animal model.""" cohort_data_export: AnimalExportConfig = Field( default_factory=AnimalExportConfig, description="Export settings for animal cohort CSV output.", ) resource_pool_export: ResourcePoolExportConfig = Field( default_factory=ResourcePoolExportConfig, description="Export settings for resource pool CSV output.", )