diff --git a/tests/example_data_input_dict.csv.gz b/tests/example_data_input_dict.csv.gz new file mode 100644 index 00000000..a88da4fa Binary files /dev/null and b/tests/example_data_input_dict.csv.gz differ diff --git a/tests/test_land.py b/tests/test_land.py index f67279ea..89e69340 100644 --- a/tests/test_land.py +++ b/tests/test_land.py @@ -24,8 +24,11 @@ from wsimod.nodes.nodes import Node from wsimod.nodes.sewer import Sewer from wsimod.nodes.storage import Reservoir +from wsimod.nodes.tanks import Tank, DecayTank from wsimod.orchestration.model import to_datetime +from pathlib import Path + class MyTestClass(TestCase): def assertDictAlmostEqual(self, d1, d2, accuracy=19): @@ -903,7 +906,7 @@ def test_soil_pool(self): d1["org-phosphorus"] = disso["P"] d1["org-nitrogen"] = disso["N"] - self.assertDictAlmostEqual(d1, r1, 15) + self.assertDictAlmostEqual(d1, r1, 14) self.assertDictAlmostEqual(d2, r2, 15) def test_crop_uptake(self): @@ -1168,6 +1171,258 @@ def test_garden(self): d3 = {"phosphate": 0, "temperature": 0, "volume": 0.2 * 0.5 * 1.5} self.assertDictAlmostEqual(d3, surface.storage, 16) + def test_land_overrides(self): + constants.set_simple_pollutants() + node = Land(name="") + node.apply_overrides( + { + "surface_residence_time": 4.9, + "subsurface_residence_time": 23.7, + "percolation_residence_time": 56.1, + } + ) + self.assertEqual(node.surface_residence_time, 4.9) + self.assertEqual(node.surface_runoff.residence_time, 4.9) + self.assertEqual(node.subsurface_residence_time, 23.7) + self.assertEqual(node.subsurface_runoff.residence_time, 23.7) + self.assertEqual(node.percolation_residence_time, 56.1) + + def test_surface_overrides(self): + constants.set_default_pollutants() + decaytank = DecayTank() + surface = Surface( + parent=decaytank, area=5, depth=0.1, pollutant_load={"nitrate": 5.7} + ) + surface.apply_overrides( + { + "surface": "test_surface", + "area": 9.8, + "depth": 7.5, + "pollutant_load": {"phosphate": 10.1}, + "decays": {"nitrate": {"constant": 0.001, "exponent": 1.005}}, + } + ) + self.assertEqual(surface.surface, "test_surface") + self.assertEqual(surface.area, 9.8) + self.assertEqual(surface.depth, 7.5) + self.assertEqual(surface.capacity, 9.8 * 7.5) + self.assertDictEqual( + surface.pollutant_load, {"nitrate": 5.7, "phosphate": 10.1} + ) + self.assertDictEqual( + surface.decays, {"nitrate": {"constant": 0.001, "exponent": 1.005}} + ) + + # override the data_input_dict + # test the format of dict + new_data_input_dict = { + ("temperature", 1): 10, + ("temperature", 2): 20, + } + surface.apply_overrides({"data_input_dict": new_data_input_dict}) + self.assertDictEqual(surface.data_input_dict, new_data_input_dict) + # test the format of str + new_data_input_dict = str( + Path(__file__).parent / "example_data_input_dict.csv.gz" + ) + surface.apply_overrides({"data_input_dict": new_data_input_dict}) + from wsimod.orchestration.model import read_csv + + new_data_input_dict = read_csv(new_data_input_dict) + self.assertDictEqual(surface.data_input_dict, new_data_input_dict) + print(dict(list(surface.data_input_dict.items())[:5])) + + def test_impervioussurface_overrides(self): + constants.set_default_pollutants() + decaytank = DecayTank() + surface = Surface(parent=decaytank, area=5, depth=0.1) + impervioussurface = ImperviousSurface( + parent=surface, area=1.5, et0_to_e=0.9, pore_depth=0.015 + ) + impervioussurface.apply_overrides( + {"surface": "test_surface", "area": 9.8, "pore_depth": 7.5, "et0_to_e": 3.5} + ) + self.assertEqual(impervioussurface.area, 9.8) + self.assertEqual(impervioussurface.pore_depth, 7.5) + self.assertEqual(impervioussurface.depth, 7.5) + self.assertEqual(impervioussurface.capacity, 9.8 * 7.5) + self.assertEqual(impervioussurface.et0_to_e, 3.5) + + def test_pervioussurface_overrides(self): + constants.set_default_pollutants() + decaytank = DecayTank() + surface = Surface(parent=decaytank, area=5, depth=0.1) + pervioussurface = PerviousSurface( + parent=surface, depth=0.5, area=1.5, initial_storage=0.5 * 1.5 * 0.25 + ) + pervioussurface.apply_overrides( + { + "field_capacity": 0.335, + "wilting_point": 0.112, + "total_porosity": 0.476, + "infiltration_capacity": 0.678, + "surface_coefficient": 0.237, + "percolation_coefficient": 0.777, + "et0_coefficient": 0.697, + "ihacres_p": 10.096, + "soil_temp_w_prev": 37.1, + "soil_temp_w_air": 23.6, + "soil_temp_w_deep": 3.4, + "soil_temp_deep": 2.2, + "surface": "test_surface", + "area": 9.8, + "depth": 7.5, + } + ) + self.assertEqual(pervioussurface.field_capacity, 0.335) + self.assertEqual(pervioussurface.wilting_point, 0.112) + self.assertEqual(pervioussurface.total_porosity, 0.476) + self.assertEqual(pervioussurface.infiltration_capacity, 0.678) + self.assertEqual(pervioussurface.surface_coefficient, 0.237) + self.assertEqual(pervioussurface.percolation_coefficient, 0.777) + self.assertEqual(pervioussurface.et0_coefficient, 0.697) + self.assertEqual(pervioussurface.ihacres_p, 10.096) + self.assertEqual(pervioussurface.soil_temp_w_prev, 37.1) + self.assertEqual(pervioussurface.soil_temp_w_air, 23.6) + self.assertEqual(pervioussurface.soil_temp_w_deep, 3.4) + self.assertEqual(pervioussurface.soil_temp_deep, 2.2) + + self.assertEqual(pervioussurface.field_capacity_m, 0.335 * 7.5) + self.assertEqual(pervioussurface.wilting_point_m, 0.112 * 7.5) + self.assertEqual(pervioussurface.depth, 0.476 * 7.5) + self.assertEqual(pervioussurface.area, 9.8) + self.assertEqual(pervioussurface.capacity, 7.5 * 0.476 * 9.8) + self.assertEqual(pervioussurface.surface, "test_surface") + self.assertEqual(pervioussurface.subsurface_coefficient, 1 - 0.777) + + def test_growingsurface_overrides(self): + constants.set_default_pollutants() + decaytank = DecayTank() + surface = Surface(parent=decaytank, area=5, depth=0.1) + pervioussurface = PerviousSurface( + parent=surface, depth=0.5, area=1.5, initial_storage=0.5 * 1.5 * 0.25 + ) + growingsurface = GrowingSurface(parent=pervioussurface, area=1.5) + overrides = { + "ET_depletion_factor": 0.521, + "crop_cover_max": 1.342, + "ground_cover_max": 1.111, + "crop_factor_stages": [1, 2, 1], + "crop_factor_stage_dates": [1, 32, 90], + "sowing_day": 35, + "harvest_day": 89, + "satact": 0.567, + "thetaupp": 4.324, + "thetalow": 3.582, + "thetapow": 7.324, + "uptake1": 1.278, + "uptake2": 2.753, + "uptake3": 5.298, + "uptake_PNratio": 3.263, + "erodibility": 2.863, + "sreroexp": 5.634, + "cohesion": 8.903, + "slope": 6.231, + "srfilt": 9.231, + "macrofilt": 7.394, + "limpar": 4.211, + "exppar": 5.872, + "hsatINs": 20.321, + "denpar": 0.204, + "adosorption_nr_limit": 1.943, + "adsorption_nr_maxiter": 6321, + "kfr": 80.2, + "nfr": 42.3, + "kadsdes": 0.972, + "bulk_density": 1672, + "field_capacity": 0.335, + "wilting_point": 0.112, + "total_porosity": 0.476, + "rooting_depth": 7.5, + } + overrides_to_check = overrides.copy() + growingsurface.apply_overrides(overrides) + + for k, v in overrides_to_check.items(): + if isinstance(v, list): + self.assertListEqual(getattr(growingsurface, k), v) + else: + self.assertEqual(getattr(growingsurface, k), v) + + harvest_sow_calendar = [ + 0, + 35, + 89, + 89 + 1, + 365, + ] + self.assertListEqual(growingsurface.harvest_sow_calendar, harvest_sow_calendar) + self.assertListEqual(growingsurface.ground_cover_stages, [0, 0, 1.111, 0, 0]) + self.assertListEqual(growingsurface.crop_cover_stages, [0, 0, 1.342, 0, 0]) + self.assertEqual(growingsurface.autumn_sow, False) + self.assertEqual(growingsurface.total_available_water, (0.335 - 0.112) * 7.5) + self.assertEqual( + growingsurface.readily_available_water, (0.335 - 0.112) * 7.5 * 0.521 + ) + self.assertEqual(growingsurface.depth, 0.476 * 7.5) + self.assertEqual(growingsurface.capacity, 0.476 * 7.5 * 1.5) + + def test_nutrientpool_overrides(self): + constants.set_default_pollutants() + decaytank = DecayTank() + surface = Surface(parent=decaytank, area=5, depth=0.1) + pervioussurface = PerviousSurface( + parent=surface, depth=0.5, area=1.5, initial_storage=0.5 * 1.5 * 0.25 + ) + growingsurface = GrowingSurface(parent=pervioussurface, area=1.5) + overrides = { + "fraction_dry_n_to_dissolved_inorganic": 0.29, + "degrhpar": {"N": 7 * 1e-1}, + "dishpar": {"P": 7 * 1e-1}, + "minfpar": {"N": 1.00013, "P": 1.000003}, + "disfpar": {"N": 1.000003, "P": 1.0000001}, + "immobdpar": {"N": 1.0056, "P": 1.2866}, + "fraction_manure_to_dissolved_inorganic": {"N": 0.35, "P": 0.21}, + "fraction_residue_to_fast": {"N": 0.61, "P": 0.71}, + } + growingsurface.nutrient_pool.apply_overrides(overrides) + self.assertEqual( + growingsurface.nutrient_pool.fraction_dry_n_to_dissolved_inorganic, 0.29 + ) + self.assertDictEqual( + growingsurface.nutrient_pool.degrhpar, {"N": 7 * 1e-1, "P": 7 * 1e-6} + ) + self.assertDictEqual( + growingsurface.nutrient_pool.dishpar, {"N": 7 * 1e-5, "P": 7 * 1e-1} + ) + self.assertDictEqual( + growingsurface.nutrient_pool.minfpar, {"N": 1.00013, "P": 1.000003} + ) + self.assertDictEqual( + growingsurface.nutrient_pool.disfpar, {"N": 1.000003, "P": 1.0000001} + ) + self.assertDictEqual( + growingsurface.nutrient_pool.immobdpar, {"N": 1.0056, "P": 1.2866} + ) + self.assertDictEqual( + growingsurface.nutrient_pool.fraction_manure_to_dissolved_inorganic, + {"N": 0.35, "P": 0.21}, + ) + self.assertDictEqual( + growingsurface.nutrient_pool.fraction_residue_to_fast, + {"N": 0.61, "P": 0.71}, + ) + + self.assertDictEqual( + growingsurface.nutrient_pool.fraction_manure_to_fast, + {"N": 1 - 0.35, "P": 1 - 0.21}, + ) + self.assertDictEqual( + growingsurface.nutrient_pool.fraction_residue_to_humus, + {"N": 1 - 0.61, "P": 1 - 0.71}, + ) + self.assertEqual(growingsurface.nutrient_pool.fraction_dry_n_to_fast, 0.71) + if __name__ == "__main__": unittest.main() diff --git a/wsimod/nodes/land.py b/wsimod/nodes/land.py index 7bcaf86a..c88f3c7e 100644 --- a/wsimod/nodes/land.py +++ b/wsimod/nodes/land.py @@ -6,6 +6,7 @@ import sys from bisect import bisect_left from math import exp, log, log10, sin +from typing import Any, Dict from wsimod.core import constants from wsimod.nodes.nodes import Node @@ -160,6 +161,30 @@ def __init__( self.mass_balance_ds.append(self.subsurface_runoff.ds) self.mass_balance_ds.append(self.percolation.ds) + def apply_overrides(self, overrides=Dict[str, Any]): + """Apply overrides to the Land. + + Enables a user to override any parameter of the residence_time and update + the residence_tank accordingly. + + Args: + overrides (Dict[str, Any]): Dict describing which parameters should + be overridden (keys) and new values (values). Defaults to {}. + """ + self.surface_residence_time = overrides.pop( + "surface_residence_time", self.surface_residence_time + ) + self.subsurface_residence_time = overrides.pop( + "subsurface_residence_time", self.subsurface_residence_time + ) + self.percolation_residence_time = overrides.pop( + "percolation_residence_time", self.percolation_residence_time + ) + self.surface_runoff.residence_time = self.surface_residence_time + self.subsurface_runoff.residence_time = self.subsurface_residence_time + self.percolation.residence_time = self.percolation_residence_time + super().apply_overrides(overrides) + def apply_irrigation(self): """Iterate over any irrigation functions (needs further testing.. @@ -352,6 +377,47 @@ def __init__( self.processes = [] self.outflows = [] + def apply_overrides(self, overrides=Dict[str, Any]): + """Override parameters. + + Enables a user to override any of the following parameters: + area and depth (both will update the capacity), pollutant_load (the + entire dict does not need to be redefined, only changed values need to + be included). + + Args: + overrides (Dict[str, Any]): Dict describing which parameters should + be overridden (keys) and new values (values). Defaults to {}. + """ + self.surface = overrides.pop("surface", self.surface) + self.pollutant_load.update(overrides.pop("pollutant_load", {})) + + self.area = overrides.pop("area", self.area) + self.depth = overrides.pop("depth", self.depth) + self.capacity = self.area * self.depth + + if "capacity" in overrides.keys(): + overrides.pop("capacity") + print( + "Warning: specifying capacity is depreciated in overrides for surface, \ + please specify depth and area instead. capacity override value has been ignored" + ) + + # overrides data_input_dict + from wsimod.orchestration.model import read_csv + + content = overrides.pop("data_input_dict", self.data_input_dict) + if isinstance(content, str): + self.data_input_dict = read_csv(content) + elif isinstance(content, dict): + self.data_input_dict = content + else: + raise ValueError( + f"{content.__class__} is not a recognised format for data_input_dict" + ) + + super().apply_overrides(overrides) + def run(self): """Call run function (called from Land node).""" if "nitrite" in constants.POLLUTANTS: @@ -550,6 +616,28 @@ def __init__(self, pore_depth=0, et0_to_e=1, **kwargs): self.outflows.append(self.push_to_sewers) + def apply_overrides(self, overrides=Dict[str, Any]): + """Override parameters. + + Enables a user to override any of the following parameters: + eto_to_e, pore_depth. + + Args: + overrides (Dict[str, Any]): Dict describing which parameters should + be overridden (keys) and new values (values). Defaults to {}. + """ + self.et0_to_e = overrides.pop("et0_to_e", self.et0_to_e) + if "depth" in overrides.keys(): + overrides.pop("depth") + print( + "ERROR: specifying depth is depreciated in overrides for \ + impervious surface, please specify pore_depth instead" + ) + self.pore_depth = overrides.pop("pore_depth", self.pore_depth) + self.depth = self.pore_depth + self.capacity = self.area * self.depth + super().apply_overrides(overrides) + def precipitation_evaporation(self): """Inflow function that is a simple rainfall-evaporation model, updating the. @@ -748,6 +836,49 @@ def __init__( self.outflows.append(self.route) + def apply_overrides(self, overrides=Dict[str, Any]): + """Override parameters. + + Enables a user to override any of the following parameters: + field_capacity, wilting_point, total_porosity, infiltration_capacity, + surface_coefficient, percolation_coefficient, et0_coefficient, ihacres_p, + soil_temp_w_prev, soil_temp_w_air, soil_temp_w_deep, soil_temp_deep, + and the corresponding parameter values, including field_capacity_m, + wilting_point_m, depth, capacity, subsurface_coefficient. + + Args: + overrides (Dict[str, Any]): Dict describing which parameters should + be overridden (keys) and new values (values). Defaults to {}. + """ + + self.depth /= self.total_porosity # restore the physical depth (root) + + overwrite_params = [ + "field_capacity", + "wilting_point", + "total_porosity", + "infiltration_capacity", + "surface_coefficient", + "percolation_coefficient", + "et0_coefficient", + "ihacres_p", + "soil_temp_w_prev", + "soil_temp_w_air", + "soil_temp_w_deep", + "soil_temp_deep", + ] + for param in overwrite_params: + setattr(self, param, overrides.pop(param, getattr(self, param))) + + self.subsurface_coefficient = 1 - self.percolation_coefficient + + super().apply_overrides(overrides) + # After the depth has been changed ... + self.field_capacity_m = self.field_capacity * self.depth + self.wilting_point_m = self.wilting_point * self.depth + self.depth *= self.total_porosity # update the simulation depth + self.capacity = self.depth * self.area + def get_cmd(self): """Calculate moisture deficit (i.e., the tank excess converted to depth). @@ -1115,22 +1246,12 @@ def __init__( self.bulk_density = 1300 # [kg/m3] super().__init__(depth=depth, **kwargs) - # Infer basic sow/harvest calendar - self.harvest_sow_calendar = [ - 0, - self.sowing_day, - self.harvest_day, - self.harvest_day + 1, - 365, - ] - self.ground_cover_stages = [0, 0, self.ground_cover_max, 0, 0] - self.crop_cover_stages = [0, 0, self.crop_cover_max, 0, 0] - - # Use day number of 181 to indicate autumn-sown (from HYPE) - if self.sowing_day > 181: - self.autumn_sow = True - else: - self.autumn_sow = False + ( + self.harvest_sow_calendar, + self.ground_cover_stages, + self.crop_cover_stages, + self.autumn_sow, + ) = self.infer_sow_harvest_calendar() # State variables self.days_after_sow = None @@ -1139,13 +1260,10 @@ def __init__( self.crop_factor = 0 self.et0_coefficient = 1 - # Calculate parameters based on capacity/wp - self.total_available_water = self.field_capacity_m - self.wilting_point_m - if self.total_available_water < 0: - print("warning: TAW < 0...") - self.readily_available_water = ( - self.total_available_water * self.ET_depletion_factor - ) + ( + self.total_available_water, + self.readily_available_water, + ) = self.calculate_available_water() # Initiliase nutrient pools self.nutrient_pool = NutrientPool() @@ -1201,6 +1319,113 @@ def __init__( "org-phosphorus" ] + def infer_sow_harvest_calendar(self): + """Infer basic sow/harvest calendar and indicate autumn-sown. + Returns: + (list): havest/sow calendar + (list): ground cover stages + (list): crop cover stages + (boolean): indication for autumn-sown crops + """ + # Infer basic sow/harvest calendar + harvest_sow_calendar = [ + 0, + self.sowing_day, + self.harvest_day, + self.harvest_day + 1, + 365, + ] + ground_cover_stages = [0, 0, self.ground_cover_max, 0, 0] + crop_cover_stages = [0, 0, self.crop_cover_max, 0, 0] + + # Use day number of 181 to indicate autumn-sown (from HYPE) + if self.sowing_day > 181: + autumn_sow = True + else: + autumn_sow = False + + return harvest_sow_calendar, ground_cover_stages, crop_cover_stages, autumn_sow + + def calculate_available_water(self): + """Calculate total/readily available water based on capacity/wp. + Returns: + (float): total available water + (float): readily available water + """ + # Calculate parameters based on capacity/wp + total_available_water = self.field_capacity_m - self.wilting_point_m + if total_available_water < 0: + print("warning: TAW < 0...") + readily_available_water = total_available_water * self.ET_depletion_factor + + return total_available_water, readily_available_water + + def apply_overrides(self, overrides=Dict[str, Any]): + """Override parameters. + + Enables a user to override any of the following parameters + + Args: + overrides (Dict[str, Any]): Dict describing which parameters should + be overridden (keys) and new values (values). Defaults to {}. + """ + overwrite_params = [ + "ET_depletion_factor", + "crop_cover_max", + "ground_cover_max", + "crop_factor_stages", + "crop_factor_stage_dates", + "sowing_day", + "harvest_day", + "satact", + "thetaupp", + "thetalow", + "thetapow", + "uptake1", + "uptake2", + "uptake3", + "uptake_PNratio", + "erodibility", + "sreroexp", + "cohesion", + "slope", + "srfilt", + "macrofilt", + "limpar", + "exppar", + "hsatINs", + "denpar", + "adosorption_nr_limit", + "adsorption_nr_maxiter", + "kfr", + "nfr", + "kadsdes", + "bulk_density", + ] + for param in overwrite_params: + setattr(self, param, overrides.pop(param, getattr(self, param))) + + if "depth" in overrides.keys(): + overrides.pop("depth") + print( + "ERROR: specifying depth is depreciated in overrides for \ + GrowingSurface, please specify rooting_depth instead" + ) + self.rooting_depth = overrides.pop("rooting_depth", self.rooting_depth) + overrides["depth"] = self.rooting_depth + super().apply_overrides(overrides) + + ( + self.harvest_sow_calendar, + self.ground_cover_stages, + self.crop_cover_stages, + self.autumn_sow, + ) = self.infer_sow_harvest_calendar() + ( + self.total_available_water, + self.readily_available_water, + ) = self.calculate_available_water() + def pull_storage(self, vqip): """Pull water from the surface, updating the surface storage VQIP. Nutrient pool pollutants (nitrate/nitrite/ammonia/phosphate/org- phosphorus/ org-nitrogen) are @@ -2103,6 +2328,20 @@ def __init__(self, irrigation_coefficient=0.1, **kwargs): super().__init__(**kwargs) + def apply_overrides(self, overrides=Dict[str, Any]): + """Override parameters. + + Enables a user to override irrigation_coefficient + + Args: + overrides (Dict[str, Any]): Dict describing which parameters should + be overridden (keys) and new values (values). Defaults to {}. + """ + self.irrigation_coefficient = overrides.pop( + "irrigation_coefficient", self.irrigation_coefficient + ) + super().apply_overrides(overrides) + def irrigate(self): """Calculate water demand for crops and call parent node to acquire water, updating surface tank and nutrient pools.""" diff --git a/wsimod/nodes/nutrient_pool.py b/wsimod/nodes/nutrient_pool.py index 54590db3..ce4b9eac 100644 --- a/wsimod/nodes/nutrient_pool.py +++ b/wsimod/nodes/nutrient_pool.py @@ -3,6 +3,8 @@ @author: barna """ +from typing import Any, Dict + from wsimod.core import constants @@ -114,14 +116,10 @@ def __init__( self.disfpar = disfpar self.immobdpar = immobdpar - self.fraction_manure_to_fast = { - x: 1 - self.fraction_manure_to_dissolved_inorganic[x] - for x in constants.NUTRIENTS - } - self.fraction_residue_to_humus = { - x: 1 - self.fraction_residue_to_fast[x] for x in constants.NUTRIENTS - } - self.fraction_dry_n_to_fast = 1 - self.fraction_dry_n_to_dissolved_inorganic + self.fraction_manure_to_fast = None + self.fraction_residue_to_humus = None + self.fraction_dry_n_to_fast = None + self.calculate_fraction_parameters() # Initialise different pools self.fast_pool = NutrientStore() @@ -137,6 +135,51 @@ def __init__( self.adsorbed_inorganic_pool, ] + def calculate_fraction_parameters(self): + """Update fractions of nutrients input transformed into other forms in soil + based on the input parameters + Returns: + (dict): fraction of manure to fast pool + (dict): fraction of plant residue to humus pool + (float): fraction of dry nitrogen deposition to fast pool + """ + self.fraction_manure_to_fast = { + x: 1 - self.fraction_manure_to_dissolved_inorganic[x] + for x in constants.NUTRIENTS + } + self.fraction_residue_to_humus = { + x: 1 - self.fraction_residue_to_fast[x] for x in constants.NUTRIENTS + } + self.fraction_dry_n_to_fast = 1 - self.fraction_dry_n_to_dissolved_inorganic + + def apply_overrides(self, overrides=Dict[str, Any]): + """Override parameters. + + Enables a user to override any of the following parameters: + eto_to_e, pore_depth. + + Args: + overrides (Dict[str, Any]): Dict describing which parameters should + be overridden (keys) and new values (values). Defaults to {}. + """ + self.fraction_dry_n_to_dissolved_inorganic = overrides.pop( + "fraction_dry_n_to_dissolved_inorganic", + self.fraction_dry_n_to_dissolved_inorganic, + ) + self.fraction_residue_to_fast.update( + overrides.pop("fraction_residue_to_fast", {}) + ) + self.fraction_manure_to_dissolved_inorganic.update( + overrides.pop("fraction_manure_to_dissolved_inorganic", {}) + ) + self.degrhpar.update(overrides.pop("degrhpar", {})) + self.dishpar.update(overrides.pop("dishpar", {})) + self.minfpar.update(overrides.pop("minfpar", {})) + self.disfpar.update(overrides.pop("disfpar", {})) + self.immobdpar.update(overrides.pop("immobdpar", {})) + + self.calculate_fraction_parameters() + def init_empty(self): """Initialise an empty nutrient to be copied.""" self.empty_nutrient = {x: 0 for x in constants.NUTRIENTS}