From 7fbc6dd04a9872519b0d6c8646b543f56c5877c8 Mon Sep 17 00:00:00 2001 From: Jacob Cook Date: Wed, 19 Mar 2025 09:44:58 +0000 Subject: [PATCH 1/7] Changed form of the pH suitability function to fix #779 --- tests/models/soil/test_env_factors.py | 6 +++--- virtual_ecosystem/models/soil/env_factors.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/models/soil/test_env_factors.py b/tests/models/soil/test_env_factors.py index 9ac400ae9..3c1bcbc3a 100644 --- a/tests/models/soil/test_env_factors.py +++ b/tests/models/soil/test_env_factors.py @@ -37,7 +37,7 @@ def test_calculate_environmental_effect_factors( ) expected_water = [1.0, 0.94414168, 0.62176357, 0.07747536] - expected_pH = [0.25, 1.0, 0.428571428, 1.0] + expected_pH = [0.25, 1.0, 0.57142857, 1.0] expected_clay_sat = [1.782, 1.102, 0.83, 1.918] env_factors = calculate_environmental_effect_factors( @@ -131,8 +131,8 @@ def test_calculate_pH_suitability(): from virtual_ecosystem.models.soil.constants import SoilConsts from virtual_ecosystem.models.soil.env_factors import calculate_pH_suitability - pH_values = np.array([3.0, 7.5, 9.0, 5.7, 2.0, 11.5]) - expected_inhib = [0.25, 1.0, 0.428571428, 1.0, 0.0, 0.0] + pH_values = np.array([3.0, 7.5, 9.0, 10.0, 5.7, 2.0, 11.5]) + expected_inhib = [0.25, 1.0, 0.57142857, 0.28571428, 1.0, 0.0, 0.0] actual_inhib = calculate_pH_suitability( soil_pH=pH_values, diff --git a/virtual_ecosystem/models/soil/env_factors.py b/virtual_ecosystem/models/soil/env_factors.py index eb307da64..70a12824c 100644 --- a/virtual_ecosystem/models/soil/env_factors.py +++ b/virtual_ecosystem/models/soil/env_factors.py @@ -209,9 +209,9 @@ def calculate_pH_suitability( lower_optimum_pH - minimum_pH ) # Linear decrease from the upper threshold to maximum pH - pH_factors[between_opt_and_max] = ( - soil_pH[between_opt_and_max] - upper_optimum_pH - ) / (maximum_pH - upper_optimum_pH) + pH_factors[between_opt_and_max] = (maximum_pH - soil_pH[between_opt_and_max]) / ( + maximum_pH - upper_optimum_pH + ) return pH_factors From 50eac5ab501bd152a990b143f36f1b9bd71990cc Mon Sep 17 00:00:00 2001 From: Jacob Cook Date: Wed, 19 Mar 2025 10:23:51 +0000 Subject: [PATCH 2/7] Added C:N and C:P ratios as enzyme properties --- tests/conftest.py | 8 +++++++ tests/models/soil/test_microbial_groups.py | 24 +++++++++++++++++-- .../config/soil_microbial_groups.toml | 10 +++++++- .../models/soil/microbial_groups.py | 6 +++++ .../models/soil/module_schema.json | 10 +++++++- 5 files changed, 54 insertions(+), 4 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 928b4a27d..2743c7ccf 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -131,6 +131,8 @@ def microbial_groups_cfg(): activation_energy_saturation = 30000 reference_temperature = 12.0 turnover_rate = 2.4e-2 + c_n_ratio = 5.2 + c_p_ratio = 16 [[soil.enzyme_class_definition]] source = "bacteria" @@ -141,6 +143,8 @@ def microbial_groups_cfg(): activation_energy_saturation = 30000 reference_temperature = 12.0 turnover_rate = 2.4e-2 + c_n_ratio = 5.2 + c_p_ratio = 16 [[soil.enzyme_class_definition]] source = "fungi" @@ -151,6 +155,8 @@ def microbial_groups_cfg(): activation_energy_saturation = 30000 reference_temperature = 12.0 turnover_rate = 2.4e-2 + c_n_ratio = 6.5 + c_p_ratio = 40.0 [[soil.enzyme_class_definition]] source = "fungi" @@ -161,6 +167,8 @@ def microbial_groups_cfg(): activation_energy_saturation = 30000 reference_temperature = 12.0 turnover_rate = 2.4e-2 + c_n_ratio = 6.5 + c_p_ratio = 40.0 """ diff --git a/tests/models/soil/test_microbial_groups.py b/tests/models/soil/test_microbial_groups.py index d5d28c4ee..d0057afc9 100644 --- a/tests/models/soil/test_microbial_groups.py +++ b/tests/models/soil/test_microbial_groups.py @@ -247,6 +247,8 @@ def test_make_full_set_of_enzymes(fixture_config): activation_energy_saturation = 30000 reference_temperature = 12.0 turnover_rate = 2.4e-2 + c_n_ratio = 5.2 + c_p_ratio = 16 [[soil.enzyme_class_definition]] source = "bacteria" @@ -257,7 +259,9 @@ def test_make_full_set_of_enzymes(fixture_config): activation_energy_saturation = 30000 reference_temperature = 12.0 turnover_rate = 2.4e-2 - + c_n_ratio = 5.2 + c_p_ratio = 16 + [[soil.enzyme_class_definition]] source = "fungi" substrate = "pom" @@ -267,6 +271,8 @@ def test_make_full_set_of_enzymes(fixture_config): activation_energy_saturation = 30000 reference_temperature = 12.0 turnover_rate = 2.4e-2 + c_n_ratio = 6.5 + c_p_ratio = 40.0 """, [ ( @@ -274,7 +280,7 @@ def test_make_full_set_of_enzymes(fixture_config): "The following expected enzyme classes are not defined: fungi_maom", ) ], - id="missing_most_fungi_maom", + id="missing_fungi_maom", ), pytest.param( # archaea included but they shouldn't be """ @@ -287,6 +293,8 @@ def test_make_full_set_of_enzymes(fixture_config): activation_energy_saturation = 30000 reference_temperature = 12.0 turnover_rate = 2.4e-2 + c_n_ratio = 5.2 + c_p_ratio = 16 [[soil.enzyme_class_definition]] source = "bacteria" @@ -297,6 +305,8 @@ def test_make_full_set_of_enzymes(fixture_config): activation_energy_saturation = 30000 reference_temperature = 12.0 turnover_rate = 2.4e-2 + c_n_ratio = 5.2 + c_p_ratio = 16 [[soil.enzyme_class_definition]] source = "fungi" @@ -307,6 +317,8 @@ def test_make_full_set_of_enzymes(fixture_config): activation_energy_saturation = 30000 reference_temperature = 12.0 turnover_rate = 2.4e-2 + c_n_ratio = 6.5 + c_p_ratio = 40.0 [[soil.enzyme_class_definition]] source = "fungi" @@ -317,6 +329,8 @@ def test_make_full_set_of_enzymes(fixture_config): activation_energy_saturation = 30000 reference_temperature = 12.0 turnover_rate = 2.4e-2 + c_n_ratio = 6.5 + c_p_ratio = 40.0 [[soil.enzyme_class_definition]] source = "fungi" @@ -327,6 +341,8 @@ def test_make_full_set_of_enzymes(fixture_config): activation_energy_saturation = 30000 reference_temperature = 12.0 turnover_rate = 2.4e-2 + c_n_ratio = 6.5 + c_p_ratio = 40.0 """, [ ( @@ -347,6 +363,8 @@ def test_make_full_set_of_enzymes(fixture_config): activation_energy_saturation = 30000 reference_temperature = 12.0 turnover_rate = 2.4e-2 + c_n_ratio = 5.2 + c_p_ratio = 16 [[soil.enzyme_class_definition]] source = "fungi" @@ -357,6 +375,8 @@ def test_make_full_set_of_enzymes(fixture_config): activation_energy_saturation = 30000 reference_temperature = 12.0 turnover_rate = 2.4e-2 + c_n_ratio = 6.5 + c_p_ratio = 40.0 """, [ ( diff --git a/virtual_ecosystem/example_data/config/soil_microbial_groups.toml b/virtual_ecosystem/example_data/config/soil_microbial_groups.toml index 4be6226a2..fd25cc06e 100644 --- a/virtual_ecosystem/example_data/config/soil_microbial_groups.toml +++ b/virtual_ecosystem/example_data/config/soil_microbial_groups.toml @@ -43,6 +43,8 @@ activation_energy_rate = 37000 activation_energy_saturation = 30000 reference_temperature = 12.0 turnover_rate = 2.4e-2 +c_n_ratio = 5.2 +c_p_ratio = 16 [[soil.enzyme_class_definition]] source = "bacteria" @@ -53,6 +55,8 @@ activation_energy_rate = 47000 activation_energy_saturation = 30000 reference_temperature = 12.0 turnover_rate = 2.4e-2 +c_n_ratio = 5.2 +c_p_ratio = 16 [[soil.enzyme_class_definition]] source = "fungi" @@ -63,6 +67,8 @@ activation_energy_rate = 37000 activation_energy_saturation = 30000 reference_temperature = 12.0 turnover_rate = 2.4e-2 +c_n_ratio = 6.5 +c_p_ratio = 40.0 [[soil.enzyme_class_definition]] source = "fungi" @@ -72,4 +78,6 @@ half_saturation_constant = 175.0 activation_energy_rate = 47000 activation_energy_saturation = 30000 reference_temperature = 12.0 -turnover_rate = 2.4e-2 \ No newline at end of file +turnover_rate = 2.4e-2 +c_n_ratio = 6.5 +c_p_ratio = 40.0 \ No newline at end of file diff --git a/virtual_ecosystem/models/soil/microbial_groups.py b/virtual_ecosystem/models/soil/microbial_groups.py index 9038de4b6..370af9681 100644 --- a/virtual_ecosystem/models/soil/microbial_groups.py +++ b/virtual_ecosystem/models/soil/microbial_groups.py @@ -40,6 +40,12 @@ class EnzymeConstants: turnover_rate: float """The turnover rate of the enzyme [day^-1].""" + c_n_ratio: float + """Ratio of carbon to nitrogen for the enzyme [unitless].""" + + c_p_ratio: float + """Ratio of carbon to phosphorus for the enzyme [unitless].""" + @dataclass(frozen=True) class MicrobialGroupConstants: diff --git a/virtual_ecosystem/models/soil/module_schema.json b/virtual_ecosystem/models/soil/module_schema.json index 5c0c112d6..771cb7dba 100644 --- a/virtual_ecosystem/models/soil/module_schema.json +++ b/virtual_ecosystem/models/soil/module_schema.json @@ -46,6 +46,12 @@ }, "turnover_rate": { "type": "number" + }, + "c_n_ratio": { + "type": "number" + }, + "c_p_ratio": { + "type": "number" } }, "required": [ @@ -56,7 +62,9 @@ "activation_energy_rate", "activation_energy_saturation", "reference_temperature", - "turnover_rate" + "turnover_rate", + "c_n_ratio", + "c_p_ratio" ] } }, From 19eb43516513b39a5ee66b93543651b442b93449 Mon Sep 17 00:00:00 2001 From: Jacob Cook Date: Fri, 21 Mar 2025 10:46:49 +0000 Subject: [PATCH 3/7] Seperated production and denaturation of enzymes based on substrate as well as microbial group --- tests/conftest.py | 4 + tests/models/soil/conftest.py | 59 ++++++ tests/models/soil/test_microbial_groups.py | 12 ++ tests/models/soil/test_pools.py | 50 +++-- .../config/soil_microbial_groups.toml | 4 + virtual_ecosystem/models/soil/constants.py | 24 --- .../models/soil/microbial_groups.py | 17 ++ .../models/soil/module_schema.json | 19 +- virtual_ecosystem/models/soil/pools.py | 181 ++++++++++-------- 9 files changed, 252 insertions(+), 118 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 2743c7ccf..22137f6ee 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -103,6 +103,8 @@ def microbial_groups_cfg(): reference_temperature = 12.0 c_n_ratio = 5.2 c_p_ratio = 16 + enzyme_production.pom = 0.005 + enzyme_production.maom = 0.005 [[soil.microbial_group_definition]] name = "fungi" @@ -121,6 +123,8 @@ def microbial_groups_cfg(): reference_temperature = 12.0 c_n_ratio = 6.5 c_p_ratio = 40.0 + enzyme_production.pom = 0.005 + enzyme_production.maom = 0.005 [[soil.enzyme_class_definition]] source = "bacteria" diff --git a/tests/models/soil/conftest.py b/tests/models/soil/conftest.py index b4d4ba0e3..d52f43ce1 100644 --- a/tests/models/soil/conftest.py +++ b/tests/models/soil/conftest.py @@ -232,3 +232,62 @@ def biomass_losses(dummy_carbon_data, functional_groups, fixture_core_components ) return {"bacteria": bacterial_biomass_loss, "fungi": fungal_biomass_loss} + + +@pytest.fixture +def growth_rates( + environmental_factors, + functional_groups, + soil_pool_data, + dummy_carbon_data, + fixture_core_components, +): + """Fixture to store growth rates of all the microbial groups.""" + from virtual_ecosystem.models.soil.constants import SoilConsts + from virtual_ecosystem.models.soil.pools import calculate_nutrient_uptake_rates + + bacterial_growth, _ = calculate_nutrient_uptake_rates( + soil_c_pool_lmwc=soil_pool_data.soil_c_pool_lmwc, + soil_n_pool_don=soil_pool_data.soil_n_pool_don, + soil_n_pool_ammonium=soil_pool_data.soil_n_pool_ammonium, + soil_n_pool_nitrate=soil_pool_data.soil_n_pool_nitrate, + soil_p_pool_dop=soil_pool_data.soil_p_pool_dop, + soil_p_pool_labile=soil_pool_data.soil_p_pool_labile, + microbial_pool_size=soil_pool_data.soil_c_pool_bacteria, + water_factor=environmental_factors.water, + pH_factor=environmental_factors.pH, + soil_temp=dummy_carbon_data["soil_temperature"][ + fixture_core_components.layer_structure.index_topsoil_scalar + ], + constants=SoilConsts, + functional_group=functional_groups["bacteria"], + ) + fungal_growth, _ = calculate_nutrient_uptake_rates( + soil_c_pool_lmwc=soil_pool_data.soil_c_pool_lmwc, + soil_n_pool_don=soil_pool_data.soil_n_pool_don, + soil_n_pool_ammonium=soil_pool_data.soil_n_pool_ammonium, + soil_n_pool_nitrate=soil_pool_data.soil_n_pool_nitrate, + soil_p_pool_dop=soil_pool_data.soil_p_pool_dop, + soil_p_pool_labile=soil_pool_data.soil_p_pool_labile, + microbial_pool_size=soil_pool_data.soil_c_pool_fungi, + water_factor=environmental_factors.water, + pH_factor=environmental_factors.pH, + soil_temp=dummy_carbon_data["soil_temperature"][ + fixture_core_components.layer_structure.index_topsoil_scalar + ], + constants=SoilConsts, + functional_group=functional_groups["fungi"], + ) + + return {"bacteria": bacterial_growth, "fungi": fungal_growth} + + +@pytest.fixture +def enzyme_production(functional_groups, growth_rates): + """Fixture to store the total production rates for each enzyme class.""" + from virtual_ecosystem.models.soil.pools import calculate_enzyme_production + + return calculate_enzyme_production( + microbial_groups=functional_groups, + growth_rates=growth_rates, + ) diff --git a/tests/models/soil/test_microbial_groups.py b/tests/models/soil/test_microbial_groups.py index d0057afc9..47355214d 100644 --- a/tests/models/soil/test_microbial_groups.py +++ b/tests/models/soil/test_microbial_groups.py @@ -61,6 +61,8 @@ def test_make_full_set_of_microbial_groups(fixture_config): reference_temperature = 12.0 c_n_ratio = 5.2 c_p_ratio = 16 + enzyme_production.pom = 0.005 + enzyme_production.maom = 0.005 """, [ ( @@ -90,6 +92,8 @@ def test_make_full_set_of_microbial_groups(fixture_config): reference_temperature = 12.0 c_n_ratio = 5.2 c_p_ratio = 16 + enzyme_production.pom = 0.005 + enzyme_production.maom = 0.005 [[soil.microbial_group_definition]] name = "fungi" @@ -108,6 +112,8 @@ def test_make_full_set_of_microbial_groups(fixture_config): reference_temperature = 12.0 c_n_ratio = 5.2 c_p_ratio = 16 + enzyme_production.pom = 0.005 + enzyme_production.maom = 0.005 [[soil.microbial_group_definition]] name = "archaea" @@ -126,6 +132,8 @@ def test_make_full_set_of_microbial_groups(fixture_config): reference_temperature = 12.0 c_n_ratio = 5.2 c_p_ratio = 16 + enzyme_production.pom = 0.005 + enzyme_production.maom = 0.005 """, [ ( @@ -154,6 +162,8 @@ def test_make_full_set_of_microbial_groups(fixture_config): reference_temperature = 12.0 c_n_ratio = 5.2 c_p_ratio = 16 + enzyme_production.pom = 0.005 + enzyme_production.maom = 0.005 [[soil.microbial_group_definition]] name = "archaea" @@ -172,6 +182,8 @@ def test_make_full_set_of_microbial_groups(fixture_config): reference_temperature = 12.0 c_n_ratio = 5.2 c_p_ratio = 16 + enzyme_production.pom = 0.005 + enzyme_production.maom = 0.005 """, [ ( diff --git a/tests/models/soil/test_pools.py b/tests/models/soil/test_pools.py index aa6d98c28..d33a62ef9 100644 --- a/tests/models/soil/test_pools.py +++ b/tests/models/soil/test_pools.py @@ -283,9 +283,7 @@ def test_negative_nutrient_leaching(dummy_carbon_data, fixture_core_components): assert np.allclose(actual_leaching.labile_P, expected_labile_P) -def test_calculate_enzyme_changes( - dummy_carbon_data, soil_pool_data, biomass_losses, enzyme_classes -): +def test_calculate_enzyme_changes(soil_pool_data, enzyme_production, enzyme_classes): """Check that the determination of enzyme pool changes works correctly.""" from virtual_ecosystem.models.soil.pools import calculate_enzyme_changes @@ -301,8 +299,7 @@ def test_calculate_enzyme_changes( actual_enzyme_changes = calculate_enzyme_changes( pools=soil_pool_data, - biomass_losses=biomass_losses, - constants=SoilConsts, + enzyme_production=enzyme_production, enzyme_classes=enzyme_classes, ) @@ -316,7 +313,9 @@ def test_calculate_enzyme_changes( ) -def test_calculate_net_enzyme_change(dummy_carbon_data, biomass_losses, enzyme_classes): +def test_calculate_net_enzyme_change( + dummy_carbon_data, enzyme_production, enzyme_classes +): """Check that the determination of net enzyme pool change works correctly.""" from virtual_ecosystem.models.soil.pools import calculate_net_enzyme_change @@ -326,8 +325,7 @@ def test_calculate_net_enzyme_change(dummy_carbon_data, biomass_losses, enzyme_c actual_net_change, actual_denat = calculate_net_enzyme_change( enzyme_pool_size=dummy_carbon_data["soil_enzyme_pom_bacteria"], - biomass_loss=biomass_losses["bacteria"], - biomass_loss_to_enzyme_class=SoilConsts.maintenance_bacteria_pom_enzyme, + enzyme_production=enzyme_production["bacteria_pom"], enzyme_turnover_rate=enzyme_classes["bacteria_pom"].turnover_rate, ) @@ -335,6 +333,27 @@ def test_calculate_net_enzyme_change(dummy_carbon_data, biomass_losses, enzyme_c assert np.allclose(actual_denat, expected_denat) +def test_calculate_enzyme_production(functional_groups, growth_rates): + """Test that the calculation of total enzyme production works as expected.""" + from virtual_ecosystem.models.soil.pools import calculate_enzyme_production + + expected_production = { + "bacteria_maom": [3.48412887e-7, 1.889194925e-6, 8.0744933e-6, 1.38567254e-7], + "bacteria_pom": [3.48412887e-7, 1.889194925e-6, 8.0744933e-6, 1.38567254e-7], + "fungi_maom": [1.33658392e-7, 1.755719195e-5, 3.49330124e-6, 6.29095335e-7], + "fungi_pom": [1.33658392e-7, 1.755719195e-5, 3.49330124e-6, 6.29095335e-7], + } + + actual_production = calculate_enzyme_production( + microbial_groups=functional_groups, growth_rates=growth_rates + ) + + assert expected_production.keys() == actual_production.keys() + + for enzyme in actual_production.keys(): + assert np.allclose(actual_production[enzyme], expected_production[enzyme]) + + def test_calculate_maintenance_biomass_synthesis( dummy_carbon_data, fixture_core_components, functional_groups ): @@ -676,7 +695,9 @@ def test_calculate_soil_nutrient_mineralisation( assert np.allclose(actual_rate, expected_rate) -def test_calculate_nutrient_flows_to_necromass(functional_groups): +def test_calculate_nutrient_flows_to_necromass( + functional_groups, enzyme_changes, enzyme_classes +): """Test that the function to calculate nutrient flows to necromass works.""" from virtual_ecosystem.models.soil.pools import ( calculate_nutrient_flows_to_necromass, @@ -687,13 +708,6 @@ def test_calculate_nutrient_flows_to_necromass(functional_groups): ) true_fungal_loss = np.array([0.008185263, 0.083731966, 0.023023140, 0.032136038]) - bacterial_enzyme_denaturation = np.array( - [0.001398696, 0.000510624, 0.001803384, 0.00018168] - ) - fungal_enzyme_denaturation = np.array( - [0.000833736, 0.000301824, 0.000246408, 0.000157752] - ) - expected_n_flow_to_necromass = [0.011914627, 0.017358086, 0.026565221, 0.006364449] expected_p_flow_to_necromass = [0.003646779, 0.003540533, 0.008051958, 0.001261101] @@ -701,9 +715,9 @@ def test_calculate_nutrient_flows_to_necromass(functional_groups): calculate_nutrient_flows_to_necromass( bacterial_loss=true_bacterial_loss, fungal_loss=true_fungal_loss, - bacterial_enzyme_denaturation=bacterial_enzyme_denaturation, - fungal_enzyme_denaturation=fungal_enzyme_denaturation, + enzyme_changes=enzyme_changes, microbial_groups=functional_groups, + enzyme_classes=enzyme_classes, ) ) diff --git a/virtual_ecosystem/example_data/config/soil_microbial_groups.toml b/virtual_ecosystem/example_data/config/soil_microbial_groups.toml index fd25cc06e..87625dae1 100644 --- a/virtual_ecosystem/example_data/config/soil_microbial_groups.toml +++ b/virtual_ecosystem/example_data/config/soil_microbial_groups.toml @@ -15,6 +15,8 @@ activation_energy_turnover = 20000 reference_temperature = 12.0 c_n_ratio = 5.2 c_p_ratio = 16 +enzyme_production.pom = 0.005 +enzyme_production.maom = 0.005 [[soil.microbial_group_definition]] name = "fungi" @@ -33,6 +35,8 @@ activation_energy_turnover = 20000 reference_temperature = 12.0 c_n_ratio = 6.5 c_p_ratio = 40.0 +enzyme_production.pom = 0.005 +enzyme_production.maom = 0.005 [[soil.enzyme_class_definition]] source = "bacteria" diff --git a/virtual_ecosystem/models/soil/constants.py b/virtual_ecosystem/models/soil/constants.py index 11b612d0a..a0f1de0fa 100644 --- a/virtual_ecosystem/models/soil/constants.py +++ b/virtual_ecosystem/models/soil/constants.py @@ -50,30 +50,6 @@ class SoilConsts(ConstantsDataclass): [unitless]. Value is taken from :cite:t:`moyano_responses_2013`. """ - maintenance_bacteria_pom_enzyme: float = 1e-2 - """Fraction of bacterial maintenance used to produce POM degrading enzymes. - - [unitless]. Value taken from :cite:t:`wang_development_2013`. - """ - - maintenance_fungi_pom_enzyme: float = 1e-2 - """Fraction of fungal maintenance used to produce POM degrading enzymes. - - [unitless]. Value taken from :cite:t:`wang_development_2013`. - """ - - maintenance_bacteria_maom_enzyme: float = 1e-2 - """Fraction of bacterial maintenance used to produce MAOM degrading enzymes. - - [unitless]. Value taken from :cite:t:`wang_development_2013`. - """ - - maintenance_fungi_maom_enzyme: float = 1e-2 - """Fraction of bacterial maintenance used to produce MAOM degrading enzymes. - - [unitless]. Value taken from :cite:t:`wang_development_2013`. - """ - # TODO - At some point, need to allow microbial and fungal environmental factors to # vary min_pH_microbes: float = 2.5 diff --git a/virtual_ecosystem/models/soil/microbial_groups.py b/virtual_ecosystem/models/soil/microbial_groups.py index 370af9681..9b26fc80a 100644 --- a/virtual_ecosystem/models/soil/microbial_groups.py +++ b/virtual_ecosystem/models/soil/microbial_groups.py @@ -105,6 +105,23 @@ class MicrobialGroupConstants: c_p_ratio: float """Ratio of carbon to phosphorus in biomass [unitless].""" + enzyme_production: dict[str, float] + """Details of the enzymes produced by the microbial groups. + + The keys are the substrates for which enzymes are produced, and the values are the + allocation to enzyme production. This allocation is expressed as a fraction of the + (gross) cellular biomass growth. + """ + + def enzyme_substrates(self) -> list[str]: + """Substrates that the microbial group produces enzymes for.""" + + return [ + substrate + for substrate, production in self.enzyme_production.items() + if production > 0.0 + ] + def make_full_set_of_microbial_groups( config: Config, diff --git a/virtual_ecosystem/models/soil/module_schema.json b/virtual_ecosystem/models/soil/module_schema.json index 771cb7dba..720050a6c 100644 --- a/virtual_ecosystem/models/soil/module_schema.json +++ b/virtual_ecosystem/models/soil/module_schema.json @@ -121,6 +121,22 @@ }, "c_p_ratio": { "type": "number" + }, + "enzyme_production": { + "description": "Allocation to production of each enzyme type", + "type": "object", + "properties": { + "pom": { + "type": "number" + }, + "maom": { + "type": "number" + } + }, + "required": [ + "pom", + "maom" + ] } }, "required": [ @@ -139,7 +155,8 @@ "activation_energy_turnover", "reference_temperature", "c_n_ratio", - "c_p_ratio" + "c_p_ratio", + "enzyme_production" ] } }, diff --git a/virtual_ecosystem/models/soil/pools.py b/virtual_ecosystem/models/soil/pools.py index 3cea519c7..a9d6cbdff 100644 --- a/virtual_ecosystem/models/soil/pools.py +++ b/virtual_ecosystem/models/soil/pools.py @@ -29,9 +29,6 @@ MicrobialGroupConstants, ) -# TODO - At this point in time I'm not adding specific phosphatase enzymes, need to -# think about adding these in future - @dataclass class MicrobialChanges: @@ -183,14 +180,26 @@ class EnzymePoolChanges: Units of [kg C m^-3 day^-1] """ - denaturation_bacterial: NDArray[np.float32] - """Total denaturation rate for all the enzyme produced by bacteria. + denaturation_pom_bacteria: NDArray[np.float32] + """Denaturation rate for the :term:`POM` degrading enzyme produced by bacteria. Units of [kg C m^-3 day^-1] """ - denaturation_fungal: NDArray[np.float32] - """Total denaturation rate for all the enzyme produced by fungi. + denaturation_maom_bacteria: NDArray[np.float32] + """Denaturation rate for the :term:`MAOM` degrading enzyme produced by bacteria. + + Units of [kg C m^-3 day^-1] + """ + + denaturation_pom_fungi: NDArray[np.float32] + """Denaturation rate for the :term:`POM` degrading enzyme produced by fungi. + + Units of [kg C m^-3 day^-1] + """ + + denaturation_maom_fungi: NDArray[np.float32] + """Denaturation rate for the :term:`MAOM` degrading enzyme produced by fungi. Units of [kg C m^-3 day^-1] """ @@ -780,36 +789,27 @@ def calculate_microbial_changes( soil_temp=soil_temp, microbial_group=functional_groups["fungi"], ) + + # Calculate the total production of each enzyme class + enzyme_production = calculate_enzyme_production( + microbial_groups=functional_groups, + growth_rates={"bacteria": bacterial_growth, "fungi": fungal_growth}, + ) + # Find changes in each enzyme pool enzyme_changes = calculate_enzyme_changes( pools=pools, - biomass_losses={ - "bacteria": bacterial_biomass_loss, - "fungi": fungal_biomass_loss, - }, - constants=constants, + enzyme_production=enzyme_production, enzyme_classes=enzyme_classes, ) - # Find fraction of loss that isn't enzyme production - true_bacterial_loss = ( - 1 - - constants.maintenance_bacteria_pom_enzyme - - constants.maintenance_bacteria_maom_enzyme - ) * bacterial_biomass_loss - true_fungal_loss = ( - 1 - - constants.maintenance_fungi_pom_enzyme - - constants.maintenance_fungi_maom_enzyme - ) * fungal_biomass_loss - # Find flow of nitrogen to necromass pool necromass_n_flow, necromass_p_flow = calculate_nutrient_flows_to_necromass( - bacterial_loss=true_bacterial_loss, - fungal_loss=true_fungal_loss, - bacterial_enzyme_denaturation=enzyme_changes.denaturation_bacterial, - fungal_enzyme_denaturation=enzyme_changes.denaturation_fungal, + bacterial_loss=bacterial_biomass_loss, + fungal_loss=fungal_biomass_loss, + enzyme_changes=enzyme_changes, microbial_groups=functional_groups, + enzyme_classes=enzyme_classes, ) return MicrobialChanges( @@ -830,10 +830,12 @@ def calculate_microbial_changes( pom_enzyme_fungi_change=enzyme_changes.net_change_pom_fungi, maom_enzyme_fungi_change=enzyme_changes.net_change_maom_fungi, necromass_generation=( - enzyme_changes.denaturation_bacterial - + enzyme_changes.denaturation_fungal - + true_bacterial_loss - + true_fungal_loss + enzyme_changes.denaturation_pom_bacteria + + enzyme_changes.denaturation_maom_bacteria + + enzyme_changes.denaturation_pom_fungi + + enzyme_changes.denaturation_maom_fungi + + bacterial_biomass_loss + + fungal_biomass_loss ), necromass_n_flow=necromass_n_flow, necromass_p_flow=necromass_p_flow, @@ -963,16 +965,14 @@ def calculate_nutrient_leaching( def calculate_enzyme_changes( pools: PoolData, - biomass_losses: dict[str, NDArray[np.float32]], - constants: SoilConsts, + enzyme_production: dict[str, NDArray[np.float32]], enzyme_classes: dict[str, EnzymeConstants], ) -> EnzymePoolChanges: """Calculate the change in each of the soil enzyme pools. Args: pools: Data class containing the various soil pools. - biomass_losses: Total rate of loss of biomass for each functional group due to - cell death, protein degradation and enzyme production [kg C m^-3 day^-1] + enzyme_production: Production rates for each class of enzyme [kg C m^-3 day^-1] constants: Set of constants for the soil model. enzyme_classes: Details of the enzyme classes used in the soil model. @@ -994,10 +994,7 @@ def calculate_enzyme_changes( enzyme_pool_size=getattr( pools, f"soil_enzyme_{substrate}_{source}" ), - biomass_loss=biomass_losses[source], - biomass_loss_to_enzyme_class=getattr( - constants, f"maintenance_{source}_{substrate}_enzyme" - ), + enzyme_production=enzyme_production[f"{source}_{substrate}"], enzyme_turnover_rate=enzyme_classes[ f"{source}_{substrate}" ].turnover_rate, @@ -1009,31 +1006,21 @@ def calculate_enzyme_changes( for source in sources } - total_denaturation = { - source: np.sum( - [ - enzyme_changes[source][substrate]["denaturation"] - for substrate in substrates - ], - axis=0, - ) - for source in sources - } - return EnzymePoolChanges( net_change_pom_bacteria=enzyme_changes["bacteria"]["pom"]["net_change"], net_change_maom_bacteria=enzyme_changes["bacteria"]["maom"]["net_change"], net_change_pom_fungi=enzyme_changes["fungi"]["pom"]["net_change"], net_change_maom_fungi=enzyme_changes["fungi"]["maom"]["net_change"], - denaturation_bacterial=total_denaturation["bacteria"], - denaturation_fungal=total_denaturation["fungi"], + denaturation_pom_bacteria=enzyme_changes["bacteria"]["pom"]["denaturation"], + denaturation_maom_bacteria=enzyme_changes["bacteria"]["maom"]["denaturation"], + denaturation_pom_fungi=enzyme_changes["fungi"]["pom"]["denaturation"], + denaturation_maom_fungi=enzyme_changes["fungi"]["maom"]["denaturation"], ) def calculate_net_enzyme_change( enzyme_pool_size: NDArray[np.float32], - biomass_loss: NDArray[np.float32], - biomass_loss_to_enzyme_class: float, + enzyme_production: NDArray[np.float32], enzyme_turnover_rate: float, ) -> tuple[NDArray[np.float32], NDArray[np.float32]]: """Calculate the change in concentration for a specific enzyme pool. @@ -1044,11 +1031,7 @@ def calculate_net_enzyme_change( Args: enzyme_pool_size: Amount of enzyme class of interest [kg C m^-3] - biomass_loss: Rate a which the microbial biomass pool of interest loses biomass, - this is a combination of enzyme excretion, protein degradation, and cell - death [kg C m^-3 day^-1] - biomass_loss_to_enzyme_class: Fraction of the biomass loss that goes to - producing the enzyme class of interest [unitless] + enzyme_production: Production rate for the enzyme in question [kg C m^-3 day^-1] enzyme_turnover_rate: Rate at which the enzyme denatures [day^-1] Returns: @@ -1057,7 +1040,6 @@ def calculate_net_enzyme_change( """ # Calculate production and turnover of each enzyme class - enzyme_production = biomass_loss_to_enzyme_class * biomass_loss enzyme_turnover = calculate_enzyme_turnover( enzyme_pool=enzyme_pool_size, turnover_rate=enzyme_turnover_rate ) @@ -1066,6 +1048,35 @@ def calculate_net_enzyme_change( return (enzyme_production - enzyme_turnover, enzyme_turnover) +def calculate_enzyme_production( + microbial_groups: dict[str, MicrobialGroupConstants], + growth_rates: dict[str, NDArray[np.float32]], +) -> dict[str, NDArray[np.float32]]: + """Calculate the total production of each enzyme class. + + This function checks which substrates each functional group produces enzymes for, + and then calculates the enzyme productions based on the growth rates and the + proportional enzyme production. + + Args: + microbial_groups: Set of microbial functional groups defined in the soil model + growth_rates: The (gross) growth rates of each microbial group [kg C m^-3 + day^-1] + + Returns: + A dictionary containing the total production rate of each enzyme class [kg C + m^-3 day^-1] + """ + + return { + f"{group.name}_{substrate}": ( + growth_rates[group.name] * group.enzyme_production[substrate] + ) + for group in microbial_groups.values() + for substrate in group.enzyme_substrates() + } + + def calculate_maintenance_biomass_synthesis( microbe_pool_size: NDArray[np.float32], soil_temp: NDArray[np.float32], @@ -1257,6 +1268,7 @@ def calculate_nutrient_uptake_rates( constants.cue_reference_temp, constants.cue_with_temperature, ) + # TODO - a portion of this should be allocated to enzyme production carbon_gain_max = carbon_uptake_rate_max * carbon_use_efficiency # Find stoichiometry of the LMWC pool and use to find maximum possible uptake rates @@ -1271,6 +1283,8 @@ def calculate_nutrient_uptake_rates( actual_carbon_gain = np.minimum.reduce( [ carbon_gain_max, + # TODO - This isn't the right ratio, it should be the (weighted) average of + # the functional group and the enzyme classes functional_group.c_n_ratio * ( organic_nitrogen_uptake_rate_max @@ -1292,6 +1306,8 @@ def calculate_nutrient_uptake_rates( # Calculate uptake/release of inorganic nitrogen based on difference between # stoichiometric demand and organic nitrogen uptake + # TODO - Again this isn't the right ratio, it should be the (weighted) average of + # the functional group and the enzyme classes nitrogen_demand = actual_carbon_gain / functional_group.c_n_ratio inorganic_nitrogen_change = nitrogen_demand - actual_organic_nitrogen_uptake @@ -1315,6 +1331,8 @@ def calculate_nutrient_uptake_rates( # Calculate uptake/release of inorganic phosphorus based on difference between # stoichiometric demand and organic phosphorus uptake + # TODO - Again this isn't the right ratio, it should be the (weighted) average of + # the functional group and the enzyme classes phosphorus_demand = actual_carbon_gain / functional_group.c_p_ratio inorganic_phosphorus_change = phosphorus_demand - actual_organic_phosphorus_uptake @@ -1651,9 +1669,9 @@ def calculate_soil_nutrient_mineralisation( def calculate_nutrient_flows_to_necromass( bacterial_loss: NDArray[np.float32], fungal_loss: NDArray[np.float32], - bacterial_enzyme_denaturation: NDArray[np.float32], - fungal_enzyme_denaturation: NDArray[np.float32], + enzyme_changes: EnzymePoolChanges, microbial_groups: dict[str, MicrobialGroupConstants], + enzyme_classes: dict[str, EnzymeConstants], ) -> tuple[NDArray[np.float32], NDArray[np.float32]]: """Calculate the rate at which nutrients flow into the necromass pool. @@ -1664,26 +1682,39 @@ def calculate_nutrient_flows_to_necromass( bacterial_loss: Rate at which bacterial biomass becomes necromass [kg C m^-3 day^-1] fungal_loss: Rate at which fungal biomass becomes necromass [kg C m^-3 day^-1] - bacterial_enzyme_denaturation: Rate at which enzymes produced by bacteria - denature [kg C m^-3 day^-1] - fungal_enzyme_denaturation: Rate at which enzymes produced by fungi denature [kg - C m^-3 day^-1] + enzyme_changes: Details of the rate change for the soil enzyme pools. microbial_groups: Set of microbial functional groups defined in the soil model + enzyme_classes: Details of the enzyme classes used by the soil model. Returns: A tuple containing the rates at which nitrogen [kg N m^-3 day^-1] and phosphorus [kg P m^-3 day^-1] are added to the soil necromass pool """ + # Calculate nutrient flows due to cellular losses + necromass_n_cellular = (bacterial_loss / microbial_groups["bacteria"].c_n_ratio) + ( + fungal_loss / microbial_groups["fungi"].c_n_ratio + ) + necromass_p_cellular = (bacterial_loss / microbial_groups["bacteria"].c_p_ratio) + ( + fungal_loss / microbial_groups["fungi"].c_p_ratio + ) + + necromass_n_enzyme = sum( + getattr(enzyme_changes, f"denaturation_{substrate}_{group}") + / enzyme_classes[f"{group}_{substrate}"].c_n_ratio + for group in ["bacteria", "fungi"] + for substrate in ["maom", "pom"] + ) + necromass_p_enzyme = sum( + getattr(enzyme_changes, f"denaturation_{substrate}_{group}") + / enzyme_classes[f"{group}_{substrate}"].c_p_ratio + for group in ["bacteria", "fungi"] + for substrate in ["maom", "pom"] + ) + return ( - (bacterial_loss / microbial_groups["bacteria"].c_n_ratio) - + (fungal_loss / microbial_groups["fungi"].c_n_ratio) - + (bacterial_enzyme_denaturation / microbial_groups["bacteria"].c_n_ratio) - + (fungal_enzyme_denaturation / microbial_groups["fungi"].c_n_ratio), - (bacterial_loss / microbial_groups["bacteria"].c_p_ratio) - + (fungal_loss / microbial_groups["fungi"].c_p_ratio) - + (bacterial_enzyme_denaturation / microbial_groups["bacteria"].c_p_ratio) - + (fungal_enzyme_denaturation / microbial_groups["fungi"].c_p_ratio), + necromass_n_cellular + necromass_n_enzyme, + necromass_p_cellular + necromass_p_enzyme, ) From 872708f3db6798050c7c8015d5f47207997acb9f Mon Sep 17 00:00:00 2001 From: Jacob Cook Date: Mon, 24 Mar 2025 12:05:47 +0000 Subject: [PATCH 4/7] Started calculating average biomass stochiometries based on weighted average of enzyme stochiometries and cellular stochiometries --- tests/models/soil/conftest.py | 20 +++- tests/models/soil/test_microbial_groups.py | 54 ++++++++++- .../models/soil/microbial_groups.py | 95 +++++++++++++++++-- virtual_ecosystem/models/soil/pools.py | 25 ++--- virtual_ecosystem/models/soil/soil_model.py | 4 +- 5 files changed, 173 insertions(+), 25 deletions(-) diff --git a/tests/models/soil/conftest.py b/tests/models/soil/conftest.py index d52f43ce1..18b8209d3 100644 --- a/tests/models/soil/conftest.py +++ b/tests/models/soil/conftest.py @@ -189,13 +189,15 @@ def maom_desorption(dummy_carbon_data): @pytest.fixture -def functional_groups(fixture_config): +def functional_groups(fixture_config, enzyme_classes): """Set of functional groups based on the soil model constants.""" from virtual_ecosystem.models.soil.microbial_groups import ( make_full_set_of_microbial_groups, ) - return make_full_set_of_microbial_groups(config=fixture_config) + return make_full_set_of_microbial_groups( + config=fixture_config, enzyme_classes=enzyme_classes + ) @pytest.fixture @@ -208,6 +210,20 @@ def enzyme_classes(fixture_config): return make_full_set_of_enzymes(config=fixture_config) +@pytest.fixture +def enzyme_changes(soil_pool_data, enzyme_production, enzyme_classes): + """Changes for each each enzyme class.""" + from virtual_ecosystem.models.soil.pools import ( + calculate_enzyme_changes, + ) + + return calculate_enzyme_changes( + pools=soil_pool_data, + enzyme_production=enzyme_production, + enzyme_classes=enzyme_classes, + ) + + @pytest.fixture def biomass_losses(dummy_carbon_data, functional_groups, fixture_core_components): """Rates of biomass loss from each microbial pool.""" diff --git a/tests/models/soil/test_microbial_groups.py b/tests/models/soil/test_microbial_groups.py index 47355214d..1e24c7e65 100644 --- a/tests/models/soil/test_microbial_groups.py +++ b/tests/models/soil/test_microbial_groups.py @@ -11,7 +11,7 @@ from virtual_ecosystem.core.config import Config, ConfigurationError -def test_make_full_set_of_microbial_groups(fixture_config): +def test_make_full_set_of_microbial_groups(fixture_config, enzyme_classes): """Test that the function to make all the microbial group works.""" from virtual_ecosystem.models.soil.microbial_groups import ( MicrobialGroupConstants, @@ -20,7 +20,9 @@ def test_make_full_set_of_microbial_groups(fixture_config): expected_groups = ["bacteria", "fungi"] - functional_groups = make_full_set_of_microbial_groups(fixture_config) + functional_groups = make_full_set_of_microbial_groups( + fixture_config, enzyme_classes=enzyme_classes + ) assert set(expected_groups) == set(functional_groups.keys()) @@ -200,7 +202,9 @@ def test_make_full_set_of_microbial_groups(fixture_config): ), ], ) -def test_make_full_set_of_microbial_groups_errors(caplog, cfg_strings, exp_log): +def test_make_full_set_of_microbial_groups_errors( + caplog, enzyme_classes, cfg_strings, exp_log +): """Check that bad configs generate errors during microbial group generation.""" from virtual_ecosystem.models.soil.microbial_groups import ( make_full_set_of_microbial_groups, @@ -210,7 +214,7 @@ def test_make_full_set_of_microbial_groups_errors(caplog, cfg_strings, exp_log): caplog.clear() with pytest.raises(ConfigurationError): - _ = make_full_set_of_microbial_groups(config) + _ = make_full_set_of_microbial_groups(config, enzyme_classes=enzyme_classes) log_check(caplog, exp_log) @@ -417,3 +421,45 @@ def test_make_full_set_of_enzymes_errors(caplog, cfg_strings, exp_log): _ = make_full_set_of_enzymes(config) log_check(caplog, exp_log) + + +def test_find_enzyme_substrates(fixture_config, enzyme_classes): + """Check method to find the full set of substrates a microbe can use works.""" + from virtual_ecosystem.models.soil.microbial_groups import MicrobialGroupConstants + + bacteria = MicrobialGroupConstants.build_microbial_group( + group_config=next( + functional_group + for functional_group in fixture_config["soil"]["microbial_group_definition"] + if functional_group["name"] == "bacteria" + ), + enzyme_classes=enzyme_classes, + ) + + assert set(bacteria.find_enzyme_substrates()) == set(["maom", "pom"]) + + +def test_calculate_new_biomass_average_nutrient_ratios(fixture_config, enzyme_classes): + """Check method to calculate average new biomass nutrient ratios works.""" + import numpy as np + + from virtual_ecosystem.models.soil.microbial_groups import ( + calculate_new_biomass_average_nutrient_ratios, + ) + + group_config = next( + functional_group + for functional_group in fixture_config["soil"]["microbial_group_definition"] + if functional_group["name"] == "bacteria" + ) + + averaged_nutrient_ratios = calculate_new_biomass_average_nutrient_ratios( + name=group_config["name"], + c_n_ratio=5.7, + c_p_ratio=15.5, + enzyme_production=group_config["enzyme_production"], + enzyme_classes=enzyme_classes, + ) + + assert np.isclose(averaged_nutrient_ratios["nitrogen"], 5.695) + assert np.isclose(averaged_nutrient_ratios["phosphorus"], 15.505) diff --git a/virtual_ecosystem/models/soil/microbial_groups.py b/virtual_ecosystem/models/soil/microbial_groups.py index 9b26fc80a..a5dc1aaaf 100644 --- a/virtual_ecosystem/models/soil/microbial_groups.py +++ b/virtual_ecosystem/models/soil/microbial_groups.py @@ -3,6 +3,7 @@ """ # noqa: D205 from dataclasses import dataclass +from typing import Any from virtual_ecosystem.core.config import Config, ConfigurationError from virtual_ecosystem.core.logger import LOGGER @@ -106,14 +107,44 @@ class MicrobialGroupConstants: """Ratio of carbon to phosphorus in biomass [unitless].""" enzyme_production: dict[str, float] - """Details of the enzymes produced by the microbial groups. + """Details of the enzymes produced by the microbial group. The keys are the substrates for which enzymes are produced, and the values are the allocation to enzyme production. This allocation is expressed as a fraction of the (gross) cellular biomass growth. """ - def enzyme_substrates(self) -> list[str]: + synthesis_nutrient_ratios: dict[str, float] + """Average carbon to nutrient ratios for the total synthesised biomass. + + Microbes have to synthesis both cellular biomass and extracellular enzymes. We + assume that this occurs in fixed unvarying proportion. This attribute stores the + carbon nutrient (nitrogen, phosphorus) ratios for the total synthesised biomass. + """ + + @classmethod + def build_microbial_group( + cls, group_config: dict[str, Any], enzyme_classes: dict[str, EnzymeConstants] + ): + """Class method to build the microbial group including enzyme information. + + Args: + group_config: The config details for microbial group in question. + enzyme_classes: Details of the enzyme classes used by the soil model. + """ + + return cls( + **group_config, + synthesis_nutrient_ratios=calculate_new_biomass_average_nutrient_ratios( + name=group_config["name"], + c_n_ratio=group_config["c_n_ratio"], + c_p_ratio=group_config["c_p_ratio"], + enzyme_production=group_config["enzyme_production"], + enzyme_classes=enzyme_classes, + ), + ) + + def find_enzyme_substrates(self) -> list[str]: """Substrates that the microbial group produces enzymes for.""" return [ @@ -123,13 +154,64 @@ def enzyme_substrates(self) -> list[str]: ] +def calculate_new_biomass_average_nutrient_ratios( + name: str, + c_n_ratio: float, + c_p_ratio: float, + enzyme_production: dict[str, float], + enzyme_classes: dict[str, EnzymeConstants], +) -> dict[str, float]: + """Calculate average carbon nutrient ratios of the newly synthesised biomass. + + Microbes have to synthesise cellular biomass as well as extracellular enzymes. This + method calculates average nutrient ratio of this total biomass synthesis by + calculating the average weighted by the relative production allocation to each + enzyme class and cellular growth. + + Args: + name: Name of the microbial group. + c_n_ratio: Ratio of carbon to nitrogen for the microbial group's cellular + biomass. + c_p_ratio: Ratio of carbon to nitrogen for the microbial group's cellular + biomass. + enzyme_production: Details of the enzymes produced by the microbial group, i.e. + which substrates are enzymes produced for, and how much (relative to + cellular synthesis) + enzyme_classes: Details of the enzyme classes used by the soil model. + """ + + total_enzyme_allocation = sum(enzyme_production.values()) + + enzyme_c_n_weighted = sum( + enzyme_classes[f"{name}_{substrate}"].c_n_ratio + * allocation + / total_enzyme_allocation + for substrate, allocation in enzyme_production.items() + ) + + enzyme_c_p_weighted = sum( + enzyme_classes[f"{name}_{substrate}"].c_p_ratio + * allocation + / total_enzyme_allocation + for substrate, allocation in enzyme_production.items() + ) + + return { + "nitrogen": (c_n_ratio + enzyme_c_n_weighted * total_enzyme_allocation) + / (1.0 + total_enzyme_allocation), + "phosphorus": (c_p_ratio + enzyme_c_p_weighted * total_enzyme_allocation) + / (1.0 + total_enzyme_allocation), + } + + def make_full_set_of_microbial_groups( - config: Config, + config: Config, enzyme_classes: dict[str, EnzymeConstants] ) -> dict[str, MicrobialGroupConstants]: """Make the full set of functional groups used in the soil model. Args: config: The complete virtual ecosystem config. + enzyme_classes: Details of the enzyme classes used by the soil model. Raises: ConfigurationError: If the soil model configuration is missing, if expected @@ -172,12 +254,13 @@ def make_full_set_of_microbial_groups( ) return { - group_name: MicrobialGroupConstants( - **next( + group_name: MicrobialGroupConstants.build_microbial_group( + group_config=next( functional_group for functional_group in config["soil"]["microbial_group_definition"] if functional_group["name"] == group_name - ) + ), + enzyme_classes=enzyme_classes, ) for group_name in expected_groups } diff --git a/virtual_ecosystem/models/soil/pools.py b/virtual_ecosystem/models/soil/pools.py index a9d6cbdff..a88612ccf 100644 --- a/virtual_ecosystem/models/soil/pools.py +++ b/virtual_ecosystem/models/soil/pools.py @@ -1073,7 +1073,7 @@ def calculate_enzyme_production( growth_rates[group.name] * group.enzyme_production[substrate] ) for group in microbial_groups.values() - for substrate in group.enzyme_substrates() + for substrate in group.find_enzyme_substrates() } @@ -1174,6 +1174,8 @@ def calculate_nutrient_uptake_rates( remain the same if nutrients are taken up following the same stoichiometry (with an adjustment made for carbon use efficiency). + TODO - Explain the assumptions underlying the new enzyme production model + The balance of mineralisation and immobilisation rates of inorganic nitrogen and phosphorus are also calculated in this function. This is done by calculating the difference between the demand for nitrogen and phosphorus and their uptake due to @@ -1268,7 +1270,6 @@ def calculate_nutrient_uptake_rates( constants.cue_reference_temp, constants.cue_with_temperature, ) - # TODO - a portion of this should be allocated to enzyme production carbon_gain_max = carbon_uptake_rate_max * carbon_use_efficiency # Find stoichiometry of the LMWC pool and use to find maximum possible uptake rates @@ -1283,15 +1284,13 @@ def calculate_nutrient_uptake_rates( actual_carbon_gain = np.minimum.reduce( [ carbon_gain_max, - # TODO - This isn't the right ratio, it should be the (weighted) average of - # the functional group and the enzyme classes - functional_group.c_n_ratio + functional_group.synthesis_nutrient_ratios["nitrogen"] * ( organic_nitrogen_uptake_rate_max + ammonium_uptake_rate_max + nitrate_uptake_rate_max ), - functional_group.c_p_ratio + functional_group.synthesis_nutrient_ratios["phosphorus"] * ( organic_phosphorus_uptake_rate_max + inorganic_phosphorus_uptake_rate_max @@ -1306,9 +1305,9 @@ def calculate_nutrient_uptake_rates( # Calculate uptake/release of inorganic nitrogen based on difference between # stoichiometric demand and organic nitrogen uptake - # TODO - Again this isn't the right ratio, it should be the (weighted) average of - # the functional group and the enzyme classes - nitrogen_demand = actual_carbon_gain / functional_group.c_n_ratio + nitrogen_demand = ( + actual_carbon_gain / functional_group.synthesis_nutrient_ratios["nitrogen"] + ) inorganic_nitrogen_change = nitrogen_demand - actual_organic_nitrogen_uptake # For immobilisation of nitrogen, the proportion of ammonium and nitrate taken up @@ -1331,9 +1330,9 @@ def calculate_nutrient_uptake_rates( # Calculate uptake/release of inorganic phosphorus based on difference between # stoichiometric demand and organic phosphorus uptake - # TODO - Again this isn't the right ratio, it should be the (weighted) average of - # the functional group and the enzyme classes - phosphorus_demand = actual_carbon_gain / functional_group.c_p_ratio + phosphorus_demand = ( + actual_carbon_gain / functional_group.synthesis_nutrient_ratios["phosphorus"] + ) inorganic_phosphorus_change = phosphorus_demand - actual_organic_phosphorus_uptake consumption_rates = NetNutrientConsumption( @@ -1349,6 +1348,8 @@ def calculate_nutrient_uptake_rates( # respired instead of being uptaken. This isn't currently of interest, but will be # in future + # TODO - consumption_rates are all still fine, but actual_carbon_gain now represents + # cell growth and enzyme production. This needs to change return actual_carbon_gain, consumption_rates diff --git a/virtual_ecosystem/models/soil/soil_model.py b/virtual_ecosystem/models/soil/soil_model.py index c9f310ecf..edc5e207c 100644 --- a/virtual_ecosystem/models/soil/soil_model.py +++ b/virtual_ecosystem/models/soil/soil_model.py @@ -199,8 +199,10 @@ def from_config( "Information required to initialise the soil model successfully extracted." ) - microbial_groups = make_full_set_of_microbial_groups(config) enzyme_classes = make_full_set_of_enzymes(config) + microbial_groups = make_full_set_of_microbial_groups( + config, enzyme_classes=enzyme_classes + ) return cls( data=data, From 1a6ee3c3a43f9558219e2850f494386afe5028e8 Mon Sep 17 00:00:00 2001 From: Jacob Cook Date: Mon, 24 Mar 2025 13:55:24 +0000 Subject: [PATCH 5/7] Started subtracting enzyme production from cellular growth total --- virtual_ecosystem/models/soil/pools.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/virtual_ecosystem/models/soil/pools.py b/virtual_ecosystem/models/soil/pools.py index a88612ccf..f525fa55c 100644 --- a/virtual_ecosystem/models/soil/pools.py +++ b/virtual_ecosystem/models/soil/pools.py @@ -1174,7 +1174,12 @@ def calculate_nutrient_uptake_rates( remain the same if nutrients are taken up following the same stoichiometry (with an adjustment made for carbon use efficiency). - TODO - Explain the assumptions underlying the new enzyme production model + Biomass synthesis is split between the synthesis of new cellular biomass and the + production of extracellular enzymes. We assume that extracellular enzymes are always + produced in fixed proportion to the rate at which new biomass is synthesised. As + such, we calculate the nutrient costs of synthesising new biomass based on a + weighted (by relative investment in production) average of the stoichiometry of the + different enzymes and the microbial group itself. The balance of mineralisation and immobilisation rates of inorganic nitrogen and phosphorus are also calculated in this function. This is done by calculating the @@ -1206,9 +1211,9 @@ def calculate_nutrient_uptake_rates( functional group Returns: - A tuple containing the rate at which microbial biomass increases due to nutrient - uptake, as well as a dataclass containing the rate at which carbon, nitrogen and - phosphorus get taken up. + A tuple containing the rate at which microbial (cellular) biomass increases due + to nutrient uptake, as well as a dataclass containing the rate at which carbon, + nitrogen and phosphorus get taken up. """ # Calculate highest possible microbial uptake rates for organic matter and inorganic @@ -1348,9 +1353,9 @@ def calculate_nutrient_uptake_rates( # respired instead of being uptaken. This isn't currently of interest, but will be # in future - # TODO - consumption_rates are all still fine, but actual_carbon_gain now represents - # cell growth and enzyme production. This needs to change - return actual_carbon_gain, consumption_rates + return actual_carbon_gain / ( + 1 + sum(functional_group.enzyme_production.values()) + ), consumption_rates def calculate_highest_achievable_nutrient_uptake( From b3c1aa75ff20b4ebadee3f6a436f8bf0e6c70e30 Mon Sep 17 00:00:00 2001 From: Jacob Cook Date: Tue, 25 Mar 2025 08:30:32 +0000 Subject: [PATCH 6/7] Fixed tests that had broken due to the changes in the enzyme production model --- tests/models/soil/conftest.py | 34 +------ tests/models/soil/test_pools.py | 126 ++++++++++++------------ tests/models/soil/test_soil_model.py | 139 +++++++++++++-------------- 3 files changed, 138 insertions(+), 161 deletions(-) diff --git a/tests/models/soil/conftest.py b/tests/models/soil/conftest.py index 18b8209d3..4936aafb2 100644 --- a/tests/models/soil/conftest.py +++ b/tests/models/soil/conftest.py @@ -2,8 +2,6 @@ import pytest -from virtual_ecosystem.models.soil.env_factors import EnvironmentalEffectFactors - @pytest.fixture def fixture_soil_config(microbial_groups_cfg): @@ -51,38 +49,16 @@ def environmental_factors(dummy_carbon_data, fixture_core_components): """Environmental factors based on dummy carbon data.""" from virtual_ecosystem.models.soil.constants import SoilConsts from virtual_ecosystem.models.soil.env_factors import ( - calculate_clay_impact_on_enzyme_saturation, - calculate_pH_suitability, - calculate_water_potential_impact_on_microbes, + calculate_environmental_effect_factors, ) - soil_constants = SoilConsts() - - water_factors = calculate_water_potential_impact_on_microbes( - water_potential=dummy_carbon_data["matric_potential"][ + return calculate_environmental_effect_factors( + soil_water_potential=dummy_carbon_data["matric_potential"][ fixture_core_components.layer_structure.index_topsoil_scalar ].to_numpy(), - water_potential_halt=soil_constants.soil_microbe_water_potential_halt, - water_potential_opt=soil_constants.soil_microbe_water_potential_optimum, - response_curvature=soil_constants.microbial_water_response_curvature, - ) - - pH_factors = calculate_pH_suitability( - soil_pH=dummy_carbon_data["pH"].to_numpy(), - maximum_pH=soil_constants.max_pH_microbes, - minimum_pH=soil_constants.min_pH_microbes, - lower_optimum_pH=soil_constants.lowest_optimal_pH_microbes, - upper_optimum_pH=soil_constants.highest_optimal_pH_microbes, - ) - - clay_saturation_factors = calculate_clay_impact_on_enzyme_saturation( + pH=dummy_carbon_data["pH"].to_numpy(), clay_fraction=dummy_carbon_data["clay_fraction"].to_numpy(), - base_protection=soil_constants.base_soil_protection, - protection_with_clay=soil_constants.soil_protection_with_clay, - ) - - return EnvironmentalEffectFactors( - water=water_factors, pH=pH_factors, clay_saturation=clay_saturation_factors + constants=SoilConsts, ) diff --git a/tests/models/soil/test_pools.py b/tests/models/soil/test_pools.py index d33a62ef9..468a999a6 100644 --- a/tests/models/soil/test_pools.py +++ b/tests/models/soil/test_pools.py @@ -46,29 +46,29 @@ def test_calculate_all_pool_updates( ) change_in_pools = { - "soil_c_pool_lmwc": [0.1178918, 0.06892161, 0.24201873, 0.03425211], - "soil_c_pool_maom": [0.03734787, 0.00340136, 0.05698608, 0.07275546], - "soil_c_pool_bacteria": [-0.054361097, -0.022606231, -0.118911406, -0.00719517], - "soil_c_pool_fungi": [-0.0083255777, -0.0819293436, -0.022969005, -0.032666056], - "soil_c_pool_pom": [0.00021589, -0.02918772, -0.01844761, 0.00520792], - "soil_c_pool_necromass": [0.010156477, 0.093205884, 0.056842818, -0.057486698], - "soil_enzyme_pom_bacteria": [1.18e-8, 1.67e-8, 1.78e-9, -1.12e-8], - "soil_enzyme_maom_bacteria": [-0.0003101, -5.0959e-5, 0.00059907, -3.7211e-5], - "soil_enzyme_pom_fungi": [-0.00054216, 0.000716408, 7.989000e-5, 0.000222079], - "soil_enzyme_maom_fungi": [-0.00012453, 0.000690584, 0.000143562, 0.00027601], - "soil_n_pool_don": [0.00180422, 0.00529461, 0.00528487, 0.00240583], - "soil_n_pool_particulate": [-1.005585e-4, 4.899227e-5, 1.05432e-4, 4.723906e-6], - "soil_n_pool_necromass": [0.00924867, 0.00082919, 0.00790357, -0.00394389], - "soil_n_pool_maom": [0.00099458, 0.01041398, 0.01344595, 0.00772835], - "soil_n_pool_ammonium": [0.00075125671, 0.02001151359, 0.00039745, 0.000172988], - "soil_n_pool_nitrate": [-0.003295899, -0.003990944, -0.001045921, -0.000642911], - "soil_p_pool_dop": [2.02317351e-4, 1.64662024e-4, 1.97339686e-4, 9.62312501e-5], - "soil_p_pool_particulate": [6.77587e-6, -7.228e-6, -2.639346e-7, 1.898709e-7], - "soil_p_pool_necromass": [0.002900315, 0.003433899, 0.007390806, 0.000848771], - "soil_p_pool_maom": [5.44660113e-4, -6.28584725e-5, 4.635421e-4, 3.0913116e-4], + "soil_c_pool_lmwc": [0.1178918, 0.06892161, 0.2489136, 0.03425211], + "soil_c_pool_maom": [0.03734787, 0.00340136, 0.05447554, 0.07275546], + "soil_c_pool_bacteria": [-0.0543618, -0.0226100, -0.1185237, -0.0071954], + "soil_c_pool_fungi": [-0.00832584, -0.08196413, -0.02280126, -0.03266732], + "soil_c_pool_pom": [0.00021589, -0.02918772, -0.0247599, 0.00520792], + "soil_c_pool_necromass": [0.01141213, 0.09537438, 0.05971513, -0.0566864], + "soil_enzyme_pom_bacteria": [-5.4395e-4, -2.2795e-4, -1.19323e-3, -7.2103e-5], + "soil_enzyme_maom_bacteria": [-8.5406e-4, -2.7893e-4, -5.9417e-4, -1.093e-4], + "soil_enzyme_pom_fungi": [-0.00062555, -0.00012062, -0.00015158, -0.00010522], + "soil_enzyme_maom_fungi": [-2.07924e-4, -1.46441e-4, -8.79093e-5, -5.1289e-5], + "soil_n_pool_don": [0.00180422, 0.00529461, 0.00549027, 0.00240583], + "soil_n_pool_particulate": [-1.005585e-4, 4.899227e-5, 7.96678e-5, 4.723906e-6], + "soil_n_pool_necromass": [0.00948372, 0.00118049, 0.00843786, -0.00381522], + "soil_n_pool_maom": [0.00099458, 0.01041398, 0.01326356, 0.00772835], + "soil_n_pool_ammonium": [0.00075125671, 0.02001151359, 0.00030892, 0.000172988], + "soil_n_pool_nitrate": [-0.003295899, -0.003990944, -0.00105915, -0.000642911], + "soil_p_pool_dop": [2.02317351e-4, 1.64662024e-4, 2.0799736e-4, 9.62312501e-5], + "soil_p_pool_particulate": [6.77587e-6, -7.228e-6, -1.29451192e-6, 1.898709e-7], + "soil_p_pool_necromass": [0.00297253, 0.00350535, 0.00755271, 0.0008742], + "soil_p_pool_maom": [5.44660113e-4, -6.28584725e-5, 4.528133e-4, 3.0913116e-4], "soil_p_pool_primary": [-4.473516e-10, -1.222973e-9, -6.33411e-10, -1.3674e-10], "soil_p_pool_secondary": [-5.050797e-7, -2.77311e-6, -7.40324e-7, -2.187697e-7], - "soil_p_pool_labile": [-1.643259e-5, -0.000295103, -9.270421e-5, -1.313285e-6], + "soil_p_pool_labile": [-1.643259e-5, -0.000295103, -1.212019e-4, -1.313285e-6], } # Make order of pools object @@ -148,21 +148,21 @@ def test_calculate_microbial_changes( from virtual_ecosystem.models.soil.pools import calculate_microbial_changes expected_mic_changes = { - "lmwc_uptake": [0.0002678173772, 0.01178568902, 0.00578389729, 0.0003198594108], - "don_uptake": [3.060766962e-6, 0.0008418340883, 8.26229727e-6, 0.0001827767514], - "ammonium_change": [3.32661813e-6, -0.0002060563, 0.00026558523, -0.0001422814], - "nitrate_change": [1.11256765e-5, -2.2895145e-5, 3.96851965e-5, -1.5809046e-5], - "dop_uptake": [3.060616979e-8, 1.346934537e-5, 3.30508087e-6, 7.31107003e-6], - "labile_p_change": [4.99284714e-6, 9.79315564e-5, 8.54931746e-5, -2.4335027e-6], - "bacteria_change": [-0.054361097, -0.022606231, -0.118911406, -0.007195167], - "fungi_change": [-0.0083255777, -0.0819293436, -0.022969005, -0.0326660561], - "pom_enzyme_bacteria_change": [1.18e-8, 1.67e-8, 1.78e-9, -1.12e-8], - "maom_enzyme_bacteria_change": [-0.0003101, -5.0959e-5, 0.00059907, -3.7211e-5], - "pom_enzyme_fungi_change": [-0.00054216, 0.000716408, 7.989000e-5, 0.000222079], - "maom_enzyme_fungi_change": [-0.00012453, 0.000690584, 0.000143562, 0.00027601], - "necromass_generation": [0.063759860, 0.107068803, 0.142793061, 0.039553892], - "necromass_n_flow": [0.011914627, 0.017358086, 0.026565221, 0.006364449], - "necromass_p_flow": [0.003646779, 0.003540533, 0.008051958, 0.001261101], + "lmwc_uptake": [0.0002678173772, 0.01178568902, 0.00771186303, 0.0003198594108], + "don_uptake": [3.060766962e-6, 0.0008418340883, 1.10163963e-5, 0.0001827767514], + "ammonium_change": [3.32661813e-6, -0.0002060563, 0.00035411364, -0.0001422814], + "nitrate_change": [1.11256765e-5, -2.2895145e-5, 5.29135951e-5, -1.5809046e-5], + "dop_uptake": [3.060616979e-8, 1.346934537e-5, 4.40677447e-6, 7.31107003e-6], + "labile_p_change": [4.99284714e-6, 9.79315564e-5, 1.13990898e-4, -2.4335027e-6], + "bacteria_change": [-0.0543618, -0.0226100, -0.1185237, -0.0071954], + "fungi_change": [-0.00832584, -0.08196413, -0.02280126, -0.03266732], + "pom_enzyme_bacteria_change": [-5.4395e-4, -2.2795e-4, -1.19323e-3, -7.2103e-5], + "maom_enzyme_bacteria_change": [-8.5406e-4, -2.7893e-4, -5.9417e-4, -1.093e-4], + "pom_enzyme_fungi_change": [-0.00062555, -0.00012062, -0.00015158, -0.00010522], + "maom_enzyme_fungi_change": [-2.07924e-4, -1.46441e-4, -8.79093e-5, -5.1289e-5], + "necromass_generation": [0.065015522, 0.109237318, 0.145665372, 0.040354202], + "necromass_n_flow": [0.01214967, 0.01770938, 0.02709952, 0.00649313], + "necromass_p_flow": [0.00371899, 0.00361198, 0.00821386, 0.00128653], } actual_mic_changes = calculate_microbial_changes( @@ -196,8 +196,8 @@ def test_calculate_enzyme_mediated_rates( from virtual_ecosystem.models.soil.pools import calculate_enzyme_mediated_rates expected_rates = { - "pom_to_lmwc": [0.001901992, 0.030246664, 0.018936876, 0.00028383], - "maom_to_lmwc": [0.00287967, 0.00699885, 0.00753161, 2.49959e-5], + "pom_to_lmwc": [0.001901992, 0.030246664, 0.0252492, 0.00028383], + "maom_to_lmwc": [0.00287967, 0.00699885, 0.0100421, 2.49959e-5], } actual_rates = calculate_enzyme_mediated_rates( @@ -289,12 +289,14 @@ def test_calculate_enzyme_changes(soil_pool_data, enzyme_production, enzyme_clas from virtual_ecosystem.models.soil.pools import calculate_enzyme_changes expected_enzyme_changes = { - "net_change_pom_bacteria": [1.18e-8, 1.67e-8, 1.78e-9, -1.12e-8], - "net_change_maom_bacteria": [-0.0003101, -5.0959e-5, 0.00059907, -3.7211e-5], - "net_change_pom_fungi": [-0.00054216, 0.000716408, 7.989000e-5, 0.000222079], - "net_change_maom_fungi": [-0.00012453, 0.000690584, 0.000143562, 0.000276007], - "denaturation_bacterial": [0.001398696, 0.000510624, 0.001803384, 0.00018168], - "denaturation_fungal": [0.000833736, 0.000301824, 0.000246408, 0.000157752], + "net_change_pom_bacteria": [-0.00054395, -0.00022795, -0.00119323, -7.21028e-5], + "net_change_maom_bacteria": [-0.00085406, -0.00027893, -0.00059417, -0.0001093], + "net_change_pom_fungi": [-0.00062555, -0.00012062, -0.00015158, -0.00010522], + "net_change_maom_fungi": [-0.00020792, -0.00014644, -8.79093e-5, -5.12891e-5], + "denaturation_maom_bacteria": [0.0008544, 0.0002808, 0.00060216, 0.00010944], + "denaturation_pom_bacteria": [0.000544296, 0.000229824, 0.001201224, 7.224e-5], + "denaturation_maom_fungi": [0.000208056, 0.000163824, 9.1368e-5, 5.1912e-5], + "denaturation_pom_fungi": [0.00062568, 0.000138, 0.00015504, 0.00010584], } actual_enzyme_changes = calculate_enzyme_changes( @@ -320,7 +322,7 @@ def test_calculate_net_enzyme_change( from virtual_ecosystem.models.soil.pools import calculate_net_enzyme_change - expected_net_change = [1.18e-8, 1.67e-8, 1.78e-9, -1.12e-8] + expected_net_change = [-5.4395104e-4, -2.2795351e-4, -1.1932295e-3, -7.2102805e-5] expected_denat = [0.000544296, 0.000229824, 0.001201224, 7.224e-5] actual_net_change, actual_denat = calculate_net_enzyme_change( @@ -338,10 +340,10 @@ def test_calculate_enzyme_production(functional_groups, growth_rates): from virtual_ecosystem.models.soil.pools import calculate_enzyme_production expected_production = { - "bacteria_maom": [3.48412887e-7, 1.889194925e-6, 8.0744933e-6, 1.38567254e-7], - "bacteria_pom": [3.48412887e-7, 1.889194925e-6, 8.0744933e-6, 1.38567254e-7], - "fungi_maom": [1.33658392e-7, 1.755719195e-5, 3.49330124e-6, 6.29095335e-7], - "fungi_pom": [1.33658392e-7, 1.755719195e-5, 3.49330124e-6, 6.29095335e-7], + "bacteria_maom": [3.44963254e-7, 1.870490025e-6, 7.99454785e-6, 1.37195301e-7], + "bacteria_pom": [3.44963254e-7, 1.870490025e-6, 7.99454785e-6, 1.37195301e-7], + "fungi_maom": [1.323350415e-7, 1.738335842e-5, 3.4587141e-6, 6.22866665e-7], + "fungi_pom": [1.323350415e-7, 1.738335842e-5, 3.4587141e-6, 6.22866665e-7], } actual_production = calculate_enzyme_production( @@ -430,14 +432,14 @@ def test_calculate_nutrient_uptake_rates( calculate_nutrient_uptake_rates, ) - expected_carbon_gain = [6.96825774e-5, 0.000377838985, 0.001211174, 2.77134508e-5] + expected_carbon_gain = [6.8992651e-5, 3.7409801e-4, 0.0015989096, 2.743906e-5] expected_consumption_rates = { - "organic_nitrogen": [2.2121431e-6, 8.17832483e-5, 5.76720686e-6, 3.29921934e-5], - "organic_phosphorus": [2.2120347e-8, 1.30853197e-6, 2.3069958e-6, 1.3196877e-6], - "carbon": [0.000193562715, 0.00114496662, 0.00403724667, 5.77363558e-5], - "inorganic_phosphorus": [4.333041e-6, 2.230641e-5, 7.339138e-5, 4.124029e-7], - "ammonium": [2.57532644e-6, -8.20971453e-6, 0.000197621226, -2.48964153e-5], - "nitrate": [8.6130261079e-6, -9.121905034e-7, 2.952964389e-5, -2.766268367e-6], + "organic_nitrogen": [2.2121431e-6, 8.1783248e-5, 7.6896091e-6, 3.2992193e-5], + "organic_phosphorus": [2.2120347e-8, 1.3085320e-6, 3.0759944e-6, 1.3196877e-6], + "carbon": [0.00019356272, 0.00114496662, 0.00538299554, 5.77363558e-5], + "inorganic_phosphorus": [4.333042e-6, 2.230641e-5, 9.785517e-5, 4.124029e-7], + "ammonium": [2.57532647e-6, -8.2097145e-6, 0.00026349497, -2.4896415e-5], + "nitrate": [8.61302608e-6, -9.1219050e-7, 3.93728584e-5, -2.7662684e-6], } actual_carbon_gain, actual_consumption_rates = calculate_nutrient_uptake_rates( @@ -478,7 +480,7 @@ def test_calculate_highest_achievable_nutrient_uptake( calculate_highest_achievable_nutrient_uptake, ) - expected_uptake = [1.29159055e-2, 8.43352433e-3, 5.77096991e-2, 5.77363558e-5] + expected_uptake = [1.29159055e-2, 8.43352433e-3, 7.6946265e-2, 5.77363558e-5] actual_uptake = calculate_highest_achievable_nutrient_uptake( labile_nutrient_pool=dummy_carbon_data["soil_c_pool_lmwc"], @@ -514,7 +516,7 @@ def test_negative_highest_achievable_nutrient_uptake_are_impossible( labile_carbon_data[1] = -0.0001 labile_carbon_data[3] = -3.7e-5 - expected_uptake = [1.29159055e-2, 0.0, 5.77096991e-2, 0.0] + expected_uptake = [1.29159055e-2, 0.0, 7.6946265e-2, 0.0] actual_uptake = calculate_highest_achievable_nutrient_uptake( labile_nutrient_pool=labile_carbon_data, @@ -546,7 +548,7 @@ def test_calculate_enzyme_mediated_decomposition( calculate_enzyme_mediated_decomposition, ) - expected_decomp = [3.39844565e-4, 8.91990315e-3, 1.25055119e-2, 4.14247999e-5] + expected_decomp = [3.39844565e-4, 8.91990315e-3, 1.66740158e-2, 4.14247999e-5] actual_decomp = calculate_enzyme_mediated_decomposition( soil_c_pool=dummy_carbon_data["soil_c_pool_pom"], @@ -684,7 +686,7 @@ def test_calculate_soil_nutrient_mineralisation( calculate_soil_nutrient_mineralisation, ) - expected_rate = [0.00013585646, 2.16036801e-5, 7.72932884e-5, 1.15848952e-5] + expected_rate = [0.00013585646, 2.16036801e-5, 1.030577177e-4, 1.15848952e-5] actual_rate = calculate_soil_nutrient_mineralisation( pool_carbon=dummy_carbon_data["soil_c_pool_pom"], @@ -761,8 +763,8 @@ def test_calculate_net_nutrient_transfers_from_maom_to_lmwc( ) expected_transfers = { - "nitrogen": [0.001004891, 0.001982692, 0.000550295, 2.911293e-6], - "phosphorus": [1.51879334e-5, 0.00014283379, 3.23215351e-5, 1.16451482e-7], + "nitrogen": [0.00100489, 0.00198269, 0.00073268, 2.91129e-6], + "phosphorus": [1.51879334e-5, 0.00014283379, 4.3050325e-5, 1.16451482e-7], } actual_transfers = calculate_net_nutrient_transfers_from_maom_to_lmwc( diff --git a/tests/models/soil/test_soil_model.py b/tests/models/soil/test_soil_model.py index 4d94947eb..965c97570 100644 --- a/tests/models/soil/test_soil_model.py +++ b/tests/models/soil/test_soil_model.py @@ -350,68 +350,67 @@ def test_update(mocker, fixture_soil_model, dummy_carbon_data): Dataset( data_vars=dict( soil_c_pool_lmwc=DataArray( - [0.10865825, 0.05329372, 0.2206471, 0.02045373], dims="cell_id" + [0.10867866, 0.0528613, 0.2235931, 0.02047065], dims="cell_id" ), soil_c_pool_maom=DataArray( - [2.51943826, 1.70859259, 4.53271067, 0.53208277], dims="cell_id" + [2.519536, 1.70883297, 4.53172237, 0.53214258], dims="cell_id" ), soil_c_pool_bacteria=DataArray( - [5.77301845, 2.28884539, 11.24102147, 0.99642359], + [5.77301795, 2.28884242, 11.24128134, 0.99642333], dims="cell_id", ), soil_c_pool_fungi=DataArray( - [0.88589888, 8.51025597, 2.19873561, 4.52379402], + [0.88589868, 8.51022834, 2.1988625, 4.5237925], dims="cell_id", ), soil_c_pool_pom=DataArray( - [0.10011147, 0.9851898, 0.69082832, 0.35260191], dims="cell_id" + [0.1001131, 0.98559395, 0.68780336, 0.35260428], dims="cell_id" ), soil_c_pool_necromass=DataArray( - [0.06200223, 0.05221315, 0.11560307, 0.08195386], dims="cell_id" + [0.0625005, 0.05307367, 0.11674337, 0.08227162], dims="cell_id" ), soil_enzyme_pom_bacteria=DataArray( - [0.02267837, 0.00957573, 0.05004943, 0.00300993], dims="cell_id" + [0.02240932, 0.0094633, 0.04945988, 0.00297424], dims="cell_id" ), soil_enzyme_maom_bacteria=DataArray( - [0.03544525, 0.01167439, 0.02538618, 0.00454144], dims="cell_id" + [0.0351762, 0.01156196, 0.02479662, 0.00450575], dims="cell_id" ), soil_enzyme_pom_fungi=DataArray( - [0.02580045, 0.00610504, 0.00649941, 0.00452008], dims="cell_id" + [0.02575935, 0.00569559, 0.00638582, 0.00435819], dims="cell_id" ), soil_enzyme_maom_fungi=DataArray( - [0.00860701, 0.00716821, 0.00387805, 0.00229989], dims="cell_id" + [0.00856592, 0.00675875, 0.00376447, 0.00213799], dims="cell_id" ), soil_n_pool_don=DataArray( - [0.00169365, 0.00383974, 0.00294508, 0.00394796], dims="cell_id" + [0.00169773, 0.00382384, 0.00304483, 0.00395096], dims="cell_id" ), soil_n_pool_particulate=DataArray( - [0.0070931, 0.00073832, 0.00290946, 0.01428801], dims="cell_id" + [0.00709321, 0.00073862, 0.00289692, 0.01428811], dims="cell_id" ), soil_n_pool_necromass=DataArray( - [0.0065757, 0.01819928, 0.02332773, 0.00957007], dims="cell_id" + [0.00666897, 0.01833887, 0.02353984, 0.00962116], dims="cell_id" ), soil_n_pool_maom=DataArray( - [0.86657283, 0.48601413, 0.33422871, 0.099723], dims="cell_id" + [0.86659183, 0.48606279, 0.33418043, 0.09973263], dims="cell_id" ), soil_n_pool_ammonium=DataArray( - [0.0004275, 0.01509396, 0.00035874, 0.00524329], dims="cell_id" + [0.00042733, 0.01509539, 0.00030326, 0.00524331], dims="cell_id" ), soil_n_pool_nitrate=DataArray( - [0.00056245, 0.00203824, -0.00016233, 0.01271296], - dims="cell_id", + [0.0005624, 0.0020384, -0.0001656, 0.01271296], dims="cell_id" ), soil_p_pool_dop=DataArray( - [0.00017829, 0.00017471, 0.00033654, 0.00018269], dims="cell_id" + [0.00018004, 0.00017463, 0.00034475, 0.00018331], dims="cell_id" ), soil_p_pool_particulate=DataArray( - [3.19431193e-5, 2.82033889e-4, 1.14152879e-4, 5.71520842e-4], + [3.19436203e-5, 2.82149510e-4, 1.13656052e-4, 5.71524688e-4], dims="cell_id", ), soil_p_pool_necromass=DataArray( - [0.0019653, 0.00148675, 0.00366575, 0.00078505], dims="cell_id" + [0.00199395, 0.0015151, 0.00373002, 0.00079515], dims="cell_id" ), soil_p_pool_maom=DataArray( - [0.01356543, 0.03483871, 0.02001331, 0.00406402], dims="cell_id" + [0.01357081, 0.03484564, 0.02002014, 0.00406592], dims="cell_id" ), soil_p_pool_primary=DataArray( [0.0019594, 0.00535662, 0.00277434, 0.00059892], dims="cell_id" @@ -420,7 +419,7 @@ def test_update(mocker, fixture_soil_model, dummy_carbon_data): [0.00705642, 0.03816755, 0.0115255, 0.00733096], dims="cell_id" ), soil_p_pool_labile=DataArray( - [-6.65834883e-6, -1.33255769e-4, 2.35622831e-7, 1.91333142e-4], + [-6.7324868e-6, -1.331584e-4, -1.860591e-5, 1.9133659e-4], dims="cell_id", ), ) @@ -594,83 +593,83 @@ def test_construct_full_soil_model( delta_pools = [ 0.1178918, 0.06892161, - 0.24201873, + 0.2489136, 0.03425211, 0.03734787, 0.00340136, - 0.05698608, + 0.05447554, 0.07275546, - -0.054361097, - -0.022606231, - -0.118911406, - -0.007195167, - -0.0083255777, - -0.0819293436, - -0.022969005, - -0.032666056, + -0.0543618, + -0.0226100, + -0.1185237, + -0.0071954, + -0.00832584, + -0.08196413, + -0.02280126, + -0.03266732, 0.00021589, -0.02918772, - -0.01844761, + -0.0247599, 0.00520792, - 0.010156477, - 0.093205884, - 0.056842818, - -0.057486698, - 1.18e-8, - 1.67e-8, - 1.78e-9, - -1.12e-8, - -0.0003101, - -5.0959e-5, - 0.00059907, - -3.7211e-5, - -0.00054216, - 0.000716408, - 7.989000e-5, - 0.000222079, - -0.00012453, - 0.000690584, - 0.000143562, - 0.00027601, + 0.01141213, + 0.09537438, + 0.05971513, + -0.0566864, + -5.4395e-4, + -2.2795e-4, + -1.19323e-3, + -7.2103e-5, + -8.5406e-4, + -2.7893e-4, + -5.9417e-4, + -1.093e-4, + -0.00062555, + -0.00012062, + -0.00015158, + -0.00010522, + -2.07924e-4, + -1.46441e-4, + -8.79093e-5, + -5.1289e-5, 0.00180422, 0.00529461, - 0.00528487, + 0.00549027, 0.00240583, -1.005585e-4, 4.899227e-5, - 1.05432e-4, + 7.96678e-5, 4.723906e-6, - 0.00924867, - 0.00082919, - 0.00790357, - -0.00394389, + 0.00948372, + 0.00118049, + 0.00843786, + -0.00381522, 0.00099458, 0.01041398, - 0.01344595, + 0.01326356, 0.00772835, 0.00075125671, 0.02001151359, - 0.00039745, + 0.00030892, 0.000172988, -0.003295899, -0.003990944, - -0.001045921, + -0.00105915, -0.000642911, 2.02317351e-4, 1.64662024e-4, - 1.97339686e-4, + 2.0799736e-4, 9.62312501e-5, 6.77587e-6, -7.228e-6, - -2.639346e-7, + -1.29451192e-6, 1.898709e-7, - 0.002900315, - 0.003433899, - 0.007390806, - 0.000848771, + 0.00297253, + 0.00350535, + 0.00755271, + 0.0008742, 5.44660113e-4, -6.28584725e-5, - 4.635421e-4, + 4.528133e-4, 3.0913116e-4, -4.473516e-10, -1.222973e-9, @@ -682,7 +681,7 @@ def test_construct_full_soil_model( -2.187697e-7, -1.643259e-5, -0.000295103, - -9.270421e-5, + -1.212019e-4, -1.313285e-6, ] From b0c898a93538e7d9fb2573548c4e39cc11fc7619 Mon Sep 17 00:00:00 2001 From: Jacob Cook Date: Wed, 26 Mar 2025 10:49:13 +0000 Subject: [PATCH 7/7] No longer both timesing and dividing by total_enzyme_allocation --- .../models/soil/microbial_groups.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/virtual_ecosystem/models/soil/microbial_groups.py b/virtual_ecosystem/models/soil/microbial_groups.py index a5dc1aaaf..1f1ac65e5 100644 --- a/virtual_ecosystem/models/soil/microbial_groups.py +++ b/virtual_ecosystem/models/soil/microbial_groups.py @@ -180,26 +180,21 @@ def calculate_new_biomass_average_nutrient_ratios( enzyme_classes: Details of the enzyme classes used by the soil model. """ - total_enzyme_allocation = sum(enzyme_production.values()) - enzyme_c_n_weighted = sum( - enzyme_classes[f"{name}_{substrate}"].c_n_ratio - * allocation - / total_enzyme_allocation + enzyme_classes[f"{name}_{substrate}"].c_n_ratio * allocation for substrate, allocation in enzyme_production.items() ) enzyme_c_p_weighted = sum( - enzyme_classes[f"{name}_{substrate}"].c_p_ratio - * allocation - / total_enzyme_allocation + enzyme_classes[f"{name}_{substrate}"].c_p_ratio * allocation for substrate, allocation in enzyme_production.items() ) + total_enzyme_allocation = sum(enzyme_production.values()) + return { - "nitrogen": (c_n_ratio + enzyme_c_n_weighted * total_enzyme_allocation) - / (1.0 + total_enzyme_allocation), - "phosphorus": (c_p_ratio + enzyme_c_p_weighted * total_enzyme_allocation) + "nitrogen": (c_n_ratio + enzyme_c_n_weighted) / (1.0 + total_enzyme_allocation), + "phosphorus": (c_p_ratio + enzyme_c_p_weighted) / (1.0 + total_enzyme_allocation), }