Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4897808
StemStochiometry basic structure.
sallymatson Apr 9, 2025
6dc22b8
Moving stochiometry logic throuhgout the setup, estimate gpp, and all…
sallymatson Apr 29, 2025
9b8df28
Draft of (N) stochiometry with tests passing.
sallymatson May 8, 2025
0c481ba
Deleting debug print statements.
sallymatson May 8, 2025
53e1ec6
Returning to original main.py
sallymatson May 8, 2025
b5d599b
Updated stochiometry with abstracted class structure.
sallymatson Jun 3, 2025
ae68e65
Resolve merge conflicts.
sallymatson Jun 3, 2025
432d756
Corrected spelling mistakes.
sallymatson Jun 3, 2025
90ffd56
Fixing tests and updating pyrealm references to rc3 convention.
sallymatson Jun 3, 2025
757d99d
Sphinx targets issue resolved.
sallymatson Jun 4, 2025
f38b116
Remove temp mycorrizha functions.
sallymatson Jun 4, 2025
3875ee8
Update timeout.
sallymatson Jun 4, 2025
5d73cb0
Add stochiometry handling for Phosphorus, clean up some code.
sallymatson Jun 5, 2025
532ee28
Updated foliage helper functions test.
sallymatson Jun 16, 2025
e84f911
Make Stochiometry test fixture and member tests.
sallymatson Jun 16, 2025
72d8d20
Add asserts for final stochiometry tests.
sallymatson Jun 17, 2025
2e1231b
Update virtual_ecosystem/models/plants/stochiometry.py
sallymatson Jul 1, 2025
90fd88e
Apply suggestions from code review
sallymatson Jul 1, 2025
048b02d
Comments from PR, including changing Tissue to ABC.
sallymatson Jul 2, 2025
9d72cfb
Merge branch 'develop' into 749-initial-stochiometry
sallymatson Jul 3, 2025
80589d6
Increasing timeout
sallymatson Jul 3, 2025
5327f9d
Merge branch 'develop' into 749-initial-stochiometry
sallymatson Jul 3, 2025
61b786a
Fix dbh bug.
sallymatson Jul 3, 2025
9c9566a
Updating comments
sallymatson Jul 3, 2025
bb7aae1
Merge branch 'develop' into 749-initial-stochiometry
sallymatson Jul 3, 2025
7f07de4
Merge branch 'develop' into 749-initial-stochiometry
sallymatson Jul 7, 2025
a847c38
Static timeout error.
sallymatson Jul 7, 2025
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
1 change: 1 addition & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ class MyReferenceStyle(AuthorYearReferenceStyle):
("py:class", "PModelConst"),
("py:class", "CoreConst"),
("py:class", "StemAllocation"),
("py:class", "StemStochiometry"),
]
intersphinx_mapping = {
"numpy": ("https://numpy.org/doc/stable/", None),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
execution:
timeout: 90
timeout: 120
jupytext:
formats: md:myst
main_language: python
Expand Down
2 changes: 1 addition & 1 deletion docs/source/using_the_ve/virtual_ecosystem_in_use.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
execution:
timeout: 60
timeout: 120
jupytext:
formats: md:myst
text_representation:
Expand Down
14 changes: 14 additions & 0 deletions tests/models/plants/test_plants_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,14 @@ def test_PlantsModel_calculate_turnover(fxt_plants_model):
)
assert np.allclose(fxt_plants_model.data["root_lignin"], consts.root_lignin)
assert np.allclose(fxt_plants_model.data["leaf_lignin"], consts.leaf_lignin)


def test_PlantsModel_update_cn_ratios(fxt_plants_model, fixture_config):
"""Test the update_cn_ratios method of the plants model."""

fxt_plants_model.update_cn_ratios()
consts = fxt_plants_model.model_constants

assert np.allclose(
fxt_plants_model.data["deadwood_c_n_ratio"], consts.deadwood_c_n_ratio
)
Expand Down Expand Up @@ -356,6 +364,10 @@ def test_PlantsModel_calculate_turnover_constant_override(
def test_PlantsModel_calculate_nutrient_uptake(fxt_plants_model):
"""Test the calculate_nutrient_uptake method of the plants model."""

# Provide transpiration values
fxt_plants_model.per_stem_transpiration = {
cell_id: np.array([10]) for cell_id in fxt_plants_model.communities.keys()
}
# Check reset
fxt_plants_model.calculate_nutrient_uptake()

Expand All @@ -364,6 +376,8 @@ def test_PlantsModel_calculate_nutrient_uptake(fxt_plants_model):
assert np.allclose(fxt_plants_model.data["plant_nitrate_uptake"], 7.5e-3)
assert np.allclose(fxt_plants_model.data["plant_phosphorus_uptake"], 3.0e-5)

# TODO: add test for element uptake


def test_PlantsModel_calculate_mycorrhizal_uptakes(fxt_plants_model):
"""Test the calculate_mycorrhizal_uptakes method of the plants model."""
Expand Down
277 changes: 277 additions & 0 deletions tests/models/plants/test_stochiometry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
"""Tests for the Stochiometry class."""

import numpy as np
import pytest


def test_FoliageTissue__init__(fxt_plants_model):
"""Test the foliage stochiometry."""
from virtual_ecosystem.models.plants.stochiometry import FoliageTissue

plant_consts = fxt_plants_model.model_constants
for cell_id in fxt_plants_model.communities.keys():
community = fxt_plants_model.communities[cell_id]

tissue_model = FoliageTissue(
community=community,
ideal_ratio=np.full(community.n_cohorts, plant_consts.foliage_c_n_ratio),
actual_element_mass=community.stem_allometry.foliage_mass
* plant_consts.foliage_c_n_ratio,
reclaim_ratio=plant_consts.leaf_turnover_c_n_ratio,
)

assert isinstance(tissue_model, FoliageTissue)


@pytest.mark.parametrize(
argnames=(
"classname, carbon_mass, deficit, element_needed_for_growth, "
"element_turnover, Cx_ratio"
),
argvalues=[
pytest.param(
"FoliageTissue",
[1.00834120e01, 3.27620602e-01, 3.85363977e-03],
[0, 0, 0],
[-0.35486191, 0.02330898, 0.87583064],
[-9.83256242e02, -3.19470237e01, -3.75777103e-01],
[0.06666667, 0.06666667, 0.06666667],
),
],
)
def test_Tissue_class_functions(
fxt_plants_model,
classname,
carbon_mass,
deficit,
element_needed_for_growth,
element_turnover,
Cx_ratio,
):
"""Test the carbon mass calculation in FoliageTissue."""
from pyrealm.demography.tmodel import StemAllocation

from virtual_ecosystem.models.plants.stochiometry import FoliageTissue

plant_consts = fxt_plants_model.model_constants
fxt_plants_model.per_stem_gpp = {
cell_id: np.array([55]) for cell_id in fxt_plants_model.communities.keys()
}
cell_id = 0
community = fxt_plants_model.communities[cell_id]
tissue_model = FoliageTissue(
community=community,
ideal_ratio=np.full(community.n_cohorts, plant_consts.foliage_c_n_ratio),
actual_element_mass=community.stem_allometry.foliage_mass
* plant_consts.foliage_c_n_ratio,
reclaim_ratio=plant_consts.leaf_turnover_c_n_ratio,
)

stem_allocation = StemAllocation(
stem_traits=community.stem_traits,
stem_allometry=community.stem_allometry,
whole_crown_gpp=fxt_plants_model.per_stem_gpp[cell_id],
)

# Assert that the calculated carbon mass matches the expected value
assert np.allclose(
tissue_model.carbon_mass,
carbon_mass,
)

assert np.allclose(
tissue_model.deficit,
deficit,
)

assert np.allclose(
tissue_model.element_needed_for_growth(stem_allocation),
element_needed_for_growth,
)

assert np.allclose(
tissue_model.element_turnover(stem_allocation),
element_turnover,
)

assert np.allclose(
tissue_model.Cx_ratio,
Cx_ratio,
)


@pytest.fixture
def fxt_stochiometry_model(fxt_plants_model):
"""Fixture for the Stochiometry class."""

from virtual_ecosystem.models.plants.stochiometry import (
FoliageTissue,
ReproductiveTissue,
RootTissue,
StemStochiometry,
WoodTissue,
)

plant_consts = fxt_plants_model.model_constants
cell_id = 0 # Assuming we are testing for the first cell
community = fxt_plants_model.communities[cell_id]

n_stochiometry = StemStochiometry(
element="N",
tissues=[
FoliageTissue(
community=community,
ideal_ratio=np.full(
community.n_cohorts,
plant_consts.foliage_c_n_ratio,
),
actual_element_mass=community.stem_allometry.foliage_mass
* plant_consts.foliage_c_n_ratio,
reclaim_ratio=plant_consts.leaf_turnover_c_n_ratio,
),
RootTissue(
community=community,
ideal_ratio=np.full(
community.n_cohorts,
plant_consts.root_turnover_c_n_ratio,
),
actual_element_mass=plant_consts.root_turnover_c_n_ratio
* community.stem_traits.zeta
* community.stem_allometry.foliage_mass,
),
WoodTissue(
community=community,
ideal_ratio=np.full(
community.n_cohorts,
plant_consts.deadwood_c_n_ratio,
),
actual_element_mass=plant_consts.deadwood_c_n_ratio
* community.stem_allometry.stem_mass,
),
ReproductiveTissue(
community=community,
ideal_ratio=np.full(
community.n_cohorts,
plant_consts.plant_reproductive_tissue_turnover_c_n_ratio,
),
actual_element_mass=community.stem_allometry.reproductive_tissue_mass
* plant_consts.plant_reproductive_tissue_turnover_c_n_ratio,
),
],
community=community,
)
return n_stochiometry


def test_Stochiometry__init__(fxt_stochiometry_model):
"""Test the Stochiometry class initialization."""

from virtual_ecosystem.models.plants.stochiometry import StemStochiometry

assert isinstance(fxt_stochiometry_model, StemStochiometry)


def test_Stochiometry_total_element_mass(fxt_stochiometry_model):
"""Test the total_element_mass method of the Stochiometry class."""

# Calculate the total element mass
total_mass = fxt_stochiometry_model.total_element_mass

assert np.allclose(total_mass, [1.31887368e05, 4.35408760e02, 5.93227761e-01])


def test_Stochiometry_tissue_deficit(fxt_stochiometry_model):
"""Test the tissue_deficit method of the Stochiometry class."""

# Calculate the tissue deficit
tissue_deficit = fxt_stochiometry_model.tissue_deficit

expected_deficit = np.array([1.01616593e03, 3.30162938e01, 3.88354401e-01])
assert np.allclose(tissue_deficit, expected_deficit)


@pytest.mark.skip(
reason="Negative growth problem. Return to this test when that problem is fixed."
)
def test_Stochiometry_account_for_growth(fxt_plants_model, fxt_stochiometry_model):
"""Test the account_for_growth method of the Stochiometry class."""

from virtual_ecosystem.models.plants.stochiometry import StemAllocation

# Create a StemAllocation object
cell_id = 0 # Assuming we are testing for the first cell
community = fxt_plants_model.communities[cell_id]
fxt_plants_model.per_stem_gpp = {
cell_id: np.array([55]) for cell_id in fxt_plants_model.communities.keys()
}
stem_allocation = StemAllocation(
stem_traits=community.stem_traits,
stem_allometry=community.stem_allometry,
whole_crown_gpp=fxt_plants_model.per_stem_gpp[cell_id],
)

assert np.allclose(
fxt_stochiometry_model.total_element_mass,
[1.31887368e05, 4.35408760e02, 5.93227761e-01],
)

# Account for growth
fxt_stochiometry_model.account_for_growth(stem_allocation)

print("new mass")

assert np.all(
fxt_stochiometry_model.total_element_mass
>= [1.31887368e05, 4.35408760e02, 5.93227761e-01]
)


def test_Stochiometry_account_for_element_loss_turnover(
fxt_plants_model, fxt_stochiometry_model
):
"""Test the account_for_element_loss_turnover method of the Stochiometry class."""

from virtual_ecosystem.models.plants.stochiometry import StemAllocation

# Create a StemAllocation object
cell_id = 0 # Assuming we are testing for the first cell
community = fxt_plants_model.communities[cell_id]
fxt_plants_model.per_stem_gpp = {
cell_id: np.array([55]) for cell_id in fxt_plants_model.communities.keys()
}
stem_allocation = StemAllocation(
stem_traits=community.stem_traits,
stem_allometry=community.stem_allometry,
whole_crown_gpp=fxt_plants_model.per_stem_gpp[cell_id],
)

assert np.allclose(fxt_stochiometry_model.element_surplus, [0.0, 0.0, 0.0])
fxt_stochiometry_model.account_for_element_loss_turnover(stem_allocation)

# The surplus should always be negative after accounting for turnover
assert np.all(fxt_stochiometry_model.element_surplus <= [0.0, 0.0, 0.0])


def test_Stochiometry_distribute_deficit(fxt_stochiometry_model):
"""Test the distribute_deficit method of the Stochiometry class.

NOTE: This method should have a more robust test with a more deliberate test value.
This will be easier to implement once growth is functioning.
"""

# Distribute the deficit
fxt_stochiometry_model.distribute_deficit(0)

assert np.allclose(fxt_stochiometry_model.element_surplus[0], [0])


def test_Stochiometry_distrubte_surplus(fxt_stochiometry_model):
"""Test the distribute_surplus method of the Stochiometry class.

NOTE: This method should have a more robust test with a more deliberate test value.
This will be easier to implement once growth is functioning.
"""

fxt_stochiometry_model.distribute_surplus(0)

assert fxt_stochiometry_model.element_surplus[0] >= 0
27 changes: 17 additions & 10 deletions virtual_ecosystem/models/plants/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,33 @@ class PlantsConsts(ConstantsDataclass):
deadwood_c_n_ratio: float = 56.5
"""Carbon to Nitrogen ratio of deadwood."""

leaf_turnover_c_n_ratio: float = 25.5
"""Carbon to Nitrogen ratio of leaf turnover."""

plant_reproductive_tissue_turnover_c_n_ratio: float = 12.5
"""Carbon to Nitrogen ratio of plant reproductive tissue turnover."""

root_turnover_c_n_ratio: float = 45.6
"""Carbon to Nitrogen ratio of root turnover."""

deadwood_c_p_ratio: float = 856.5
"""Carbon to Phosphorous ratio of deadwood."""

leaf_turnover_c_n_ratio: float = 25.5
"""Carbon to Nitrogen ratio of leaf turnover (senesced leaves)."""

leaf_turnover_c_p_ratio: float = 415.0
"""Carbon to Phosphorous ratio of leaf turnover."""
"""Carbon to Phosphorous ratio of leaf turnover (senesced leaves)."""

plant_reproductive_tissue_turnover_c_n_ratio: float = 12.5
"""Carbon to Nitrogen ratio of plant reproductive tissue turnover."""

plant_reproductive_tissue_turnover_c_p_ratio: float = 125.5
"""Carbon to Phosphorous ratio of plant reproductive tissue turnover."""

root_turnover_c_p_ratio: float = 656.7
"""Carbon to Phosphorous ratio of root turnover."""

root_turnover_c_n_ratio: float = 45.6
"""Carbon to Nitrogen ratio of root turnover."""

foliage_c_n_ratio: float = 15
"""Carbon to Nitrogen ratio of foliage on trees."""

foliage_c_p_ratio: float = 300
"""Carbon to Phosphorous ratio of foliage on trees."""

subcanopy_extinction_coef: float = 0.5
"""The extinction coefficient of subcanopy vegetation (unitless)."""

Expand All @@ -85,6 +91,7 @@ class PlantsConsts(ConstantsDataclass):
subcanopy_sprout_yield: float = 0.5
"""The fraction of subcanopy seedbank mass that is realised as subcanopy vegetation
mass (kg kg-1)."""

root_exudates: float = 0.5
"""Fraction of GPP topslice allocated to root exudates."""

Expand Down
Loading