From 8331a6d9339cbcb816c512236cd888d238176222 Mon Sep 17 00:00:00 2001 From: Jacob Cook Date: Tue, 1 Oct 2024 15:42:52 +0100 Subject: [PATCH 01/14] Added variables to link animal herbivory waste products to the litter model --- tests/models/animals/test_animal_model.py | 4 +++ virtual_ecosystem/data_variables.toml | 28 +++++++++++++++++++ .../models/animal/animal_model.py | 15 ++++++++-- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/tests/models/animals/test_animal_model.py b/tests/models/animals/test_animal_model.py index 9ea7ffe6f..096a7ee47 100644 --- a/tests/models/animals/test_animal_model.py +++ b/tests/models/animals/test_animal_model.py @@ -180,6 +180,10 @@ def test_animal_model_initialization( (INFO, "Adding data array for 'litter_consumption_woody'"), (INFO, "Adding data array for 'litter_consumption_below_metabolic'"), (INFO, "Adding data array for 'litter_consumption_below_structural'"), + (INFO, "Adding data array for 'herbivory_waste_leaf_carbon'"), + (INFO, "Adding data array for 'herbivory_waste_leaf_nitrogen'"), + (INFO, "Adding data array for 'herbivory_waste_leaf_phosphorus'"), + (INFO, "Adding data array for 'herbivory_waste_leaf_lignin'"), ), id="success", ), diff --git a/virtual_ecosystem/data_variables.toml b/virtual_ecosystem/data_variables.toml index 65c0cd9ad..d5ab69b32 100644 --- a/virtual_ecosystem/data_variables.toml +++ b/virtual_ecosystem/data_variables.toml @@ -775,6 +775,34 @@ name = "decomposed_carcasses_phosphorus" unit = "kg P m^-3 day^-1" variable_type = "float" +[[variable]] +axis = ["spatial"] +description = "Mass of leaves added to the litter due to herbivore mechanical inefficiency" +name = "herbivory_waste_leaf_carbon" +unit = "kg C m^-2" +variable_type = "float" + +[[variable]] +axis = ["spatial"] +description = "Carbon nitrogen ratio of leaves added to litter due to herbivore mechanical inefficiency" +name = "herbivory_waste_leaf_nitrogen" +unit = "-" +variable_type = "float" + +[[variable]] +axis = ["spatial"] +description = "Carbon phosphorus ratio of leaves added to litter due to herbivore mechanical inefficiency" +name = "herbivory_waste_leaf_phosphorus" +unit = "-" +variable_type = "float" + +[[variable]] +axis = ["spatial"] +description = "Lignin proportion of leaves added to litter due to herbivore mechanical inefficiency" +name = "herbivory_waste_leaf_lignin" +unit = "-" +variable_type = "float" + [[variable]] axis = ["spatial"] description = "Amount of above-ground metabolic litter that has been consumed by animals" diff --git a/virtual_ecosystem/models/animal/animal_model.py b/virtual_ecosystem/models/animal/animal_model.py index 3f86eb9cd..8461f98ff 100644 --- a/virtual_ecosystem/models/animal/animal_model.py +++ b/virtual_ecosystem/models/animal/animal_model.py @@ -67,6 +67,10 @@ class AnimalModel( "decomposed_carcasses_carbon", "decomposed_carcasses_nitrogen", "decomposed_carcasses_phosphorus", + "herbivory_waste_leaf_carbon", + "herbivory_waste_leaf_nitrogen", + "herbivory_waste_leaf_phosphorus", + "herbivory_waste_leaf_lignin", "litter_consumption_above_metabolic", "litter_consumption_above_structural", "litter_consumption_woody", @@ -80,6 +84,10 @@ class AnimalModel( "decomposed_carcasses_carbon", "decomposed_carcasses_nitrogen", "decomposed_carcasses_phosphorus", + "herbivory_waste_leaf_carbon", + "herbivory_waste_leaf_nitrogen", + "herbivory_waste_leaf_phosphorus", + "herbivory_waste_leaf_lignin", "total_animal_respiration", "litter_consumption_above_metabolic", "litter_consumption_above_structural", @@ -303,11 +311,12 @@ def update(self, time_index: int, **kwargs: Any) -> None: # soil and litter models can be extracted additions_to_soil = self.calculate_soil_additions() litter_consumption = self.calculate_total_litter_consumption(litter_pools) - # TODO - Actually do something with this - _ = self.calculate_litter_additions_from_herbivory() + litter_additions = self.calculate_litter_additions_from_herbivory() # Update the data object with the changes to soil and litter pools - self.data.add_from_dict(additions_to_soil | litter_consumption) + self.data.add_from_dict( + additions_to_soil | litter_consumption | litter_additions + ) # TODO - TEST THIS! # Update population densities self.update_population_densities() From 99e0f454e991a80516a336a8cf1af0f8e866c164 Mon Sep 17 00:00:00 2001 From: Jacob Cook Date: Wed, 2 Oct 2024 09:46:27 +0100 Subject: [PATCH 02/14] Collected the functions to calculate litter input splits into a single class --- tests/models/litter/conftest.py | 53 ++-- tests/models/litter/test_input_partition.py | 44 +--- .../models/litter/input_partition.py | 243 ++++++++---------- .../models/litter/litter_model.py | 48 ++-- 4 files changed, 159 insertions(+), 229 deletions(-) diff --git a/tests/models/litter/conftest.py b/tests/models/litter/conftest.py index c0bf89959..cc8b89a3d 100644 --- a/tests/models/litter/conftest.py +++ b/tests/models/litter/conftest.py @@ -126,53 +126,22 @@ def decay_rates(dummy_litter_data, fixture_core_components, post_consumption_poo @pytest.fixture -def metabolic_splits(dummy_litter_data): +def metabolic_splits(input_partition): """Metabolic splits for the various plant inputs.""" - from virtual_ecosystem.models.litter.input_partition import ( - calculate_metabolic_proportions_of_input, - ) - - metabolic_splits = calculate_metabolic_proportions_of_input( - leaf_turnover_lignin_proportion=dummy_litter_data[ - "leaf_turnover_lignin" - ].to_numpy(), - reproduct_turnover_lignin_proportion=dummy_litter_data[ - "plant_reproductive_tissue_turnover_lignin" - ].to_numpy(), - root_turnover_lignin_proportion=dummy_litter_data[ - "root_turnover_lignin" - ].to_numpy(), - leaf_turnover_c_n_ratio=dummy_litter_data["leaf_turnover_c_n_ratio"].to_numpy(), - reproduct_turnover_c_n_ratio=dummy_litter_data[ - "plant_reproductive_tissue_turnover_c_n_ratio" - ].to_numpy(), - root_turnover_c_n_ratio=dummy_litter_data["root_turnover_c_n_ratio"].to_numpy(), - leaf_turnover_c_p_ratio=dummy_litter_data["leaf_turnover_c_p_ratio"].to_numpy(), - reproduct_turnover_c_p_ratio=dummy_litter_data[ - "plant_reproductive_tissue_turnover_c_p_ratio" - ].to_numpy(), - root_turnover_c_p_ratio=dummy_litter_data["root_turnover_c_p_ratio"].to_numpy(), - constants=LitterConsts, + metabolic_splits = input_partition.calculate_metabolic_proportions_of_input( + constants=LitterConsts ) return metabolic_splits @pytest.fixture -def plant_inputs(dummy_litter_data, metabolic_splits): +def plant_inputs(input_partition, metabolic_splits): """Plant inputs to each of the litter pools.""" - from virtual_ecosystem.models.litter.input_partition import ( - partion_plant_inputs_between_pools, - ) - - plant_inputs = partion_plant_inputs_between_pools( - deadwood_production=dummy_litter_data["deadwood_production"], - leaf_turnover=dummy_litter_data["leaf_turnover"], - reproduct_turnover=dummy_litter_data["plant_reproductive_tissue_turnover"], - root_turnover=dummy_litter_data["root_turnover"], - metabolic_splits=metabolic_splits, + plant_inputs = input_partition.partion_plant_inputs_between_pools( + metabolic_splits=metabolic_splits ) return plant_inputs @@ -188,6 +157,16 @@ def litter_chemistry(dummy_litter_data): return litter_chemistry +@pytest.fixture +def input_partition(dummy_litter_data): + """InputPartition object to be use throughout testing.""" + from virtual_ecosystem.models.litter.input_partition import InputPartition + + input_partition = InputPartition(dummy_litter_data) + + return input_partition + + @pytest.fixture def input_lignin(dummy_litter_data, plant_inputs, litter_chemistry): """Lignin proportion of the relevant input flows.""" diff --git a/tests/models/litter/test_input_partition.py b/tests/models/litter/test_input_partition.py index 8abced0d6..3af47e1c8 100644 --- a/tests/models/litter/test_input_partition.py +++ b/tests/models/litter/test_input_partition.py @@ -9,12 +9,17 @@ from virtual_ecosystem.models.litter.constants import LitterConsts -def test_calculate_metabolic_proportions_of_input(dummy_litter_data): - """Test that function to calculate metabolic input proportions works as expected.""" +def test_input_partition_initialisation(dummy_litter_data): + """Test that the InputPartition class initialises as expected.""" + from virtual_ecosystem.models.litter.input_partition import InputPartition + + input_partition = InputPartition(dummy_litter_data) + + assert input_partition.data == dummy_litter_data - from virtual_ecosystem.models.litter.input_partition import ( - calculate_metabolic_proportions_of_input, - ) + +def test_calculate_metabolic_proportions_of_input(input_partition): + """Test that function to calculate metabolic input proportions works as expected.""" expected_proportions = { "leaves": [0.812403025, 0.640197595, 0.424077745, 0.0089426731], @@ -22,22 +27,7 @@ def test_calculate_metabolic_proportions_of_input(dummy_litter_data): "roots": [0.588394858, 0.379571377, 0.5024461477, 0.410125012], } - actual_proportions = calculate_metabolic_proportions_of_input( - leaf_turnover_lignin_proportion=dummy_litter_data["leaf_turnover_lignin"], - reproduct_turnover_lignin_proportion=dummy_litter_data[ - "plant_reproductive_tissue_turnover_lignin" - ], - root_turnover_lignin_proportion=dummy_litter_data["root_turnover_lignin"], - leaf_turnover_c_n_ratio=dummy_litter_data["leaf_turnover_c_n_ratio"], - reproduct_turnover_c_n_ratio=dummy_litter_data[ - "plant_reproductive_tissue_turnover_c_n_ratio" - ], - root_turnover_c_n_ratio=dummy_litter_data["root_turnover_c_n_ratio"], - leaf_turnover_c_p_ratio=dummy_litter_data["leaf_turnover_c_p_ratio"], - reproduct_turnover_c_p_ratio=dummy_litter_data[ - "plant_reproductive_tissue_turnover_c_p_ratio" - ], - root_turnover_c_p_ratio=dummy_litter_data["root_turnover_c_p_ratio"], + actual_proportions = input_partition.calculate_metabolic_proportions_of_input( constants=LitterConsts, ) @@ -47,24 +37,16 @@ def test_calculate_metabolic_proportions_of_input(dummy_litter_data): assert np.allclose(actual_proportions[key], expected_proportions[key]) -def test_partion_plant_inputs_between_pools(dummy_litter_data, metabolic_splits): +def test_partion_plant_inputs_between_pools(input_partition, metabolic_splits): """Check function to partition inputs into litter pools works as expected.""" - from virtual_ecosystem.models.litter.input_partition import ( - partion_plant_inputs_between_pools, - ) - expected_woody = [0.075, 0.099, 0.063, 0.033] expected_above_meta = [0.02447376, 0.00644323, 0.01102713, 0.00340132] expected_above_struct = [0.00552624, 0.00135677, 0.01252287, 0.02884868] expected_below_meta = [0.01588666, 0.00797100, 0.00015073, 0.01021211] expected_below_struct = [0.01111334, 0.013029, 0.00014927, 0.01468789] - actual_splits = partion_plant_inputs_between_pools( - deadwood_production=dummy_litter_data["deadwood_production"], - leaf_turnover=dummy_litter_data["leaf_turnover"], - reproduct_turnover=dummy_litter_data["plant_reproductive_tissue_turnover"], - root_turnover=dummy_litter_data["root_turnover"], + actual_splits = input_partition.partion_plant_inputs_between_pools( metabolic_splits=metabolic_splits, ) diff --git a/virtual_ecosystem/models/litter/input_partition.py b/virtual_ecosystem/models/litter/input_partition.py index 612125db9..7a2cfa2ba 100644 --- a/virtual_ecosystem/models/litter/input_partition.py +++ b/virtual_ecosystem/models/litter/input_partition.py @@ -1,147 +1,132 @@ -"""The ``models.litter.input_partition`` module handles the partitioning of dead plant -matter into the various pools of the litter model. +"""The ``models.litter.input_partition`` module handles the partitioning of plant +matter into the various pools of the litter model. This plant matter comes from both +natural tissue death as well as from mechanical inefficiencies in herbivory. """ # noqa: D205 import numpy as np from numpy.typing import NDArray +from virtual_ecosystem.core.data import Data from virtual_ecosystem.core.logger import LOGGER from virtual_ecosystem.models.litter.constants import LitterConsts -# TODO - It makes sense for the animal pools to be handled here, but need to think about -# how the partition works with the plant partition, Animals do not contain lignin, so if -# I used the standard function on animal carcasses and excrement the maximum amount -# (85%) will end up in the metabolic pool, which I think is basically fine, with bones -# not being explicitly modelled I think this is fine. This will have to change once -# bones are included. - - -def calculate_metabolic_proportions_of_input( - leaf_turnover_lignin_proportion: NDArray[np.float32], - reproduct_turnover_lignin_proportion: NDArray[np.float32], - root_turnover_lignin_proportion: NDArray[np.float32], - leaf_turnover_c_n_ratio: NDArray[np.float32], - reproduct_turnover_c_n_ratio: NDArray[np.float32], - root_turnover_c_n_ratio: NDArray[np.float32], - leaf_turnover_c_p_ratio: NDArray[np.float32], - reproduct_turnover_c_p_ratio: NDArray[np.float32], - root_turnover_c_p_ratio: NDArray[np.float32], - constants: LitterConsts, -) -> dict[str, NDArray[np.float32]]: - """Calculate the proportion of each input type that flows to the metabolic pool. - - This function is used for roots, leaves and reproductive tissue, but not deadwood - because everything goes into a single woody litter pool. It is not used for animal - inputs either as they all flow into just the metabolic pool. - Args: - leaf_turnover_lignin_proportion: Proportion of carbon in turned over leaves that - is lignin [kg lignin kg C^-1] - reproduct_turnover_lignin_proportion: Proportion of carbon in turned over - reproductive tissues that is lignin [kg lignin kg C^-1] - root_turnover_lignin_proportion: Proportion of carbon in turned over roots that - is lignin [kg lignin kg C^-1] - leaf_turnover_c_n_ratio: Carbon:nitrogen ratio of turned over leaves [unitless] - reproduct_turnover_c_n_ratio: Carbon:nitrogen ratio of turned over reproductive - tissues [unitless] - root_turnover_c_n_ratio: Carbon:nitrogen ratio of turned over roots [unitless] - leaf_turnover_c_p_ratio: Carbon:phosphorus ratio of turned over leaves - [unitless] - reproduct_turnover_c_p_ratio: Carbon:phosphorus ratio of turned over - reproductive tissues [unitless] - root_turnover_c_p_ratio: Carbon:phosphorus ratio of turned over roots [unitless] - constants: Set of constants for the litter model. +class InputPartition: + """This class handles the partitioning of plant matter between litter pools. - Returns: - A dictionary containing the proportion of the input that goes to the relevant - metabolic pool. This is for three input types: leaves, reproductive tissues and - roots [unitless] + This class contains methods to calculate the partition of input plant matter between + the different litter pools based on the contents of the `data` object. """ - # Calculate split of each input biomass type - leaves_metabolic_split = split_pool_into_metabolic_and_structural_litter( - lignin_proportion=leaf_turnover_lignin_proportion, - carbon_nitrogen_ratio=leaf_turnover_c_n_ratio, - carbon_phosphorus_ratio=leaf_turnover_c_p_ratio, - max_metabolic_fraction=constants.max_metabolic_fraction_of_input, - split_sensitivity_nitrogen=constants.metabolic_split_nitrogen_sensitivity, - split_sensitivity_phosphorus=constants.metabolic_split_phosphorus_sensitivity, - ) - repoduct_metabolic_split = split_pool_into_metabolic_and_structural_litter( - lignin_proportion=reproduct_turnover_lignin_proportion, - carbon_nitrogen_ratio=reproduct_turnover_c_n_ratio, - carbon_phosphorus_ratio=reproduct_turnover_c_p_ratio, - max_metabolic_fraction=constants.max_metabolic_fraction_of_input, - split_sensitivity_nitrogen=constants.metabolic_split_nitrogen_sensitivity, - split_sensitivity_phosphorus=constants.metabolic_split_phosphorus_sensitivity, - ) - roots_metabolic_split = split_pool_into_metabolic_and_structural_litter( - lignin_proportion=root_turnover_lignin_proportion, - carbon_nitrogen_ratio=root_turnover_c_n_ratio, - carbon_phosphorus_ratio=root_turnover_c_p_ratio, - max_metabolic_fraction=constants.max_metabolic_fraction_of_input, - split_sensitivity_nitrogen=constants.metabolic_split_nitrogen_sensitivity, - split_sensitivity_phosphorus=constants.metabolic_split_phosphorus_sensitivity, - ) - - return { - "leaves": leaves_metabolic_split, - "reproductive": repoduct_metabolic_split, - "roots": roots_metabolic_split, - } - - -def partion_plant_inputs_between_pools( - deadwood_production: NDArray[np.float32], - leaf_turnover: NDArray[np.float32], - reproduct_turnover: NDArray[np.float32], - root_turnover: NDArray[np.float32], - metabolic_splits: dict[str, NDArray[np.float32]], -): - """Function to partition input biomass between the various litter pools. - - All deadwood is added to the woody litter pool. Reproductive biomass (fruits and - flowers) and leaves are split between the above ground metabolic and structural - pools based on lignin concentration and carbon nitrogen ratios. Root biomass is - split between the below ground metabolic and structural pools based on lignin - concentration and carbon nitrogen ratios. + def __init__(self, data: Data): + self.data = data + + def calculate_metabolic_proportions_of_input( + self, constants: LitterConsts + ) -> dict[str, NDArray[np.float32]]: + """Calculate the proportion of each input type that flows to the metabolic pool. + + This function is used for roots, leaves and reproductive tissue, but not + deadwood because everything goes into a single woody litter pool. It is not used + for animal inputs either as they all flow into just the metabolic pool. + + Args: + constants: Set of constants for the litter model. + + Returns: + A dictionary containing the proportion of the input that goes to the + relevant metabolic pool. This is for three input types: leaves, reproductive + tissues and roots [unitless] + """ + + # Calculate split of each input biomass type + leaves_metabolic_split = split_pool_into_metabolic_and_structural_litter( + lignin_proportion=self.data["leaf_turnover_lignin"].to_numpy(), + carbon_nitrogen_ratio=self.data["leaf_turnover_c_n_ratio"].to_numpy(), + carbon_phosphorus_ratio=self.data["leaf_turnover_c_p_ratio"].to_numpy(), + max_metabolic_fraction=constants.max_metabolic_fraction_of_input, + split_sensitivity_nitrogen=constants.metabolic_split_nitrogen_sensitivity, + split_sensitivity_phosphorus=constants.metabolic_split_phosphorus_sensitivity, + ) - Args: - deadwood_production: Amount of dead wood produced [kg C m^-2] - leaf_turnover: Amount of leaf turnover [kg C m^-2] - reproduct_turnover: Turnover of plant reproductive tissues (i.e. fruits and - flowers) [kg C m^-2] - root_turnover: Turnover of roots (coarse and fine) turnover [kg C m^-2] - metabolic_splits: Dictionary containing the proportion of each input that goes - to the relevant metabolic pool. This is for three input types: leaves, - reproductive tissues and roots [unitless] + repoduct_metabolic_split = split_pool_into_metabolic_and_structural_litter( + lignin_proportion=self.data[ + "plant_reproductive_tissue_turnover_lignin" + ].to_numpy(), + carbon_nitrogen_ratio=self.data[ + "plant_reproductive_tissue_turnover_c_n_ratio" + ].to_numpy(), + carbon_phosphorus_ratio=self.data[ + "plant_reproductive_tissue_turnover_c_p_ratio" + ].to_numpy(), + max_metabolic_fraction=constants.max_metabolic_fraction_of_input, + split_sensitivity_nitrogen=constants.metabolic_split_nitrogen_sensitivity, + split_sensitivity_phosphorus=constants.metabolic_split_phosphorus_sensitivity, + ) - Returns: - A dictionary containing the biomass flow into each of the five litter pools - (woody, above ground metabolic, above ground structural, below ground metabolic - and below ground structural) - """ + roots_metabolic_split = split_pool_into_metabolic_and_structural_litter( + lignin_proportion=self.data["root_turnover_lignin"].to_numpy(), + carbon_nitrogen_ratio=self.data["root_turnover_c_n_ratio"].to_numpy(), + carbon_phosphorus_ratio=self.data["root_turnover_c_p_ratio"].to_numpy(), + max_metabolic_fraction=constants.max_metabolic_fraction_of_input, + split_sensitivity_nitrogen=constants.metabolic_split_nitrogen_sensitivity, + split_sensitivity_phosphorus=constants.metabolic_split_phosphorus_sensitivity, + ) - # Calculate input to each of the five litter pools - woody_input = deadwood_production - above_ground_metabolic_input = ( - metabolic_splits["leaves"] * leaf_turnover - + metabolic_splits["reproductive"] * reproduct_turnover - ) - above_ground_strutural_input = ( - (1 - metabolic_splits["leaves"]) * leaf_turnover - + (1 - metabolic_splits["reproductive"]) * reproduct_turnover - ) # fmt: off - below_ground_metabolic_input = metabolic_splits["roots"] * root_turnover - below_ground_structural_input = (1 - metabolic_splits["roots"]) * root_turnover - - return { - "woody": woody_input, - "above_ground_metabolic": above_ground_metabolic_input, - "above_ground_structural": above_ground_strutural_input, - "below_ground_metabolic": below_ground_metabolic_input, - "below_ground_structural": below_ground_structural_input, - } + return { + "leaves": leaves_metabolic_split, + "reproductive": repoduct_metabolic_split, + "roots": roots_metabolic_split, + } + + def partion_plant_inputs_between_pools( + self, metabolic_splits: dict[str, NDArray[np.float32]] + ): + """Function to partition input biomass between the various litter pools. + + All deadwood is added to the woody litter pool. Reproductive biomass (fruits and + flowers) and leaves are split between the above ground metabolic and structural + pools based on lignin concentration and carbon nitrogen ratios. Root biomass is + split between the below ground metabolic and structural pools based on lignin + concentration and carbon nitrogen ratios. + + Args: + metabolic_splits: Dictionary containing the proportion of each input that + goes to the relevant metabolic pool. This is for three input types: + leaves, reproductive tissues and roots [unitless] + + Returns: + A dictionary containing the biomass flow into each of the five litter pools + (woody, above ground metabolic, above ground structural, below ground + metabolic and below ground structural) + """ + + # Calculate input to each of the five litter pools + woody_input = self.data["deadwood_production"] + above_ground_metabolic_input = ( + metabolic_splits["leaves"] * self.data["leaf_turnover"] + + metabolic_splits["reproductive"] + * self.data["plant_reproductive_tissue_turnover"] + ) + above_ground_strutural_input = (1 - metabolic_splits["leaves"]) * self.data[ + "leaf_turnover" + ] + (1 - metabolic_splits["reproductive"]) * self.data[ + "plant_reproductive_tissue_turnover" + ] + below_ground_metabolic_input = ( + metabolic_splits["roots"] * self.data["root_turnover"] + ) + below_ground_structural_input = (1 - metabolic_splits["roots"]) * self.data[ + "root_turnover" + ] + + return { + "woody": woody_input, + "above_ground_metabolic": above_ground_metabolic_input, + "above_ground_structural": above_ground_strutural_input, + "below_ground_metabolic": below_ground_metabolic_input, + "below_ground_structural": below_ground_structural_input, + } def split_pool_into_metabolic_and_structural_litter( diff --git a/virtual_ecosystem/models/litter/litter_model.py b/virtual_ecosystem/models/litter/litter_model.py index e26297c62..d2a90f7f2 100644 --- a/virtual_ecosystem/models/litter/litter_model.py +++ b/virtual_ecosystem/models/litter/litter_model.py @@ -49,10 +49,7 @@ class instance. If errors crop here when converting the information from the con ) from virtual_ecosystem.models.litter.chemistry import LitterChemistry from virtual_ecosystem.models.litter.constants import LitterConsts -from virtual_ecosystem.models.litter.input_partition import ( - calculate_metabolic_proportions_of_input, - partion_plant_inputs_between_pools, -) +from virtual_ecosystem.models.litter.input_partition import InputPartition class LitterModel( @@ -111,6 +108,10 @@ class LitterModel( "leaf_turnover_c_n_ratio", "plant_reproductive_tissue_turnover_c_n_ratio", "root_turnover_c_n_ratio", + "herbivory_waste_leaf_carbon", + "herbivory_waste_leaf_nitrogen", + "herbivory_waste_leaf_phosphorus", + "herbivory_waste_leaf_lignin", "litter_consumption_above_metabolic", "litter_consumption_above_structural", "litter_consumption_woody", @@ -234,6 +235,9 @@ def __init__( self.litter_chemistry = LitterChemistry(data, constants=model_constants) """Litter chemistry object for tracking of litter pool chemistries.""" + self.input_partition = InputPartition(data) + """Input partition object for tracking split between litter pools.""" + self.model_constants = model_constants """Set of constants for the litter model.""" @@ -321,37 +325,16 @@ def update(self, time_index: int, **kwargs: Any) -> None: constants=self.model_constants, ) + # TODO - This will need to take new input # Find the plant inputs to each of the litter pools - metabolic_splits = calculate_metabolic_proportions_of_input( - leaf_turnover_lignin_proportion=self.data[ - "leaf_turnover_lignin" - ].to_numpy(), - reproduct_turnover_lignin_proportion=self.data[ - "plant_reproductive_tissue_turnover_lignin" - ].to_numpy(), - root_turnover_lignin_proportion=self.data[ - "root_turnover_lignin" - ].to_numpy(), - leaf_turnover_c_n_ratio=self.data["leaf_turnover_c_n_ratio"].to_numpy(), - reproduct_turnover_c_n_ratio=self.data[ - "plant_reproductive_tissue_turnover_c_n_ratio" - ].to_numpy(), - root_turnover_c_n_ratio=self.data["root_turnover_c_n_ratio"].to_numpy(), - leaf_turnover_c_p_ratio=self.data["leaf_turnover_c_p_ratio"].to_numpy(), - reproduct_turnover_c_p_ratio=self.data[ - "plant_reproductive_tissue_turnover_c_p_ratio" - ].to_numpy(), - root_turnover_c_p_ratio=self.data["root_turnover_c_p_ratio"].to_numpy(), - constants=self.model_constants, + metabolic_splits = ( + self.input_partition.calculate_metabolic_proportions_of_input( + constants=self.model_constants, + ) ) - plant_inputs = partion_plant_inputs_between_pools( - deadwood_production=self.data["deadwood_production"].to_numpy(), - leaf_turnover=self.data["leaf_turnover"].to_numpy(), - reproduct_turnover=self.data[ - "plant_reproductive_tissue_turnover" - ].to_numpy(), - root_turnover=self.data["root_turnover"].to_numpy(), + # TODO - This will need to take new input + plant_inputs = self.input_partition.partion_plant_inputs_between_pools( metabolic_splits=metabolic_splits, ) @@ -365,6 +348,7 @@ def update(self, time_index: int, **kwargs: Any) -> None: ).magnitude, ) + # TODO - THIS USES THE DATA OBJECT DIRECTLY SO HAS TO BE REVISED # Calculate all the litter chemistry changes updated_chemistries = self.litter_chemistry.calculate_new_pool_chemistries( plant_inputs=plant_inputs, From 857be5dbb15131e27d062c63dbd050fb68d704db Mon Sep 17 00:00:00 2001 From: Jacob Cook Date: Wed, 2 Oct 2024 10:59:59 +0100 Subject: [PATCH 03/14] Added a function that combines herbivory and plant derived matter before it gets divided between litter pools --- tests/models/litter/conftest.py | 4 + tests/models/litter/test_input_partition.py | 30 ++++++++ .../models/litter/input_partition.py | 75 +++++++++++++++++++ 3 files changed, 109 insertions(+) diff --git a/tests/models/litter/conftest.py b/tests/models/litter/conftest.py index cc8b89a3d..7c4d19acd 100644 --- a/tests/models/litter/conftest.py +++ b/tests/models/litter/conftest.py @@ -80,6 +80,10 @@ def dummy_litter_data(fixture_core_components): "litter_consumption_woody": [0.4773833, 0.385701, 0.373456, 0.162192], "litter_consumption_below_metabolic": [0.010373, 0.005794, 0.010181, 0.013494], "litter_consumption_below_structural": [0.013547, 0.011674, 0.012738, 0.009168], + "herbivory_waste_leaf_carbon": [3e-5, 2.1e-3, 2.85e-3, 2.7e-3], + "herbivory_waste_leaf_nitrogen": [23.1, 33.5, 23.1, 17.3], + "herbivory_waste_leaf_phosphorus": [212.5, 344.8, 334.8, 420.1], + "herbivory_waste_leaf_lignin": [0.13, 0.08, 0.27, 0.22], } for var, vals in pool_values.items(): diff --git a/tests/models/litter/test_input_partition.py b/tests/models/litter/test_input_partition.py index 3af47e1c8..7a626f172 100644 --- a/tests/models/litter/test_input_partition.py +++ b/tests/models/litter/test_input_partition.py @@ -18,6 +18,36 @@ def test_input_partition_initialisation(dummy_litter_data): assert input_partition.data == dummy_litter_data +def test_combine_input_sources(input_partition): + """Test that function to combine input sources works as expected.""" + + expected_combined = { + "leaf_mass": [0.02703, 0.0024, 0.02385, 0.0312], + "root_mass": [0.027, 0.021, 0.0003, 0.0249], + "deadwood_mass": [0.075, 0.099, 0.063, 0.033], + "reprod_mass": [0.003, 0.0075, 0.00255, 0.00375], + "leaf_lignin": [0.05008879, 0.10125, 0.29641509, 0.53971154], + "root_lignin": [0.2, 0.35, 0.27, 0.4], + "deadwood_lignin": [0.233, 0.545, 0.612, 0.378], + "reprod_lignin": [0.01, 0.03, 0.04, 0.02], + "leaf_nitrogen": [15.00899, 32.5, 40.710063, 53.929808], + "root_nitrogen": [30.3, 45.6, 43.3, 37.1], + "deadwood_nitrogen": [60.7, 57.9, 73.1, 55.1], + "reprod_nitrogen": [12.5, 23.8, 15.7, 18.2], + "leaf_phosphorus": [414.77525, 342.625, 528.24654, 384.29231], + "root_phosphorus": [656.7, 450.6, 437.3, 371.9], + "deadwood_phosphorus": [856.5, 675.4, 933.2, 888.8], + "reprod_phosphorus": [125.5, 105.0, 145.0, 189.2], + } + + actual_combined = input_partition.combine_input_sources() + + assert set(expected_combined.keys()) == set(actual_combined.keys()) + + for key in actual_combined.keys(): + assert np.allclose(actual_combined[key], expected_combined[key]) + + def test_calculate_metabolic_proportions_of_input(input_partition): """Test that function to calculate metabolic input proportions works as expected.""" diff --git a/virtual_ecosystem/models/litter/input_partition.py b/virtual_ecosystem/models/litter/input_partition.py index 7a2cfa2ba..88c09563f 100644 --- a/virtual_ecosystem/models/litter/input_partition.py +++ b/virtual_ecosystem/models/litter/input_partition.py @@ -5,6 +5,7 @@ import numpy as np from numpy.typing import NDArray +from xarray import DataArray from virtual_ecosystem.core.data import Data from virtual_ecosystem.core.logger import LOGGER @@ -21,6 +22,80 @@ class InputPartition: def __init__(self, data: Data): self.data = data + def combine_input_sources(self) -> dict[str, DataArray]: + """Combine the plant death and herbivory inputs into a single total input. + + The total input for each plant matter type (leaves, roots, deadwood, + reproductive tissue) is returned, the chemical concentration of each of these + new pools is also calculated. + + TODO - At the moment there is only leaf input defined so this function doesn't + really do anything for the other types of plant matter. Once input is defined + for them this function should be updated to actually do something with them. + + Returns: + A dictionary containing the total pool size for each input pools [kg C + m^-3], as well as the chemistry proportions (lignin, nitrogen and + phosphorus) of each of these pools [unitless]. + """ + + # Calculate totals for each plant matter type + leaf_total = ( + self.data["leaf_turnover"] + self.data["herbivory_waste_leaf_carbon"] + ) + root_total = self.data["root_turnover"] + deadwood_total = self.data["deadwood_production"] + reprod_total = self.data["plant_reproductive_tissue_turnover"] + + # Calculate leaf lignin concentrations for each combined pool + leaf_lignin = ( + self.data["leaf_turnover_lignin"] * self.data["leaf_turnover"] + + self.data["herbivory_waste_leaf_lignin"] + * self.data["herbivory_waste_leaf_carbon"] + ) / (leaf_total) + root_lignin = self.data["root_turnover_lignin"] + deadwood_lignin = self.data["deadwood_lignin"] + reprod_lignin = self.data["plant_reproductive_tissue_turnover_lignin"] + + # Calculate leaf nitrogen concentrations for each combined pool + leaf_nitrogen = ( + self.data["leaf_turnover_c_n_ratio"] * self.data["leaf_turnover"] + + self.data["herbivory_waste_leaf_nitrogen"] + * self.data["herbivory_waste_leaf_carbon"] + ) / (leaf_total) + root_nitrogen = self.data["root_turnover_c_n_ratio"] + deadwood_nitrogen = self.data["deadwood_c_n_ratio"] + reprod_nitrogen = self.data["plant_reproductive_tissue_turnover_c_n_ratio"] + + # Calculate leaf phosphorus concentrations for each combined pool + leaf_phosphorus = ( + self.data["leaf_turnover_c_p_ratio"] * self.data["leaf_turnover"] + + self.data["herbivory_waste_leaf_phosphorus"] + * self.data["herbivory_waste_leaf_carbon"] + ) / (leaf_total) + root_phosphorus = self.data["root_turnover_c_p_ratio"] + deadwood_phosphorus = self.data["deadwood_c_p_ratio"] + reprod_phosphorus = self.data["plant_reproductive_tissue_turnover_c_p_ratio"] + + return { + "leaf_mass": leaf_total, + "root_mass": root_total, + "deadwood_mass": deadwood_total, + "reprod_mass": reprod_total, + "leaf_lignin": leaf_lignin, + "root_lignin": root_lignin, + "deadwood_lignin": deadwood_lignin, + "reprod_lignin": reprod_lignin, + "leaf_nitrogen": leaf_nitrogen, + "root_nitrogen": root_nitrogen, + "deadwood_nitrogen": deadwood_nitrogen, + "reprod_nitrogen": reprod_nitrogen, + "leaf_phosphorus": leaf_phosphorus, + "root_phosphorus": root_phosphorus, + "deadwood_phosphorus": deadwood_phosphorus, + "reprod_phosphorus": reprod_phosphorus, + } + def calculate_metabolic_proportions_of_input( self, constants: LitterConsts ) -> dict[str, NDArray[np.float32]]: From ae204d30a3723025c193342d020646366908b315 Mon Sep 17 00:00:00 2001 From: Jacob Cook Date: Thu, 3 Oct 2024 10:20:53 +0100 Subject: [PATCH 04/14] Added a method to collect all the litter input calculation stages --- tests/models/litter/test_input_partition.py | 35 ++++++++++++++++++- .../models/litter/input_partition.py | 29 +++++++++++++++ .../models/litter/litter_model.py | 12 ++----- 3 files changed, 66 insertions(+), 10 deletions(-) diff --git a/tests/models/litter/test_input_partition.py b/tests/models/litter/test_input_partition.py index 7a626f172..dc27b2282 100644 --- a/tests/models/litter/test_input_partition.py +++ b/tests/models/litter/test_input_partition.py @@ -18,6 +18,39 @@ def test_input_partition_initialisation(dummy_litter_data): assert input_partition.data == dummy_litter_data +def test_determine_all_plant_to_litter_flows(input_partition): + """Test that function to determine plant to litter flows works correctly.""" + + expected_proportions = { + "leaves": [0.812403025, 0.640197595, 0.424077745, 0.0089426731], + "reproductive": [0.8462925685, 0.833489905, 0.83196046, 0.8390536408], + "roots": [0.588394858, 0.379571377, 0.5024461477, 0.410125012], + } + + expected_splits = { + "woody": [0.075, 0.099, 0.063, 0.033], + "above_ground_metabolic": [0.02447376, 0.00644323, 0.01102713, 0.00340132], + "above_ground_structural": [0.00552624, 0.00135677, 0.01252287, 0.02884868], + "below_ground_metabolic": [0.01588666, 0.00797100, 0.00015073, 0.01021211], + "below_ground_structural": [0.01111334, 0.013029, 0.00014927, 0.01468789], + } + + actual_proportions, actual_splits = ( + input_partition.determine_all_plant_to_litter_flows(constants=LitterConsts) + ) + + # Check that all keys match and have correct values for both dictionaries + assert set(expected_proportions.keys()) == set(actual_proportions.keys()) + + for key in actual_proportions.keys(): + assert np.allclose(actual_proportions[key], expected_proportions[key]) + + assert set(expected_splits.keys()) == set(actual_splits.keys()) + + for key in actual_splits.keys(): + assert np.allclose(actual_splits[key], expected_splits[key]) + + def test_combine_input_sources(input_partition): """Test that function to combine input sources works as expected.""" @@ -58,7 +91,7 @@ def test_calculate_metabolic_proportions_of_input(input_partition): } actual_proportions = input_partition.calculate_metabolic_proportions_of_input( - constants=LitterConsts, + constants=LitterConsts ) assert set(expected_proportions.keys()) == set(actual_proportions.keys()) diff --git a/virtual_ecosystem/models/litter/input_partition.py b/virtual_ecosystem/models/litter/input_partition.py index 88c09563f..168e6e483 100644 --- a/virtual_ecosystem/models/litter/input_partition.py +++ b/virtual_ecosystem/models/litter/input_partition.py @@ -22,6 +22,35 @@ class InputPartition: def __init__(self, data: Data): self.data = data + def determine_all_plant_to_litter_flows( + self, constants: LitterConsts + ) -> tuple[dict[str, NDArray[np.float32]], dict[str, NDArray[np.float32]]]: + """Determine the total flow to each litter pool from dead plant matter. + + TODO - At the moment this doesn't combine the different input flows, but it will + soon. And when it does I need to describe it in this docstring. + + Args: + constants: Set of constants for the litter model. + + Returns: + Two dictionaries, the first of which provides the proportion of the input + that goes to the relevant metabolic pool for each input type (expect + deadwood) [unitless]. The second dictionary provides the total plant biomass + flow into each of the litter pools [kg C m^-2]. + """ + + # Find the plant inputs to each of the litter pools + metabolic_splits = self.calculate_metabolic_proportions_of_input( + constants=constants + ) + + plant_inputs = self.partion_plant_inputs_between_pools( + metabolic_splits=metabolic_splits + ) + + return metabolic_splits, plant_inputs + def combine_input_sources(self) -> dict[str, DataArray]: """Combine the plant death and herbivory inputs into a single total input. diff --git a/virtual_ecosystem/models/litter/litter_model.py b/virtual_ecosystem/models/litter/litter_model.py index d2a90f7f2..c202ee333 100644 --- a/virtual_ecosystem/models/litter/litter_model.py +++ b/virtual_ecosystem/models/litter/litter_model.py @@ -325,19 +325,13 @@ def update(self, time_index: int, **kwargs: Any) -> None: constants=self.model_constants, ) - # TODO - This will need to take new input - # Find the plant inputs to each of the litter pools - metabolic_splits = ( - self.input_partition.calculate_metabolic_proportions_of_input( + # Find total plant inputs and how they get split between pools + metabolic_splits, plant_inputs = ( + self.input_partition.determine_all_plant_to_litter_flows( constants=self.model_constants, ) ) - # TODO - This will need to take new input - plant_inputs = self.input_partition.partion_plant_inputs_between_pools( - metabolic_splits=metabolic_splits, - ) - # Calculate the updated pool masses updated_pools = calculate_updated_pools( post_consumption_pools=consumed_pools, From febc9a3ff3c2e01e850584014d03d95cc27b3a5e Mon Sep 17 00:00:00 2001 From: Jacob Cook Date: Thu, 3 Oct 2024 11:24:06 +0100 Subject: [PATCH 05/14] Changed input partitioning flow to use both plant biomass input streams --- tests/models/litter/conftest.py | 17 +++-- tests/models/litter/test_carbon.py | 4 +- tests/models/litter/test_input_partition.py | 24 ++++--- tests/models/litter/test_litter_model.py | 4 +- .../models/litter/input_partition.py | 69 ++++++++++--------- 5 files changed, 66 insertions(+), 52 deletions(-) diff --git a/tests/models/litter/conftest.py b/tests/models/litter/conftest.py index 7c4d19acd..74e3668ec 100644 --- a/tests/models/litter/conftest.py +++ b/tests/models/litter/conftest.py @@ -130,22 +130,22 @@ def decay_rates(dummy_litter_data, fixture_core_components, post_consumption_poo @pytest.fixture -def metabolic_splits(input_partition): +def metabolic_splits(input_partition, total_litter_input): """Metabolic splits for the various plant inputs.""" metabolic_splits = input_partition.calculate_metabolic_proportions_of_input( - constants=LitterConsts + total_input=total_litter_input, constants=LitterConsts ) return metabolic_splits @pytest.fixture -def plant_inputs(input_partition, metabolic_splits): +def plant_inputs(input_partition, metabolic_splits, total_litter_input): """Plant inputs to each of the litter pools.""" plant_inputs = input_partition.partion_plant_inputs_between_pools( - metabolic_splits=metabolic_splits + metabolic_splits=metabolic_splits, total_input=total_litter_input ) return plant_inputs @@ -236,6 +236,15 @@ def post_consumption_pools(dummy_litter_data): return post_consumption_pools +@pytest.fixture +def total_litter_input(input_partition): + """Total input mass a chemistry for each plant biomass type.""" + + total_litter_input = input_partition.combine_input_sources() + + return total_litter_input + + @pytest.fixture def updated_pools(dummy_litter_data, decay_rates, plant_inputs, post_consumption_pools): """Updated carbon mass of each pool.""" diff --git a/tests/models/litter/test_carbon.py b/tests/models/litter/test_carbon.py index 415fd7248..e452477a8 100644 --- a/tests/models/litter/test_carbon.py +++ b/tests/models/litter/test_carbon.py @@ -146,8 +146,8 @@ def test_calculate_updated_pools( from virtual_ecosystem.models.litter.carbon import calculate_updated_pools expected_pools = { - "above_metabolic": [0.3154561, 0.15193439, 0.07892301, 0.0712972], - "above_structural": [0.50519138, 0.25011962, 0.10250070, 0.11882651], + "above_metabolic": [0.3154788, 0.15354349, 0.080772679, 0.073701212], + "above_structural": [0.5051986807, 0.2506105228, 0.1035010262, 0.1191224962], "woody": [4.77403361, 11.89845863, 7.3598224, 7.3298224], "below_metabolic": [0.3976309, 0.3630269, 0.06787947, 0.07794085], "below_structural": [0.61050583, 0.32205947352, 0.02014514530, 0.03468376530], diff --git a/tests/models/litter/test_input_partition.py b/tests/models/litter/test_input_partition.py index dc27b2282..6e59e5884 100644 --- a/tests/models/litter/test_input_partition.py +++ b/tests/models/litter/test_input_partition.py @@ -18,19 +18,19 @@ def test_input_partition_initialisation(dummy_litter_data): assert input_partition.data == dummy_litter_data -def test_determine_all_plant_to_litter_flows(input_partition): +def test_determine_all_plant_to_litter_flows(input_partition, total_litter_input): """Test that function to determine plant to litter flows works correctly.""" expected_proportions = { - "leaves": [0.812403025, 0.640197595, 0.424077745, 0.0089426731], + "leaves": [0.8123412282, 0.7504823457, 0.4509559749, 0.0852205423], "reproductive": [0.8462925685, 0.833489905, 0.83196046, 0.8390536408], "roots": [0.588394858, 0.379571377, 0.5024461477, 0.410125012], } expected_splits = { "woody": [0.075, 0.099, 0.063, 0.033], - "above_ground_metabolic": [0.02447376, 0.00644323, 0.01102713, 0.00340132], - "above_ground_structural": [0.00552624, 0.00135677, 0.01252287, 0.02884868], + "above_ground_metabolic": [0.02449646, 0.00805233, 0.012876799, 0.005805332], + "above_ground_structural": [0.00553354, 0.00184767, 0.01352320, 0.02914467], "below_ground_metabolic": [0.01588666, 0.00797100, 0.00015073, 0.01021211], "below_ground_structural": [0.01111334, 0.013029, 0.00014927, 0.01468789], } @@ -81,17 +81,17 @@ def test_combine_input_sources(input_partition): assert np.allclose(actual_combined[key], expected_combined[key]) -def test_calculate_metabolic_proportions_of_input(input_partition): +def test_calculate_metabolic_proportions_of_input(input_partition, total_litter_input): """Test that function to calculate metabolic input proportions works as expected.""" expected_proportions = { - "leaves": [0.812403025, 0.640197595, 0.424077745, 0.0089426731], + "leaves": [0.8123412282, 0.7504823457, 0.4509559749, 0.0852205423], "reproductive": [0.8462925685, 0.833489905, 0.83196046, 0.8390536408], "roots": [0.588394858, 0.379571377, 0.5024461477, 0.410125012], } actual_proportions = input_partition.calculate_metabolic_proportions_of_input( - constants=LitterConsts + total_input=total_litter_input, constants=LitterConsts ) assert set(expected_proportions.keys()) == set(actual_proportions.keys()) @@ -100,17 +100,19 @@ def test_calculate_metabolic_proportions_of_input(input_partition): assert np.allclose(actual_proportions[key], expected_proportions[key]) -def test_partion_plant_inputs_between_pools(input_partition, metabolic_splits): +def test_partion_plant_inputs_between_pools( + input_partition, metabolic_splits, total_litter_input +): """Check function to partition inputs into litter pools works as expected.""" expected_woody = [0.075, 0.099, 0.063, 0.033] - expected_above_meta = [0.02447376, 0.00644323, 0.01102713, 0.00340132] - expected_above_struct = [0.00552624, 0.00135677, 0.01252287, 0.02884868] + expected_above_meta = [0.02449646, 0.00805233, 0.012876799, 0.005805332] + expected_above_struct = [0.00553354, 0.00184767, 0.01352320, 0.02914467] expected_below_meta = [0.01588666, 0.00797100, 0.00015073, 0.01021211] expected_below_struct = [0.01111334, 0.013029, 0.00014927, 0.01468789] actual_splits = input_partition.partion_plant_inputs_between_pools( - metabolic_splits=metabolic_splits, + total_input=total_litter_input, metabolic_splits=metabolic_splits ) assert np.allclose(actual_splits["woody"], expected_woody) diff --git a/tests/models/litter/test_litter_model.py b/tests/models/litter/test_litter_model.py index bcc878580..3420c9ea8 100644 --- a/tests/models/litter/test_litter_model.py +++ b/tests/models/litter/test_litter_model.py @@ -457,8 +457,8 @@ def test_generate_litter_model( def test_update(fixture_litter_model, dummy_litter_data): """Test to check that the update step works and increments the update step.""" - end_above_meta = [0.3154561, 0.15193439, 0.07892301, 0.0712972] - end_above_struct = [0.50519138, 0.25011962, 0.10250070, 0.11882651] + end_above_meta = [0.3154788, 0.15354349, 0.080772679, 0.073701212] + end_above_struct = [0.5051986807, 0.2506105228, 0.1035010262, 0.1191224962] end_woody = [4.77403361, 11.89845863, 7.3598224, 7.3298224] end_below_meta = [0.3976309, 0.3630269, 0.06787947, 0.07794085] end_below_struct = [0.61050583, 0.32205947352, 0.02014514530, 0.03468376530] diff --git a/virtual_ecosystem/models/litter/input_partition.py b/virtual_ecosystem/models/litter/input_partition.py index 168e6e483..72b233d72 100644 --- a/virtual_ecosystem/models/litter/input_partition.py +++ b/virtual_ecosystem/models/litter/input_partition.py @@ -27,8 +27,9 @@ def determine_all_plant_to_litter_flows( ) -> tuple[dict[str, NDArray[np.float32]], dict[str, NDArray[np.float32]]]: """Determine the total flow to each litter pool from dead plant matter. - TODO - At the moment this doesn't combine the different input flows, but it will - soon. And when it does I need to describe it in this docstring. + This method first combines the two different input streams for dead plant matter + (plant tissue death and herbivory waste) to find the total input of each plant + biomass type. This total is then used in the subsequent calculations. Args: constants: Set of constants for the litter model. @@ -40,13 +41,16 @@ def determine_all_plant_to_litter_flows( flow into each of the litter pools [kg C m^-2]. """ + # Find the total input for each plant matter type + total_input = self.combine_input_sources() + # Find the plant inputs to each of the litter pools metabolic_splits = self.calculate_metabolic_proportions_of_input( - constants=constants + total_input=total_input, constants=constants ) plant_inputs = self.partion_plant_inputs_between_pools( - metabolic_splits=metabolic_splits + total_input=total_input, metabolic_splits=metabolic_splits ) return metabolic_splits, plant_inputs @@ -126,7 +130,7 @@ def combine_input_sources(self) -> dict[str, DataArray]: } def calculate_metabolic_proportions_of_input( - self, constants: LitterConsts + self, total_input: dict[str, DataArray], constants: LitterConsts ) -> dict[str, NDArray[np.float32]]: """Calculate the proportion of each input type that flows to the metabolic pool. @@ -135,6 +139,9 @@ def calculate_metabolic_proportions_of_input( for animal inputs either as they all flow into just the metabolic pool. Args: + total_input: The total pool size for each input pool [kg C m^-3], as well as + the chemical proportions (lignin, nitrogen and phosphorus) of each of + these pools [unitless]. constants: Set of constants for the litter model. Returns: @@ -145,33 +152,27 @@ def calculate_metabolic_proportions_of_input( # Calculate split of each input biomass type leaves_metabolic_split = split_pool_into_metabolic_and_structural_litter( - lignin_proportion=self.data["leaf_turnover_lignin"].to_numpy(), - carbon_nitrogen_ratio=self.data["leaf_turnover_c_n_ratio"].to_numpy(), - carbon_phosphorus_ratio=self.data["leaf_turnover_c_p_ratio"].to_numpy(), + lignin_proportion=total_input["leaf_lignin"].to_numpy(), + carbon_nitrogen_ratio=total_input["leaf_nitrogen"].to_numpy(), + carbon_phosphorus_ratio=total_input["leaf_phosphorus"].to_numpy(), max_metabolic_fraction=constants.max_metabolic_fraction_of_input, split_sensitivity_nitrogen=constants.metabolic_split_nitrogen_sensitivity, split_sensitivity_phosphorus=constants.metabolic_split_phosphorus_sensitivity, ) repoduct_metabolic_split = split_pool_into_metabolic_and_structural_litter( - lignin_proportion=self.data[ - "plant_reproductive_tissue_turnover_lignin" - ].to_numpy(), - carbon_nitrogen_ratio=self.data[ - "plant_reproductive_tissue_turnover_c_n_ratio" - ].to_numpy(), - carbon_phosphorus_ratio=self.data[ - "plant_reproductive_tissue_turnover_c_p_ratio" - ].to_numpy(), + lignin_proportion=total_input["reprod_lignin"].to_numpy(), + carbon_nitrogen_ratio=total_input["reprod_nitrogen"].to_numpy(), + carbon_phosphorus_ratio=total_input["reprod_phosphorus"].to_numpy(), max_metabolic_fraction=constants.max_metabolic_fraction_of_input, split_sensitivity_nitrogen=constants.metabolic_split_nitrogen_sensitivity, split_sensitivity_phosphorus=constants.metabolic_split_phosphorus_sensitivity, ) roots_metabolic_split = split_pool_into_metabolic_and_structural_litter( - lignin_proportion=self.data["root_turnover_lignin"].to_numpy(), - carbon_nitrogen_ratio=self.data["root_turnover_c_n_ratio"].to_numpy(), - carbon_phosphorus_ratio=self.data["root_turnover_c_p_ratio"].to_numpy(), + lignin_proportion=total_input["root_lignin"].to_numpy(), + carbon_nitrogen_ratio=total_input["root_nitrogen"].to_numpy(), + carbon_phosphorus_ratio=total_input["root_phosphorus"].to_numpy(), max_metabolic_fraction=constants.max_metabolic_fraction_of_input, split_sensitivity_nitrogen=constants.metabolic_split_nitrogen_sensitivity, split_sensitivity_phosphorus=constants.metabolic_split_phosphorus_sensitivity, @@ -184,7 +185,9 @@ def calculate_metabolic_proportions_of_input( } def partion_plant_inputs_between_pools( - self, metabolic_splits: dict[str, NDArray[np.float32]] + self, + total_input: dict[str, DataArray], + metabolic_splits: dict[str, NDArray[np.float32]], ): """Function to partition input biomass between the various litter pools. @@ -195,6 +198,9 @@ def partion_plant_inputs_between_pools( concentration and carbon nitrogen ratios. Args: + total_input: The total pool size for each input pool [kg C m^-3], as well as + the chemical proportions (lignin, nitrogen and phosphorus) of each of + these pools [unitless]. metabolic_splits: Dictionary containing the proportion of each input that goes to the relevant metabolic pool. This is for three input types: leaves, reproductive tissues and roots [unitless] @@ -206,22 +212,19 @@ def partion_plant_inputs_between_pools( """ # Calculate input to each of the five litter pools - woody_input = self.data["deadwood_production"] + woody_input = total_input["deadwood_mass"] above_ground_metabolic_input = ( - metabolic_splits["leaves"] * self.data["leaf_turnover"] - + metabolic_splits["reproductive"] - * self.data["plant_reproductive_tissue_turnover"] + metabolic_splits["leaves"] * total_input["leaf_mass"] + + metabolic_splits["reproductive"] * total_input["reprod_mass"] ) - above_ground_strutural_input = (1 - metabolic_splits["leaves"]) * self.data[ - "leaf_turnover" - ] + (1 - metabolic_splits["reproductive"]) * self.data[ - "plant_reproductive_tissue_turnover" - ] + above_ground_strutural_input = (1 - metabolic_splits["leaves"]) * total_input[ + "leaf_mass" + ] + (1 - metabolic_splits["reproductive"]) * total_input["reprod_mass"] below_ground_metabolic_input = ( - metabolic_splits["roots"] * self.data["root_turnover"] + metabolic_splits["roots"] * total_input["root_mass"] ) - below_ground_structural_input = (1 - metabolic_splits["roots"]) * self.data[ - "root_turnover" + below_ground_structural_input = (1 - metabolic_splits["roots"]) * total_input[ + "root_mass" ] return { From 5a4d632a9c873e7baa33c70a8db774b64265a585 Mon Sep 17 00:00:00 2001 From: Jacob Cook Date: Thu, 3 Oct 2024 12:45:52 +0100 Subject: [PATCH 06/14] Changed LitterChemistry methods to take in input chemistries directly rather than reading them from the data object --- virtual_ecosystem/models/litter/chemistry.py | 510 +++++++++--------- .../models/litter/input_partition.py | 187 ++++--- .../models/litter/litter_model.py | 7 +- 3 files changed, 348 insertions(+), 356 deletions(-) diff --git a/virtual_ecosystem/models/litter/chemistry.py b/virtual_ecosystem/models/litter/chemistry.py index 590c7d9c5..c029048ca 100644 --- a/virtual_ecosystem/models/litter/chemistry.py +++ b/virtual_ecosystem/models/litter/chemistry.py @@ -39,6 +39,7 @@ def calculate_new_pool_chemistries( plant_inputs: dict[str, NDArray[np.float32]], metabolic_splits: dict[str, NDArray[np.float32]], updated_pools: dict[str, NDArray[np.float32]], + total_input: dict[str, DataArray], ) -> dict[str, DataArray]: """Method to calculate the updated chemistry of each litter pool. @@ -54,19 +55,25 @@ def calculate_new_pool_chemistries( leaves, reproductive tissues and roots [unitless] updated_pools: Dictionary containing the updated pool densities for all 5 litter pools [kg C m^-2] + total_input: The total pool size for each input pool [kg C m^-3], as well as + the chemical proportions (lignin, nitrogen and phosphorus) of each of + these pools [unitless]. """ # Find lignin and nitrogen contents of the litter input flows - input_lignin = self.calculate_litter_input_lignin_concentrations( + input_lignin = calculate_litter_input_lignin_concentrations( plant_input_above_struct=plant_inputs["above_ground_structural"], plant_input_below_struct=plant_inputs["below_ground_structural"], + total_input=total_input, ) - input_c_n_ratios = self.calculate_litter_input_nitrogen_ratios( + input_c_n_ratios = calculate_litter_input_nitrogen_ratios( metabolic_splits=metabolic_splits, + total_input=total_input, struct_to_meta_nitrogen_ratio=self.structural_to_metabolic_n_ratio, ) - input_c_p_ratios = self.calculate_litter_input_phosphorus_ratios( + input_c_p_ratios = calculate_litter_input_phosphorus_ratios( metabolic_splits=metabolic_splits, + total_input=total_input, struct_to_meta_phosphorus_ratio=self.structural_to_metabolic_p_ratio, ) @@ -121,263 +128,6 @@ def calculate_new_pool_chemistries( return lignin_changes | nitrogen_changes | phosphorus_changes - def calculate_litter_input_lignin_concentrations( - self, - plant_input_below_struct: NDArray[np.float32], - plant_input_above_struct: NDArray[np.float32], - ) -> dict[str, NDArray[np.float32]]: - """Calculate the concentration of lignin for each plant biomass to litter flow. - - By definition the metabolic litter pools do not contain lignin, so all input - lignin flows to the structural and woody pools. As the input biomass gets split - between pools, the lignin concentration of the input to the structural pools - will be higher than it was in the input biomass. - - For the woody litter there's no structural-metabolic split so the lignin - concentration of the litter input is the same as that of the dead wood - production. For the below ground structural litter, the total lignin content of - root input must be found, this is then converted back into a concentration - relative to the input into the below structural litter pool. For the above - ground structural litter pool, the same approach is taken with the combined - total lignin content of the leaf and reproductive matter inputs being found, and - then converted to a back into a concentration. - - Args: - plant_input_below_struct: Plant input to below ground structural litter pool - [kg C m^-2] - plant_input_above_struct: Plant input to above ground structural litter pool - [kg C m^-2] - - Returns: - Dictionary containing the lignin concentration of the input to each of the - three lignin containing litter pools (woody, above and below ground - structural) [kg lignin kg C^-1] - """ - - lignin_proportion_woody = self.data["deadwood_lignin"] - - lignin_proportion_below_structural = ( - self.data["root_turnover_lignin"] - * self.data["root_turnover"] - / plant_input_below_struct - ) - - lignin_proportion_above_structural = ( - (self.data["leaf_turnover_lignin"] * self.data["leaf_turnover"]) - + ( - self.data["plant_reproductive_tissue_turnover_lignin"] - * self.data["plant_reproductive_tissue_turnover"] - ) - ) / plant_input_above_struct - - return { - "woody": lignin_proportion_woody.to_numpy(), - "below_structural": lignin_proportion_below_structural.to_numpy(), - "above_structural": lignin_proportion_above_structural.to_numpy(), - } - - def calculate_litter_input_nitrogen_ratios( - self, - metabolic_splits: dict[str, NDArray[np.float32]], - struct_to_meta_nitrogen_ratio: float, - ) -> dict[str, NDArray[np.float32]]: - """Calculate the carbon to nitrogen ratio for each plant biomass to litter flow. - - The ratio for the input to the woody litter pool just matches the ratio of the - deadwood input. For the below ground pools, the ratios of the flows from root - turnover into the metabolic and structural pools is calculated. A similar - approach is taken for the above ground metabolic and structural pools, but here - a weighted average of the two contributions to each pool (leaf and reproductive - tissue turnover) must be taken. - - Args: - metabolic_splits: Dictionary containing the proportion of each input that - goes to the relevant metabolic pool. This is for three input types: - leaves, reproductive tissues and roots [unitless] - struct_to_meta_nitrogen_ratio: Ratio of the carbon to nitrogen ratios of - structural vs metabolic litter pools [unitless] - - Returns: - Dictionary containing the carbon to nitrogen ratios of the input to each of - the pools [unitless] - """ - - # Calculate c_n_ratio split for each (non-wood) input biomass type - root_c_n_ratio_meta, root_c_n_ratio_struct = ( - calculate_nutrient_split_between_litter_pools( - input_c_nut_ratio=self.data["root_turnover_c_n_ratio"].to_numpy(), - metabolic_split=metabolic_splits["roots"], - struct_to_meta_nutrient_ratio=struct_to_meta_nitrogen_ratio, - ) - ) - - leaf_c_n_ratio_meta, leaf_c_n_ratio_struct = ( - calculate_nutrient_split_between_litter_pools( - input_c_nut_ratio=self.data["leaf_turnover_c_n_ratio"].to_numpy(), - metabolic_split=metabolic_splits["leaves"], - struct_to_meta_nutrient_ratio=struct_to_meta_nitrogen_ratio, - ) - ) - - reprod_c_n_ratio_meta, reprod_c_n_ratio_struct = ( - calculate_nutrient_split_between_litter_pools( - input_c_nut_ratio=self.data[ - "plant_reproductive_tissue_turnover_c_n_ratio" - ].to_numpy(), - metabolic_split=metabolic_splits["reproductive"], - struct_to_meta_nutrient_ratio=struct_to_meta_nitrogen_ratio, - ) - ) - - c_n_ratio_below_metabolic = root_c_n_ratio_meta - c_n_ratio_below_structural = root_c_n_ratio_struct - c_n_ratio_woody = self.data["deadwood_c_n_ratio"].to_numpy() - # Inputs with multiple sources have to be weighted - c_n_ratio_above_metabolic = np.divide( - ( - leaf_c_n_ratio_meta - * self.data["leaf_turnover"] - * metabolic_splits["leaves"] - ) - + ( - reprod_c_n_ratio_meta - * self.data["plant_reproductive_tissue_turnover"] - * metabolic_splits["reproductive"] - ), - (self.data["leaf_turnover"] * metabolic_splits["leaves"]) - + ( - self.data["plant_reproductive_tissue_turnover"] - * metabolic_splits["reproductive"] - ), - ) - - c_n_ratio_above_structural = np.divide( - ( - leaf_c_n_ratio_struct - * self.data["leaf_turnover"] - * (1 - metabolic_splits["leaves"]) - ) - + ( - reprod_c_n_ratio_struct - * self.data["plant_reproductive_tissue_turnover"] - * (1 - metabolic_splits["reproductive"]) - ), - (self.data["leaf_turnover"] * (1 - metabolic_splits["leaves"])) - + ( - self.data["plant_reproductive_tissue_turnover"] - * (1 - metabolic_splits["reproductive"]) - ), - ) - - return { - "woody": c_n_ratio_woody, - "below_metabolic": c_n_ratio_below_metabolic, - "below_structural": c_n_ratio_below_structural, - "above_metabolic": c_n_ratio_above_metabolic, - "above_structural": c_n_ratio_above_structural, - } - - def calculate_litter_input_phosphorus_ratios( - self, - metabolic_splits: dict[str, NDArray[np.float32]], - struct_to_meta_phosphorus_ratio: float, - ) -> dict[str, NDArray[np.float32]]: - """Calculate carbon to phosphorus ratio for each plant biomass to litter flow. - - The ratio for the input to the woody litter pool just matches the ratio of the - deadwood input. For the below ground pools, the ratios of the flows from root - turnover into the metabolic and structural pools is calculated. A similar - approach is taken for the above ground metabolic and structural pools, but here - a weighted average of the two contributions to each pool (leaf and reproductive - tissue turnover) must be taken. - - Args: - metabolic_splits: Dictionary containing the proportion of each input that - goes to the relevant metabolic pool. This is for three input types: - leaves, reproductive tissues and roots [unitless] - struct_to_meta_phosphorus_ratio: Ratio of the carbon to phosphorus ratios of - structural vs metabolic litter pools [unitless] - - Returns: - Dictionary containing the carbon to phosphorus ratios of the input to each - of the pools [unitless] - """ - - # Calculate c_p_ratio split for each (non-wood) input biomass type - root_c_p_ratio_meta, root_c_p_ratio_struct = ( - calculate_nutrient_split_between_litter_pools( - input_c_nut_ratio=self.data["root_turnover_c_p_ratio"].to_numpy(), - metabolic_split=metabolic_splits["roots"], - struct_to_meta_nutrient_ratio=struct_to_meta_phosphorus_ratio, - ) - ) - - leaf_c_p_ratio_meta, leaf_c_p_ratio_struct = ( - calculate_nutrient_split_between_litter_pools( - input_c_nut_ratio=self.data["leaf_turnover_c_p_ratio"].to_numpy(), - metabolic_split=metabolic_splits["leaves"], - struct_to_meta_nutrient_ratio=struct_to_meta_phosphorus_ratio, - ) - ) - - reprod_c_p_ratio_meta, reprod_c_p_ratio_struct = ( - calculate_nutrient_split_between_litter_pools( - input_c_nut_ratio=self.data[ - "plant_reproductive_tissue_turnover_c_p_ratio" - ].to_numpy(), - metabolic_split=metabolic_splits["reproductive"], - struct_to_meta_nutrient_ratio=struct_to_meta_phosphorus_ratio, - ) - ) - - c_p_ratio_below_metabolic = root_c_p_ratio_meta - c_p_ratio_below_structural = root_c_p_ratio_struct - c_p_ratio_woody = self.data["deadwood_c_p_ratio"].to_numpy() - # Inputs with multiple sources have to be weighted - c_p_ratio_above_metabolic = np.divide( - ( - leaf_c_p_ratio_meta - * self.data["leaf_turnover"] - * metabolic_splits["leaves"] - ) - + ( - reprod_c_p_ratio_meta - * self.data["plant_reproductive_tissue_turnover"] - * metabolic_splits["reproductive"] - ), - (self.data["leaf_turnover"] * metabolic_splits["leaves"]) - + ( - self.data["plant_reproductive_tissue_turnover"] - * metabolic_splits["reproductive"] - ), - ) - - c_p_ratio_above_structural = np.divide( - ( - leaf_c_p_ratio_struct - * self.data["leaf_turnover"] - * (1 - metabolic_splits["leaves"]) - ) - + ( - reprod_c_p_ratio_struct - * self.data["plant_reproductive_tissue_turnover"] - * (1 - metabolic_splits["reproductive"]) - ), - (self.data["leaf_turnover"] * (1 - metabolic_splits["leaves"])) - + ( - self.data["plant_reproductive_tissue_turnover"] - * (1 - metabolic_splits["reproductive"]) - ), - ) - - return { - "woody": c_p_ratio_woody, - "below_metabolic": c_p_ratio_below_metabolic, - "below_structural": c_p_ratio_below_structural, - "above_metabolic": c_p_ratio_above_metabolic, - "above_structural": c_p_ratio_above_structural, - } - def calculate_lignin_updates( self, plant_inputs: dict[str, NDArray[np.float32]], @@ -660,6 +410,246 @@ def calculate_P_mineralisation( return total_P_mineralisation_rate / active_microbe_depth +def calculate_litter_input_lignin_concentrations( + plant_input_below_struct: NDArray[np.float32], + plant_input_above_struct: NDArray[np.float32], + total_input: dict[str, DataArray], +) -> dict[str, NDArray[np.float32]]: + """Calculate the concentration of lignin for each plant biomass to litter flow. + + By definition the metabolic litter pools do not contain lignin, so all input + lignin flows to the structural and woody pools. As the input biomass gets split + between pools, the lignin concentration of the input to the structural pools + will be higher than it was in the input biomass. + + For the woody litter there's no structural-metabolic split so the lignin + concentration of the litter input is the same as that of the dead wood + production. For the below ground structural litter, the total lignin content of + root input must be found, this is then converted back into a concentration + relative to the input into the below structural litter pool. For the above + ground structural litter pool, the same approach is taken with the combined + total lignin content of the leaf and reproductive matter inputs being found, and + then converted to a back into a concentration. + + Args: + plant_input_below_struct: Plant input to below ground structural litter pool + [kg C m^-2] + plant_input_above_struct: Plant input to above ground structural litter pool + [kg C m^-2] + total_input: The total pool size for each input pool [kg C m^-3], as well as + the chemical proportions (lignin, nitrogen and phosphorus) of each of these + pools [unitless]. + + Returns: + Dictionary containing the lignin concentration of the input to each of the + three lignin containing litter pools (woody, above and below ground + structural) [kg lignin kg C^-1] + """ + + lignin_proportion_woody = total_input["deadwood_mass"] + + lignin_proportion_below_structural = ( + total_input["root_lignin"] * total_input["root_mass"] / plant_input_below_struct + ) + + lignin_proportion_above_structural = ( + (total_input["leaf_lignin"] * total_input["leaf_mass"]) + + (total_input["reprod_lignin"] * total_input["reprod_mass"]) + ) / plant_input_above_struct + + return { + "woody": lignin_proportion_woody.to_numpy(), + "below_structural": lignin_proportion_below_structural.to_numpy(), + "above_structural": lignin_proportion_above_structural.to_numpy(), + } + + +def calculate_litter_input_nitrogen_ratios( + metabolic_splits: dict[str, NDArray[np.float32]], + total_input: dict[str, DataArray], + struct_to_meta_nitrogen_ratio: float, +) -> dict[str, NDArray[np.float32]]: + """Calculate the carbon to nitrogen ratio for each plant biomass to litter flow. + + The ratio for the input to the woody litter pool just matches the ratio of the + deadwood input. For the below ground pools, the ratios of the flows from root + turnover into the metabolic and structural pools is calculated. A similar + approach is taken for the above ground metabolic and structural pools, but here + a weighted average of the two contributions to each pool (leaf and reproductive + tissue turnover) must be taken. + + Args: + metabolic_splits: Dictionary containing the proportion of each input that + goes to the relevant metabolic pool. This is for three input types: + leaves, reproductive tissues and roots [unitless] + total_input: The total pool size for each input pool [kg C m^-3], as well as + the chemical proportions (lignin, nitrogen and phosphorus) of each of these + pools [unitless]. + struct_to_meta_nitrogen_ratio: Ratio of the carbon to nitrogen ratios of + structural vs metabolic litter pools [unitless] + + Returns: + Dictionary containing the carbon to nitrogen ratios of the input to each of + the pools [unitless] + """ + + # Calculate c_n_ratio split for each (non-wood) input biomass type + root_c_n_ratio_meta, root_c_n_ratio_struct = ( + calculate_nutrient_split_between_litter_pools( + input_c_nut_ratio=total_input["root_nitrogen"].to_numpy(), + metabolic_split=metabolic_splits["roots"], + struct_to_meta_nutrient_ratio=struct_to_meta_nitrogen_ratio, + ) + ) + + leaf_c_n_ratio_meta, leaf_c_n_ratio_struct = ( + calculate_nutrient_split_between_litter_pools( + input_c_nut_ratio=total_input["leaf_nitrogen"].to_numpy(), + metabolic_split=metabolic_splits["leaves"], + struct_to_meta_nutrient_ratio=struct_to_meta_nitrogen_ratio, + ) + ) + + reprod_c_n_ratio_meta, reprod_c_n_ratio_struct = ( + calculate_nutrient_split_between_litter_pools( + input_c_nut_ratio=total_input["reprod_nitrogen"].to_numpy(), + metabolic_split=metabolic_splits["reproductive"], + struct_to_meta_nutrient_ratio=struct_to_meta_nitrogen_ratio, + ) + ) + + c_n_ratio_below_metabolic = root_c_n_ratio_meta + c_n_ratio_below_structural = root_c_n_ratio_struct + c_n_ratio_woody = total_input["deadwood_nitrogen"].to_numpy() + # Inputs with multiple sources have to be weighted + c_n_ratio_above_metabolic = np.divide( + (leaf_c_n_ratio_meta * total_input["leaf_mass"] * metabolic_splits["leaves"]) + + ( + reprod_c_n_ratio_meta + * total_input["reprod_mass"] + * metabolic_splits["reproductive"] + ), + (total_input["leaf_mass"] * metabolic_splits["leaves"]) + + (total_input["reprod_mass"] * metabolic_splits["reproductive"]), + ) + + c_n_ratio_above_structural = np.divide( + ( + leaf_c_n_ratio_struct + * total_input["leaf_mass"] + * (1 - metabolic_splits["leaves"]) + ) + + ( + reprod_c_n_ratio_struct + * total_input["reprod_mass"] + * (1 - metabolic_splits["reproductive"]) + ), + (total_input["leaf_mass"] * (1 - metabolic_splits["leaves"])) + + (total_input["reprod_mass"] * (1 - metabolic_splits["reproductive"])), + ) + + return { + "woody": c_n_ratio_woody, + "below_metabolic": c_n_ratio_below_metabolic, + "below_structural": c_n_ratio_below_structural, + "above_metabolic": c_n_ratio_above_metabolic, + "above_structural": c_n_ratio_above_structural, + } + + +def calculate_litter_input_phosphorus_ratios( + metabolic_splits: dict[str, NDArray[np.float32]], + total_input: dict[str, DataArray], + struct_to_meta_phosphorus_ratio: float, +) -> dict[str, NDArray[np.float32]]: + """Calculate carbon to phosphorus ratio for each plant biomass to litter flow. + + The ratio for the input to the woody litter pool just matches the ratio of the + deadwood input. For the below ground pools, the ratios of the flows from root + turnover into the metabolic and structural pools is calculated. A similar approach + is taken for the above ground metabolic and structural pools, but here a weighted + average of the two contributions to each pool (leaf and reproductive tissue + turnover) must be taken. + + Args: + metabolic_splits: Dictionary containing the proportion of each input that + goes to the relevant metabolic pool. This is for three input types: leaves, + reproductive tissues and roots [unitless] + total_input: The total pool size for each input pool [kg C m^-3], as well as + the chemical proportions (lignin, nitrogen and phosphorus) of each of these + pools [unitless]. + struct_to_meta_phosphorus_ratio: Ratio of the carbon to phosphorus ratios of + structural vs metabolic litter pools [unitless] + + Returns: + Dictionary containing the carbon to phosphorus ratios of the input to each of + the pools [unitless] + """ + + # Calculate c_p_ratio split for each (non-wood) input biomass type + root_c_p_ratio_meta, root_c_p_ratio_struct = ( + calculate_nutrient_split_between_litter_pools( + input_c_nut_ratio=total_input["root_phosphorus"].to_numpy(), + metabolic_split=metabolic_splits["roots"], + struct_to_meta_nutrient_ratio=struct_to_meta_phosphorus_ratio, + ) + ) + + leaf_c_p_ratio_meta, leaf_c_p_ratio_struct = ( + calculate_nutrient_split_between_litter_pools( + input_c_nut_ratio=total_input["leaf_phosphorus"].to_numpy(), + metabolic_split=metabolic_splits["leaves"], + struct_to_meta_nutrient_ratio=struct_to_meta_phosphorus_ratio, + ) + ) + + reprod_c_p_ratio_meta, reprod_c_p_ratio_struct = ( + calculate_nutrient_split_between_litter_pools( + input_c_nut_ratio=total_input["reprod_phosphorus"].to_numpy(), + metabolic_split=metabolic_splits["reproductive"], + struct_to_meta_nutrient_ratio=struct_to_meta_phosphorus_ratio, + ) + ) + + c_p_ratio_below_metabolic = root_c_p_ratio_meta + c_p_ratio_below_structural = root_c_p_ratio_struct + c_p_ratio_woody = total_input["deadwood_phosphorus"].to_numpy() + # Inputs with multiple sources have to be weighted + c_p_ratio_above_metabolic = np.divide( + (leaf_c_p_ratio_meta * total_input["leaf_mass"] * metabolic_splits["leaves"]) + + ( + reprod_c_p_ratio_meta + * total_input["reprod_mass"] + * metabolic_splits["reproductive"] + ), + (total_input["leaf_mass"] * metabolic_splits["leaves"]) + + (total_input["reprod_mass"] * metabolic_splits["reproductive"]), + ) + + c_p_ratio_above_structural = np.divide( + ( + leaf_c_p_ratio_struct + * total_input["leaf_mass"] + * (1 - metabolic_splits["leaves"]) + ) + + ( + reprod_c_p_ratio_struct + * total_input["reprod_mass"] + * (1 - metabolic_splits["reproductive"]) + ), + (total_input["leaf_mass"] * (1 - metabolic_splits["leaves"])) + + (total_input["reprod_mass"] * (1 - metabolic_splits["reproductive"])), + ) + + return { + "woody": c_p_ratio_woody, + "below_metabolic": c_p_ratio_below_metabolic, + "below_structural": c_p_ratio_below_structural, + "above_metabolic": c_p_ratio_above_metabolic, + "above_structural": c_p_ratio_above_structural, + } + + def calculate_litter_chemistry_factor( lignin_proportion: NDArray[np.float32], lignin_inhibition_factor: float ) -> NDArray[np.float32]: diff --git a/virtual_ecosystem/models/litter/input_partition.py b/virtual_ecosystem/models/litter/input_partition.py index 72b233d72..d5c09578f 100644 --- a/virtual_ecosystem/models/litter/input_partition.py +++ b/virtual_ecosystem/models/litter/input_partition.py @@ -45,11 +45,11 @@ def determine_all_plant_to_litter_flows( total_input = self.combine_input_sources() # Find the plant inputs to each of the litter pools - metabolic_splits = self.calculate_metabolic_proportions_of_input( + metabolic_splits = calculate_metabolic_proportions_of_input( total_input=total_input, constants=constants ) - plant_inputs = self.partion_plant_inputs_between_pools( + plant_inputs = partion_plant_inputs_between_pools( total_input=total_input, metabolic_splits=metabolic_splits ) @@ -129,111 +129,110 @@ def combine_input_sources(self) -> dict[str, DataArray]: "reprod_phosphorus": reprod_phosphorus, } - def calculate_metabolic_proportions_of_input( - self, total_input: dict[str, DataArray], constants: LitterConsts - ) -> dict[str, NDArray[np.float32]]: - """Calculate the proportion of each input type that flows to the metabolic pool. - This function is used for roots, leaves and reproductive tissue, but not - deadwood because everything goes into a single woody litter pool. It is not used - for animal inputs either as they all flow into just the metabolic pool. +def calculate_metabolic_proportions_of_input( + total_input: dict[str, DataArray], constants: LitterConsts +) -> dict[str, NDArray[np.float32]]: + """Calculate the proportion of each input type that flows to the metabolic pool. - Args: - total_input: The total pool size for each input pool [kg C m^-3], as well as - the chemical proportions (lignin, nitrogen and phosphorus) of each of - these pools [unitless]. - constants: Set of constants for the litter model. + This function is used for roots, leaves and reproductive tissue, but not deadwood + because everything goes into a single woody litter pool. It is not used for animal + inputs either as they all flow into just the metabolic pool. - Returns: - A dictionary containing the proportion of the input that goes to the - relevant metabolic pool. This is for three input types: leaves, reproductive - tissues and roots [unitless] - """ + Args: + total_input: The total pool size for each input pool [kg C m^-3], as well as + the chemical proportions (lignin, nitrogen and phosphorus) of each of these + pools [unitless]. + constants: Set of constants for the litter model. - # Calculate split of each input biomass type - leaves_metabolic_split = split_pool_into_metabolic_and_structural_litter( - lignin_proportion=total_input["leaf_lignin"].to_numpy(), - carbon_nitrogen_ratio=total_input["leaf_nitrogen"].to_numpy(), - carbon_phosphorus_ratio=total_input["leaf_phosphorus"].to_numpy(), - max_metabolic_fraction=constants.max_metabolic_fraction_of_input, - split_sensitivity_nitrogen=constants.metabolic_split_nitrogen_sensitivity, - split_sensitivity_phosphorus=constants.metabolic_split_phosphorus_sensitivity, - ) + Returns: + A dictionary containing the proportion of the input that goes to the relevant + metabolic pool. This is for three input types: leaves, reproductive tissues and + roots [unitless] + """ - repoduct_metabolic_split = split_pool_into_metabolic_and_structural_litter( - lignin_proportion=total_input["reprod_lignin"].to_numpy(), - carbon_nitrogen_ratio=total_input["reprod_nitrogen"].to_numpy(), - carbon_phosphorus_ratio=total_input["reprod_phosphorus"].to_numpy(), - max_metabolic_fraction=constants.max_metabolic_fraction_of_input, - split_sensitivity_nitrogen=constants.metabolic_split_nitrogen_sensitivity, - split_sensitivity_phosphorus=constants.metabolic_split_phosphorus_sensitivity, - ) + # Calculate split of each input biomass type + leaves_metabolic_split = split_pool_into_metabolic_and_structural_litter( + lignin_proportion=total_input["leaf_lignin"].to_numpy(), + carbon_nitrogen_ratio=total_input["leaf_nitrogen"].to_numpy(), + carbon_phosphorus_ratio=total_input["leaf_phosphorus"].to_numpy(), + max_metabolic_fraction=constants.max_metabolic_fraction_of_input, + split_sensitivity_nitrogen=constants.metabolic_split_nitrogen_sensitivity, + split_sensitivity_phosphorus=constants.metabolic_split_phosphorus_sensitivity, + ) - roots_metabolic_split = split_pool_into_metabolic_and_structural_litter( - lignin_proportion=total_input["root_lignin"].to_numpy(), - carbon_nitrogen_ratio=total_input["root_nitrogen"].to_numpy(), - carbon_phosphorus_ratio=total_input["root_phosphorus"].to_numpy(), - max_metabolic_fraction=constants.max_metabolic_fraction_of_input, - split_sensitivity_nitrogen=constants.metabolic_split_nitrogen_sensitivity, - split_sensitivity_phosphorus=constants.metabolic_split_phosphorus_sensitivity, - ) + repoduct_metabolic_split = split_pool_into_metabolic_and_structural_litter( + lignin_proportion=total_input["reprod_lignin"].to_numpy(), + carbon_nitrogen_ratio=total_input["reprod_nitrogen"].to_numpy(), + carbon_phosphorus_ratio=total_input["reprod_phosphorus"].to_numpy(), + max_metabolic_fraction=constants.max_metabolic_fraction_of_input, + split_sensitivity_nitrogen=constants.metabolic_split_nitrogen_sensitivity, + split_sensitivity_phosphorus=constants.metabolic_split_phosphorus_sensitivity, + ) - return { - "leaves": leaves_metabolic_split, - "reproductive": repoduct_metabolic_split, - "roots": roots_metabolic_split, - } + roots_metabolic_split = split_pool_into_metabolic_and_structural_litter( + lignin_proportion=total_input["root_lignin"].to_numpy(), + carbon_nitrogen_ratio=total_input["root_nitrogen"].to_numpy(), + carbon_phosphorus_ratio=total_input["root_phosphorus"].to_numpy(), + max_metabolic_fraction=constants.max_metabolic_fraction_of_input, + split_sensitivity_nitrogen=constants.metabolic_split_nitrogen_sensitivity, + split_sensitivity_phosphorus=constants.metabolic_split_phosphorus_sensitivity, + ) - def partion_plant_inputs_between_pools( - self, - total_input: dict[str, DataArray], - metabolic_splits: dict[str, NDArray[np.float32]], - ): - """Function to partition input biomass between the various litter pools. + return { + "leaves": leaves_metabolic_split, + "reproductive": repoduct_metabolic_split, + "roots": roots_metabolic_split, + } - All deadwood is added to the woody litter pool. Reproductive biomass (fruits and - flowers) and leaves are split between the above ground metabolic and structural - pools based on lignin concentration and carbon nitrogen ratios. Root biomass is - split between the below ground metabolic and structural pools based on lignin - concentration and carbon nitrogen ratios. - Args: - total_input: The total pool size for each input pool [kg C m^-3], as well as - the chemical proportions (lignin, nitrogen and phosphorus) of each of - these pools [unitless]. - metabolic_splits: Dictionary containing the proportion of each input that - goes to the relevant metabolic pool. This is for three input types: - leaves, reproductive tissues and roots [unitless] +def partion_plant_inputs_between_pools( + total_input: dict[str, DataArray], + metabolic_splits: dict[str, NDArray[np.float32]], +): + """Function to partition input biomass between the various litter pools. - Returns: - A dictionary containing the biomass flow into each of the five litter pools - (woody, above ground metabolic, above ground structural, below ground - metabolic and below ground structural) - """ + All deadwood is added to the woody litter pool. Reproductive biomass (fruits and + flowers) and leaves are split between the above ground metabolic and structural + pools based on lignin concentration and carbon nitrogen ratios. Root biomass is + split between the below ground metabolic and structural pools based on lignin + concentration and carbon nitrogen ratios. - # Calculate input to each of the five litter pools - woody_input = total_input["deadwood_mass"] - above_ground_metabolic_input = ( - metabolic_splits["leaves"] * total_input["leaf_mass"] - + metabolic_splits["reproductive"] * total_input["reprod_mass"] - ) - above_ground_strutural_input = (1 - metabolic_splits["leaves"]) * total_input[ - "leaf_mass" - ] + (1 - metabolic_splits["reproductive"]) * total_input["reprod_mass"] - below_ground_metabolic_input = ( - metabolic_splits["roots"] * total_input["root_mass"] - ) - below_ground_structural_input = (1 - metabolic_splits["roots"]) * total_input[ - "root_mass" - ] + Args: + total_input: The total pool size for each input pool [kg C m^-3], as well as + the chemical proportions (lignin, nitrogen and phosphorus) of each of + these pools [unitless]. + metabolic_splits: Dictionary containing the proportion of each input that + goes to the relevant metabolic pool. This is for three input types: + leaves, reproductive tissues and roots [unitless] - return { - "woody": woody_input, - "above_ground_metabolic": above_ground_metabolic_input, - "above_ground_structural": above_ground_strutural_input, - "below_ground_metabolic": below_ground_metabolic_input, - "below_ground_structural": below_ground_structural_input, - } + Returns: + A dictionary containing the biomass flow into each of the five litter pools + (woody, above ground metabolic, above ground structural, below ground + metabolic and below ground structural) + """ + + # Calculate input to each of the five litter pools + woody_input = total_input["deadwood_mass"] + above_ground_metabolic_input = ( + metabolic_splits["leaves"] * total_input["leaf_mass"] + + metabolic_splits["reproductive"] * total_input["reprod_mass"] + ) + above_ground_strutural_input = (1 - metabolic_splits["leaves"]) * total_input[ + "leaf_mass" + ] + (1 - metabolic_splits["reproductive"]) * total_input["reprod_mass"] + below_ground_metabolic_input = metabolic_splits["roots"] * total_input["root_mass"] + below_ground_structural_input = (1 - metabolic_splits["roots"]) * total_input[ + "root_mass" + ] + + return { + "woody": woody_input, + "above_ground_metabolic": above_ground_metabolic_input, + "above_ground_structural": above_ground_strutural_input, + "below_ground_metabolic": below_ground_metabolic_input, + "below_ground_structural": below_ground_structural_input, + } def split_pool_into_metabolic_and_structural_litter( diff --git a/virtual_ecosystem/models/litter/litter_model.py b/virtual_ecosystem/models/litter/litter_model.py index c202ee333..f23e99cc6 100644 --- a/virtual_ecosystem/models/litter/litter_model.py +++ b/virtual_ecosystem/models/litter/litter_model.py @@ -325,7 +325,8 @@ def update(self, time_index: int, **kwargs: Any) -> None: constants=self.model_constants, ) - # Find total plant inputs and how they get split between pools + # TODO - REALLY NEED TO THINK ABOUT WHAT'S BEING RETURNED HERE, SO THAT + # LITTER_CHEMISTRY HAS THE INFO IT NEEDS metabolic_splits, plant_inputs = ( self.input_partition.determine_all_plant_to_litter_flows( constants=self.model_constants, @@ -342,12 +343,14 @@ def update(self, time_index: int, **kwargs: Any) -> None: ).magnitude, ) - # TODO - THIS USES THE DATA OBJECT DIRECTLY SO HAS TO BE REVISED + # TODO - This will need to change when I've worked out what I'm actually doing + # with total_input # Calculate all the litter chemistry changes updated_chemistries = self.litter_chemistry.calculate_new_pool_chemistries( plant_inputs=plant_inputs, metabolic_splits=metabolic_splits, updated_pools=updated_pools, + total_input={}, ) # Calculate the total mineralisation rates from the litter From 8c653c9ab691b2677af3d6ed5833c3554af213c2 Mon Sep 17 00:00:00 2001 From: Jacob Cook Date: Wed, 9 Oct 2024 11:16:04 +0100 Subject: [PATCH 07/14] Collected all the litter details into a single dataclass --- tests/models/litter/conftest.py | 82 ++++--- tests/models/litter/test_carbon.py | 4 +- tests/models/litter/test_chemistry.py | 59 ++--- tests/models/litter/test_input_partition.py | 97 ++++---- virtual_ecosystem/models/litter/carbon.py | 21 +- virtual_ecosystem/models/litter/chemistry.py | 222 +++++++++--------- .../models/litter/input_partition.py | 170 +++++++++----- .../models/litter/litter_model.py | 20 +- 8 files changed, 379 insertions(+), 296 deletions(-) diff --git a/tests/models/litter/conftest.py b/tests/models/litter/conftest.py index 74e3668ec..b812418a0 100644 --- a/tests/models/litter/conftest.py +++ b/tests/models/litter/conftest.py @@ -129,28 +129,6 @@ def decay_rates(dummy_litter_data, fixture_core_components, post_consumption_poo return decay_rates -@pytest.fixture -def metabolic_splits(input_partition, total_litter_input): - """Metabolic splits for the various plant inputs.""" - - metabolic_splits = input_partition.calculate_metabolic_proportions_of_input( - total_input=total_litter_input, constants=LitterConsts - ) - - return metabolic_splits - - -@pytest.fixture -def plant_inputs(input_partition, metabolic_splits, total_litter_input): - """Plant inputs to each of the litter pools.""" - - plant_inputs = input_partition.partion_plant_inputs_between_pools( - metabolic_splits=metabolic_splits, total_input=total_litter_input - ) - - return plant_inputs - - @pytest.fixture def litter_chemistry(dummy_litter_data): """LitterChemistry object to be use throughout testing.""" @@ -172,23 +150,28 @@ def input_partition(dummy_litter_data): @pytest.fixture -def input_lignin(dummy_litter_data, plant_inputs, litter_chemistry): +def input_lignin(input_details): """Lignin proportion of the relevant input flows.""" + from virtual_ecosystem.models.litter.chemistry import ( + calculate_litter_input_lignin_concentrations, + ) - input_lignin = litter_chemistry.calculate_litter_input_lignin_concentrations( - plant_input_below_struct=plant_inputs["below_ground_structural"], - plant_input_above_struct=plant_inputs["above_ground_structural"], + input_lignin = calculate_litter_input_lignin_concentrations( + input_details=input_details ) return input_lignin @pytest.fixture -def input_c_n_ratios(dummy_litter_data, metabolic_splits, litter_chemistry): +def input_c_n_ratios(input_details): """Carbon:nitrogen ratio of each input flow.""" + from virtual_ecosystem.models.litter.chemistry import ( + calculate_litter_input_nitrogen_ratios, + ) - input_c_n_ratios = litter_chemistry.calculate_litter_input_nitrogen_ratios( - metabolic_splits=metabolic_splits, + input_c_n_ratios = calculate_litter_input_nitrogen_ratios( + input_details=input_details, struct_to_meta_nitrogen_ratio=LitterConsts.structural_to_metabolic_n_ratio, ) @@ -196,17 +179,35 @@ def input_c_n_ratios(dummy_litter_data, metabolic_splits, litter_chemistry): @pytest.fixture -def input_c_p_ratios(dummy_litter_data, metabolic_splits, litter_chemistry): +def input_c_p_ratios(input_details): """Carbon:nitrogen ratio of each input flow.""" + from virtual_ecosystem.models.litter.chemistry import ( + calculate_litter_input_phosphorus_ratios, + ) - input_c_p_ratios = litter_chemistry.calculate_litter_input_phosphorus_ratios( - metabolic_splits=metabolic_splits, + input_c_p_ratios = calculate_litter_input_phosphorus_ratios( + input_details=input_details, struct_to_meta_phosphorus_ratio=LitterConsts.structural_to_metabolic_p_ratio, ) return input_c_p_ratios +@pytest.fixture +def metabolic_splits(total_litter_input): + """Metabolic splits for the various plant inputs.""" + from virtual_ecosystem.models.litter.input_partition import ( + calculate_metabolic_proportions_of_input, + ) + + metabolic_splits = calculate_metabolic_proportions_of_input( + total_input=total_litter_input, + constants=LitterConsts, + ) + + return metabolic_splits + + @pytest.fixture def post_consumption_pools(dummy_litter_data): """Pool sizes after animal consumption for each litter pool.""" @@ -246,15 +247,28 @@ def total_litter_input(input_partition): @pytest.fixture -def updated_pools(dummy_litter_data, decay_rates, plant_inputs, post_consumption_pools): +def updated_pools( + dummy_litter_data, decay_rates, post_consumption_pools, input_details +): """Updated carbon mass of each pool.""" from virtual_ecosystem.models.litter.carbon import calculate_updated_pools updated_pools = calculate_updated_pools( post_consumption_pools=post_consumption_pools, decay_rates=decay_rates, - plant_inputs=plant_inputs, + input_details=input_details, update_interval=2.0, ) return updated_pools + + +@pytest.fixture +def input_details(input_partition): + """Complete set of details for inputs to the litter model.""" + + input_details = input_partition.determine_all_plant_to_litter_flows( + constants=LitterConsts + ) + + return input_details diff --git a/tests/models/litter/test_carbon.py b/tests/models/litter/test_carbon.py index e452477a8..d889fb88f 100644 --- a/tests/models/litter/test_carbon.py +++ b/tests/models/litter/test_carbon.py @@ -140,7 +140,7 @@ def test_calculate_total_C_mineralised(decay_rates): def test_calculate_updated_pools( - dummy_litter_data, decay_rates, plant_inputs, post_consumption_pools + dummy_litter_data, decay_rates, post_consumption_pools, input_details ): """Test that the function to calculate the pool values after the update works.""" from virtual_ecosystem.models.litter.carbon import calculate_updated_pools @@ -156,7 +156,7 @@ def test_calculate_updated_pools( actual_pools = calculate_updated_pools( post_consumption_pools=post_consumption_pools, decay_rates=decay_rates, - plant_inputs=plant_inputs, + input_details=input_details, update_interval=2.0, ) diff --git a/tests/models/litter/test_chemistry.py b/tests/models/litter/test_chemistry.py index 2a0213114..1c9ea8051 100644 --- a/tests/models/litter/test_chemistry.py +++ b/tests/models/litter/test_chemistry.py @@ -27,7 +27,7 @@ def test_calculate_litter_chemistry_factor(): def test_calculate_new_pool_chemistries( - dummy_litter_data, plant_inputs, metabolic_splits, updated_pools, litter_chemistry + dummy_litter_data, input_details, updated_pools, litter_chemistry ): """Test that function to calculate updated pool chemistries works correctly.""" @@ -48,8 +48,7 @@ def test_calculate_new_pool_chemistries( } actual_chemistries = litter_chemistry.calculate_new_pool_chemistries( - plant_inputs=plant_inputs, - metabolic_splits=metabolic_splits, + input_details=input_details, updated_pools=updated_pools, ) @@ -60,7 +59,7 @@ def test_calculate_new_pool_chemistries( def test_calculate_lignin_updates( - dummy_litter_data, plant_inputs, input_lignin, updated_pools, litter_chemistry + input_lignin, updated_pools, litter_chemistry, input_details ): """Test that the function to calculate the lignin updates works as expected.""" @@ -72,7 +71,7 @@ def test_calculate_lignin_updates( actual_lignin = litter_chemistry.calculate_lignin_updates( input_lignin=input_lignin, - plant_inputs=plant_inputs, + input_details=input_details, updated_pools=updated_pools, ) @@ -106,7 +105,7 @@ def test_calculate_change_in_chemical_concentration( def test_calculate_c_n_ratio_updates( - dummy_litter_data, plant_inputs, input_c_n_ratios, updated_pools, litter_chemistry + dummy_litter_data, input_details, input_c_n_ratios, updated_pools, litter_chemistry ): """Test that calculation of C:N ratio updates works properly.""" @@ -119,7 +118,7 @@ def test_calculate_c_n_ratio_updates( } actual_change = litter_chemistry.calculate_c_n_ratio_updates( - plant_inputs=plant_inputs, + input_details=input_details, input_c_n_ratios=input_c_n_ratios, updated_pools=updated_pools, ) @@ -131,7 +130,7 @@ def test_calculate_c_n_ratio_updates( def test_calculate_c_p_ratio_updates( - dummy_litter_data, plant_inputs, input_c_p_ratios, updated_pools, litter_chemistry + dummy_litter_data, input_details, input_c_p_ratios, updated_pools, litter_chemistry ): """Test that calculation of C:P ratio updates works properly.""" @@ -144,7 +143,7 @@ def test_calculate_c_p_ratio_updates( } actual_change = litter_chemistry.calculate_c_p_ratio_updates( - plant_inputs=plant_inputs, + input_details=input_details, input_c_p_ratios=input_c_p_ratios, updated_pools=updated_pools, ) @@ -181,18 +180,18 @@ def test_calculate_P_mineralisation(dummy_litter_data, decay_rates, litter_chemi assert np.allclose(actual_p_mineral, expected_p_mineral) -def test_calculate_litter_input_lignin_concentrations( - dummy_litter_data, plant_inputs, litter_chemistry -): +def test_calculate_litter_input_lignin_concentrations(dummy_litter_data, input_details): """Check calculation of lignin concentrations of each plant flow to litter.""" + from virtual_ecosystem.models.litter.chemistry import ( + calculate_litter_input_lignin_concentrations, + ) expected_woody = [0.233, 0.545, 0.612, 0.378] expected_concs_above_struct = [0.24971768, 0.22111396, 0.51122474, 0.56571041] expected_concs_below_struct = [0.48590258, 0.56412613, 0.54265483, 0.67810978] - actual_concs = litter_chemistry.calculate_litter_input_lignin_concentrations( - plant_input_below_struct=plant_inputs["below_ground_structural"], - plant_input_above_struct=plant_inputs["above_ground_structural"], + actual_concs = calculate_litter_input_lignin_concentrations( + input_details=input_details, ) assert np.allclose(actual_concs["woody"], expected_woody) @@ -200,10 +199,11 @@ def test_calculate_litter_input_lignin_concentrations( assert np.allclose(actual_concs["below_structural"], expected_concs_below_struct) -def test_calculate_litter_input_nitrogen_ratios( - dummy_litter_data, metabolic_splits, litter_chemistry -): +def test_calculate_litter_input_nitrogen_ratios(dummy_litter_data, input_details): """Check function to calculate the C:N ratios of input to each litter pool works.""" + from virtual_ecosystem.models.litter.chemistry import ( + calculate_litter_input_nitrogen_ratios, + ) expected_c_n_ratios = { "woody": [60.7, 57.9, 73.1, 55.1], @@ -213,8 +213,8 @@ def test_calculate_litter_input_nitrogen_ratios( "above_structural": [42.5018709, 69.9028550, 64.6044513, 57.7622747], } - actual_c_n_ratios = litter_chemistry.calculate_litter_input_nitrogen_ratios( - metabolic_splits=metabolic_splits, + actual_c_n_ratios = calculate_litter_input_nitrogen_ratios( + input_details=input_details, struct_to_meta_nitrogen_ratio=LitterConsts.structural_to_metabolic_n_ratio, ) @@ -224,10 +224,11 @@ def test_calculate_litter_input_nitrogen_ratios( assert np.allclose(actual_c_n_ratios[key], expected_c_n_ratios[key]) -def test_calculate_litter_input_phosphorus_ratios( - dummy_litter_data, metabolic_splits, litter_chemistry -): +def test_calculate_litter_input_phosphorus_ratios(dummy_litter_data, input_details): """Check function to calculate the C:P ratios of input to each litter pool works.""" + from virtual_ecosystem.models.litter.chemistry import ( + calculate_litter_input_phosphorus_ratios, + ) expected_c_p_ratios = { "woody": [856.5, 675.4, 933.2, 888.8], @@ -237,8 +238,8 @@ def test_calculate_litter_input_phosphorus_ratios( "above_structural": [1118.95921, 343.440873, 825.333331, 387.658509], } - actual_c_p_ratios = litter_chemistry.calculate_litter_input_phosphorus_ratios( - metabolic_splits=metabolic_splits, + actual_c_p_ratios = calculate_litter_input_phosphorus_ratios( + input_details=input_details, struct_to_meta_phosphorus_ratio=LitterConsts.structural_to_metabolic_p_ratio, ) @@ -249,7 +250,7 @@ def test_calculate_litter_input_phosphorus_ratios( def test_calculate_nutrient_split_between_litter_pools( - dummy_litter_data, metabolic_splits + dummy_litter_data, input_details ): """Check the function to calculate the nutrient split between litter pools.""" from virtual_ecosystem.models.litter.chemistry import ( @@ -261,7 +262,7 @@ def test_calculate_nutrient_split_between_litter_pools( actual_meta_c_n, actual_struct_c_n = calculate_nutrient_split_between_litter_pools( input_c_nut_ratio=dummy_litter_data["root_turnover_c_n_ratio"], - metabolic_split=metabolic_splits["roots"], + metabolic_split=input_details.roots_meta_split, struct_to_meta_nutrient_ratio=LitterConsts.structural_to_metabolic_n_ratio, ) @@ -278,7 +279,7 @@ def test_calculate_nutrient_split_between_litter_pools( assert np.allclose( dummy_litter_data["root_turnover_c_n_ratio"], ( - actual_meta_c_n * metabolic_splits["roots"] - + actual_struct_c_n * (1 - metabolic_splits["roots"]) + actual_meta_c_n * input_details.roots_meta_split + + actual_struct_c_n * (1 - input_details.roots_meta_split) ), ) diff --git a/tests/models/litter/test_input_partition.py b/tests/models/litter/test_input_partition.py index 6e59e5884..3e2cd4d00 100644 --- a/tests/models/litter/test_input_partition.py +++ b/tests/models/litter/test_input_partition.py @@ -20,35 +20,45 @@ def test_input_partition_initialisation(dummy_litter_data): def test_determine_all_plant_to_litter_flows(input_partition, total_litter_input): """Test that function to determine plant to litter flows works correctly.""" - - expected_proportions = { - "leaves": [0.8123412282, 0.7504823457, 0.4509559749, 0.0852205423], - "reproductive": [0.8462925685, 0.833489905, 0.83196046, 0.8390536408], - "roots": [0.588394858, 0.379571377, 0.5024461477, 0.410125012], - } - - expected_splits = { - "woody": [0.075, 0.099, 0.063, 0.033], - "above_ground_metabolic": [0.02449646, 0.00805233, 0.012876799, 0.005805332], - "above_ground_structural": [0.00553354, 0.00184767, 0.01352320, 0.02914467], - "below_ground_metabolic": [0.01588666, 0.00797100, 0.00015073, 0.01021211], - "below_ground_structural": [0.01111334, 0.013029, 0.00014927, 0.01468789], + from dataclasses import asdict + + expected_details = { + "leaves_meta_split": [0.8123412282, 0.7504823457, 0.4509559749, 0.0852205423], + "reproduct_meta_split": [0.8462925685, 0.833489905, 0.83196046, 0.8390536408], + "roots_meta_split": [0.588394858, 0.379571377, 0.5024461477, 0.410125012], + "input_woody": [0.075, 0.099, 0.063, 0.033], + "input_above_metabolic": [0.02449646, 0.00805233, 0.0128768, 0.00580533], + "input_above_structural": [0.00553354, 0.00184767, 0.0135232, 0.0291447], + "input_below_metabolic": [0.01588666, 0.007971, 0.00015073, 0.01021211], + "input_below_structural": [0.01111334, 0.013029, 0.00014927, 0.01468789], + "leaf_mass": [0.02703, 0.0024, 0.02385, 0.0312], + "root_mass": [0.027, 0.021, 0.0003, 0.0249], + "deadwood_mass": [0.075, 0.099, 0.063, 0.033], + "reprod_mass": [0.003, 0.0075, 0.00255, 0.00375], + "leaf_lignin": [0.05008879, 0.10125, 0.29641509, 0.53971154], + "root_lignin": [0.2, 0.35, 0.27, 0.4], + "deadwood_lignin": [0.233, 0.545, 0.612, 0.378], + "reprod_lignin": [0.01, 0.03, 0.04, 0.02], + "leaf_nitrogen": [15.00899, 32.5, 40.710063, 53.929808], + "root_nitrogen": [30.3, 45.6, 43.3, 37.1], + "deadwood_nitrogen": [60.7, 57.9, 73.1, 55.1], + "reprod_nitrogen": [12.5, 23.8, 15.7, 18.2], + "leaf_phosphorus": [414.77525, 342.625, 528.24654, 384.29231], + "root_phosphorus": [656.7, 450.6, 437.3, 371.9], + "deadwood_phosphorus": [856.5, 675.4, 933.2, 888.8], + "reprod_phosphorus": [125.5, 105.0, 145.0, 189.2], } - actual_proportions, actual_splits = ( - input_partition.determine_all_plant_to_litter_flows(constants=LitterConsts) + actual_details = input_partition.determine_all_plant_to_litter_flows( + constants=LitterConsts ) + actual_details = asdict(actual_details) # Check that all keys match and have correct values for both dictionaries - assert set(expected_proportions.keys()) == set(actual_proportions.keys()) - - for key in actual_proportions.keys(): - assert np.allclose(actual_proportions[key], expected_proportions[key]) + assert set(expected_details.keys()) == set(actual_details.keys()) - assert set(expected_splits.keys()) == set(actual_splits.keys()) - - for key in actual_splits.keys(): - assert np.allclose(actual_splits[key], expected_splits[key]) + for key in actual_details.keys(): + assert np.allclose(actual_details[key], expected_details[key]) def test_combine_input_sources(input_partition): @@ -83,14 +93,17 @@ def test_combine_input_sources(input_partition): def test_calculate_metabolic_proportions_of_input(input_partition, total_litter_input): """Test that function to calculate metabolic input proportions works as expected.""" + from virtual_ecosystem.models.litter.input_partition import ( + calculate_metabolic_proportions_of_input, + ) expected_proportions = { - "leaves": [0.8123412282, 0.7504823457, 0.4509559749, 0.0852205423], - "reproductive": [0.8462925685, 0.833489905, 0.83196046, 0.8390536408], - "roots": [0.588394858, 0.379571377, 0.5024461477, 0.410125012], + "leaves_meta_split": [0.8123412282, 0.7504823457, 0.4509559749, 0.0852205423], + "reproduct_meta_split": [0.8462925685, 0.833489905, 0.83196046, 0.8390536408], + "roots_meta_split": [0.588394858, 0.379571377, 0.5024461477, 0.410125012], } - actual_proportions = input_partition.calculate_metabolic_proportions_of_input( + actual_proportions = calculate_metabolic_proportions_of_input( total_input=total_litter_input, constants=LitterConsts ) @@ -100,26 +113,28 @@ def test_calculate_metabolic_proportions_of_input(input_partition, total_litter_ assert np.allclose(actual_proportions[key], expected_proportions[key]) -def test_partion_plant_inputs_between_pools( - input_partition, metabolic_splits, total_litter_input -): +def test_partion_plant_inputs_between_pools(metabolic_splits, total_litter_input): """Check function to partition inputs into litter pools works as expected.""" + from virtual_ecosystem.models.litter.input_partition import ( + partion_plant_inputs_between_pools, + ) - expected_woody = [0.075, 0.099, 0.063, 0.033] - expected_above_meta = [0.02449646, 0.00805233, 0.012876799, 0.005805332] - expected_above_struct = [0.00553354, 0.00184767, 0.01352320, 0.02914467] - expected_below_meta = [0.01588666, 0.00797100, 0.00015073, 0.01021211] - expected_below_struct = [0.01111334, 0.013029, 0.00014927, 0.01468789] + expected_inputs = { + "input_woody": [0.075, 0.099, 0.063, 0.033], + "input_above_metabolic": [0.02449646, 0.00805233, 0.0128768, 0.00580533], + "input_above_structural": [0.00553354, 0.00184767, 0.0135232, 0.02914467], + "input_below_metabolic": [0.01588666, 0.007971, 0.00015073, 0.01021211], + "input_below_structural": [0.01111334, 0.013029, 0.00014927, 0.01468789], + } - actual_splits = input_partition.partion_plant_inputs_between_pools( + actual_inputs = partion_plant_inputs_between_pools( total_input=total_litter_input, metabolic_splits=metabolic_splits ) - assert np.allclose(actual_splits["woody"], expected_woody) - assert np.allclose(actual_splits["above_ground_metabolic"], expected_above_meta) - assert np.allclose(actual_splits["above_ground_structural"], expected_above_struct) - assert np.allclose(actual_splits["below_ground_metabolic"], expected_below_meta) - assert np.allclose(actual_splits["below_ground_structural"], expected_below_struct) + assert set(expected_inputs.keys()) == set(actual_inputs.keys()) + + for key in actual_inputs.keys(): + assert np.allclose(actual_inputs[key], expected_inputs[key]) def test_split_pool_into_metabolic_and_structural_litter(dummy_litter_data): diff --git a/virtual_ecosystem/models/litter/carbon.py b/virtual_ecosystem/models/litter/carbon.py index a968dbdbf..2460dd19f 100644 --- a/virtual_ecosystem/models/litter/carbon.py +++ b/virtual_ecosystem/models/litter/carbon.py @@ -24,6 +24,7 @@ from virtual_ecosystem.models.litter.env_factors import ( calculate_environmental_factors, ) +from virtual_ecosystem.models.litter.input_partition import InputDetails def calculate_post_consumption_pools( @@ -218,7 +219,7 @@ def calculate_total_C_mineralised( def calculate_updated_pools( post_consumption_pools: dict[str, NDArray[np.float32]], decay_rates: dict[str, NDArray[np.float32]], - plant_inputs: dict[str, NDArray[np.float32]], + input_details: InputDetails, update_interval: float, ) -> dict[str, NDArray[np.float32]]: """Calculate the updated mass of each litter pool. @@ -231,8 +232,10 @@ def calculate_updated_pools( subtracted [kg C m^-2] decay_rates: Dictionary containing the rates of decay for all 5 litter pools [kg C m^-2 day^-1] - plant_inputs: Dictionary containing the amount of each litter type that is added - from the plant model in this time step [kg C m^-2] + input_details: An InputDetails instance containing the total input of each plant + biomass type, the proportion of the input that goes to the relevant + metabolic pool for each input type (expect deadwood) and the total input + into each litter pool. update_interval: Interval that the litter pools are being updated for [days] constants: Set of constants for the litter model @@ -244,17 +247,19 @@ def calculate_updated_pools( # Net pool changes are found by combining input and decay rates, and then # multiplying by the update time step. - change_in_metabolic_above = plant_inputs["above_ground_metabolic"] - ( + change_in_metabolic_above = input_details.input_above_metabolic - ( decay_rates["metabolic_above"] * update_interval ) - change_in_structural_above = plant_inputs["above_ground_structural"] - ( + change_in_structural_above = input_details.input_above_structural - ( decay_rates["structural_above"] * update_interval ) - change_in_woody = plant_inputs["woody"] - (decay_rates["woody"] * update_interval) - change_in_metabolic_below = plant_inputs["below_ground_metabolic"] - ( + change_in_woody = input_details.input_woody - ( + decay_rates["woody"] * update_interval + ) + change_in_metabolic_below = input_details.input_below_metabolic - ( decay_rates["metabolic_below"] * update_interval ) - change_in_structural_below = plant_inputs["below_ground_structural"] - ( + change_in_structural_below = input_details.input_below_structural - ( decay_rates["structural_below"] * update_interval ) diff --git a/virtual_ecosystem/models/litter/chemistry.py b/virtual_ecosystem/models/litter/chemistry.py index c029048ca..d47c8b350 100644 --- a/virtual_ecosystem/models/litter/chemistry.py +++ b/virtual_ecosystem/models/litter/chemistry.py @@ -19,6 +19,7 @@ from virtual_ecosystem.core.data import Data from virtual_ecosystem.models.litter.constants import LitterConsts +from virtual_ecosystem.models.litter.input_partition import InputDetails class LitterChemistry: @@ -36,10 +37,8 @@ def __init__(self, data: Data, constants: LitterConsts): def calculate_new_pool_chemistries( self, - plant_inputs: dict[str, NDArray[np.float32]], - metabolic_splits: dict[str, NDArray[np.float32]], updated_pools: dict[str, NDArray[np.float32]], - total_input: dict[str, DataArray], + input_details: InputDetails, ) -> dict[str, DataArray]: """Method to calculate the updated chemistry of each litter pool. @@ -48,48 +47,40 @@ def calculate_new_pool_chemistries( lignin, so it is only updated for those pools. Args: - plant_inputs: Dictionary containing the amount of each litter type that is - added from the plant model in this time step [kg C m^-2] - metabolic_splits: Dictionary containing the proportion of each input that - goes to the relevant metabolic pool. This is for three input types: - leaves, reproductive tissues and roots [unitless] updated_pools: Dictionary containing the updated pool densities for all 5 litter pools [kg C m^-2] - total_input: The total pool size for each input pool [kg C m^-3], as well as - the chemical proportions (lignin, nitrogen and phosphorus) of each of - these pools [unitless]. + input_details: An InputDetails instance containing the total input of each + plant biomass type, the proportion of the input that goes to the + relevant metabolic pool for each input type (expect deadwood) and the + total input into each litter pool. """ # Find lignin and nitrogen contents of the litter input flows input_lignin = calculate_litter_input_lignin_concentrations( - plant_input_above_struct=plant_inputs["above_ground_structural"], - plant_input_below_struct=plant_inputs["below_ground_structural"], - total_input=total_input, + input_details=input_details, ) input_c_n_ratios = calculate_litter_input_nitrogen_ratios( - metabolic_splits=metabolic_splits, - total_input=total_input, + input_details=input_details, struct_to_meta_nitrogen_ratio=self.structural_to_metabolic_n_ratio, ) input_c_p_ratios = calculate_litter_input_phosphorus_ratios( - metabolic_splits=metabolic_splits, - total_input=total_input, + input_details=input_details, struct_to_meta_phosphorus_ratio=self.structural_to_metabolic_p_ratio, ) # Then use to find the changes change_in_lignin = self.calculate_lignin_updates( - plant_inputs=plant_inputs, + input_details=input_details, input_lignin=input_lignin, updated_pools=updated_pools, ) change_in_c_n_ratios = self.calculate_c_n_ratio_updates( - plant_inputs=plant_inputs, + input_details=input_details, input_c_n_ratios=input_c_n_ratios, updated_pools=updated_pools, ) change_in_c_p_ratios = self.calculate_c_p_ratio_updates( - plant_inputs=plant_inputs, + input_details=input_details, input_c_p_ratios=input_c_p_ratios, updated_pools=updated_pools, ) @@ -130,7 +121,7 @@ def calculate_new_pool_chemistries( def calculate_lignin_updates( self, - plant_inputs: dict[str, NDArray[np.float32]], + input_details: InputDetails, input_lignin: dict[str, NDArray[np.float32]], updated_pools: dict[str, NDArray[np.float32]], ) -> dict[str, NDArray[np.float32]]: @@ -141,8 +132,10 @@ def calculate_lignin_updates( used in an integration process. Args: - plant_inputs: Dictionary containing the amount of each litter type that is - added from the plant model in this time step [kg C m^-2] + input_details: An InputDetails instance containing the total input of each + plant biomass type, the proportion of the input that goes to the + relevant metabolic pool for each input type (expect deadwood) and the + total input into each litter pool. input_lignin: Dictionary containing the lignin concentration of the input to each of the three lignin containing litter pools [kg lignin kg C^-1] updated_pools: Dictionary containing the updated pool densities for all 5 @@ -155,19 +148,19 @@ def calculate_lignin_updates( """ change_in_lignin_above_structural = calculate_change_in_chemical_concentration( - input_carbon=plant_inputs["above_ground_structural"], + input_carbon=input_details.input_above_structural, updated_pool_carbon=updated_pools["above_structural"], input_conc=input_lignin["above_structural"], old_pool_conc=self.data["lignin_above_structural"].to_numpy(), ) change_in_lignin_woody = calculate_change_in_chemical_concentration( - input_carbon=plant_inputs["woody"], + input_carbon=input_details.input_woody, updated_pool_carbon=updated_pools["woody"], input_conc=input_lignin["woody"], old_pool_conc=self.data["lignin_woody"].to_numpy(), ) change_in_lignin_below_structural = calculate_change_in_chemical_concentration( - input_carbon=plant_inputs["below_ground_structural"], + input_carbon=input_details.input_below_structural, updated_pool_carbon=updated_pools["below_structural"], input_conc=input_lignin["below_structural"], old_pool_conc=self.data["lignin_below_structural"].to_numpy(), @@ -181,7 +174,7 @@ def calculate_lignin_updates( def calculate_c_n_ratio_updates( self, - plant_inputs: dict[str, NDArray[np.float32]], + input_details: InputDetails, input_c_n_ratios: dict[str, NDArray[np.float32]], updated_pools: dict[str, NDArray[np.float32]], ) -> dict[str, NDArray[np.float32]]: @@ -191,8 +184,10 @@ def calculate_c_n_ratio_updates( be used in an integration process. Args: - plant_inputs: Dictionary containing the amount of each litter type that is - added from the plant model in this time step [kg C m^-2] + input_details: An InputDetails instance containing the total input of each + plant biomass type, the proportion of the input that goes to the + relevant metabolic pool for each input type (expect deadwood) and the + total input into each litter pool. input_c_n_ratios: Dictionary containing the carbon to nitrogen ratios of the input to each of the litter pools [unitless] updated_pools: Dictionary containing the updated pool densities for all 5 @@ -204,31 +199,31 @@ def calculate_c_n_ratio_updates( """ change_in_n_above_metabolic = calculate_change_in_chemical_concentration( - input_carbon=plant_inputs["above_ground_metabolic"], + input_carbon=input_details.input_above_metabolic, updated_pool_carbon=updated_pools["above_metabolic"], input_conc=input_c_n_ratios["above_metabolic"], old_pool_conc=self.data["c_n_ratio_above_metabolic"].to_numpy(), ) change_in_n_above_structural = calculate_change_in_chemical_concentration( - input_carbon=plant_inputs["above_ground_structural"], + input_carbon=input_details.input_above_structural, updated_pool_carbon=updated_pools["above_structural"], input_conc=input_c_n_ratios["above_structural"], old_pool_conc=self.data["c_n_ratio_above_structural"].to_numpy(), ) change_in_n_woody = calculate_change_in_chemical_concentration( - input_carbon=plant_inputs["woody"], + input_carbon=input_details.input_woody, updated_pool_carbon=updated_pools["woody"], input_conc=input_c_n_ratios["woody"], old_pool_conc=self.data["c_n_ratio_woody"].to_numpy(), ) change_in_n_below_metabolic = calculate_change_in_chemical_concentration( - input_carbon=plant_inputs["below_ground_metabolic"], + input_carbon=input_details.input_below_metabolic, updated_pool_carbon=updated_pools["below_metabolic"], input_conc=input_c_n_ratios["below_metabolic"], old_pool_conc=self.data["c_n_ratio_below_metabolic"].to_numpy(), ) change_in_n_below_structural = calculate_change_in_chemical_concentration( - input_carbon=plant_inputs["below_ground_structural"], + input_carbon=input_details.input_below_structural, updated_pool_carbon=updated_pools["below_structural"], input_conc=input_c_n_ratios["below_structural"], old_pool_conc=self.data["c_n_ratio_below_structural"].to_numpy(), @@ -244,7 +239,7 @@ def calculate_c_n_ratio_updates( def calculate_c_p_ratio_updates( self, - plant_inputs: dict[str, NDArray[np.float32]], + input_details: InputDetails, input_c_p_ratios: dict[str, NDArray[np.float32]], updated_pools: dict[str, NDArray[np.float32]], ) -> dict[str, NDArray[np.float32]]: @@ -254,8 +249,10 @@ def calculate_c_p_ratio_updates( be used in an integration process. Args: - plant_inputs: Dictionary containing the amount of each litter type that is - added from the plant model in this time step [kg C m^-2] + input_details: An InputDetails instance containing the total input of each + plant biomass type, the proportion of the input that goes to the + relevant metabolic pool for each input type (expect deadwood) and the + total input into each litter pool. input_c_p_ratios: Dictionary containing the carbon to phosphorus ratios of the input to each of the litter pools [unitless] updated_pools: Dictionary containing the updated pool densities for all 5 @@ -267,31 +264,31 @@ def calculate_c_p_ratio_updates( """ change_in_p_above_metabolic = calculate_change_in_chemical_concentration( - input_carbon=plant_inputs["above_ground_metabolic"], + input_carbon=input_details.input_above_metabolic, updated_pool_carbon=updated_pools["above_metabolic"], input_conc=input_c_p_ratios["above_metabolic"], old_pool_conc=self.data["c_p_ratio_above_metabolic"].to_numpy(), ) change_in_p_above_structural = calculate_change_in_chemical_concentration( - input_carbon=plant_inputs["above_ground_structural"], + input_carbon=input_details.input_above_structural, updated_pool_carbon=updated_pools["above_structural"], input_conc=input_c_p_ratios["above_structural"], old_pool_conc=self.data["c_p_ratio_above_structural"].to_numpy(), ) change_in_p_woody = calculate_change_in_chemical_concentration( - input_carbon=plant_inputs["woody"], + input_carbon=input_details.input_woody, updated_pool_carbon=updated_pools["woody"], input_conc=input_c_p_ratios["woody"], old_pool_conc=self.data["c_p_ratio_woody"].to_numpy(), ) change_in_p_below_metabolic = calculate_change_in_chemical_concentration( - input_carbon=plant_inputs["below_ground_metabolic"], + input_carbon=input_details.input_below_metabolic, updated_pool_carbon=updated_pools["below_metabolic"], input_conc=input_c_p_ratios["below_metabolic"], old_pool_conc=self.data["c_p_ratio_below_metabolic"].to_numpy(), ) change_in_p_below_structural = calculate_change_in_chemical_concentration( - input_carbon=plant_inputs["below_ground_structural"], + input_carbon=input_details.input_below_structural, updated_pool_carbon=updated_pools["below_structural"], input_conc=input_c_p_ratios["below_structural"], old_pool_conc=self.data["c_p_ratio_below_structural"].to_numpy(), @@ -411,9 +408,7 @@ def calculate_P_mineralisation( def calculate_litter_input_lignin_concentrations( - plant_input_below_struct: NDArray[np.float32], - plant_input_above_struct: NDArray[np.float32], - total_input: dict[str, DataArray], + input_details: InputDetails, ) -> dict[str, NDArray[np.float32]]: """Calculate the concentration of lignin for each plant biomass to litter flow. @@ -432,13 +427,10 @@ def calculate_litter_input_lignin_concentrations( then converted to a back into a concentration. Args: - plant_input_below_struct: Plant input to below ground structural litter pool - [kg C m^-2] - plant_input_above_struct: Plant input to above ground structural litter pool - [kg C m^-2] - total_input: The total pool size for each input pool [kg C m^-3], as well as - the chemical proportions (lignin, nitrogen and phosphorus) of each of these - pools [unitless]. + input_details: An InputDetails instance containing the total input of each + plant biomass type, the proportion of the input that goes to the relevant + metabolic pool for each input type (expect deadwood) and the total input + into each litter pool. Returns: Dictionary containing the lignin concentration of the input to each of the @@ -446,27 +438,28 @@ def calculate_litter_input_lignin_concentrations( structural) [kg lignin kg C^-1] """ - lignin_proportion_woody = total_input["deadwood_mass"] + lignin_proportion_woody = input_details.deadwood_mass lignin_proportion_below_structural = ( - total_input["root_lignin"] * total_input["root_mass"] / plant_input_below_struct + input_details.root_lignin + * input_details.root_mass + / input_details.input_below_structural ) lignin_proportion_above_structural = ( - (total_input["leaf_lignin"] * total_input["leaf_mass"]) - + (total_input["reprod_lignin"] * total_input["reprod_mass"]) - ) / plant_input_above_struct + (input_details.leaf_lignin * input_details.leaf_mass) + + (input_details.reprod_lignin * input_details.reprod_mass) + ) / input_details.input_above_structural return { - "woody": lignin_proportion_woody.to_numpy(), - "below_structural": lignin_proportion_below_structural.to_numpy(), - "above_structural": lignin_proportion_above_structural.to_numpy(), + "woody": lignin_proportion_woody, + "below_structural": lignin_proportion_below_structural, + "above_structural": lignin_proportion_above_structural, } def calculate_litter_input_nitrogen_ratios( - metabolic_splits: dict[str, NDArray[np.float32]], - total_input: dict[str, DataArray], + input_details: InputDetails, struct_to_meta_nitrogen_ratio: float, ) -> dict[str, NDArray[np.float32]]: """Calculate the carbon to nitrogen ratio for each plant biomass to litter flow. @@ -479,12 +472,10 @@ def calculate_litter_input_nitrogen_ratios( tissue turnover) must be taken. Args: - metabolic_splits: Dictionary containing the proportion of each input that - goes to the relevant metabolic pool. This is for three input types: - leaves, reproductive tissues and roots [unitless] - total_input: The total pool size for each input pool [kg C m^-3], as well as - the chemical proportions (lignin, nitrogen and phosphorus) of each of these - pools [unitless]. + input_details: An InputDetails instance containing the total input of each + plant biomass type, the proportion of the input that goes to the relevant + metabolic pool for each input type (expect deadwood) and the total input + into each litter pool. struct_to_meta_nitrogen_ratio: Ratio of the carbon to nitrogen ratios of structural vs metabolic litter pools [unitless] @@ -496,56 +487,60 @@ def calculate_litter_input_nitrogen_ratios( # Calculate c_n_ratio split for each (non-wood) input biomass type root_c_n_ratio_meta, root_c_n_ratio_struct = ( calculate_nutrient_split_between_litter_pools( - input_c_nut_ratio=total_input["root_nitrogen"].to_numpy(), - metabolic_split=metabolic_splits["roots"], + input_c_nut_ratio=input_details.root_nitrogen, + metabolic_split=input_details.roots_meta_split, struct_to_meta_nutrient_ratio=struct_to_meta_nitrogen_ratio, ) ) leaf_c_n_ratio_meta, leaf_c_n_ratio_struct = ( calculate_nutrient_split_between_litter_pools( - input_c_nut_ratio=total_input["leaf_nitrogen"].to_numpy(), - metabolic_split=metabolic_splits["leaves"], + input_c_nut_ratio=input_details.leaf_nitrogen, + metabolic_split=input_details.leaves_meta_split, struct_to_meta_nutrient_ratio=struct_to_meta_nitrogen_ratio, ) ) reprod_c_n_ratio_meta, reprod_c_n_ratio_struct = ( calculate_nutrient_split_between_litter_pools( - input_c_nut_ratio=total_input["reprod_nitrogen"].to_numpy(), - metabolic_split=metabolic_splits["reproductive"], + input_c_nut_ratio=input_details.reprod_nitrogen, + metabolic_split=input_details.reproduct_meta_split, struct_to_meta_nutrient_ratio=struct_to_meta_nitrogen_ratio, ) ) c_n_ratio_below_metabolic = root_c_n_ratio_meta c_n_ratio_below_structural = root_c_n_ratio_struct - c_n_ratio_woody = total_input["deadwood_nitrogen"].to_numpy() + c_n_ratio_woody = input_details.deadwood_nitrogen # Inputs with multiple sources have to be weighted c_n_ratio_above_metabolic = np.divide( - (leaf_c_n_ratio_meta * total_input["leaf_mass"] * metabolic_splits["leaves"]) + ( + leaf_c_n_ratio_meta + * input_details.leaf_mass + * input_details.leaves_meta_split + ) + ( reprod_c_n_ratio_meta - * total_input["reprod_mass"] - * metabolic_splits["reproductive"] + * input_details.reprod_mass + * input_details.reproduct_meta_split ), - (total_input["leaf_mass"] * metabolic_splits["leaves"]) - + (total_input["reprod_mass"] * metabolic_splits["reproductive"]), + (input_details.leaf_mass * input_details.leaves_meta_split) + + (input_details.reprod_mass * input_details.reproduct_meta_split), ) c_n_ratio_above_structural = np.divide( ( leaf_c_n_ratio_struct - * total_input["leaf_mass"] - * (1 - metabolic_splits["leaves"]) + * input_details.leaf_mass + * (1 - input_details.leaves_meta_split) ) + ( reprod_c_n_ratio_struct - * total_input["reprod_mass"] - * (1 - metabolic_splits["reproductive"]) + * input_details.reprod_mass + * (1 - input_details.reproduct_meta_split) ), - (total_input["leaf_mass"] * (1 - metabolic_splits["leaves"])) - + (total_input["reprod_mass"] * (1 - metabolic_splits["reproductive"])), + (input_details.leaf_mass * (1 - input_details.leaves_meta_split)) + + (input_details.reprod_mass * (1 - input_details.reproduct_meta_split)), ) return { @@ -558,8 +553,7 @@ def calculate_litter_input_nitrogen_ratios( def calculate_litter_input_phosphorus_ratios( - metabolic_splits: dict[str, NDArray[np.float32]], - total_input: dict[str, DataArray], + input_details: InputDetails, struct_to_meta_phosphorus_ratio: float, ) -> dict[str, NDArray[np.float32]]: """Calculate carbon to phosphorus ratio for each plant biomass to litter flow. @@ -572,12 +566,10 @@ def calculate_litter_input_phosphorus_ratios( turnover) must be taken. Args: - metabolic_splits: Dictionary containing the proportion of each input that - goes to the relevant metabolic pool. This is for three input types: leaves, - reproductive tissues and roots [unitless] - total_input: The total pool size for each input pool [kg C m^-3], as well as - the chemical proportions (lignin, nitrogen and phosphorus) of each of these - pools [unitless]. + input_details: An InputDetails instance containing the total input of each + plant biomass type, the proportion of the input that goes to the relevant + metabolic pool for each input type (expect deadwood) and the total input + into each litter pool. struct_to_meta_phosphorus_ratio: Ratio of the carbon to phosphorus ratios of structural vs metabolic litter pools [unitless] @@ -589,56 +581,60 @@ def calculate_litter_input_phosphorus_ratios( # Calculate c_p_ratio split for each (non-wood) input biomass type root_c_p_ratio_meta, root_c_p_ratio_struct = ( calculate_nutrient_split_between_litter_pools( - input_c_nut_ratio=total_input["root_phosphorus"].to_numpy(), - metabolic_split=metabolic_splits["roots"], + input_c_nut_ratio=input_details.root_phosphorus, + metabolic_split=input_details.roots_meta_split, struct_to_meta_nutrient_ratio=struct_to_meta_phosphorus_ratio, ) ) leaf_c_p_ratio_meta, leaf_c_p_ratio_struct = ( calculate_nutrient_split_between_litter_pools( - input_c_nut_ratio=total_input["leaf_phosphorus"].to_numpy(), - metabolic_split=metabolic_splits["leaves"], + input_c_nut_ratio=input_details.leaf_phosphorus, + metabolic_split=input_details.leaves_meta_split, struct_to_meta_nutrient_ratio=struct_to_meta_phosphorus_ratio, ) ) reprod_c_p_ratio_meta, reprod_c_p_ratio_struct = ( calculate_nutrient_split_between_litter_pools( - input_c_nut_ratio=total_input["reprod_phosphorus"].to_numpy(), - metabolic_split=metabolic_splits["reproductive"], + input_c_nut_ratio=input_details.reprod_phosphorus, + metabolic_split=input_details.reproduct_meta_split, struct_to_meta_nutrient_ratio=struct_to_meta_phosphorus_ratio, ) ) c_p_ratio_below_metabolic = root_c_p_ratio_meta c_p_ratio_below_structural = root_c_p_ratio_struct - c_p_ratio_woody = total_input["deadwood_phosphorus"].to_numpy() + c_p_ratio_woody = input_details.deadwood_phosphorus # Inputs with multiple sources have to be weighted c_p_ratio_above_metabolic = np.divide( - (leaf_c_p_ratio_meta * total_input["leaf_mass"] * metabolic_splits["leaves"]) + ( + leaf_c_p_ratio_meta + * input_details.leaf_mass + * input_details.leaves_meta_split + ) + ( reprod_c_p_ratio_meta - * total_input["reprod_mass"] - * metabolic_splits["reproductive"] + * input_details.reprod_mass + * input_details.reproduct_meta_split ), - (total_input["leaf_mass"] * metabolic_splits["leaves"]) - + (total_input["reprod_mass"] * metabolic_splits["reproductive"]), + (input_details.leaf_mass * input_details.leaves_meta_split) + + (input_details.reprod_mass * input_details.reproduct_meta_split), ) c_p_ratio_above_structural = np.divide( ( leaf_c_p_ratio_struct - * total_input["leaf_mass"] - * (1 - metabolic_splits["leaves"]) + * input_details.leaf_mass + * (1 - input_details.leaves_meta_split) ) + ( reprod_c_p_ratio_struct - * total_input["reprod_mass"] - * (1 - metabolic_splits["reproductive"]) + * input_details.reprod_mass + * (1 - input_details.reproduct_meta_split) ), - (total_input["leaf_mass"] * (1 - metabolic_splits["leaves"])) - + (total_input["reprod_mass"] * (1 - metabolic_splits["reproductive"])), + (input_details.leaf_mass * (1 - input_details.leaves_meta_split)) + + (input_details.reprod_mass * (1 - input_details.reproduct_meta_split)), ) return { diff --git a/virtual_ecosystem/models/litter/input_partition.py b/virtual_ecosystem/models/litter/input_partition.py index d5c09578f..26637fffb 100644 --- a/virtual_ecosystem/models/litter/input_partition.py +++ b/virtual_ecosystem/models/litter/input_partition.py @@ -3,15 +3,75 @@ natural tissue death as well as from mechanical inefficiencies in herbivory. """ # noqa: D205 +from dataclasses import dataclass + import numpy as np from numpy.typing import NDArray -from xarray import DataArray from virtual_ecosystem.core.data import Data from virtual_ecosystem.core.logger import LOGGER from virtual_ecosystem.models.litter.constants import LitterConsts +@dataclass +class InputDetails: + """The full details of the input to the litter model.""" + + leaf_mass: NDArray[np.float32] + """Total leaf input mass to litter [kg C m^-2]""" + root_mass: NDArray[np.float32] + """Total root input mass to litter [kg C m^-2]""" + deadwood_mass: NDArray[np.float32] + """Total deadwood input mass to litter [kg C m^-2]""" + reprod_mass: NDArray[np.float32] + """Total plant reproductive tissue input mass to litter [kg C m^-2]""" + + leaf_lignin: NDArray[np.float32] + """Lignin proportion of leaf input [unitless]""" + root_lignin: NDArray[np.float32] + """Lignin proportion of root input [unitless]""" + deadwood_lignin: NDArray[np.float32] + """Lignin proportion of deadwood input [unitless]""" + reprod_lignin: NDArray[np.float32] + """Lignin proportion of reproductive tissue input [unitless]""" + + leaf_nitrogen: NDArray[np.float32] + """Carbon nitrogen ratio of leaf input [unitless]""" + root_nitrogen: NDArray[np.float32] + """Carbon nitrogen ratio of root input [unitless]""" + deadwood_nitrogen: NDArray[np.float32] + """Carbon nitrogen ratio of deadwood input [unitless]""" + reprod_nitrogen: NDArray[np.float32] + """Carbon nitrogen ratio of reproductive tissue input [unitless]""" + + leaf_phosphorus: NDArray[np.float32] + """Carbon phosphorus ratio of leaf input [unitless]""" + root_phosphorus: NDArray[np.float32] + """Carbon phosphorus ratio of root input [unitless]""" + deadwood_phosphorus: NDArray[np.float32] + """Carbon phosphorus ratio of deadwood input [unitless]""" + reprod_phosphorus: NDArray[np.float32] + """Carbon phosphorus ratio of reproductive tissue input [unitless]""" + + leaves_meta_split: NDArray[np.float32] + """Fraction of leaf input that goes to metabolic litter [unitless]""" + reproduct_meta_split: NDArray[np.float32] + """Fraction of leaf input that goes to metabolic litter [unitless]""" + roots_meta_split: NDArray[np.float32] + """Fraction of leaf input that goes to metabolic litter [unitless]""" + + input_woody: NDArray[np.float32] + """Total input to the woody litter pool [kg C m^-2]""" + input_above_metabolic: NDArray[np.float32] + """Total input to the above ground metabolic litter pool [kg C m^-2]""" + input_above_structural: NDArray[np.float32] + """Total input to the above ground structural litter pool [kg C m^-2]""" + input_below_metabolic: NDArray[np.float32] + """Total input to the below ground metabolic litter pool [kg C m^-2]""" + input_below_structural: NDArray[np.float32] + """Total input to the below ground structural litter pool [kg C m^-2]""" + + class InputPartition: """This class handles the partitioning of plant matter between litter pools. @@ -24,7 +84,7 @@ def __init__(self, data: Data): def determine_all_plant_to_litter_flows( self, constants: LitterConsts - ) -> tuple[dict[str, NDArray[np.float32]], dict[str, NDArray[np.float32]]]: + ) -> InputDetails: """Determine the total flow to each litter pool from dead plant matter. This method first combines the two different input streams for dead plant matter @@ -35,10 +95,10 @@ def determine_all_plant_to_litter_flows( constants: Set of constants for the litter model. Returns: - Two dictionaries, the first of which provides the proportion of the input - that goes to the relevant metabolic pool for each input type (expect - deadwood) [unitless]. The second dictionary provides the total plant biomass - flow into each of the litter pools [kg C m^-2]. + An InputDetails instance containing the total input of each plant biomass + type, the proportion of the input that goes to the relevant metabolic pool + for each input type (expect deadwood) and the total input into each litter + pool. """ # Find the total input for each plant matter type @@ -53,9 +113,9 @@ def determine_all_plant_to_litter_flows( total_input=total_input, metabolic_splits=metabolic_splits ) - return metabolic_splits, plant_inputs + return InputDetails(**metabolic_splits, **plant_inputs, **total_input) - def combine_input_sources(self) -> dict[str, DataArray]: + def combine_input_sources(self) -> dict[str, NDArray[np.float32]]: """Combine the plant death and herbivory inputs into a single total input. The total input for each plant matter type (leaves, roots, deadwood, @@ -111,27 +171,27 @@ def combine_input_sources(self) -> dict[str, DataArray]: reprod_phosphorus = self.data["plant_reproductive_tissue_turnover_c_p_ratio"] return { - "leaf_mass": leaf_total, - "root_mass": root_total, - "deadwood_mass": deadwood_total, - "reprod_mass": reprod_total, - "leaf_lignin": leaf_lignin, - "root_lignin": root_lignin, - "deadwood_lignin": deadwood_lignin, - "reprod_lignin": reprod_lignin, - "leaf_nitrogen": leaf_nitrogen, - "root_nitrogen": root_nitrogen, - "deadwood_nitrogen": deadwood_nitrogen, - "reprod_nitrogen": reprod_nitrogen, - "leaf_phosphorus": leaf_phosphorus, - "root_phosphorus": root_phosphorus, - "deadwood_phosphorus": deadwood_phosphorus, - "reprod_phosphorus": reprod_phosphorus, + "leaf_mass": leaf_total.to_numpy(), + "root_mass": root_total.to_numpy(), + "deadwood_mass": deadwood_total.to_numpy(), + "reprod_mass": reprod_total.to_numpy(), + "leaf_lignin": leaf_lignin.to_numpy(), + "root_lignin": root_lignin.to_numpy(), + "deadwood_lignin": deadwood_lignin.to_numpy(), + "reprod_lignin": reprod_lignin.to_numpy(), + "leaf_nitrogen": leaf_nitrogen.to_numpy(), + "root_nitrogen": root_nitrogen.to_numpy(), + "deadwood_nitrogen": deadwood_nitrogen.to_numpy(), + "reprod_nitrogen": reprod_nitrogen.to_numpy(), + "leaf_phosphorus": leaf_phosphorus.to_numpy(), + "root_phosphorus": root_phosphorus.to_numpy(), + "deadwood_phosphorus": deadwood_phosphorus.to_numpy(), + "reprod_phosphorus": reprod_phosphorus.to_numpy(), } def calculate_metabolic_proportions_of_input( - total_input: dict[str, DataArray], constants: LitterConsts + total_input: dict[str, NDArray[np.float32]], constants: LitterConsts ) -> dict[str, NDArray[np.float32]]: """Calculate the proportion of each input type that flows to the metabolic pool. @@ -153,41 +213,41 @@ def calculate_metabolic_proportions_of_input( # Calculate split of each input biomass type leaves_metabolic_split = split_pool_into_metabolic_and_structural_litter( - lignin_proportion=total_input["leaf_lignin"].to_numpy(), - carbon_nitrogen_ratio=total_input["leaf_nitrogen"].to_numpy(), - carbon_phosphorus_ratio=total_input["leaf_phosphorus"].to_numpy(), + lignin_proportion=total_input["leaf_lignin"], + carbon_nitrogen_ratio=total_input["leaf_nitrogen"], + carbon_phosphorus_ratio=total_input["leaf_phosphorus"], max_metabolic_fraction=constants.max_metabolic_fraction_of_input, split_sensitivity_nitrogen=constants.metabolic_split_nitrogen_sensitivity, split_sensitivity_phosphorus=constants.metabolic_split_phosphorus_sensitivity, ) repoduct_metabolic_split = split_pool_into_metabolic_and_structural_litter( - lignin_proportion=total_input["reprod_lignin"].to_numpy(), - carbon_nitrogen_ratio=total_input["reprod_nitrogen"].to_numpy(), - carbon_phosphorus_ratio=total_input["reprod_phosphorus"].to_numpy(), + lignin_proportion=total_input["reprod_lignin"], + carbon_nitrogen_ratio=total_input["reprod_nitrogen"], + carbon_phosphorus_ratio=total_input["reprod_phosphorus"], max_metabolic_fraction=constants.max_metabolic_fraction_of_input, split_sensitivity_nitrogen=constants.metabolic_split_nitrogen_sensitivity, split_sensitivity_phosphorus=constants.metabolic_split_phosphorus_sensitivity, ) roots_metabolic_split = split_pool_into_metabolic_and_structural_litter( - lignin_proportion=total_input["root_lignin"].to_numpy(), - carbon_nitrogen_ratio=total_input["root_nitrogen"].to_numpy(), - carbon_phosphorus_ratio=total_input["root_phosphorus"].to_numpy(), + lignin_proportion=total_input["root_lignin"], + carbon_nitrogen_ratio=total_input["root_nitrogen"], + carbon_phosphorus_ratio=total_input["root_phosphorus"], max_metabolic_fraction=constants.max_metabolic_fraction_of_input, split_sensitivity_nitrogen=constants.metabolic_split_nitrogen_sensitivity, split_sensitivity_phosphorus=constants.metabolic_split_phosphorus_sensitivity, ) return { - "leaves": leaves_metabolic_split, - "reproductive": repoduct_metabolic_split, - "roots": roots_metabolic_split, + "leaves_meta_split": leaves_metabolic_split, + "reproduct_meta_split": repoduct_metabolic_split, + "roots_meta_split": roots_metabolic_split, } def partion_plant_inputs_between_pools( - total_input: dict[str, DataArray], + total_input: dict[str, NDArray[np.float32]], metabolic_splits: dict[str, NDArray[np.float32]], ): """Function to partition input biomass between the various litter pools. @@ -199,7 +259,7 @@ def partion_plant_inputs_between_pools( concentration and carbon nitrogen ratios. Args: - total_input: The total pool size for each input pool [kg C m^-3], as well as + total_input: The total pool size for each input pool [kg C m^-2], as well as the chemical proportions (lignin, nitrogen and phosphorus) of each of these pools [unitless]. metabolic_splits: Dictionary containing the proportion of each input that @@ -215,23 +275,27 @@ def partion_plant_inputs_between_pools( # Calculate input to each of the five litter pools woody_input = total_input["deadwood_mass"] above_ground_metabolic_input = ( - metabolic_splits["leaves"] * total_input["leaf_mass"] - + metabolic_splits["reproductive"] * total_input["reprod_mass"] + metabolic_splits["leaves_meta_split"] * total_input["leaf_mass"] + + metabolic_splits["reproduct_meta_split"] * total_input["reprod_mass"] + ) + above_ground_strutural_input = ( + 1 - metabolic_splits["leaves_meta_split"] + ) * total_input["leaf_mass"] + ( + 1 - metabolic_splits["reproduct_meta_split"] + ) * total_input["reprod_mass"] + below_ground_metabolic_input = ( + metabolic_splits["roots_meta_split"] * total_input["root_mass"] ) - above_ground_strutural_input = (1 - metabolic_splits["leaves"]) * total_input[ - "leaf_mass" - ] + (1 - metabolic_splits["reproductive"]) * total_input["reprod_mass"] - below_ground_metabolic_input = metabolic_splits["roots"] * total_input["root_mass"] - below_ground_structural_input = (1 - metabolic_splits["roots"]) * total_input[ - "root_mass" - ] + below_ground_structural_input = ( + 1 - metabolic_splits["roots_meta_split"] + ) * total_input["root_mass"] return { - "woody": woody_input, - "above_ground_metabolic": above_ground_metabolic_input, - "above_ground_structural": above_ground_strutural_input, - "below_ground_metabolic": below_ground_metabolic_input, - "below_ground_structural": below_ground_structural_input, + "input_woody": woody_input, + "input_above_metabolic": above_ground_metabolic_input, + "input_above_structural": above_ground_strutural_input, + "input_below_metabolic": below_ground_metabolic_input, + "input_below_structural": below_ground_structural_input, } diff --git a/virtual_ecosystem/models/litter/litter_model.py b/virtual_ecosystem/models/litter/litter_model.py index f23e99cc6..bc55e3d4b 100644 --- a/virtual_ecosystem/models/litter/litter_model.py +++ b/virtual_ecosystem/models/litter/litter_model.py @@ -16,9 +16,6 @@ class instance. If errors crop here when converting the information from the con be reported as one. """ # noqa: D205 -# TODO - At the moment this model only receives nothing from the animal model. In -# future, litter flows due to waste from herbivory need to be added. - # FUTURE - Potentially make a more numerically accurate version of this model by using # differential equations at some point. In reality, litter chemistry should change # continuously with time not just at the final time step as in the current @@ -325,32 +322,23 @@ def update(self, time_index: int, **kwargs: Any) -> None: constants=self.model_constants, ) - # TODO - REALLY NEED TO THINK ABOUT WHAT'S BEING RETURNED HERE, SO THAT - # LITTER_CHEMISTRY HAS THE INFO IT NEEDS - metabolic_splits, plant_inputs = ( - self.input_partition.determine_all_plant_to_litter_flows( - constants=self.model_constants, - ) + input_details = self.input_partition.determine_all_plant_to_litter_flows( + constants=self.model_constants, ) # Calculate the updated pool masses updated_pools = calculate_updated_pools( post_consumption_pools=consumed_pools, decay_rates=decay_rates, - plant_inputs=plant_inputs, + input_details=input_details, update_interval=self.model_timing.update_interval_quantity.to( "day" ).magnitude, ) - # TODO - This will need to change when I've worked out what I'm actually doing - # with total_input # Calculate all the litter chemistry changes updated_chemistries = self.litter_chemistry.calculate_new_pool_chemistries( - plant_inputs=plant_inputs, - metabolic_splits=metabolic_splits, - updated_pools=updated_pools, - total_input={}, + updated_pools=updated_pools, input_details=input_details ) # Calculate the total mineralisation rates from the litter From e7b234b34812f785c0e8def1c2dd3e93d0a47c2c Mon Sep 17 00:00:00 2001 From: Jacob Cook Date: Wed, 9 Oct 2024 16:03:56 +0100 Subject: [PATCH 08/14] Fixed broken litter chemistry unit tests --- tests/models/litter/test_chemistry.py | 32 ++++++++++---------- tests/models/litter/test_litter_model.py | 10 +++--- virtual_ecosystem/models/litter/chemistry.py | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/tests/models/litter/test_chemistry.py b/tests/models/litter/test_chemistry.py index 1c9ea8051..f9c4bdeb4 100644 --- a/tests/models/litter/test_chemistry.py +++ b/tests/models/litter/test_chemistry.py @@ -32,16 +32,16 @@ def test_calculate_new_pool_chemistries( """Test that function to calculate updated pool chemistries works correctly.""" expected_chemistries = { - "lignin_above_structural": [0.49726219, 0.10065698, 0.67693666, 0.6673972], + "lignin_above_structural": [0.49726272, 0.10113017, 0.67782882, 0.67072519], "lignin_woody": [0.49580543, 0.7978783, 0.35224272, 0.35012606], "lignin_below_structural": [0.49974338, 0.26270806, 0.74846367, 0.71955592], - "c_n_ratio_above_metabolic": [7.3918226, 8.9320212, 10.413317, 9.8624367], - "c_n_ratio_above_structural": [37.5547150, 43.3448492, 48.0974058, 52.0359678], + "c_n_ratio_above_metabolic": [7.3921805, 9.0161456, 10.4324728, 9.9183441], + "c_n_ratio_above_structural": [37.554988, 43.431768, 48.067581, 52.065169], "c_n_ratio_woody": [55.5816919, 63.2550698, 47.5208477, 59.0819914], "c_n_ratio_below_metabolic": [10.7299421, 11.3394567, 15.1984024, 12.2222413], "c_n_ratio_below_structural": [50.6228215, 55.9998994, 73.0948342, 58.6661277], - "c_p_ratio_above_metabolic": [69.965838, 68.549282, 107.38423, 96.583573], - "c_p_ratio_above_structural": [346.048307, 472.496124, 465.834123, 525.882608], + "c_p_ratio_above_metabolic": [69.966598, 69.674548, 108.426751, 96.143488], + "c_p_ratio_above_structural": [346.05231, 473.330293, 467.818240, 532.420899], "c_p_ratio_woody": [560.22870571, 762.56863636, 848.03530307, 600.40427444], "c_p_ratio_below_metabolic": [308.200782, 405.110726, 314.824814, 372.870229], "c_p_ratio_below_structural": [563.06464, 597.68324, 772.78968, 609.82810], @@ -64,7 +64,7 @@ def test_calculate_lignin_updates( """Test that the function to calculate the lignin updates works as expected.""" expected_lignin = { - "above_structural": [-0.00273781, 0.00065698, -0.02306334, -0.03260280], + "above_structural": [-0.0027373, 0.001130172, -0.022171178, -0.029274812], "woody": [-0.00419457, -0.0021217, 0.00224272, 0.00012606], "below_structural": [-0.00025662, 0.01270806, -0.00153633, -0.03044408], } @@ -110,8 +110,8 @@ def test_calculate_c_n_ratio_updates( """Test that calculation of C:N ratio updates works properly.""" expected_change = { - "above_metabolic": [0.091822576, 0.232021211, 0.313317200, 0.062436702], - "above_structural": [0.05471499, 0.14484922, 2.29740576, 1.835967773], + "above_metabolic": [0.0921805, 0.3161456, 0.3324728, 0.1183441], + "above_structural": [0.05498852, 0.2317676, 2.2675813, 1.8651688], "woody": [0.0816919, -0.0449302, 0.2208477, -0.0180086], "below_metabolic": [0.02994209, 0.03945672, -0.00159759, -0.17775875], "below_structural": [0.12282146, 0.39989943, -0.00516585, -2.53387232], @@ -135,8 +135,8 @@ def test_calculate_c_p_ratio_updates( """Test that calculation of C:P ratio updates works properly.""" expected_change = { - "above_metabolic": [12.665838, -0.15071757, 7.28423174, 0.78357281], - "above_structural": [8.5483073, -0.7038764, 50.034123, -44.317392], + "above_metabolic": [12.666598, 0.9745483, 8.3267513, 0.3434882], + "above_structural": [8.5523105, 0.13029263, 52.0182397, -37.7791012], "woody": [4.72870571, -0.73136364, 0.73530307, 1.30427444], "below_metabolic": [-2.49921796, -6.18927446, -0.37518617, -39.52977135], "below_structural": [12.56464272, 2.08324337, -0.31032454, -41.37190224], @@ -180,14 +180,14 @@ def test_calculate_P_mineralisation(dummy_litter_data, decay_rates, litter_chemi assert np.allclose(actual_p_mineral, expected_p_mineral) -def test_calculate_litter_input_lignin_concentrations(dummy_litter_data, input_details): +def test_calculate_litter_input_lignin_concentrations(input_details): """Check calculation of lignin concentrations of each plant flow to litter.""" from virtual_ecosystem.models.litter.chemistry import ( calculate_litter_input_lignin_concentrations, ) expected_woody = [0.233, 0.545, 0.612, 0.378] - expected_concs_above_struct = [0.24971768, 0.22111396, 0.51122474, 0.56571041] + expected_concs_above_struct = [0.2500931, 0.2532920, 0.5303109, 0.5803457] expected_concs_below_struct = [0.48590258, 0.56412613, 0.54265483, 0.67810978] actual_concs = calculate_litter_input_lignin_concentrations( @@ -209,8 +209,8 @@ def test_calculate_litter_input_nitrogen_ratios(dummy_litter_data, input_details "woody": [60.7, 57.9, 73.1, 55.1], "below_metabolic": [11.449427, 13.09700, 14.48056, 11.04331], "below_structural": [57.24714, 65.48498, 72.40281, 55.21655], - "above_metabolic": [8.48355299, 14.17116914, 12.3424635, 11.10877484], - "above_structural": [42.5018709, 69.9028550, 64.6044513, 57.7622747], + "above_metabolic": [8.4871511, 14.7283297, 12.1855116, 11.3024309], + "above_structural": [42.52031784, 74.63602461, 63.15513757, 57.82346359], } actual_c_n_ratios = calculate_litter_input_nitrogen_ratios( @@ -234,8 +234,8 @@ def test_calculate_litter_input_phosphorus_ratios(dummy_litter_data, input_detai "woody": [856.5, 675.4, 933.2, 888.8], "below_metabolic": [248.1465, 129.418998, 146.243645, 110.700999], "below_structural": [1240.73249721, 647.09498874, 731.2182237, 553.50499377], - "above_metabolic": [220.55713162, 65.14600889, 152.23446238, 112.22496062], - "above_structural": [1118.95921, 343.440873, 825.333331, 387.658509], + "above_metabolic": [220.42737, 87.282889, 152.331456, 100.160733], + "above_structural": [1118.30505, 490.872368, 813.926271, 415.786304], } actual_c_p_ratios = calculate_litter_input_phosphorus_ratios( diff --git a/tests/models/litter/test_litter_model.py b/tests/models/litter/test_litter_model.py index 3420c9ea8..8ba88dbd6 100644 --- a/tests/models/litter/test_litter_model.py +++ b/tests/models/litter/test_litter_model.py @@ -462,16 +462,16 @@ def test_update(fixture_litter_model, dummy_litter_data): end_woody = [4.77403361, 11.89845863, 7.3598224, 7.3298224] end_below_meta = [0.3976309, 0.3630269, 0.06787947, 0.07794085] end_below_struct = [0.61050583, 0.32205947352, 0.02014514530, 0.03468376530] - end_lignin_above_struct = [0.49726219, 0.10065698, 0.67693666, 0.6673972] + end_lignin_above_struct = [0.49726272, 0.10113017, 0.67782882, 0.67072519] end_lignin_woody = [0.49580543, 0.7978783, 0.35224272, 0.35012606] end_lignin_below_struct = [0.49974338, 0.26270806, 0.74846367, 0.71955592] - end_c_n_above_metabolic = [7.3918226, 8.9320212, 10.413317, 9.8624367] - end_c_n_above_structural = [37.5547150, 43.3448492, 48.0974058, 52.0359678] + end_c_n_above_metabolic = [7.3921805, 9.0161456, 10.4324728, 9.9183441] + end_c_n_above_structural = [37.554988, 43.431768, 48.067581, 52.065169] end_c_n_woody = [55.5816919, 63.2550698, 47.5208477, 59.0819914] end_c_n_below_metabolic = [10.7299421, 11.3394567, 15.1984024, 12.2222413] end_c_n_below_structural = [50.6228215, 55.9998994, 73.0948342, 58.6661277] - end_c_p_above_metabolic = [69.965838, 68.549282, 107.38423, 96.583573] - end_c_p_above_structural = [346.048307, 472.496124, 465.834123, 525.882608] + end_c_p_above_metabolic = [69.966598, 69.674548, 108.426751, 96.143488] + end_c_p_above_structural = [346.05231, 473.330293, 467.818240, 532.420899] end_c_p_woody = [560.22870571, 762.56863636, 848.03530307, 600.40427444] end_c_p_below_metabolic = [308.200782, 405.110726, 314.824814, 372.870229] end_c_p_below_structural = [563.06464, 597.68324, 772.78968, 609.82810] diff --git a/virtual_ecosystem/models/litter/chemistry.py b/virtual_ecosystem/models/litter/chemistry.py index d47c8b350..0ea0d2cfd 100644 --- a/virtual_ecosystem/models/litter/chemistry.py +++ b/virtual_ecosystem/models/litter/chemistry.py @@ -438,7 +438,7 @@ def calculate_litter_input_lignin_concentrations( structural) [kg lignin kg C^-1] """ - lignin_proportion_woody = input_details.deadwood_mass + lignin_proportion_woody = input_details.deadwood_lignin lignin_proportion_below_structural = ( input_details.root_lignin From 22584fc9aa72af85cea6ca8261dd7d206b1a615b Mon Sep 17 00:00:00 2001 From: Jacob Cook Date: Thu, 10 Oct 2024 14:38:23 +0100 Subject: [PATCH 09/14] Renamed InputDetails to LitterInputs --- virtual_ecosystem/models/litter/carbon.py | 6 ++-- virtual_ecosystem/models/litter/chemistry.py | 30 +++++++++---------- .../models/litter/input_partition.py | 8 ++--- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/virtual_ecosystem/models/litter/carbon.py b/virtual_ecosystem/models/litter/carbon.py index 2460dd19f..fbcd1cd19 100644 --- a/virtual_ecosystem/models/litter/carbon.py +++ b/virtual_ecosystem/models/litter/carbon.py @@ -24,7 +24,7 @@ from virtual_ecosystem.models.litter.env_factors import ( calculate_environmental_factors, ) -from virtual_ecosystem.models.litter.input_partition import InputDetails +from virtual_ecosystem.models.litter.input_partition import LitterInputs def calculate_post_consumption_pools( @@ -219,7 +219,7 @@ def calculate_total_C_mineralised( def calculate_updated_pools( post_consumption_pools: dict[str, NDArray[np.float32]], decay_rates: dict[str, NDArray[np.float32]], - input_details: InputDetails, + input_details: LitterInputs, update_interval: float, ) -> dict[str, NDArray[np.float32]]: """Calculate the updated mass of each litter pool. @@ -232,7 +232,7 @@ def calculate_updated_pools( subtracted [kg C m^-2] decay_rates: Dictionary containing the rates of decay for all 5 litter pools [kg C m^-2 day^-1] - input_details: An InputDetails instance containing the total input of each plant + input_details: An LitterInputs instance containing the total input of each plant biomass type, the proportion of the input that goes to the relevant metabolic pool for each input type (expect deadwood) and the total input into each litter pool. diff --git a/virtual_ecosystem/models/litter/chemistry.py b/virtual_ecosystem/models/litter/chemistry.py index 0ea0d2cfd..44e4ac45a 100644 --- a/virtual_ecosystem/models/litter/chemistry.py +++ b/virtual_ecosystem/models/litter/chemistry.py @@ -19,7 +19,7 @@ from virtual_ecosystem.core.data import Data from virtual_ecosystem.models.litter.constants import LitterConsts -from virtual_ecosystem.models.litter.input_partition import InputDetails +from virtual_ecosystem.models.litter.input_partition import LitterInputs class LitterChemistry: @@ -38,7 +38,7 @@ def __init__(self, data: Data, constants: LitterConsts): def calculate_new_pool_chemistries( self, updated_pools: dict[str, NDArray[np.float32]], - input_details: InputDetails, + input_details: LitterInputs, ) -> dict[str, DataArray]: """Method to calculate the updated chemistry of each litter pool. @@ -49,7 +49,7 @@ def calculate_new_pool_chemistries( Args: updated_pools: Dictionary containing the updated pool densities for all 5 litter pools [kg C m^-2] - input_details: An InputDetails instance containing the total input of each + input_details: An LitterInputs instance containing the total input of each plant biomass type, the proportion of the input that goes to the relevant metabolic pool for each input type (expect deadwood) and the total input into each litter pool. @@ -121,7 +121,7 @@ def calculate_new_pool_chemistries( def calculate_lignin_updates( self, - input_details: InputDetails, + input_details: LitterInputs, input_lignin: dict[str, NDArray[np.float32]], updated_pools: dict[str, NDArray[np.float32]], ) -> dict[str, NDArray[np.float32]]: @@ -132,7 +132,7 @@ def calculate_lignin_updates( used in an integration process. Args: - input_details: An InputDetails instance containing the total input of each + input_details: An LitterInputs instance containing the total input of each plant biomass type, the proportion of the input that goes to the relevant metabolic pool for each input type (expect deadwood) and the total input into each litter pool. @@ -174,7 +174,7 @@ def calculate_lignin_updates( def calculate_c_n_ratio_updates( self, - input_details: InputDetails, + input_details: LitterInputs, input_c_n_ratios: dict[str, NDArray[np.float32]], updated_pools: dict[str, NDArray[np.float32]], ) -> dict[str, NDArray[np.float32]]: @@ -184,7 +184,7 @@ def calculate_c_n_ratio_updates( be used in an integration process. Args: - input_details: An InputDetails instance containing the total input of each + input_details: An LitterInputs instance containing the total input of each plant biomass type, the proportion of the input that goes to the relevant metabolic pool for each input type (expect deadwood) and the total input into each litter pool. @@ -239,7 +239,7 @@ def calculate_c_n_ratio_updates( def calculate_c_p_ratio_updates( self, - input_details: InputDetails, + input_details: LitterInputs, input_c_p_ratios: dict[str, NDArray[np.float32]], updated_pools: dict[str, NDArray[np.float32]], ) -> dict[str, NDArray[np.float32]]: @@ -249,7 +249,7 @@ def calculate_c_p_ratio_updates( be used in an integration process. Args: - input_details: An InputDetails instance containing the total input of each + input_details: An LitterInputs instance containing the total input of each plant biomass type, the proportion of the input that goes to the relevant metabolic pool for each input type (expect deadwood) and the total input into each litter pool. @@ -408,7 +408,7 @@ def calculate_P_mineralisation( def calculate_litter_input_lignin_concentrations( - input_details: InputDetails, + input_details: LitterInputs, ) -> dict[str, NDArray[np.float32]]: """Calculate the concentration of lignin for each plant biomass to litter flow. @@ -427,7 +427,7 @@ def calculate_litter_input_lignin_concentrations( then converted to a back into a concentration. Args: - input_details: An InputDetails instance containing the total input of each + input_details: An LitterInputs instance containing the total input of each plant biomass type, the proportion of the input that goes to the relevant metabolic pool for each input type (expect deadwood) and the total input into each litter pool. @@ -459,7 +459,7 @@ def calculate_litter_input_lignin_concentrations( def calculate_litter_input_nitrogen_ratios( - input_details: InputDetails, + input_details: LitterInputs, struct_to_meta_nitrogen_ratio: float, ) -> dict[str, NDArray[np.float32]]: """Calculate the carbon to nitrogen ratio for each plant biomass to litter flow. @@ -472,7 +472,7 @@ def calculate_litter_input_nitrogen_ratios( tissue turnover) must be taken. Args: - input_details: An InputDetails instance containing the total input of each + input_details: An LitterInputs instance containing the total input of each plant biomass type, the proportion of the input that goes to the relevant metabolic pool for each input type (expect deadwood) and the total input into each litter pool. @@ -553,7 +553,7 @@ def calculate_litter_input_nitrogen_ratios( def calculate_litter_input_phosphorus_ratios( - input_details: InputDetails, + input_details: LitterInputs, struct_to_meta_phosphorus_ratio: float, ) -> dict[str, NDArray[np.float32]]: """Calculate carbon to phosphorus ratio for each plant biomass to litter flow. @@ -566,7 +566,7 @@ def calculate_litter_input_phosphorus_ratios( turnover) must be taken. Args: - input_details: An InputDetails instance containing the total input of each + input_details: An LitterInputs instance containing the total input of each plant biomass type, the proportion of the input that goes to the relevant metabolic pool for each input type (expect deadwood) and the total input into each litter pool. diff --git a/virtual_ecosystem/models/litter/input_partition.py b/virtual_ecosystem/models/litter/input_partition.py index 26637fffb..1e52ffbbd 100644 --- a/virtual_ecosystem/models/litter/input_partition.py +++ b/virtual_ecosystem/models/litter/input_partition.py @@ -14,7 +14,7 @@ @dataclass -class InputDetails: +class LitterInputs: """The full details of the input to the litter model.""" leaf_mass: NDArray[np.float32] @@ -84,7 +84,7 @@ def __init__(self, data: Data): def determine_all_plant_to_litter_flows( self, constants: LitterConsts - ) -> InputDetails: + ) -> LitterInputs: """Determine the total flow to each litter pool from dead plant matter. This method first combines the two different input streams for dead plant matter @@ -95,7 +95,7 @@ def determine_all_plant_to_litter_flows( constants: Set of constants for the litter model. Returns: - An InputDetails instance containing the total input of each plant biomass + An LitterInputs instance containing the total input of each plant biomass type, the proportion of the input that goes to the relevant metabolic pool for each input type (expect deadwood) and the total input into each litter pool. @@ -113,7 +113,7 @@ def determine_all_plant_to_litter_flows( total_input=total_input, metabolic_splits=metabolic_splits ) - return InputDetails(**metabolic_splits, **plant_inputs, **total_input) + return LitterInputs(**metabolic_splits, **plant_inputs, **total_input) def combine_input_sources(self) -> dict[str, NDArray[np.float32]]: """Combine the plant death and herbivory inputs into a single total input. From 1a88fe5a93fdff21c3d50de04cc66cbd4e1cb0e5 Mon Sep 17 00:00:00 2001 From: Jacob Cook Date: Thu, 10 Oct 2024 15:28:34 +0100 Subject: [PATCH 10/14] Removed InputPartition class and moved relevant code into a factory method for the LitterInputs class --- tests/models/litter/conftest.py | 22 +-- tests/models/litter/test_input_partition.py | 38 ++-- .../models/litter/input_partition.py | 166 +++++++++--------- .../models/litter/litter_model.py | 17 +- 4 files changed, 113 insertions(+), 130 deletions(-) diff --git a/tests/models/litter/conftest.py b/tests/models/litter/conftest.py index b812418a0..d55b564e9 100644 --- a/tests/models/litter/conftest.py +++ b/tests/models/litter/conftest.py @@ -139,16 +139,6 @@ def litter_chemistry(dummy_litter_data): return litter_chemistry -@pytest.fixture -def input_partition(dummy_litter_data): - """InputPartition object to be use throughout testing.""" - from virtual_ecosystem.models.litter.input_partition import InputPartition - - input_partition = InputPartition(dummy_litter_data) - - return input_partition - - @pytest.fixture def input_lignin(input_details): """Lignin proportion of the relevant input flows.""" @@ -238,10 +228,11 @@ def post_consumption_pools(dummy_litter_data): @pytest.fixture -def total_litter_input(input_partition): +def total_litter_input(dummy_litter_data): """Total input mass a chemistry for each plant biomass type.""" + from virtual_ecosystem.models.litter.input_partition import combine_input_sources - total_litter_input = input_partition.combine_input_sources() + total_litter_input = combine_input_sources(dummy_litter_data) return total_litter_input @@ -264,11 +255,12 @@ def updated_pools( @pytest.fixture -def input_details(input_partition): +def input_details(dummy_litter_data): """Complete set of details for inputs to the litter model.""" + from virtual_ecosystem.models.litter.input_partition import LitterInputs - input_details = input_partition.determine_all_plant_to_litter_flows( - constants=LitterConsts + input_details = LitterInputs.create_from_data( + data=dummy_litter_data, constants=LitterConsts ) return input_details diff --git a/tests/models/litter/test_input_partition.py b/tests/models/litter/test_input_partition.py index 3e2cd4d00..b91094fe4 100644 --- a/tests/models/litter/test_input_partition.py +++ b/tests/models/litter/test_input_partition.py @@ -9,20 +9,13 @@ from virtual_ecosystem.models.litter.constants import LitterConsts -def test_input_partition_initialisation(dummy_litter_data): - """Test that the InputPartition class initialises as expected.""" - from virtual_ecosystem.models.litter.input_partition import InputPartition - - input_partition = InputPartition(dummy_litter_data) - - assert input_partition.data == dummy_litter_data - - -def test_determine_all_plant_to_litter_flows(input_partition, total_litter_input): +def test_determine_all_plant_to_litter_flows(dummy_litter_data): """Test that function to determine plant to litter flows works correctly.""" from dataclasses import asdict - expected_details = { + from virtual_ecosystem.models.litter.input_partition import LitterInputs + + expected_inputs = { "leaves_meta_split": [0.8123412282, 0.7504823457, 0.4509559749, 0.0852205423], "reproduct_meta_split": [0.8462925685, 0.833489905, 0.83196046, 0.8390536408], "roots_meta_split": [0.588394858, 0.379571377, 0.5024461477, 0.410125012], @@ -49,20 +42,25 @@ def test_determine_all_plant_to_litter_flows(input_partition, total_litter_input "reprod_phosphorus": [125.5, 105.0, 145.0, 189.2], } - actual_details = input_partition.determine_all_plant_to_litter_flows( - constants=LitterConsts + litter_inputs = LitterInputs.create_from_data( + data=dummy_litter_data, constants=LitterConsts ) - actual_details = asdict(actual_details) + # Check that the right sort of object has been created + assert isinstance(litter_inputs, LitterInputs) + + # Then convert to a dict to check the values + litter_inputs = asdict(litter_inputs) # Check that all keys match and have correct values for both dictionaries - assert set(expected_details.keys()) == set(actual_details.keys()) + assert set(expected_inputs.keys()) == set(litter_inputs.keys()) - for key in actual_details.keys(): - assert np.allclose(actual_details[key], expected_details[key]) + for key in litter_inputs.keys(): + assert np.allclose(litter_inputs[key], expected_inputs[key]) -def test_combine_input_sources(input_partition): +def test_combine_input_sources(dummy_litter_data): """Test that function to combine input sources works as expected.""" + from virtual_ecosystem.models.litter.input_partition import combine_input_sources expected_combined = { "leaf_mass": [0.02703, 0.0024, 0.02385, 0.0312], @@ -83,7 +81,7 @@ def test_combine_input_sources(input_partition): "reprod_phosphorus": [125.5, 105.0, 145.0, 189.2], } - actual_combined = input_partition.combine_input_sources() + actual_combined = combine_input_sources(dummy_litter_data) assert set(expected_combined.keys()) == set(actual_combined.keys()) @@ -91,7 +89,7 @@ def test_combine_input_sources(input_partition): assert np.allclose(actual_combined[key], expected_combined[key]) -def test_calculate_metabolic_proportions_of_input(input_partition, total_litter_input): +def test_calculate_metabolic_proportions_of_input(total_litter_input): """Test that function to calculate metabolic input proportions works as expected.""" from virtual_ecosystem.models.litter.input_partition import ( calculate_metabolic_proportions_of_input, diff --git a/virtual_ecosystem/models/litter/input_partition.py b/virtual_ecosystem/models/litter/input_partition.py index 1e52ffbbd..08c841229 100644 --- a/virtual_ecosystem/models/litter/input_partition.py +++ b/virtual_ecosystem/models/litter/input_partition.py @@ -3,6 +3,8 @@ natural tissue death as well as from mechanical inefficiencies in herbivory. """ # noqa: D205 +from __future__ import annotations + from dataclasses import dataclass import numpy as np @@ -15,7 +17,7 @@ @dataclass class LitterInputs: - """The full details of the input to the litter model.""" + """The full set input flows to the litter model.""" leaf_mass: NDArray[np.float32] """Total leaf input mass to litter [kg C m^-2]""" @@ -71,27 +73,18 @@ class LitterInputs: input_below_structural: NDArray[np.float32] """Total input to the below ground structural litter pool [kg C m^-2]""" - -class InputPartition: - """This class handles the partitioning of plant matter between litter pools. - - This class contains methods to calculate the partition of input plant matter between - the different litter pools based on the contents of the `data` object. - """ - - def __init__(self, data: Data): - self.data = data - - def determine_all_plant_to_litter_flows( - self, constants: LitterConsts - ) -> LitterInputs: - """Determine the total flow to each litter pool from dead plant matter. + @classmethod + def create_from_data(cls, data: Data, constants: LitterConsts) -> LitterInputs: + """Factory method to populate the various litter input flows. This method first combines the two different input streams for dead plant matter (plant tissue death and herbivory waste) to find the total input of each plant - biomass type. This total is then used in the subsequent calculations. + biomass type. This is then used to find the split between metabolic and + structural litter pools for each plant matter class (expect deadwood). Finally, + the total flow to each litter pool is calculated. Args: + data: The `Data` object to be used to populate the litter input details. constants: Set of constants for the litter model. Returns: @@ -102,7 +95,7 @@ def determine_all_plant_to_litter_flows( """ # Find the total input for each plant matter type - total_input = self.combine_input_sources() + total_input = combine_input_sources(data) # Find the plant inputs to each of the litter pools metabolic_splits = calculate_metabolic_proportions_of_input( @@ -115,79 +108,78 @@ def determine_all_plant_to_litter_flows( return LitterInputs(**metabolic_splits, **plant_inputs, **total_input) - def combine_input_sources(self) -> dict[str, NDArray[np.float32]]: - """Combine the plant death and herbivory inputs into a single total input. - The total input for each plant matter type (leaves, roots, deadwood, - reproductive tissue) is returned, the chemical concentration of each of these - new pools is also calculated. +def combine_input_sources(data: Data) -> dict[str, NDArray[np.float32]]: + """Combine the plant death and herbivory inputs into a single total input. - TODO - At the moment there is only leaf input defined so this function doesn't - really do anything for the other types of plant matter. Once input is defined - for them this function should be updated to actually do something with them. + The total input for each plant matter type (leaves, roots, deadwood, + reproductive tissue) is returned, the chemical concentration of each of these + new pools is also calculated. - Returns: - A dictionary containing the total pool size for each input pools [kg C - m^-3], as well as the chemistry proportions (lignin, nitrogen and - phosphorus) of each of these pools [unitless]. - """ + TODO - At the moment there is only leaf input defined so this function doesn't + really do anything for the other types of plant matter. Once input is defined + for them this function should be updated to actually do something with them. - # Calculate totals for each plant matter type - leaf_total = ( - self.data["leaf_turnover"] + self.data["herbivory_waste_leaf_carbon"] - ) - root_total = self.data["root_turnover"] - deadwood_total = self.data["deadwood_production"] - reprod_total = self.data["plant_reproductive_tissue_turnover"] - - # Calculate leaf lignin concentrations for each combined pool - leaf_lignin = ( - self.data["leaf_turnover_lignin"] * self.data["leaf_turnover"] - + self.data["herbivory_waste_leaf_lignin"] - * self.data["herbivory_waste_leaf_carbon"] - ) / (leaf_total) - root_lignin = self.data["root_turnover_lignin"] - deadwood_lignin = self.data["deadwood_lignin"] - reprod_lignin = self.data["plant_reproductive_tissue_turnover_lignin"] - - # Calculate leaf nitrogen concentrations for each combined pool - leaf_nitrogen = ( - self.data["leaf_turnover_c_n_ratio"] * self.data["leaf_turnover"] - + self.data["herbivory_waste_leaf_nitrogen"] - * self.data["herbivory_waste_leaf_carbon"] - ) / (leaf_total) - root_nitrogen = self.data["root_turnover_c_n_ratio"] - deadwood_nitrogen = self.data["deadwood_c_n_ratio"] - reprod_nitrogen = self.data["plant_reproductive_tissue_turnover_c_n_ratio"] - - # Calculate leaf phosphorus concentrations for each combined pool - leaf_phosphorus = ( - self.data["leaf_turnover_c_p_ratio"] * self.data["leaf_turnover"] - + self.data["herbivory_waste_leaf_phosphorus"] - * self.data["herbivory_waste_leaf_carbon"] - ) / (leaf_total) - root_phosphorus = self.data["root_turnover_c_p_ratio"] - deadwood_phosphorus = self.data["deadwood_c_p_ratio"] - reprod_phosphorus = self.data["plant_reproductive_tissue_turnover_c_p_ratio"] - - return { - "leaf_mass": leaf_total.to_numpy(), - "root_mass": root_total.to_numpy(), - "deadwood_mass": deadwood_total.to_numpy(), - "reprod_mass": reprod_total.to_numpy(), - "leaf_lignin": leaf_lignin.to_numpy(), - "root_lignin": root_lignin.to_numpy(), - "deadwood_lignin": deadwood_lignin.to_numpy(), - "reprod_lignin": reprod_lignin.to_numpy(), - "leaf_nitrogen": leaf_nitrogen.to_numpy(), - "root_nitrogen": root_nitrogen.to_numpy(), - "deadwood_nitrogen": deadwood_nitrogen.to_numpy(), - "reprod_nitrogen": reprod_nitrogen.to_numpy(), - "leaf_phosphorus": leaf_phosphorus.to_numpy(), - "root_phosphorus": root_phosphorus.to_numpy(), - "deadwood_phosphorus": deadwood_phosphorus.to_numpy(), - "reprod_phosphorus": reprod_phosphorus.to_numpy(), - } + Args: + data: The `Data` object to be used to populate the litter input streams. + + Returns: + A dictionary containing the total pool size for each input pools [kg C + m^-3], as well as the chemistry proportions (lignin, nitrogen and + phosphorus) of each of these pools [unitless]. + """ + + # Calculate totals for each plant matter type + leaf_total = data["leaf_turnover"] + data["herbivory_waste_leaf_carbon"] + root_total = data["root_turnover"] + deadwood_total = data["deadwood_production"] + reprod_total = data["plant_reproductive_tissue_turnover"] + + # Calculate leaf lignin concentrations for each combined pool + leaf_lignin = ( + data["leaf_turnover_lignin"] * data["leaf_turnover"] + + data["herbivory_waste_leaf_lignin"] * data["herbivory_waste_leaf_carbon"] + ) / (leaf_total) + root_lignin = data["root_turnover_lignin"] + deadwood_lignin = data["deadwood_lignin"] + reprod_lignin = data["plant_reproductive_tissue_turnover_lignin"] + + # Calculate leaf nitrogen concentrations for each combined pool + leaf_nitrogen = ( + data["leaf_turnover_c_n_ratio"] * data["leaf_turnover"] + + data["herbivory_waste_leaf_nitrogen"] * data["herbivory_waste_leaf_carbon"] + ) / (leaf_total) + root_nitrogen = data["root_turnover_c_n_ratio"] + deadwood_nitrogen = data["deadwood_c_n_ratio"] + reprod_nitrogen = data["plant_reproductive_tissue_turnover_c_n_ratio"] + + # Calculate leaf phosphorus concentrations for each combined pool + leaf_phosphorus = ( + data["leaf_turnover_c_p_ratio"] * data["leaf_turnover"] + + data["herbivory_waste_leaf_phosphorus"] * data["herbivory_waste_leaf_carbon"] + ) / (leaf_total) + root_phosphorus = data["root_turnover_c_p_ratio"] + deadwood_phosphorus = data["deadwood_c_p_ratio"] + reprod_phosphorus = data["plant_reproductive_tissue_turnover_c_p_ratio"] + + return { + "leaf_mass": leaf_total.to_numpy(), + "root_mass": root_total.to_numpy(), + "deadwood_mass": deadwood_total.to_numpy(), + "reprod_mass": reprod_total.to_numpy(), + "leaf_lignin": leaf_lignin.to_numpy(), + "root_lignin": root_lignin.to_numpy(), + "deadwood_lignin": deadwood_lignin.to_numpy(), + "reprod_lignin": reprod_lignin.to_numpy(), + "leaf_nitrogen": leaf_nitrogen.to_numpy(), + "root_nitrogen": root_nitrogen.to_numpy(), + "deadwood_nitrogen": deadwood_nitrogen.to_numpy(), + "reprod_nitrogen": reprod_nitrogen.to_numpy(), + "leaf_phosphorus": leaf_phosphorus.to_numpy(), + "root_phosphorus": root_phosphorus.to_numpy(), + "deadwood_phosphorus": deadwood_phosphorus.to_numpy(), + "reprod_phosphorus": reprod_phosphorus.to_numpy(), + } def calculate_metabolic_proportions_of_input( diff --git a/virtual_ecosystem/models/litter/litter_model.py b/virtual_ecosystem/models/litter/litter_model.py index bc55e3d4b..8b7ffe6be 100644 --- a/virtual_ecosystem/models/litter/litter_model.py +++ b/virtual_ecosystem/models/litter/litter_model.py @@ -46,7 +46,7 @@ class instance. If errors crop here when converting the information from the con ) from virtual_ecosystem.models.litter.chemistry import LitterChemistry from virtual_ecosystem.models.litter.constants import LitterConsts -from virtual_ecosystem.models.litter.input_partition import InputPartition +from virtual_ecosystem.models.litter.input_partition import LitterInputs class LitterModel( @@ -105,6 +105,10 @@ class LitterModel( "leaf_turnover_c_n_ratio", "plant_reproductive_tissue_turnover_c_n_ratio", "root_turnover_c_n_ratio", + "deadwood_c_p_ratio", + "leaf_turnover_c_p_ratio", + "plant_reproductive_tissue_turnover_c_p_ratio", + "root_turnover_c_p_ratio", "herbivory_waste_leaf_carbon", "herbivory_waste_leaf_nitrogen", "herbivory_waste_leaf_phosphorus", @@ -232,9 +236,6 @@ def __init__( self.litter_chemistry = LitterChemistry(data, constants=model_constants) """Litter chemistry object for tracking of litter pool chemistries.""" - self.input_partition = InputPartition(data) - """Input partition object for tracking split between litter pools.""" - self.model_constants = model_constants """Set of constants for the litter model.""" @@ -322,15 +323,15 @@ def update(self, time_index: int, **kwargs: Any) -> None: constants=self.model_constants, ) - input_details = self.input_partition.determine_all_plant_to_litter_flows( - constants=self.model_constants, + litter_inputs = LitterInputs.create_from_data( + self.data, constants=self.model_constants ) # Calculate the updated pool masses updated_pools = calculate_updated_pools( post_consumption_pools=consumed_pools, decay_rates=decay_rates, - input_details=input_details, + input_details=litter_inputs, update_interval=self.model_timing.update_interval_quantity.to( "day" ).magnitude, @@ -338,7 +339,7 @@ def update(self, time_index: int, **kwargs: Any) -> None: # Calculate all the litter chemistry changes updated_chemistries = self.litter_chemistry.calculate_new_pool_chemistries( - updated_pools=updated_pools, input_details=input_details + updated_pools=updated_pools, input_details=litter_inputs ) # Calculate the total mineralisation rates from the litter From 309a7afa1092c501406e8304d56de7b12be1fd4c Mon Sep 17 00:00:00 2001 From: Jacob Cook Date: Thu, 10 Oct 2024 16:04:31 +0100 Subject: [PATCH 11/14] Renamed input_partition submodule to just simply be inputs --- docs/source/_toc.yaml | 4 ++-- .../litter/{input_partition.md => inputs.md} | 0 tests/models/litter/conftest.py | 6 +++--- .../{test_input_partition.py => test_inputs.py} | 14 +++++++------- virtual_ecosystem/models/litter/__init__.py | 4 ++-- virtual_ecosystem/models/litter/carbon.py | 2 +- virtual_ecosystem/models/litter/chemistry.py | 4 ++-- .../litter/{input_partition.py => inputs.py} | 6 +++--- virtual_ecosystem/models/litter/litter_model.py | 2 +- 9 files changed, 21 insertions(+), 21 deletions(-) rename docs/source/api/models/litter/{input_partition.md => inputs.md} (100%) rename tests/models/litter/{test_input_partition.py => test_inputs.py} (94%) rename virtual_ecosystem/models/litter/{input_partition.py => inputs.py} (98%) diff --git a/docs/source/_toc.yaml b/docs/source/_toc.yaml index 07741f0e8..d81372afd 100644 --- a/docs/source/_toc.yaml +++ b/docs/source/_toc.yaml @@ -183,8 +183,8 @@ subtrees: title: The constants submodule - file: api/models/litter/env_factors title: The env_factors submodule - - file: api/models/litter/input_partition - title: The input_partition submodule + - file: api/models/litter/inputs + title: The inputs submodule - file: api/models/litter/litter_model title: The litter_model submodule - file: api/models/soil diff --git a/docs/source/api/models/litter/input_partition.md b/docs/source/api/models/litter/inputs.md similarity index 100% rename from docs/source/api/models/litter/input_partition.md rename to docs/source/api/models/litter/inputs.md diff --git a/tests/models/litter/conftest.py b/tests/models/litter/conftest.py index d55b564e9..585631065 100644 --- a/tests/models/litter/conftest.py +++ b/tests/models/litter/conftest.py @@ -186,7 +186,7 @@ def input_c_p_ratios(input_details): @pytest.fixture def metabolic_splits(total_litter_input): """Metabolic splits for the various plant inputs.""" - from virtual_ecosystem.models.litter.input_partition import ( + from virtual_ecosystem.models.litter.inputs import ( calculate_metabolic_proportions_of_input, ) @@ -230,7 +230,7 @@ def post_consumption_pools(dummy_litter_data): @pytest.fixture def total_litter_input(dummy_litter_data): """Total input mass a chemistry for each plant biomass type.""" - from virtual_ecosystem.models.litter.input_partition import combine_input_sources + from virtual_ecosystem.models.litter.inputs import combine_input_sources total_litter_input = combine_input_sources(dummy_litter_data) @@ -257,7 +257,7 @@ def updated_pools( @pytest.fixture def input_details(dummy_litter_data): """Complete set of details for inputs to the litter model.""" - from virtual_ecosystem.models.litter.input_partition import LitterInputs + from virtual_ecosystem.models.litter.inputs import LitterInputs input_details = LitterInputs.create_from_data( data=dummy_litter_data, constants=LitterConsts diff --git a/tests/models/litter/test_input_partition.py b/tests/models/litter/test_inputs.py similarity index 94% rename from tests/models/litter/test_input_partition.py rename to tests/models/litter/test_inputs.py index b91094fe4..627eb290c 100644 --- a/tests/models/litter/test_input_partition.py +++ b/tests/models/litter/test_inputs.py @@ -1,4 +1,4 @@ -"""Test module for models.litter.input_partition.py.""" +"""Test module for models.litter.inputs.py.""" from logging import ERROR @@ -13,7 +13,7 @@ def test_determine_all_plant_to_litter_flows(dummy_litter_data): """Test that function to determine plant to litter flows works correctly.""" from dataclasses import asdict - from virtual_ecosystem.models.litter.input_partition import LitterInputs + from virtual_ecosystem.models.litter.inputs import LitterInputs expected_inputs = { "leaves_meta_split": [0.8123412282, 0.7504823457, 0.4509559749, 0.0852205423], @@ -60,7 +60,7 @@ def test_determine_all_plant_to_litter_flows(dummy_litter_data): def test_combine_input_sources(dummy_litter_data): """Test that function to combine input sources works as expected.""" - from virtual_ecosystem.models.litter.input_partition import combine_input_sources + from virtual_ecosystem.models.litter.inputs import combine_input_sources expected_combined = { "leaf_mass": [0.02703, 0.0024, 0.02385, 0.0312], @@ -91,7 +91,7 @@ def test_combine_input_sources(dummy_litter_data): def test_calculate_metabolic_proportions_of_input(total_litter_input): """Test that function to calculate metabolic input proportions works as expected.""" - from virtual_ecosystem.models.litter.input_partition import ( + from virtual_ecosystem.models.litter.inputs import ( calculate_metabolic_proportions_of_input, ) @@ -113,7 +113,7 @@ def test_calculate_metabolic_proportions_of_input(total_litter_input): def test_partion_plant_inputs_between_pools(metabolic_splits, total_litter_input): """Check function to partition inputs into litter pools works as expected.""" - from virtual_ecosystem.models.litter.input_partition import ( + from virtual_ecosystem.models.litter.inputs import ( partion_plant_inputs_between_pools, ) @@ -138,7 +138,7 @@ def test_partion_plant_inputs_between_pools(metabolic_splits, total_litter_input def test_split_pool_into_metabolic_and_structural_litter(dummy_litter_data): """Check function to split input biomass between litter pools works as expected.""" - from virtual_ecosystem.models.litter.input_partition import ( + from virtual_ecosystem.models.litter.inputs import ( split_pool_into_metabolic_and_structural_litter, ) @@ -188,7 +188,7 @@ def test_split_pool_into_metabolic_and_structural_litter_bad_data( ): """Check that pool split functions raises an error if out of bounds data is used.""" - from virtual_ecosystem.models.litter.input_partition import ( + from virtual_ecosystem.models.litter.inputs import ( split_pool_into_metabolic_and_structural_litter, ) diff --git a/virtual_ecosystem/models/litter/__init__.py b/virtual_ecosystem/models/litter/__init__.py index 10fb8710c..d0885c29c 100644 --- a/virtual_ecosystem/models/litter/__init__.py +++ b/virtual_ecosystem/models/litter/__init__.py @@ -11,8 +11,8 @@ pools that the litter model is comprised of. * :mod:`~virtual_ecosystem.models.litter.chemistry` tracks the chemistry (lignin, nitrogen and phosphorus) of the litter pools. -* :mod:`~virtual_ecosystem.models.litter.input_partition` handles the partitioning - of biomass input between the different litter pools. +* :mod:`~virtual_ecosystem.models.litter.inputs` handles the partitioning of biomass + input between the different litter pools. * :mod:`~virtual_ecosystem.models.litter.env_factors` provides the functions capturing the impact of environmental factors on litter decay. * :mod:`~virtual_ecosystem.models.litter.constants` provides a set of dataclasses diff --git a/virtual_ecosystem/models/litter/carbon.py b/virtual_ecosystem/models/litter/carbon.py index fbcd1cd19..c804bad42 100644 --- a/virtual_ecosystem/models/litter/carbon.py +++ b/virtual_ecosystem/models/litter/carbon.py @@ -24,7 +24,7 @@ from virtual_ecosystem.models.litter.env_factors import ( calculate_environmental_factors, ) -from virtual_ecosystem.models.litter.input_partition import LitterInputs +from virtual_ecosystem.models.litter.inputs import LitterInputs def calculate_post_consumption_pools( diff --git a/virtual_ecosystem/models/litter/chemistry.py b/virtual_ecosystem/models/litter/chemistry.py index 44e4ac45a..9f22d4c52 100644 --- a/virtual_ecosystem/models/litter/chemistry.py +++ b/virtual_ecosystem/models/litter/chemistry.py @@ -8,7 +8,7 @@ Nitrogen and phosphorus contents do not have an explicit impact on decay rates, instead these contents determine how input material is split between pools (see -:mod:`~virtual_ecosystem.models.litter.input_partition`), which indirectly captures the +:mod:`~virtual_ecosystem.models.litter.inputs`), which indirectly captures the impact of N and P stoichiometry on litter decomposition rates. By contrast, the impact of lignin on decay rates is directly calculated. """ # noqa: D205 @@ -19,7 +19,7 @@ from virtual_ecosystem.core.data import Data from virtual_ecosystem.models.litter.constants import LitterConsts -from virtual_ecosystem.models.litter.input_partition import LitterInputs +from virtual_ecosystem.models.litter.inputs import LitterInputs class LitterChemistry: diff --git a/virtual_ecosystem/models/litter/input_partition.py b/virtual_ecosystem/models/litter/inputs.py similarity index 98% rename from virtual_ecosystem/models/litter/input_partition.py rename to virtual_ecosystem/models/litter/inputs.py index 08c841229..795d4a4d3 100644 --- a/virtual_ecosystem/models/litter/input_partition.py +++ b/virtual_ecosystem/models/litter/inputs.py @@ -1,6 +1,6 @@ -"""The ``models.litter.input_partition`` module handles the partitioning of plant -matter into the various pools of the litter model. This plant matter comes from both -natural tissue death as well as from mechanical inefficiencies in herbivory. +"""The ``models.litter.inputs`` module handles the partitioning of plant matter into the +various pools of the litter model. This plant matter comes from both natural tissue +death as well as from mechanical inefficiencies in herbivory. """ # noqa: D205 from __future__ import annotations diff --git a/virtual_ecosystem/models/litter/litter_model.py b/virtual_ecosystem/models/litter/litter_model.py index 8b7ffe6be..3082647f0 100644 --- a/virtual_ecosystem/models/litter/litter_model.py +++ b/virtual_ecosystem/models/litter/litter_model.py @@ -46,7 +46,7 @@ class instance. If errors crop here when converting the information from the con ) from virtual_ecosystem.models.litter.chemistry import LitterChemistry from virtual_ecosystem.models.litter.constants import LitterConsts -from virtual_ecosystem.models.litter.input_partition import LitterInputs +from virtual_ecosystem.models.litter.inputs import LitterInputs class LitterModel( From a03f6adc2437f329cb81e1867bfe61790cf070e9 Mon Sep 17 00:00:00 2001 From: Jacob Cook Date: Fri, 11 Oct 2024 08:36:36 +0100 Subject: [PATCH 12/14] Fixed broken docs links that arose because of renaming litter.inputs submodule --- docs/source/api/models/litter/inputs.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/api/models/litter/inputs.md b/docs/source/api/models/litter/inputs.md index 8dbbf4e4d..c5f045cfc 100644 --- a/docs/source/api/models/litter/inputs.md +++ b/docs/source/api/models/litter/inputs.md @@ -14,10 +14,10 @@ kernelspec: name: python3 --- -# API documentation for the {mod}`~virtual_ecosystem.models.litter.input_partition` module +# API documentation for the {mod}`~virtual_ecosystem.models.litter.inputs` module ```{eval-rst} -.. automodule:: virtual_ecosystem.models.litter.input_partition +.. automodule:: virtual_ecosystem.models.litter.inputs :autosummary: :members: ``` From 6dfa543f120cd0587120dbaa8e3514a46596f654 Mon Sep 17 00:00:00 2001 From: Jacob Cook Date: Fri, 11 Oct 2024 14:25:06 +0100 Subject: [PATCH 13/14] Made LitterInputs dataclass a frozen dataclass --- virtual_ecosystem/models/litter/inputs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtual_ecosystem/models/litter/inputs.py b/virtual_ecosystem/models/litter/inputs.py index 795d4a4d3..4f01c8f21 100644 --- a/virtual_ecosystem/models/litter/inputs.py +++ b/virtual_ecosystem/models/litter/inputs.py @@ -15,7 +15,7 @@ from virtual_ecosystem.models.litter.constants import LitterConsts -@dataclass +@dataclass(frozen=True) class LitterInputs: """The full set input flows to the litter model.""" From c45e6f2562adac7125697329fd944d02a4b0788c Mon Sep 17 00:00:00 2001 From: Jacob Cook Date: Fri, 11 Oct 2024 14:31:15 +0100 Subject: [PATCH 14/14] Consitently renamed input_details to litter_inputs --- tests/models/litter/conftest.py | 22 +-- tests/models/litter/test_carbon.py | 4 +- tests/models/litter/test_chemistry.py | 36 ++-- virtual_ecosystem/models/litter/carbon.py | 14 +- virtual_ecosystem/models/litter/chemistry.py | 156 +++++++++--------- .../models/litter/litter_model.py | 4 +- 6 files changed, 118 insertions(+), 118 deletions(-) diff --git a/tests/models/litter/conftest.py b/tests/models/litter/conftest.py index 585631065..c37829fd4 100644 --- a/tests/models/litter/conftest.py +++ b/tests/models/litter/conftest.py @@ -140,28 +140,28 @@ def litter_chemistry(dummy_litter_data): @pytest.fixture -def input_lignin(input_details): +def input_lignin(litter_inputs): """Lignin proportion of the relevant input flows.""" from virtual_ecosystem.models.litter.chemistry import ( calculate_litter_input_lignin_concentrations, ) input_lignin = calculate_litter_input_lignin_concentrations( - input_details=input_details + litter_inputs=litter_inputs ) return input_lignin @pytest.fixture -def input_c_n_ratios(input_details): +def input_c_n_ratios(litter_inputs): """Carbon:nitrogen ratio of each input flow.""" from virtual_ecosystem.models.litter.chemistry import ( calculate_litter_input_nitrogen_ratios, ) input_c_n_ratios = calculate_litter_input_nitrogen_ratios( - input_details=input_details, + litter_inputs=litter_inputs, struct_to_meta_nitrogen_ratio=LitterConsts.structural_to_metabolic_n_ratio, ) @@ -169,14 +169,14 @@ def input_c_n_ratios(input_details): @pytest.fixture -def input_c_p_ratios(input_details): +def input_c_p_ratios(litter_inputs): """Carbon:nitrogen ratio of each input flow.""" from virtual_ecosystem.models.litter.chemistry import ( calculate_litter_input_phosphorus_ratios, ) input_c_p_ratios = calculate_litter_input_phosphorus_ratios( - input_details=input_details, + litter_inputs=litter_inputs, struct_to_meta_phosphorus_ratio=LitterConsts.structural_to_metabolic_p_ratio, ) @@ -239,7 +239,7 @@ def total_litter_input(dummy_litter_data): @pytest.fixture def updated_pools( - dummy_litter_data, decay_rates, post_consumption_pools, input_details + dummy_litter_data, decay_rates, post_consumption_pools, litter_inputs ): """Updated carbon mass of each pool.""" from virtual_ecosystem.models.litter.carbon import calculate_updated_pools @@ -247,7 +247,7 @@ def updated_pools( updated_pools = calculate_updated_pools( post_consumption_pools=post_consumption_pools, decay_rates=decay_rates, - input_details=input_details, + litter_inputs=litter_inputs, update_interval=2.0, ) @@ -255,12 +255,12 @@ def updated_pools( @pytest.fixture -def input_details(dummy_litter_data): +def litter_inputs(dummy_litter_data): """Complete set of details for inputs to the litter model.""" from virtual_ecosystem.models.litter.inputs import LitterInputs - input_details = LitterInputs.create_from_data( + litter_inputs = LitterInputs.create_from_data( data=dummy_litter_data, constants=LitterConsts ) - return input_details + return litter_inputs diff --git a/tests/models/litter/test_carbon.py b/tests/models/litter/test_carbon.py index d889fb88f..21b93515b 100644 --- a/tests/models/litter/test_carbon.py +++ b/tests/models/litter/test_carbon.py @@ -140,7 +140,7 @@ def test_calculate_total_C_mineralised(decay_rates): def test_calculate_updated_pools( - dummy_litter_data, decay_rates, post_consumption_pools, input_details + dummy_litter_data, decay_rates, post_consumption_pools, litter_inputs ): """Test that the function to calculate the pool values after the update works.""" from virtual_ecosystem.models.litter.carbon import calculate_updated_pools @@ -156,7 +156,7 @@ def test_calculate_updated_pools( actual_pools = calculate_updated_pools( post_consumption_pools=post_consumption_pools, decay_rates=decay_rates, - input_details=input_details, + litter_inputs=litter_inputs, update_interval=2.0, ) diff --git a/tests/models/litter/test_chemistry.py b/tests/models/litter/test_chemistry.py index f9c4bdeb4..c6ea58c96 100644 --- a/tests/models/litter/test_chemistry.py +++ b/tests/models/litter/test_chemistry.py @@ -27,7 +27,7 @@ def test_calculate_litter_chemistry_factor(): def test_calculate_new_pool_chemistries( - dummy_litter_data, input_details, updated_pools, litter_chemistry + dummy_litter_data, litter_inputs, updated_pools, litter_chemistry ): """Test that function to calculate updated pool chemistries works correctly.""" @@ -48,7 +48,7 @@ def test_calculate_new_pool_chemistries( } actual_chemistries = litter_chemistry.calculate_new_pool_chemistries( - input_details=input_details, + litter_inputs=litter_inputs, updated_pools=updated_pools, ) @@ -59,7 +59,7 @@ def test_calculate_new_pool_chemistries( def test_calculate_lignin_updates( - input_lignin, updated_pools, litter_chemistry, input_details + input_lignin, updated_pools, litter_chemistry, litter_inputs ): """Test that the function to calculate the lignin updates works as expected.""" @@ -71,7 +71,7 @@ def test_calculate_lignin_updates( actual_lignin = litter_chemistry.calculate_lignin_updates( input_lignin=input_lignin, - input_details=input_details, + litter_inputs=litter_inputs, updated_pools=updated_pools, ) @@ -105,7 +105,7 @@ def test_calculate_change_in_chemical_concentration( def test_calculate_c_n_ratio_updates( - dummy_litter_data, input_details, input_c_n_ratios, updated_pools, litter_chemistry + dummy_litter_data, litter_inputs, input_c_n_ratios, updated_pools, litter_chemistry ): """Test that calculation of C:N ratio updates works properly.""" @@ -118,7 +118,7 @@ def test_calculate_c_n_ratio_updates( } actual_change = litter_chemistry.calculate_c_n_ratio_updates( - input_details=input_details, + litter_inputs=litter_inputs, input_c_n_ratios=input_c_n_ratios, updated_pools=updated_pools, ) @@ -130,7 +130,7 @@ def test_calculate_c_n_ratio_updates( def test_calculate_c_p_ratio_updates( - dummy_litter_data, input_details, input_c_p_ratios, updated_pools, litter_chemistry + dummy_litter_data, litter_inputs, input_c_p_ratios, updated_pools, litter_chemistry ): """Test that calculation of C:P ratio updates works properly.""" @@ -143,7 +143,7 @@ def test_calculate_c_p_ratio_updates( } actual_change = litter_chemistry.calculate_c_p_ratio_updates( - input_details=input_details, + litter_inputs=litter_inputs, input_c_p_ratios=input_c_p_ratios, updated_pools=updated_pools, ) @@ -180,7 +180,7 @@ def test_calculate_P_mineralisation(dummy_litter_data, decay_rates, litter_chemi assert np.allclose(actual_p_mineral, expected_p_mineral) -def test_calculate_litter_input_lignin_concentrations(input_details): +def test_calculate_litter_input_lignin_concentrations(litter_inputs): """Check calculation of lignin concentrations of each plant flow to litter.""" from virtual_ecosystem.models.litter.chemistry import ( calculate_litter_input_lignin_concentrations, @@ -191,7 +191,7 @@ def test_calculate_litter_input_lignin_concentrations(input_details): expected_concs_below_struct = [0.48590258, 0.56412613, 0.54265483, 0.67810978] actual_concs = calculate_litter_input_lignin_concentrations( - input_details=input_details, + litter_inputs=litter_inputs, ) assert np.allclose(actual_concs["woody"], expected_woody) @@ -199,7 +199,7 @@ def test_calculate_litter_input_lignin_concentrations(input_details): assert np.allclose(actual_concs["below_structural"], expected_concs_below_struct) -def test_calculate_litter_input_nitrogen_ratios(dummy_litter_data, input_details): +def test_calculate_litter_input_nitrogen_ratios(dummy_litter_data, litter_inputs): """Check function to calculate the C:N ratios of input to each litter pool works.""" from virtual_ecosystem.models.litter.chemistry import ( calculate_litter_input_nitrogen_ratios, @@ -214,7 +214,7 @@ def test_calculate_litter_input_nitrogen_ratios(dummy_litter_data, input_details } actual_c_n_ratios = calculate_litter_input_nitrogen_ratios( - input_details=input_details, + litter_inputs=litter_inputs, struct_to_meta_nitrogen_ratio=LitterConsts.structural_to_metabolic_n_ratio, ) @@ -224,7 +224,7 @@ def test_calculate_litter_input_nitrogen_ratios(dummy_litter_data, input_details assert np.allclose(actual_c_n_ratios[key], expected_c_n_ratios[key]) -def test_calculate_litter_input_phosphorus_ratios(dummy_litter_data, input_details): +def test_calculate_litter_input_phosphorus_ratios(dummy_litter_data, litter_inputs): """Check function to calculate the C:P ratios of input to each litter pool works.""" from virtual_ecosystem.models.litter.chemistry import ( calculate_litter_input_phosphorus_ratios, @@ -239,7 +239,7 @@ def test_calculate_litter_input_phosphorus_ratios(dummy_litter_data, input_detai } actual_c_p_ratios = calculate_litter_input_phosphorus_ratios( - input_details=input_details, + litter_inputs=litter_inputs, struct_to_meta_phosphorus_ratio=LitterConsts.structural_to_metabolic_p_ratio, ) @@ -250,7 +250,7 @@ def test_calculate_litter_input_phosphorus_ratios(dummy_litter_data, input_detai def test_calculate_nutrient_split_between_litter_pools( - dummy_litter_data, input_details + dummy_litter_data, litter_inputs ): """Check the function to calculate the nutrient split between litter pools.""" from virtual_ecosystem.models.litter.chemistry import ( @@ -262,7 +262,7 @@ def test_calculate_nutrient_split_between_litter_pools( actual_meta_c_n, actual_struct_c_n = calculate_nutrient_split_between_litter_pools( input_c_nut_ratio=dummy_litter_data["root_turnover_c_n_ratio"], - metabolic_split=input_details.roots_meta_split, + metabolic_split=litter_inputs.roots_meta_split, struct_to_meta_nutrient_ratio=LitterConsts.structural_to_metabolic_n_ratio, ) @@ -279,7 +279,7 @@ def test_calculate_nutrient_split_between_litter_pools( assert np.allclose( dummy_litter_data["root_turnover_c_n_ratio"], ( - actual_meta_c_n * input_details.roots_meta_split - + actual_struct_c_n * (1 - input_details.roots_meta_split) + actual_meta_c_n * litter_inputs.roots_meta_split + + actual_struct_c_n * (1 - litter_inputs.roots_meta_split) ), ) diff --git a/virtual_ecosystem/models/litter/carbon.py b/virtual_ecosystem/models/litter/carbon.py index c804bad42..f4b8383d5 100644 --- a/virtual_ecosystem/models/litter/carbon.py +++ b/virtual_ecosystem/models/litter/carbon.py @@ -219,7 +219,7 @@ def calculate_total_C_mineralised( def calculate_updated_pools( post_consumption_pools: dict[str, NDArray[np.float32]], decay_rates: dict[str, NDArray[np.float32]], - input_details: LitterInputs, + litter_inputs: LitterInputs, update_interval: float, ) -> dict[str, NDArray[np.float32]]: """Calculate the updated mass of each litter pool. @@ -232,7 +232,7 @@ def calculate_updated_pools( subtracted [kg C m^-2] decay_rates: Dictionary containing the rates of decay for all 5 litter pools [kg C m^-2 day^-1] - input_details: An LitterInputs instance containing the total input of each plant + litter_inputs: An LitterInputs instance containing the total input of each plant biomass type, the proportion of the input that goes to the relevant metabolic pool for each input type (expect deadwood) and the total input into each litter pool. @@ -247,19 +247,19 @@ def calculate_updated_pools( # Net pool changes are found by combining input and decay rates, and then # multiplying by the update time step. - change_in_metabolic_above = input_details.input_above_metabolic - ( + change_in_metabolic_above = litter_inputs.input_above_metabolic - ( decay_rates["metabolic_above"] * update_interval ) - change_in_structural_above = input_details.input_above_structural - ( + change_in_structural_above = litter_inputs.input_above_structural - ( decay_rates["structural_above"] * update_interval ) - change_in_woody = input_details.input_woody - ( + change_in_woody = litter_inputs.input_woody - ( decay_rates["woody"] * update_interval ) - change_in_metabolic_below = input_details.input_below_metabolic - ( + change_in_metabolic_below = litter_inputs.input_below_metabolic - ( decay_rates["metabolic_below"] * update_interval ) - change_in_structural_below = input_details.input_below_structural - ( + change_in_structural_below = litter_inputs.input_below_structural - ( decay_rates["structural_below"] * update_interval ) diff --git a/virtual_ecosystem/models/litter/chemistry.py b/virtual_ecosystem/models/litter/chemistry.py index 9f22d4c52..37e6532e5 100644 --- a/virtual_ecosystem/models/litter/chemistry.py +++ b/virtual_ecosystem/models/litter/chemistry.py @@ -38,7 +38,7 @@ def __init__(self, data: Data, constants: LitterConsts): def calculate_new_pool_chemistries( self, updated_pools: dict[str, NDArray[np.float32]], - input_details: LitterInputs, + litter_inputs: LitterInputs, ) -> dict[str, DataArray]: """Method to calculate the updated chemistry of each litter pool. @@ -49,7 +49,7 @@ def calculate_new_pool_chemistries( Args: updated_pools: Dictionary containing the updated pool densities for all 5 litter pools [kg C m^-2] - input_details: An LitterInputs instance containing the total input of each + litter_inputs: An LitterInputs instance containing the total input of each plant biomass type, the proportion of the input that goes to the relevant metabolic pool for each input type (expect deadwood) and the total input into each litter pool. @@ -57,30 +57,30 @@ def calculate_new_pool_chemistries( # Find lignin and nitrogen contents of the litter input flows input_lignin = calculate_litter_input_lignin_concentrations( - input_details=input_details, + litter_inputs=litter_inputs, ) input_c_n_ratios = calculate_litter_input_nitrogen_ratios( - input_details=input_details, + litter_inputs=litter_inputs, struct_to_meta_nitrogen_ratio=self.structural_to_metabolic_n_ratio, ) input_c_p_ratios = calculate_litter_input_phosphorus_ratios( - input_details=input_details, + litter_inputs=litter_inputs, struct_to_meta_phosphorus_ratio=self.structural_to_metabolic_p_ratio, ) # Then use to find the changes change_in_lignin = self.calculate_lignin_updates( - input_details=input_details, + litter_inputs=litter_inputs, input_lignin=input_lignin, updated_pools=updated_pools, ) change_in_c_n_ratios = self.calculate_c_n_ratio_updates( - input_details=input_details, + litter_inputs=litter_inputs, input_c_n_ratios=input_c_n_ratios, updated_pools=updated_pools, ) change_in_c_p_ratios = self.calculate_c_p_ratio_updates( - input_details=input_details, + litter_inputs=litter_inputs, input_c_p_ratios=input_c_p_ratios, updated_pools=updated_pools, ) @@ -121,7 +121,7 @@ def calculate_new_pool_chemistries( def calculate_lignin_updates( self, - input_details: LitterInputs, + litter_inputs: LitterInputs, input_lignin: dict[str, NDArray[np.float32]], updated_pools: dict[str, NDArray[np.float32]], ) -> dict[str, NDArray[np.float32]]: @@ -132,7 +132,7 @@ def calculate_lignin_updates( used in an integration process. Args: - input_details: An LitterInputs instance containing the total input of each + litter_inputs: An LitterInputs instance containing the total input of each plant biomass type, the proportion of the input that goes to the relevant metabolic pool for each input type (expect deadwood) and the total input into each litter pool. @@ -148,19 +148,19 @@ def calculate_lignin_updates( """ change_in_lignin_above_structural = calculate_change_in_chemical_concentration( - input_carbon=input_details.input_above_structural, + input_carbon=litter_inputs.input_above_structural, updated_pool_carbon=updated_pools["above_structural"], input_conc=input_lignin["above_structural"], old_pool_conc=self.data["lignin_above_structural"].to_numpy(), ) change_in_lignin_woody = calculate_change_in_chemical_concentration( - input_carbon=input_details.input_woody, + input_carbon=litter_inputs.input_woody, updated_pool_carbon=updated_pools["woody"], input_conc=input_lignin["woody"], old_pool_conc=self.data["lignin_woody"].to_numpy(), ) change_in_lignin_below_structural = calculate_change_in_chemical_concentration( - input_carbon=input_details.input_below_structural, + input_carbon=litter_inputs.input_below_structural, updated_pool_carbon=updated_pools["below_structural"], input_conc=input_lignin["below_structural"], old_pool_conc=self.data["lignin_below_structural"].to_numpy(), @@ -174,7 +174,7 @@ def calculate_lignin_updates( def calculate_c_n_ratio_updates( self, - input_details: LitterInputs, + litter_inputs: LitterInputs, input_c_n_ratios: dict[str, NDArray[np.float32]], updated_pools: dict[str, NDArray[np.float32]], ) -> dict[str, NDArray[np.float32]]: @@ -184,7 +184,7 @@ def calculate_c_n_ratio_updates( be used in an integration process. Args: - input_details: An LitterInputs instance containing the total input of each + litter_inputs: An LitterInputs instance containing the total input of each plant biomass type, the proportion of the input that goes to the relevant metabolic pool for each input type (expect deadwood) and the total input into each litter pool. @@ -199,31 +199,31 @@ def calculate_c_n_ratio_updates( """ change_in_n_above_metabolic = calculate_change_in_chemical_concentration( - input_carbon=input_details.input_above_metabolic, + input_carbon=litter_inputs.input_above_metabolic, updated_pool_carbon=updated_pools["above_metabolic"], input_conc=input_c_n_ratios["above_metabolic"], old_pool_conc=self.data["c_n_ratio_above_metabolic"].to_numpy(), ) change_in_n_above_structural = calculate_change_in_chemical_concentration( - input_carbon=input_details.input_above_structural, + input_carbon=litter_inputs.input_above_structural, updated_pool_carbon=updated_pools["above_structural"], input_conc=input_c_n_ratios["above_structural"], old_pool_conc=self.data["c_n_ratio_above_structural"].to_numpy(), ) change_in_n_woody = calculate_change_in_chemical_concentration( - input_carbon=input_details.input_woody, + input_carbon=litter_inputs.input_woody, updated_pool_carbon=updated_pools["woody"], input_conc=input_c_n_ratios["woody"], old_pool_conc=self.data["c_n_ratio_woody"].to_numpy(), ) change_in_n_below_metabolic = calculate_change_in_chemical_concentration( - input_carbon=input_details.input_below_metabolic, + input_carbon=litter_inputs.input_below_metabolic, updated_pool_carbon=updated_pools["below_metabolic"], input_conc=input_c_n_ratios["below_metabolic"], old_pool_conc=self.data["c_n_ratio_below_metabolic"].to_numpy(), ) change_in_n_below_structural = calculate_change_in_chemical_concentration( - input_carbon=input_details.input_below_structural, + input_carbon=litter_inputs.input_below_structural, updated_pool_carbon=updated_pools["below_structural"], input_conc=input_c_n_ratios["below_structural"], old_pool_conc=self.data["c_n_ratio_below_structural"].to_numpy(), @@ -239,7 +239,7 @@ def calculate_c_n_ratio_updates( def calculate_c_p_ratio_updates( self, - input_details: LitterInputs, + litter_inputs: LitterInputs, input_c_p_ratios: dict[str, NDArray[np.float32]], updated_pools: dict[str, NDArray[np.float32]], ) -> dict[str, NDArray[np.float32]]: @@ -249,7 +249,7 @@ def calculate_c_p_ratio_updates( be used in an integration process. Args: - input_details: An LitterInputs instance containing the total input of each + litter_inputs: An LitterInputs instance containing the total input of each plant biomass type, the proportion of the input that goes to the relevant metabolic pool for each input type (expect deadwood) and the total input into each litter pool. @@ -264,31 +264,31 @@ def calculate_c_p_ratio_updates( """ change_in_p_above_metabolic = calculate_change_in_chemical_concentration( - input_carbon=input_details.input_above_metabolic, + input_carbon=litter_inputs.input_above_metabolic, updated_pool_carbon=updated_pools["above_metabolic"], input_conc=input_c_p_ratios["above_metabolic"], old_pool_conc=self.data["c_p_ratio_above_metabolic"].to_numpy(), ) change_in_p_above_structural = calculate_change_in_chemical_concentration( - input_carbon=input_details.input_above_structural, + input_carbon=litter_inputs.input_above_structural, updated_pool_carbon=updated_pools["above_structural"], input_conc=input_c_p_ratios["above_structural"], old_pool_conc=self.data["c_p_ratio_above_structural"].to_numpy(), ) change_in_p_woody = calculate_change_in_chemical_concentration( - input_carbon=input_details.input_woody, + input_carbon=litter_inputs.input_woody, updated_pool_carbon=updated_pools["woody"], input_conc=input_c_p_ratios["woody"], old_pool_conc=self.data["c_p_ratio_woody"].to_numpy(), ) change_in_p_below_metabolic = calculate_change_in_chemical_concentration( - input_carbon=input_details.input_below_metabolic, + input_carbon=litter_inputs.input_below_metabolic, updated_pool_carbon=updated_pools["below_metabolic"], input_conc=input_c_p_ratios["below_metabolic"], old_pool_conc=self.data["c_p_ratio_below_metabolic"].to_numpy(), ) change_in_p_below_structural = calculate_change_in_chemical_concentration( - input_carbon=input_details.input_below_structural, + input_carbon=litter_inputs.input_below_structural, updated_pool_carbon=updated_pools["below_structural"], input_conc=input_c_p_ratios["below_structural"], old_pool_conc=self.data["c_p_ratio_below_structural"].to_numpy(), @@ -408,7 +408,7 @@ def calculate_P_mineralisation( def calculate_litter_input_lignin_concentrations( - input_details: LitterInputs, + litter_inputs: LitterInputs, ) -> dict[str, NDArray[np.float32]]: """Calculate the concentration of lignin for each plant biomass to litter flow. @@ -427,7 +427,7 @@ def calculate_litter_input_lignin_concentrations( then converted to a back into a concentration. Args: - input_details: An LitterInputs instance containing the total input of each + litter_inputs: An LitterInputs instance containing the total input of each plant biomass type, the proportion of the input that goes to the relevant metabolic pool for each input type (expect deadwood) and the total input into each litter pool. @@ -438,18 +438,18 @@ def calculate_litter_input_lignin_concentrations( structural) [kg lignin kg C^-1] """ - lignin_proportion_woody = input_details.deadwood_lignin + lignin_proportion_woody = litter_inputs.deadwood_lignin lignin_proportion_below_structural = ( - input_details.root_lignin - * input_details.root_mass - / input_details.input_below_structural + litter_inputs.root_lignin + * litter_inputs.root_mass + / litter_inputs.input_below_structural ) lignin_proportion_above_structural = ( - (input_details.leaf_lignin * input_details.leaf_mass) - + (input_details.reprod_lignin * input_details.reprod_mass) - ) / input_details.input_above_structural + (litter_inputs.leaf_lignin * litter_inputs.leaf_mass) + + (litter_inputs.reprod_lignin * litter_inputs.reprod_mass) + ) / litter_inputs.input_above_structural return { "woody": lignin_proportion_woody, @@ -459,7 +459,7 @@ def calculate_litter_input_lignin_concentrations( def calculate_litter_input_nitrogen_ratios( - input_details: LitterInputs, + litter_inputs: LitterInputs, struct_to_meta_nitrogen_ratio: float, ) -> dict[str, NDArray[np.float32]]: """Calculate the carbon to nitrogen ratio for each plant biomass to litter flow. @@ -472,7 +472,7 @@ def calculate_litter_input_nitrogen_ratios( tissue turnover) must be taken. Args: - input_details: An LitterInputs instance containing the total input of each + litter_inputs: An LitterInputs instance containing the total input of each plant biomass type, the proportion of the input that goes to the relevant metabolic pool for each input type (expect deadwood) and the total input into each litter pool. @@ -487,60 +487,60 @@ def calculate_litter_input_nitrogen_ratios( # Calculate c_n_ratio split for each (non-wood) input biomass type root_c_n_ratio_meta, root_c_n_ratio_struct = ( calculate_nutrient_split_between_litter_pools( - input_c_nut_ratio=input_details.root_nitrogen, - metabolic_split=input_details.roots_meta_split, + input_c_nut_ratio=litter_inputs.root_nitrogen, + metabolic_split=litter_inputs.roots_meta_split, struct_to_meta_nutrient_ratio=struct_to_meta_nitrogen_ratio, ) ) leaf_c_n_ratio_meta, leaf_c_n_ratio_struct = ( calculate_nutrient_split_between_litter_pools( - input_c_nut_ratio=input_details.leaf_nitrogen, - metabolic_split=input_details.leaves_meta_split, + input_c_nut_ratio=litter_inputs.leaf_nitrogen, + metabolic_split=litter_inputs.leaves_meta_split, struct_to_meta_nutrient_ratio=struct_to_meta_nitrogen_ratio, ) ) reprod_c_n_ratio_meta, reprod_c_n_ratio_struct = ( calculate_nutrient_split_between_litter_pools( - input_c_nut_ratio=input_details.reprod_nitrogen, - metabolic_split=input_details.reproduct_meta_split, + input_c_nut_ratio=litter_inputs.reprod_nitrogen, + metabolic_split=litter_inputs.reproduct_meta_split, struct_to_meta_nutrient_ratio=struct_to_meta_nitrogen_ratio, ) ) c_n_ratio_below_metabolic = root_c_n_ratio_meta c_n_ratio_below_structural = root_c_n_ratio_struct - c_n_ratio_woody = input_details.deadwood_nitrogen + c_n_ratio_woody = litter_inputs.deadwood_nitrogen # Inputs with multiple sources have to be weighted c_n_ratio_above_metabolic = np.divide( ( leaf_c_n_ratio_meta - * input_details.leaf_mass - * input_details.leaves_meta_split + * litter_inputs.leaf_mass + * litter_inputs.leaves_meta_split ) + ( reprod_c_n_ratio_meta - * input_details.reprod_mass - * input_details.reproduct_meta_split + * litter_inputs.reprod_mass + * litter_inputs.reproduct_meta_split ), - (input_details.leaf_mass * input_details.leaves_meta_split) - + (input_details.reprod_mass * input_details.reproduct_meta_split), + (litter_inputs.leaf_mass * litter_inputs.leaves_meta_split) + + (litter_inputs.reprod_mass * litter_inputs.reproduct_meta_split), ) c_n_ratio_above_structural = np.divide( ( leaf_c_n_ratio_struct - * input_details.leaf_mass - * (1 - input_details.leaves_meta_split) + * litter_inputs.leaf_mass + * (1 - litter_inputs.leaves_meta_split) ) + ( reprod_c_n_ratio_struct - * input_details.reprod_mass - * (1 - input_details.reproduct_meta_split) + * litter_inputs.reprod_mass + * (1 - litter_inputs.reproduct_meta_split) ), - (input_details.leaf_mass * (1 - input_details.leaves_meta_split)) - + (input_details.reprod_mass * (1 - input_details.reproduct_meta_split)), + (litter_inputs.leaf_mass * (1 - litter_inputs.leaves_meta_split)) + + (litter_inputs.reprod_mass * (1 - litter_inputs.reproduct_meta_split)), ) return { @@ -553,7 +553,7 @@ def calculate_litter_input_nitrogen_ratios( def calculate_litter_input_phosphorus_ratios( - input_details: LitterInputs, + litter_inputs: LitterInputs, struct_to_meta_phosphorus_ratio: float, ) -> dict[str, NDArray[np.float32]]: """Calculate carbon to phosphorus ratio for each plant biomass to litter flow. @@ -566,7 +566,7 @@ def calculate_litter_input_phosphorus_ratios( turnover) must be taken. Args: - input_details: An LitterInputs instance containing the total input of each + litter_inputs: An LitterInputs instance containing the total input of each plant biomass type, the proportion of the input that goes to the relevant metabolic pool for each input type (expect deadwood) and the total input into each litter pool. @@ -581,60 +581,60 @@ def calculate_litter_input_phosphorus_ratios( # Calculate c_p_ratio split for each (non-wood) input biomass type root_c_p_ratio_meta, root_c_p_ratio_struct = ( calculate_nutrient_split_between_litter_pools( - input_c_nut_ratio=input_details.root_phosphorus, - metabolic_split=input_details.roots_meta_split, + input_c_nut_ratio=litter_inputs.root_phosphorus, + metabolic_split=litter_inputs.roots_meta_split, struct_to_meta_nutrient_ratio=struct_to_meta_phosphorus_ratio, ) ) leaf_c_p_ratio_meta, leaf_c_p_ratio_struct = ( calculate_nutrient_split_between_litter_pools( - input_c_nut_ratio=input_details.leaf_phosphorus, - metabolic_split=input_details.leaves_meta_split, + input_c_nut_ratio=litter_inputs.leaf_phosphorus, + metabolic_split=litter_inputs.leaves_meta_split, struct_to_meta_nutrient_ratio=struct_to_meta_phosphorus_ratio, ) ) reprod_c_p_ratio_meta, reprod_c_p_ratio_struct = ( calculate_nutrient_split_between_litter_pools( - input_c_nut_ratio=input_details.reprod_phosphorus, - metabolic_split=input_details.reproduct_meta_split, + input_c_nut_ratio=litter_inputs.reprod_phosphorus, + metabolic_split=litter_inputs.reproduct_meta_split, struct_to_meta_nutrient_ratio=struct_to_meta_phosphorus_ratio, ) ) c_p_ratio_below_metabolic = root_c_p_ratio_meta c_p_ratio_below_structural = root_c_p_ratio_struct - c_p_ratio_woody = input_details.deadwood_phosphorus + c_p_ratio_woody = litter_inputs.deadwood_phosphorus # Inputs with multiple sources have to be weighted c_p_ratio_above_metabolic = np.divide( ( leaf_c_p_ratio_meta - * input_details.leaf_mass - * input_details.leaves_meta_split + * litter_inputs.leaf_mass + * litter_inputs.leaves_meta_split ) + ( reprod_c_p_ratio_meta - * input_details.reprod_mass - * input_details.reproduct_meta_split + * litter_inputs.reprod_mass + * litter_inputs.reproduct_meta_split ), - (input_details.leaf_mass * input_details.leaves_meta_split) - + (input_details.reprod_mass * input_details.reproduct_meta_split), + (litter_inputs.leaf_mass * litter_inputs.leaves_meta_split) + + (litter_inputs.reprod_mass * litter_inputs.reproduct_meta_split), ) c_p_ratio_above_structural = np.divide( ( leaf_c_p_ratio_struct - * input_details.leaf_mass - * (1 - input_details.leaves_meta_split) + * litter_inputs.leaf_mass + * (1 - litter_inputs.leaves_meta_split) ) + ( reprod_c_p_ratio_struct - * input_details.reprod_mass - * (1 - input_details.reproduct_meta_split) + * litter_inputs.reprod_mass + * (1 - litter_inputs.reproduct_meta_split) ), - (input_details.leaf_mass * (1 - input_details.leaves_meta_split)) - + (input_details.reprod_mass * (1 - input_details.reproduct_meta_split)), + (litter_inputs.leaf_mass * (1 - litter_inputs.leaves_meta_split)) + + (litter_inputs.reprod_mass * (1 - litter_inputs.reproduct_meta_split)), ) return { diff --git a/virtual_ecosystem/models/litter/litter_model.py b/virtual_ecosystem/models/litter/litter_model.py index 3082647f0..6b2200a1f 100644 --- a/virtual_ecosystem/models/litter/litter_model.py +++ b/virtual_ecosystem/models/litter/litter_model.py @@ -331,7 +331,7 @@ def update(self, time_index: int, **kwargs: Any) -> None: updated_pools = calculate_updated_pools( post_consumption_pools=consumed_pools, decay_rates=decay_rates, - input_details=litter_inputs, + litter_inputs=litter_inputs, update_interval=self.model_timing.update_interval_quantity.to( "day" ).magnitude, @@ -339,7 +339,7 @@ def update(self, time_index: int, **kwargs: Any) -> None: # Calculate all the litter chemistry changes updated_chemistries = self.litter_chemistry.calculate_new_pool_chemistries( - updated_pools=updated_pools, input_details=litter_inputs + updated_pools=updated_pools, litter_inputs=litter_inputs ) # Calculate the total mineralisation rates from the litter