diff --git a/tests/test_storage.py b/tests/test_storage.py index 1bc53dbb..ac3d5590 100644 --- a/tests/test_storage.py +++ b/tests/test_storage.py @@ -181,7 +181,6 @@ def test_river_pull(self): constants.set_simple_pollutants() river = River( name="", - depth=2, length=200, width=20, velocity=0.2 * 86400, @@ -206,7 +205,6 @@ def test_river_depth(self): constants.set_simple_pollutants() river = River( name="", - depth=2, length=200, width=20, velocity=0.2 * 86400, @@ -295,6 +293,122 @@ def test_riverreservoir_environmental(self): } self.assertDictAlmostEqual(d2, reservoir.tank.storage, 15) + def test_storage_overrides(self): + constants.set_default_pollutants() + storage = Storage( + name="", decays={"phosphate": {"constant": 1.005, "exponent": 1.005}} + ) + storage.apply_overrides( + { + "capacity": 1.43, + "area": 2.36, + "datum": 0.32, + "decays": {"nitrite": {"constant": 10.005, "exponent": 1.105}}, + } + ) + self.assertEqual(storage.capacity, 1.43) + self.assertEqual(storage.tank.capacity, 1.43) + self.assertEqual(storage.area, 2.36) + self.assertEqual(storage.tank.area, 2.36) + self.assertEqual(storage.datum, 0.32) + self.assertEqual(storage.tank.datum, 0.32) + self.assertDictEqual( + storage.decays, + { + "phosphate": {"constant": 1.005, "exponent": 1.005}, + "nitrite": {"constant": 10.005, "exponent": 1.105}, + }, + ) + self.assertDictEqual( + storage.tank.decays, + { + "phosphate": {"constant": 1.005, "exponent": 1.005}, + "nitrite": {"constant": 10.005, "exponent": 1.105}, + }, + ) + + def test_groundwater_overrides(self): + groundwater = Groundwater( + name="", decays={"phosphate": {"constant": 1.005, "exponent": 1.005}} + ) + groundwater.apply_overrides( + { + "residence_time": 27.3, + "infiltration_threshold": 200, + "infiltration_pct": 0.667, + "capacity": 1.43, + "area": 2.36, + "datum": 0.32, + "decays": {"nitrite": {"constant": 10.005, "exponent": 1.105}}, + } + ) + self.assertEqual(groundwater.residence_time, 27.3) + self.assertEqual(groundwater.infiltration_threshold, 200) + self.assertEqual(groundwater.infiltration_pct, 0.667) + self.assertEqual(groundwater.capacity, 1.43) + self.assertEqual(groundwater.tank.capacity, 1.43) + self.assertEqual(groundwater.area, 2.36) + self.assertEqual(groundwater.tank.area, 2.36) + self.assertEqual(groundwater.datum, 0.32) + self.assertEqual(groundwater.tank.datum, 0.32) + self.assertDictEqual( + groundwater.decays, + { + "phosphate": {"constant": 1.005, "exponent": 1.005}, + "nitrite": {"constant": 10.005, "exponent": 1.105}, + }, + ) + self.assertDictEqual( + groundwater.tank.decays, + { + "phosphate": {"constant": 1.005, "exponent": 1.005}, + "nitrite": {"constant": 10.005, "exponent": 1.105}, + }, + ) + + def test_river_overrides(self): + river = River(name="") + overrides = { + "length": 27.3, + "width": 200, + "velocity": 0.667, + "damp": 1.43, + "mrf": 2.36, + "uptake_PNratio": 0.32, + "bulk_density": 5.32, + "denpar_w": 0.432, + "T_wdays": 23.5, + "halfsatINwater": 3.269, + "hsatTP": 2.431, + "limpppar": 6.473, + "prodNpar": 7.821, + "prodPpar": 8.231, + "muptNpar": 6.213, + "muptPpar": 7.021, + "max_temp_lag": 3.213, + "max_phosphorus_lag": 78.321, + } + overrides_to_check = overrides.copy() + river.apply_overrides(overrides) + for k, v in overrides_to_check.items(): + if k == "area": + v = 27.3 * 200 + self.assertEqual(river.tank.area, v) + if k == "capacity": + v = constants.UNBOUNDED_CAPACITY + self.assertEqual(river.tank.capacity, v) + self.assertEqual(getattr(river, k), v) + # test runtimeerrors + self.assertRaises(RuntimeError, lambda: river.apply_overrides({"area": 75.2})) + self.assertRaises( + RuntimeError, lambda: river.apply_overrides({"capacity": 123}) + ) + + def test_riverreservoir_overrides(self): + riverreservoir = RiverReservoir(name="") + riverreservoir.apply_overrides({"environmental_flow": 154}) + self.assertEqual(riverreservoir.environmental_flow, 154) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_tanks.py b/tests/test_tanks.py index 0b1f9456..94f7942c 100644 --- a/tests/test_tanks.py +++ b/tests/test_tanks.py @@ -3,6 +3,7 @@ import unittest from unittest import TestCase +from wsimod.core import constants from wsimod.nodes.nodes import Node from wsimod.nodes.tanks import ( DecayQueueTank, @@ -14,6 +15,10 @@ class MyTestClass(TestCase): + def setUp(self): + """""" + constants.set_simple_pollutants() + def assertDictAlmostEqual(self, d1, d2, accuracy=19): """ diff --git a/wsimod/nodes/storage.py b/wsimod/nodes/storage.py index 2cd75990..a4147e1c 100644 --- a/wsimod/nodes/storage.py +++ b/wsimod/nodes/storage.py @@ -4,6 +4,7 @@ @author: bdobson Converted to totals on 2022-05-03 """ from math import exp +from typing import Any, Dict from wsimod.core import constants from wsimod.nodes.nodes import Node @@ -73,6 +74,30 @@ def __init__( # Mass balance self.mass_balance_ds.append(lambda: self.tank.ds()) + def apply_overrides(self, overrides=Dict[str, Any]): + """Override parameters. + + Enables a user to override any of the following parameters: + capacity, area, datum, decays. + + Args: + overrides (Dict[str, Any]): Dict describing which parameters should + be overridden (keys) and new values (values). Defaults to {}. + """ + # not using pop as these items need to stay + # in the overrides to be fed into the tank overrides + if "capacity" in overrides.keys(): + self.capacity = overrides["capacity"] + if "area" in overrides.keys(): + self.area = overrides["area"] + if "datum" in overrides.keys(): + self.datum = overrides["datum"] + if "decays" in overrides.keys(): + self.decays.update(overrides["decays"]) + # apply tank overrides + self.tank.apply_overrides(overrides) + super().apply_overrides(overrides) + def push_set_storage(self, vqip): """A node wrapper for the tank push_storage. @@ -172,6 +197,23 @@ def __init__( self.data_input_dict = data_input_dict super().__init__(**kwargs) + def apply_overrides(self, overrides=Dict[str, Any]): + """Override parameters. + + Enables a user to override any of the following parameters: + residence_time, infiltration_threshold, infiltration_pct. + + Args: + overrides (Dict[str, Any]): Dict describing which parameters should + be overridden (keys) and new values (values). Defaults to {}. + """ + self.residence_time = overrides.pop("residence_time", self.residence_time) + self.infiltration_threshold = overrides.pop( + "infiltration_threshold", self.infiltration_threshold + ) + self.infiltration_pct = overrides.pop("infiltration_pct", self.infiltration_pct) + super().apply_overrides(overrides) + def distribute(self): """Calculate outflow with residence time and send to Nodes or Rivers.""" avail = self.tank.get_avail()["volume"] / self.residence_time @@ -267,6 +309,19 @@ def __init__(self, timearea={0: 1}, data_input_dict={}, **kwargs): initial_storage=self.initial_storage, ) + def apply_overrides(self, overrides=Dict[str, Any]): + """Override parameters. + + Enables a user to override any of the following parameters: + timearea. + + Args: + overrides (Dict[str, Any]): Dict describing which parameters should + be overridden (keys) and new values (values). Defaults to {}. + """ + self.timearea = overrides.pop("timearea", self.timearea) + super().apply_overrides(overrides) + def push_set_timearea(self, vqip): """Push setting that enables timearea behaviour, (see __init__ for description).Used to receive flow that is assumed to occur widely across some @@ -450,7 +505,12 @@ def __init__( _Units_: m3/day """ # Set parameters - self.depth = depth # [m] + self.depth = depth + if depth != 2: + raise RuntimeError( + "warning: the depth parameter is unused by River nodes because it is \ + intended for capacity to be unbounded. It may be removed in a future version." + ) self.length = length # [m] self.width = width # [m] self.velocity = velocity # [m/dt] @@ -491,16 +551,6 @@ def __init__( ) self.muptNpar = 0.001 # [kg/m2/day] nitrogen macrophyte uptake rate self.muptPpar = 0.0001 # 0.01, # [kg/m2/day] phosphorus macrophyte uptake rate - self.qbank_365_days = [1e6, 1e6] # [m3/day] store outflow in the previous year - self.qbank = ( - 1e6 # [m3/day] bankfull flow = second largest outflow in the previous year - ) - self.qbankcorrpar = 0.001 # [-] correction coefficient for qbank flow - self.sedexppar = 1 # [-] - self.EPC0 = 0.05 * constants.MG_L_TO_KG_M3 # [mg/l] - self.kd_s = 0 * constants.MG_L_TO_KG_M3 # 6 * 1e-6, # [kg/m3] - self.kadsdes_s = 2 # 0.9, # [-] - self.Dsed = 0.2 # [m] self.max_temp_lag = 20 self.lagged_temperatures = [] @@ -552,6 +602,56 @@ def __init__( # self.get_dt_excess()['volume'])) _ = self.tank.push_storage(vqip_, force=True) # return self.extract_vqip(vqip, vqip_) + def apply_overrides(self, overrides=Dict[str, Any]): + """Override parameters. + + Enables a user to override any of the following parameters: + timearea. + + Args: + overrides (Dict[str, Any]): Dict describing which parameters should + be overridden (keys) and new values (values). Defaults to {}. + """ + overwrite_params = set( + [ + "length", + "width", + "velocity", + "damp", + "mrf", + "uptake_PNratio", + "bulk_density", + "denpar_w", + "T_wdays", + "halfsatINwater", + "hsatTP", + "limpppar", + "prodNpar", + "prodPpar", + "muptNpar", + "muptPpar", + "max_temp_lag", + "max_phosphorus_lag", + ] + ) + + for param in overwrite_params.intersection(overrides.keys()): + setattr(self, param, overrides.pop(param)) + + if "area" in overrides.keys(): + raise RuntimeError( + "ERROR: specifying area is depreciated in overrides \ + for river, please specify length and width instead" + ) + overrides["area"] = self.length * self.width + if "capacity" in overrides.keys(): + raise RuntimeError( + "ERROR: specifying capacity is depreciated in overrides \ + for river, it is always set as unbounded capacity" + ) + overrides["capacity"] = constants.UNBOUNDED_CAPACITY + super().apply_overrides(overrides) + def pull_check_river(self, vqip=None): """Check amount of water that can be pulled from river tank and upstream. @@ -937,6 +1037,21 @@ def __init__(self, environmental_flow=0, **kwargs): self.__class__.__name__ = "Reservoir" + def apply_overrides(self, overrides=Dict[str, Any]): + """Override parameters. + + Enables a user to override any of the following parameters: + environmental_flow. + + Args: + overrides (Dict[str, Any]): Dict describing which parameters should + be overridden (keys) and new values (values). Defaults to {}. + """ + self.environmental_flow = overrides.pop( + "environmental_flow", self.environmental_flow + ) + super().apply_overrides(overrides) + def push_set_river_reservoir(self, vqip): """Receive water.