From 7cfab64ec702a3542bc88744a2ff34093389edfb Mon Sep 17 00:00:00 2001 From: barneydobson Date: Tue, 27 Feb 2024 09:46:39 +0000 Subject: [PATCH 1/4] Define and test `apply_overrides` for WTW --- tests/test_wtw.py | 60 ++++++++++++++++++++++++++++++++++++++++++++- wsimod/nodes/wtw.py | 59 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 112 insertions(+), 7 deletions(-) diff --git a/tests/test_wtw.py b/tests/test_wtw.py index c3f819b..94acff4 100644 --- a/tests/test_wtw.py +++ b/tests/test_wtw.py @@ -40,8 +40,66 @@ def test_excess(self): self.assertEqual(2, wtw.get_excess_throughput()) def test_treat(self): - pass + constants.set_simple_pollutants() + wtw = WTW( + name="", + treatment_throughput_capacity=10, + ) + wtw.current_input= {'volume' : 8, + 'phosphate' : 5, + 'temperature' : constants.DECAY_REFERENCE_TEMPERATURE} + wtw.treat_current_input() + self.assertEqual(8 * wtw.process_parameters['volume']['constant'], + wtw.treated["volume"]) + self.assertEqual(5 * wtw.process_parameters['phosphate']['constant'], + wtw.treated["phosphate"]) + self.assertEqual(8 * wtw.liquor_multiplier['volume'], + wtw.liquor['volume']) + self.assertEqual(5 * wtw.liquor_multiplier['phosphate'], + wtw.liquor['phosphate']) + self.assertEqual(5 - 5 * wtw.process_parameters['phosphate']['constant']\ + - 5 * wtw.liquor_multiplier['phosphate'], + wtw.solids['phosphate']) + + def test_override(self): + wtw = WTW( + name="", + treatment_throughput_capacity=10, + percent_solids = 0.1, + liquor_multiplier = {'volume' : 0.05, 'phosphate' : 0.5}, + process_parameters = {'phosphate' : {'constant' : 0.1, + 'exponent' : 1.001}} + ) + + wtw.apply_overrides({'percent_solids' : 0.05}) + self.assertAlmostEqual(wtw.process_parameters['volume']['constant'], + 0.9) + self.assertEqual(wtw.percent_solids, 0.05) + + wtw.apply_overrides({'percent_solids' : 0.1, + 'liquor_multiplier' : {'volume' : 0.1}}) + + self.assertEqual(wtw.process_parameters['volume']['constant'], + 0.8) + self.assertEqual(wtw.liquor_multiplier['volume'], 0.1) + self.assertEqual(wtw.liquor_multiplier['phosphate'], 0.5) + + wtw.apply_overrides({'percent_solids' : 0.1, + 'liquor_multiplier' : {'volume' : 0.1, + 'phosphate' : 0.01}}) + self.assertEqual(wtw.liquor_multiplier['phosphate'], 0.01) + wtw.apply_overrides({'process_parameters' : {'phosphate' : {'constant' : 0.01}}}) + self.assertEqual(wtw.process_parameters['phosphate']['constant'], 0.01) + self.assertEqual(wtw.process_parameters['phosphate']['exponent'], 1.001) + overrides = {'process_parameters' : {'phosphate' : {'exponent' : 1.01}}, + 'liquor_multiplier' : {'phosphate' : 0.1}, + 'percent_solids' : 0.1, + 'treatment_throughput_capacity' : 20, + 'name' : 'new_name'} + wtw.apply_overrides(overrides) + self.assertSetEqual(set(overrides.keys()), set(['name'])) + self.assertEqual(wtw.treatment_throughput_capacity, 20) if __name__ == "__main__": unittest.main() diff --git a/wsimod/nodes/wtw.py b/wsimod/nodes/wtw.py index 7cec54a..ae055f1 100644 --- a/wsimod/nodes/wtw.py +++ b/wsimod/nodes/wtw.py @@ -6,7 +6,7 @@ """ from wsimod.core import constants from wsimod.nodes.nodes import Node, Tank - +from typing import Any, Dict class WTW(Node): """""" @@ -71,18 +71,18 @@ def __init__( for x in constants.ADDITIVE_POLLUTANTS } if len(liquor_multiplier) > 0: - self.liquor_multiplier = liquor_multiplier + self._liquor_multiplier = liquor_multiplier else: - self.liquor_multiplier = {x: 0.7 for x in constants.ADDITIVE_POLLUTANTS} - self.liquor_multiplier["volume"] = 0.03 + self._liquor_multiplier = {x: 0.7 for x in constants.ADDITIVE_POLLUTANTS} + self._liquor_multiplier["volume"] = 0.03 - self.percent_solids = percent_solids + self._percent_solids = percent_solids # Update args super().__init__(name) self.process_parameters["volume"] = { - "constant": 1 - self.percent_solids - self.liquor_multiplier["volume"] + "constant": self.calculate_volume() } # Update handlers @@ -94,6 +94,53 @@ def __init__( self.treated = self.empty_vqip() self.liquor = self.empty_vqip() self.solids = self.empty_vqip() + + def calculate_volume(self): + """Calculate the volume proportion of treated water. + + Returns: + (float): Volume of treated water + """ + return 1 - self._percent_solids - self._liquor_multiplier["volume"] + + @property + def percent_solids(self): + return self._percent_solids + + @percent_solids.setter + def percent_solids(self, value): + self._percent_solids = value + self.process_parameters["volume"]["constant"] = self.calculate_volume() + + @property + def liquor_multiplier(self): + return self._liquor_multiplier + + @liquor_multiplier.setter + def liquor_multiplier(self, value): + self._liquor_multiplier.update(value) + self.process_parameters["volume"]["constant"] = self.calculate_volume() + + def apply_overrides(self, overrides = Dict[str, Any]): + """Apply overrides to the process parameters and liquor multipliers. + + Args: + overrides (Dict[str, Any]): Dict describing which parameters should + be overridden (keys) and new values (values). Defaults to {}. + """ + self.percent_solids = overrides.pop("percent_solids", + self._percent_solids) + self.liquor_multiplier = overrides.pop("liquor_multiplier", + self._liquor_multiplier) + process_parameters = overrides.pop("process_parameters", {}) + for key, value in process_parameters.items(): + self.process_parameters[key].update(value) + + self.treatment_throughput_capacity = overrides.pop( + "treatment_throughput_capacity", + self.treatment_throughput_capacity) + if len(overrides) > 0: + print(f"No override behaviour defined for: {overrides.keys()}") def get_excess_throughput(self): """How much excess treatment capacity is there. From 82c74551df7b3b7241a151ab1afbda1694f3dd54 Mon Sep 17 00:00:00 2001 From: barneydobson Date: Tue, 27 Feb 2024 10:22:13 +0000 Subject: [PATCH 2/4] Add and test WWTW overrides --- tests/test_wtw.py | 15 ++++++++++++++- wsimod/nodes/wtw.py | 17 +++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/tests/test_wtw.py b/tests/test_wtw.py index 94acff4..1a2061f 100644 --- a/tests/test_wtw.py +++ b/tests/test_wtw.py @@ -10,7 +10,7 @@ from unittest import TestCase from wsimod.core import constants -from wsimod.nodes.wtw import WTW +from wsimod.nodes.wtw import WTW, WWTW class MyTestClass(TestCase): @@ -101,5 +101,18 @@ def test_override(self): wtw.apply_overrides(overrides) self.assertSetEqual(set(overrides.keys()), set(['name'])) self.assertEqual(wtw.treatment_throughput_capacity, 20) + + def test_wwtw_overrides(self): + wwtw = WWTW(name='') + vol = wwtw.process_parameters['volume']['constant'] + wwtw.apply_overrides({'treatment_throughput_capacity' : 20, + 'process_parameters' : {'phosphate' : + {'constant' : 0.01}}, + 'stormwater_storage_capacity': 100}) + self.assertEqual(wwtw.treatment_throughput_capacity, 20) + self.assertEqual(wwtw.process_parameters['phosphate']['constant'], 0.01) + self.assertEqual(wwtw.process_parameters['volume']['constant'], vol) + self.assertEqual(wwtw.stormwater_storage_capacity, 100) + if __name__ == "__main__": unittest.main() diff --git a/wsimod/nodes/wtw.py b/wsimod/nodes/wtw.py index ae055f1..0974460 100644 --- a/wsimod/nodes/wtw.py +++ b/wsimod/nodes/wtw.py @@ -286,6 +286,23 @@ def __init__( lambda: self.ds_vqip(self.liquor, self.liquor_) ) # Change in liquor + def apply_overrides(self, overrides=Dict[str, Any]): + """Apply overrides to the stormwater tank and WWTW. + """ + self.stormwater_storage_capacity = overrides.pop( + "stormwater_storage_capacity", + self.stormwater_storage_capacity) + self.stormwater_storage_area = overrides.pop( + "stormwater_storage_area", + self.stormwater_storage_area) + self.stormwater_storage_elevation = overrides.pop( + "stormwater_storage_elevation", + self.stormwater_storage_elevation) + self.stormwater_tank.area = self.stormwater_storage_area + self.stormwater_tank.capacity = self.stormwater_storage_capacity + self.stormwater_tank.datum = self.stormwater_storage_elevation + super().apply_overrides(overrides) + def calculate_discharge(self): """Clear stormwater tank if possible, and call treat_current_input.""" # Run WWTW model From 2f6b5454de0c65fbd246c1bbeab0171f1810c158 Mon Sep 17 00:00:00 2001 From: Dobson Date: Thu, 29 Feb 2024 12:21:54 +0000 Subject: [PATCH 3/4] Update wtw.py --- wsimod/nodes/wtw.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/wsimod/nodes/wtw.py b/wsimod/nodes/wtw.py index 0974460..3951d19 100644 --- a/wsimod/nodes/wtw.py +++ b/wsimod/nodes/wtw.py @@ -9,7 +9,12 @@ from typing import Any, Dict class WTW(Node): - """""" + """A generic Water Treatment Works (WTW) node. + + This class is a generic water treatment works node. It is intended to be + subclassed into freshwater and wastewater treatment works (FWTW and WWTW + respectively). + """ def __init__( self, @@ -122,7 +127,12 @@ def liquor_multiplier(self, value): self.process_parameters["volume"]["constant"] = self.calculate_volume() def apply_overrides(self, overrides = Dict[str, Any]): - """Apply overrides to the process parameters and liquor multipliers. + """Override parameters. + + Enables a user to override any of the following parameters: + percent_solids, treatment_throughput_capacity, process_parameters (the + entire dict does not need to be redefined, only changed values need to + be included), liquor_multiplier (as with process_parameters). Args: overrides (Dict[str, Any]): Dict describing which parameters should @@ -211,7 +221,7 @@ def end_timestep(self): class WWTW(WTW): - """""" + """Wastewater Treatment Works (WWTW) node.""" def __init__( self, @@ -288,6 +298,13 @@ def __init__( def apply_overrides(self, overrides=Dict[str, Any]): """Apply overrides to the stormwater tank and WWTW. + + Enables a user to override any parameter of the stormwater tank, and + then calls any overrides in WTW. + + Args: + overrides (Dict[str, Any]): Dict describing which parameters should + be overridden (keys) and new values (values). Defaults to {}. """ self.stormwater_storage_capacity = overrides.pop( "stormwater_storage_capacity", From 2825fbf4eeb712aec6935cef3354060a3436120c Mon Sep 17 00:00:00 2001 From: Dobson Date: Thu, 29 Feb 2024 12:24:52 +0000 Subject: [PATCH 4/4] Move empty override checking to Node --- wsimod/nodes/nodes.py | 7 ++++++- wsimod/nodes/wtw.py | 3 +-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/wsimod/nodes/nodes.py b/wsimod/nodes/nodes.py index 173928a..22de49c 100644 --- a/wsimod/nodes/nodes.py +++ b/wsimod/nodes/nodes.py @@ -83,10 +83,15 @@ def apply_overrides(self, overrides: Dict[str, Any] = {}) -> None: """Apply overrides to the node. + The Node does not have any overwriteable parameters. So if any + overrides are passed up to the node, this means that there are unused + parameters from the Node subclass, which is flagged. + Args: overrides (dict, optional): Dictionary of overrides. Defaults to {}. """ - pass + if len(overrides) > 0: + print(f"No override behaviour defined for: {overrides.keys()}") def total_in(self): """Sum flow and pollutant amounts entering a node via in_arcs. diff --git a/wsimod/nodes/wtw.py b/wsimod/nodes/wtw.py index 3951d19..db99d56 100644 --- a/wsimod/nodes/wtw.py +++ b/wsimod/nodes/wtw.py @@ -149,8 +149,7 @@ def apply_overrides(self, overrides = Dict[str, Any]): self.treatment_throughput_capacity = overrides.pop( "treatment_throughput_capacity", self.treatment_throughput_capacity) - if len(overrides) > 0: - print(f"No override behaviour defined for: {overrides.keys()}") + super().apply_overrides(overrides) def get_excess_throughput(self): """How much excess treatment capacity is there.