Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions tests/models/animals/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,25 @@ def animal_data_for_model_instance(fixture_core_components):
)
data["air_temperature"] = air_temperature

# Adding in litter variables as these are also needed now
litter_pools = DataArray(np.full(grid.n_cells, fill_value=1.5), dims="cell_id")
litter_ratios = DataArray(np.full(grid.n_cells, fill_value=25.5), dims="cell_id")
data["litter_pool_above_metabolic"] = litter_pools
data["litter_pool_above_structural"] = litter_pools
data["litter_pool_woody"] = litter_pools
data["litter_pool_below_metabolic"] = litter_pools
data["litter_pool_below_structural"] = litter_pools
data["c_n_ratio_above_metabolic"] = litter_ratios
data["c_n_ratio_above_structural"] = litter_ratios
data["c_n_ratio_woody"] = litter_ratios
data["c_n_ratio_below_metabolic"] = litter_ratios
data["c_n_ratio_below_structural"] = litter_ratios
data["c_p_ratio_above_metabolic"] = litter_ratios
data["c_p_ratio_above_structural"] = litter_ratios
data["c_p_ratio_woody"] = litter_ratios
data["c_p_ratio_below_metabolic"] = litter_ratios
data["c_p_ratio_below_structural"] = litter_ratios

return data


Expand Down Expand Up @@ -310,3 +329,50 @@ def animal_list_instance(herbivore_functional_group_instance, constants_instance
)
for idx in range(3)
]


@pytest.fixture
def litter_data_instance(fixture_core_components):
"""Creates a dummy litter data for use in tests."""

from virtual_ecosystem.core.data import Data

# Setup the data object with four cells.
data = Data(fixture_core_components.grid)

# The required data is now added. This is basically the 5 litter pool sizes and
# stoichiometric ratios
data_values = {
"litter_pool_above_metabolic": [0.3, 0.15, 0.07, 0.07],
"litter_pool_above_structural": [0.5, 0.25, 0.09, 0.09],
"litter_pool_woody": [4.7, 11.8, 7.3, 7.3],
"litter_pool_below_metabolic": [0.4, 0.37, 0.07, 0.07],
"litter_pool_below_structural": [0.6, 0.31, 0.02, 0.02],
"c_n_ratio_above_metabolic": [7.3, 8.7, 10.1, 9.8],
"c_n_ratio_above_structural": [37.5, 43.2, 45.8, 50.2],
"c_n_ratio_woody": [55.5, 63.3, 47.3, 59.1],
"c_n_ratio_below_metabolic": [10.7, 11.3, 15.2, 12.4],
"c_n_ratio_below_structural": [50.5, 55.6, 73.1, 61.2],
"c_p_ratio_above_metabolic": [57.3, 68.7, 100.1, 95.8],
"c_p_ratio_above_structural": [337.5, 473.2, 415.8, 570.2],
"c_p_ratio_woody": [555.5, 763.3, 847.3, 599.1],
"c_p_ratio_below_metabolic": [310.7, 411.3, 315.2, 412.4],
"c_p_ratio_below_structural": [550.5, 595.6, 773.1, 651.2],
}

for var_name, var_values in data_values.items():
data[var_name] = DataArray(var_values, dims=["cell_id"])

return data


@pytest.fixture
def litter_pool_instance(litter_data_instance):
"""Fixture for a litter pool class to be used in tests."""
from virtual_ecosystem.models.animal.decay import LitterPool

return LitterPool(
pool_name="above_metabolic",
data=litter_data_instance,
cell_area=10000,
)
125 changes: 125 additions & 0 deletions tests/models/animals/test_animal_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@ def test_animal_model_initialization(
(INFO, "Adding data array for 'decomposed_carcasses_carbon'"),
(INFO, "Adding data array for 'decomposed_carcasses_nitrogen'"),
(INFO, "Adding data array for 'decomposed_carcasses_phosphorus'"),
(INFO, "Adding data array for 'litter_consumption_above_metabolic'"),
(INFO, "Adding data array for 'litter_consumption_above_structural'"),
(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'"),
),
id="success",
),
Expand Down Expand Up @@ -285,6 +290,126 @@ def test_update_method_time_index_argument(
assert True


def test_populate_litter_pools(
litter_data_instance,
fixture_core_components,
functional_group_list_instance,
constants_instance,
):
"""Test that function to populate animal consumable litter pool works properly."""
from virtual_ecosystem.models.animal.animal_model import AnimalModel

model = AnimalModel(
data=litter_data_instance,
core_components=fixture_core_components,
functional_groups=functional_group_list_instance,
model_constants=constants_instance,
)

litter_pools = model.populate_litter_pools()
# Check that all five pools have been populated, with the correct values
pool_names = [
"above_metabolic",
"above_structural",
"woody",
"below_metabolic",
"below_structural",
]
for pool_name in pool_names:
assert np.allclose(
litter_pools[pool_name].mass_current,
litter_data_instance[f"litter_pool_{pool_name}"]
* fixture_core_components.grid.cell_area,
)
assert np.allclose(
litter_pools[pool_name].c_n_ratio,
litter_data_instance[f"c_n_ratio_{pool_name}"],
)
assert np.allclose(
litter_pools[pool_name].c_p_ratio,
litter_data_instance[f"c_p_ratio_{pool_name}"],
)


def test_calculate_total_litter_consumption(
litter_data_instance,
fixture_core_components,
functional_group_list_instance,
constants_instance,
):
"""Test that calculation of total consumption of litter by animals is correct."""
from copy import deepcopy

from virtual_ecosystem.models.animal.animal_model import AnimalModel
from virtual_ecosystem.models.animal.decay import LitterPool

model = AnimalModel(
data=litter_data_instance,
core_components=fixture_core_components,
functional_groups=functional_group_list_instance,
model_constants=constants_instance,
)

new_data = deepcopy(litter_data_instance)
# Add new values for each pool
new_data["litter_pool_above_metabolic"] = (
litter_data_instance["litter_pool_above_metabolic"] - 0.03
)
new_data["litter_pool_above_structural"] = (
litter_data_instance["litter_pool_above_structural"] - 0.04
)
new_data["litter_pool_woody"] = litter_data_instance["litter_pool_woody"] - 1.2
new_data["litter_pool_below_metabolic"] = (
litter_data_instance["litter_pool_below_metabolic"] - 0.06
)
new_data["litter_pool_below_structural"] = (
litter_data_instance["litter_pool_below_structural"] - 0.01
)

# Make an updated set of litter pools
pool_names = [
"above_metabolic",
"above_structural",
"woody",
"below_metabolic",
"below_structural",
]
new_litter_pools = {
pool_name: LitterPool(
pool_name=pool_name,
data=new_data,
cell_area=fixture_core_components.grid.cell_area,
)
for pool_name in pool_names
}

# Calculate litter consumption
consumption = model.calculate_total_litter_consumption(
litter_pools=new_litter_pools
)

assert np.allclose(
consumption["litter_consumption_above_metabolic"],
0.03 * np.ones(4),
)
assert np.allclose(
consumption["litter_consumption_above_structural"],
0.04 * np.ones(4),
)
assert np.allclose(
consumption["litter_consumption_woody"],
1.2 * np.ones(4),
)
assert np.allclose(
consumption["litter_consumption_below_metabolic"],
0.06 * np.ones(4),
)
assert np.allclose(
consumption["litter_consumption_below_structural"],
0.01 * np.ones(4),
)


def test_calculate_soil_additions(functional_group_list_instance):
"""Test that soil additions from animal model are calculated correctly."""

Expand Down
46 changes: 46 additions & 0 deletions tests/models/animals/test_decay.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,49 @@ def test_find_decay_consumed_split(decay_rate, scavenging_rate, expected_split):
)

assert actual_split == expected_split


class TestLitterPool:
"""Test LitterPool class."""

def test_get_eaten(self, litter_pool_instance, herbivore_cohort_instance):
"""Test the get_eaten method for LitterPool."""
import pytest

consumed_mass = 50.0 # Define a mass to be consumed for the test
cell_id = 3
initial_mass_current = litter_pool_instance.mass_current[cell_id]
initial_c_n_ratio = litter_pool_instance.c_n_ratio[cell_id]
initial_c_p_ratio = litter_pool_instance.c_p_ratio[cell_id]

actual_mass_gain = litter_pool_instance.get_eaten(
consumed_mass, herbivore_cohort_instance, grid_cell_id=cell_id
)

# Check if the plant mass has been correctly reduced
assert litter_pool_instance.mass_current[cell_id] == pytest.approx(
initial_mass_current
- (
consumed_mass
* herbivore_cohort_instance.functional_group.mechanical_efficiency
)
), "Litter mass should be reduced by the consumed amount."

# Check if the actual mass gain matches the expected value after
# efficiency adjustments
expected_mass_gain = (
consumed_mass
* herbivore_cohort_instance.functional_group.mechanical_efficiency
* herbivore_cohort_instance.functional_group.conversion_efficiency
)
assert actual_mass_gain == pytest.approx(
expected_mass_gain
), "Actual mass gain should match expected value after efficiency adjustments."

# Check that carbon:nitrogen and carbon:phosphorus ratios remain unchanged
assert initial_c_n_ratio == pytest.approx(
litter_pool_instance.c_n_ratio[cell_id]
)
assert initial_c_p_ratio == pytest.approx(
litter_pool_instance.c_p_ratio[cell_id]
)
56 changes: 41 additions & 15 deletions tests/models/litter/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ def dummy_litter_data(fixture_core_components):
# manner. The repeated fourth value is simply to adapt three hand validated examples
# to the shared fixture core components grid
pool_values = {
"litter_pool_above_metabolic": [0.3, 0.15, 0.07, 0.07],
"litter_pool_above_structural": [0.5, 0.25, 0.09, 0.09],
"litter_pool_woody": [4.7, 11.8, 7.3, 7.3],
"litter_pool_below_metabolic": [0.4, 0.37, 0.07, 0.07],
"litter_pool_below_structural": [0.6, 0.31, 0.02, 0.02],
"litter_pool_above_metabolic": [0.319785, 0.161631, 0.086129, 0.093456],
"litter_pool_above_structural": [0.52097, 0.26609, 0.10019, 0.09988],
"litter_pool_woody": [5.1773833, 12.185701, 7.673456, 7.462192],
"litter_pool_below_metabolic": [0.410373, 0.375794, 0.080181, 0.083494],
"litter_pool_below_structural": [0.613547, 0.321674, 0.032738, 0.029168],
"lignin_above_structural": [0.5, 0.1, 0.7, 0.7],
"lignin_woody": [0.5, 0.8, 0.35, 0.35],
"lignin_below_structural": [0.5, 0.25, 0.75, 0.75],
Expand Down Expand Up @@ -75,6 +75,11 @@ def dummy_litter_data(fixture_core_components):
"leaf_turnover_c_p_ratio": [415.0, 327.4, 554.5, 380.9],
"plant_reproductive_tissue_turnover_c_p_ratio": [125.5, 105.0, 145.0, 189.2],
"root_turnover_c_p_ratio": [656.7, 450.6, 437.3, 371.9],
"litter_consumption_above_metabolic": [0.019785, 0.011631, 0.016129, 0.023456],
"litter_consumption_above_structural": [0.02097, 0.01609, 0.01019, 0.00988],
"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],
}

for var, vals in pool_values.items():
Expand All @@ -100,17 +105,13 @@ def dummy_litter_data(fixture_core_components):


@pytest.fixture
def decay_rates(dummy_litter_data, fixture_core_components):
def decay_rates(dummy_litter_data, fixture_core_components, post_consumption_pools):
"""Decay rates for the various litter pools."""

from virtual_ecosystem.models.litter.carbon import calculate_decay_rates

decay_rates = calculate_decay_rates(
above_metabolic=dummy_litter_data["litter_pool_above_metabolic"].to_numpy(),
above_structural=dummy_litter_data["litter_pool_above_structural"].to_numpy(),
woody=dummy_litter_data["litter_pool_woody"].to_numpy(),
below_metabolic=dummy_litter_data["litter_pool_below_metabolic"].to_numpy(),
below_structural=dummy_litter_data["litter_pool_below_structural"].to_numpy(),
post_consumption_pools=post_consumption_pools,
lignin_above_structural=dummy_litter_data["lignin_above_structural"].to_numpy(),
lignin_woody=dummy_litter_data["lignin_woody"].to_numpy(),
lignin_below_structural=dummy_litter_data["lignin_below_structural"].to_numpy(),
Expand Down Expand Up @@ -224,16 +225,41 @@ def input_c_p_ratios(dummy_litter_data, metabolic_splits, litter_chemistry):


@pytest.fixture
def updated_pools(dummy_litter_data, decay_rates, plant_inputs):
"""Updated carbon mass of each pool."""
from virtual_ecosystem.models.litter.carbon import calculate_updated_pools
def post_consumption_pools(dummy_litter_data):
"""Pool sizes after animal consumption for each litter pool."""
from virtual_ecosystem.models.litter.carbon import calculate_post_consumption_pools

updated_pools = calculate_updated_pools(
post_consumption_pools = calculate_post_consumption_pools(
above_metabolic=dummy_litter_data["litter_pool_above_metabolic"].to_numpy(),
above_structural=dummy_litter_data["litter_pool_above_structural"].to_numpy(),
woody=dummy_litter_data["litter_pool_woody"].to_numpy(),
below_metabolic=dummy_litter_data["litter_pool_below_metabolic"].to_numpy(),
below_structural=dummy_litter_data["litter_pool_below_structural"].to_numpy(),
consumption_above_metabolic=dummy_litter_data[
"litter_consumption_above_metabolic"
].to_numpy(),
consumption_above_structural=dummy_litter_data[
"litter_consumption_above_structural"
].to_numpy(),
consumption_woody=dummy_litter_data["litter_consumption_woody"].to_numpy(),
consumption_below_metabolic=dummy_litter_data[
"litter_consumption_below_metabolic"
].to_numpy(),
consumption_below_structural=dummy_litter_data[
"litter_consumption_below_structural"
].to_numpy(),
)

return post_consumption_pools


@pytest.fixture
def updated_pools(dummy_litter_data, decay_rates, plant_inputs, post_consumption_pools):
"""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,
update_interval=2.0,
Expand Down
Loading