diff --git a/tests/test_nodes.py b/tests/test_nodes.py index b285f712..576ef7b9 100644 --- a/tests/test_nodes.py +++ b/tests/test_nodes.py @@ -11,14 +11,7 @@ from wsimod.arcs.arcs import Arc from wsimod.core import constants -from wsimod.nodes.nodes import ( - DecayQueueTank, - DecayTank, - Node, - QueueTank, - ResidenceTank, - Tank, -) +from wsimod.nodes.nodes import Node from wsimod.nodes.storage import Storage from wsimod.nodes.waste import Waste @@ -424,242 +417,6 @@ def test_data_read(self): self.assertEqual(15, node.get_data_input("temperature")) - def test_tank_ds(self): - tank = Tank( - capacity=10, - initial_storage={"volume": 5, "phosphate": 0.4, "temperature": 10}, - ) - tank.end_timestep() - - d1 = {"volume": 2, "phosphate": 0.01, "temperature": 15} - - _ = tank.push_storage(d1) - - diff = tank.ds() - - d2 = {"volume": 2, "phosphate": 0.01, "temperature": 0} - - self.assertDictAlmostEqual(d2, diff, 16) - - def test_ponded(self): - tank = Tank( - capacity=10, - initial_storage={"volume": 15, "phosphate": 0.4, "temperature": 10}, - ) - d1 = {"volume": 5, "phosphate": 0.4 / 3, "temperature": 10} - reply = tank.pull_ponded() - self.assertDictAlmostEqual(d1, reply) - - def test_tank_get_avail(self): - d1 = {"volume": 5, "phosphate": 0.4, "temperature": 10} - tank = Tank(capacity=10, initial_storage=d1) - - reply = tank.get_avail() - self.assertDictAlmostEqual(d1, reply) - - reply = tank.get_avail({"volume": 2.5}) - d2 = {"volume": 2.5, "phosphate": 0.2, "temperature": 10} - self.assertDictAlmostEqual(d2, reply) - - reply = tank.get_avail({"volume": 10}) - self.assertDictAlmostEqual(d1, reply) - - def test_tank_get_excess(self): - d1 = {"volume": 7.5, "phosphate": 0.4, "temperature": 10} - tank = Tank(capacity=10, initial_storage=d1) - - d2 = {"volume": 2.5, "phosphate": 0.4 / 3, "temperature": 10} - reply = tank.get_excess() - self.assertDictAlmostEqual(d2, reply) - - d2 = {"volume": 1, "phosphate": 0.4 * 1 / 7.5, "temperature": 10} - reply = tank.get_excess({"volume": 1}) - self.assertDictAlmostEqual(d2, reply) - - def test_tank_push_storage(self): - d1 = {"volume": 7.5, "phosphate": 0.4, "temperature": 10} - tank = Tank(capacity=10, initial_storage=d1) - - d2 = {"volume": 5, "phosphate": 0.4, "temperature": 15} - - d3 = {"volume": 2.5, "phosphate": 0.2, "temperature": 15} - reply = tank.push_storage(d2) - self.assertDictAlmostEqual(d3, reply) - - d4 = {"volume": 0, "phosphate": 0, "temperature": 0} - reply = tank.push_storage(d2, force=True) - self.assertDictAlmostEqual(d4, reply) - - def test_tank_pull_storage(self): - d1 = {"volume": 7.5, "phosphate": 0.4, "temperature": 10} - tank = Tank(capacity=10, initial_storage=d1) - - d2 = {"volume": 5, "phosphate": 0.4 * 5 / 7.5, "temperature": 10} - - reply = tank.pull_storage({"volume": 5}) - self.assertDictAlmostEqual(d2, reply) - - d3 = {"volume": 2.5, "phosphate": 0.4 * 2.5 / 7.5, "temperature": 10} - - reply = tank.pull_storage({"volume": 5}) - - self.assertDictAlmostEqual(d3, reply, 15) - - def test_tank_pull_pollutants(self): - d1 = {"volume": 7.5, "phosphate": 0.4, "temperature": 10} - tank = Tank(capacity=10, initial_storage=d1) - - d2 = {"volume": 5, "phosphate": 0.1, "temperature": 10} - - reply = tank.pull_pollutants(d2) - self.assertDictAlmostEqual(d2, reply) - - reply = tank.pull_pollutants(d2) - d3 = {"volume": 2.5, "phosphate": 0.1, "temperature": 10} - self.assertDictAlmostEqual(d3, reply, 15) - - def test_tank_head(self): - d1 = {"volume": 7.5, "phosphate": 0.4, "temperature": 10} - tank = Tank(capacity=10, initial_storage=d1, datum=5, area=2.5) - - reply = tank.get_head() - self.assertEqual(8, reply) - - reply = tank.get_head(datum=-1) - self.assertEqual(2, reply) - - reply = tank.get_head(non_head_storage=2) - self.assertEqual(7.2, reply) - - reply = tank.get_head(non_head_storage=10) - self.assertEqual(5, reply) - - def test_evap(self): - d1 = {"volume": 7.5, "phosphate": 0.4, "temperature": 10} - tank = Tank(capacity=10, initial_storage=d1) - - d2 = {"volume": 0, "phosphate": 0.4, "temperature": 10} - - reply = tank.evaporate(10) - self.assertEqual(7.5, reply) - self.assertDictAlmostEqual(d2, tank.storage) - - def test_residence_tank(self): - d1 = {"volume": 7.5, "phosphate": 0.4, "temperature": 10} - tank = ResidenceTank(residence_time=3, initial_storage=d1) - - d2 = {"volume": 2.5, "phosphate": 0.4 / 3, "temperature": 10} - reply = tank.pull_outflow() - self.assertDictAlmostEqual(d2, reply) - - def test_decay_tank(self): - node = Node(name="", data_input_dict={("temperature", 1): 15}) - node.t = 1 - d1 = {"volume": 8, "phosphate": 0.4, "temperature": 10} - - tank = DecayTank( - decays={"phosphate": {"constant": 0.001, "exponent": 1.005}}, - initial_storage=d1, - parent=node, - ) - _ = tank.pull_storage({"volume": 2}) - - d3 = {"volume": -2, "phosphate": -0.1, "temperature": 0} - - diff = tank.decay_ds() - self.assertDictAlmostEqual(d3, diff, 16) - - tank.end_timestep_decay() - - d2 = { - "volume": 6, - "phosphate": 0.3 - 0.3 * 0.001 * 1.005 ** (15 - 20), - "temperature": 10, - } - - self.assertDictAlmostEqual(d2, tank.storage, 16) - - self.assertAlmostEqual( - 0.3 * 0.001 * 1.005 ** (15 - 20), tank.total_decayed["phosphate"] - ) - - def test_queue_push(self): - d1 = {"volume": 5, "phosphate": 0.4, "temperature": 10} - tank = QueueTank(number_of_timesteps=1, capacity=10, initial_storage=d1) - - d2 = {"volume": 1, "phosphate": 0.1, "temperature": 15} - - tank.push_storage(d2) - - d3 = {"volume": 6, "phosphate": 0.5, "temperature": (5 * 10 + 15) / 6} - - self.assertDictAlmostEqual(d3, tank.storage) - self.assertDictAlmostEqual(d1, tank.active_storage) - self.assertDictAlmostEqual(d2, tank.internal_arc.queue[1]) - - tank.push_storage(d2, force=True) - self.assertDictAlmostEqual(d3, tank.active_storage) - - tank.end_timestep() - - d4 = {"volume": 7, "phosphate": 0.6, "temperature": ((5 * 10) + (15 * 2)) / 7} - self.assertDictAlmostEqual(d4, tank.active_storage) - - def test_queue_pull(self): - d1 = {"volume": 5, "phosphate": 0.4, "temperature": 10} - tank = QueueTank(number_of_timesteps=1, capacity=10, initial_storage=d1) - d2 = {"volume": 1, "phosphate": 0.1, "temperature": 15} - reply = tank.push_storage(d2) - - reply = tank.pull_storage({"volume": 6}) - self.assertDictAlmostEqual(d1, reply) - tank.end_timestep() - self.assertDictAlmostEqual(d2, tank.active_storage) - - def test_queue_pull_exact(self): - d1 = {"volume": 5, "phosphate": 0.4, "temperature": 10} - tank = QueueTank(number_of_timesteps=1, capacity=10, initial_storage=d1) - d2 = {"volume": 1, "phosphate": 0.1, "temperature": 15} - reply = tank.push_storage(d2) - - reply = tank.pull_storage_exact( - {"volume": 6, "phosphate": 0.1, "temperature": 10} - ) - - d3 = {"volume": 5, "phosphate": 0.1, "temperature": 10} - self.assertDictAlmostEqual(d3, reply) - - reply = tank.pull_storage_exact( - {"volume": 0, "phosphate": 0.6, "temperature": 10} - ) - d4 = {"volume": 0, "phosphate": 0.3, "temperature": 10} - self.assertDictAlmostEqual(d4, reply, 16) - - def test_decay_queue(self): - node = Node(name="", data_input_dict={("temperature", 1): 15}) - node.t = 1 - d1 = {"volume": 5, "phosphate": 0.4, "temperature": 10} - tank = DecayQueueTank( - number_of_timesteps=1, - capacity=10, - initial_storage=d1, - decays={"phosphate": {"constant": 0.001, "exponent": 1.005}}, - parent=node, - ) - - d2 = {"volume": 1, "phosphate": 0.1, "temperature": 15} - - _ = tank.push_storage(d2) - - tank.end_timestep() - - d4 = { - "volume": 6, - "phosphate": 0.4 + 0.1 * (1 - 0.001 * 1.005 ** (15 - 20)), - "temperature": ((5 * 10) + (15 * 1)) / 6, - } - self.assertDictAlmostEqual(d4, tank.storage, 15) - if __name__ == "__main__": unittest.main() diff --git a/tests/test_tanks.py b/tests/test_tanks.py new file mode 100644 index 00000000..5c6c759d --- /dev/null +++ b/tests/test_tanks.py @@ -0,0 +1,268 @@ +"""Tests for the tanks module.""" + +import unittest +from unittest import TestCase + +from wsimod.nodes.nodes import Node +from wsimod.nodes.tanks import ( + DecayQueueTank, + DecayTank, + QueueTank, + ResidenceTank, + Tank, +) + + +class MyTestClass(TestCase): + def assertDictAlmostEqual(self, d1, d2, accuracy=19): + """ + + Args: + d1: + d2: + accuracy: + """ + for d in [d1, d2]: + for key, item in d.items(): + d[key] = round(item, accuracy) + self.assertDictEqual(d1, d2) + + def test_tank_ds(self): + tank = Tank( + capacity=10, + initial_storage={"volume": 5, "phosphate": 0.4, "temperature": 10}, + ) + tank.end_timestep() + + d1 = {"volume": 2, "phosphate": 0.01, "temperature": 15} + + _ = tank.push_storage(d1) + + diff = tank.ds() + + d2 = {"volume": 2, "phosphate": 0.01, "temperature": 0} + + self.assertDictAlmostEqual(d2, diff, 16) + + def test_ponded(self): + tank = Tank( + capacity=10, + initial_storage={"volume": 15, "phosphate": 0.4, "temperature": 10}, + ) + d1 = {"volume": 5, "phosphate": 0.4 / 3, "temperature": 10} + reply = tank.pull_ponded() + self.assertDictAlmostEqual(d1, reply) + + def test_tank_get_avail(self): + d1 = {"volume": 5, "phosphate": 0.4, "temperature": 10} + tank = Tank(capacity=10, initial_storage=d1) + + reply = tank.get_avail() + self.assertDictAlmostEqual(d1, reply) + + reply = tank.get_avail({"volume": 2.5}) + d2 = {"volume": 2.5, "phosphate": 0.2, "temperature": 10} + self.assertDictAlmostEqual(d2, reply) + + reply = tank.get_avail({"volume": 10}) + self.assertDictAlmostEqual(d1, reply) + + def test_tank_get_excess(self): + d1 = {"volume": 7.5, "phosphate": 0.4, "temperature": 10} + tank = Tank(capacity=10, initial_storage=d1) + + d2 = {"volume": 2.5, "phosphate": 0.4 / 3, "temperature": 10} + reply = tank.get_excess() + self.assertDictAlmostEqual(d2, reply) + + d2 = {"volume": 1, "phosphate": 0.4 * 1 / 7.5, "temperature": 10} + reply = tank.get_excess({"volume": 1}) + self.assertDictAlmostEqual(d2, reply) + + def test_tank_push_storage(self): + d1 = {"volume": 7.5, "phosphate": 0.4, "temperature": 10} + tank = Tank(capacity=10, initial_storage=d1) + + d2 = {"volume": 5, "phosphate": 0.4, "temperature": 15} + + d3 = {"volume": 2.5, "phosphate": 0.2, "temperature": 15} + reply = tank.push_storage(d2) + self.assertDictAlmostEqual(d3, reply) + + d4 = {"volume": 0, "phosphate": 0, "temperature": 0} + reply = tank.push_storage(d2, force=True) + self.assertDictAlmostEqual(d4, reply) + + def test_tank_pull_storage(self): + d1 = {"volume": 7.5, "phosphate": 0.4, "temperature": 10} + tank = Tank(capacity=10, initial_storage=d1) + + d2 = {"volume": 5, "phosphate": 0.4 * 5 / 7.5, "temperature": 10} + + reply = tank.pull_storage({"volume": 5}) + self.assertDictAlmostEqual(d2, reply) + + d3 = {"volume": 2.5, "phosphate": 0.4 * 2.5 / 7.5, "temperature": 10} + + reply = tank.pull_storage({"volume": 5}) + + self.assertDictAlmostEqual(d3, reply, 15) + + def test_tank_pull_pollutants(self): + d1 = {"volume": 7.5, "phosphate": 0.4, "temperature": 10} + tank = Tank(capacity=10, initial_storage=d1) + + d2 = {"volume": 5, "phosphate": 0.1, "temperature": 10} + + reply = tank.pull_pollutants(d2) + self.assertDictAlmostEqual(d2, reply) + + reply = tank.pull_pollutants(d2) + d3 = {"volume": 2.5, "phosphate": 0.1, "temperature": 10} + self.assertDictAlmostEqual(d3, reply, 15) + + def test_tank_head(self): + d1 = {"volume": 7.5, "phosphate": 0.4, "temperature": 10} + tank = Tank(capacity=10, initial_storage=d1, datum=5, area=2.5) + + reply = tank.get_head() + self.assertEqual(8, reply) + + reply = tank.get_head(datum=-1) + self.assertEqual(2, reply) + + reply = tank.get_head(non_head_storage=2) + self.assertEqual(7.2, reply) + + reply = tank.get_head(non_head_storage=10) + self.assertEqual(5, reply) + + def test_evap(self): + d1 = {"volume": 7.5, "phosphate": 0.4, "temperature": 10} + tank = Tank(capacity=10, initial_storage=d1) + + d2 = {"volume": 0, "phosphate": 0.4, "temperature": 10} + + reply = tank.evaporate(10) + self.assertEqual(7.5, reply) + self.assertDictAlmostEqual(d2, tank.storage) + + def test_residence_tank(self): + d1 = {"volume": 7.5, "phosphate": 0.4, "temperature": 10} + tank = ResidenceTank(residence_time=3, initial_storage=d1) + + d2 = {"volume": 2.5, "phosphate": 0.4 / 3, "temperature": 10} + reply = tank.pull_outflow() + self.assertDictAlmostEqual(d2, reply) + + def test_decay_tank(self): + node = Node(name="", data_input_dict={("temperature", 1): 15}) + node.t = 1 + d1 = {"volume": 8, "phosphate": 0.4, "temperature": 10} + + tank = DecayTank( + decays={"phosphate": {"constant": 0.001, "exponent": 1.005}}, + initial_storage=d1, + parent=node, + ) + _ = tank.pull_storage({"volume": 2}) + + d3 = {"volume": -2, "phosphate": -0.1, "temperature": 0} + + diff = tank.decay_ds() + self.assertDictAlmostEqual(d3, diff, 16) + + tank.end_timestep_decay() + + d2 = { + "volume": 6, + "phosphate": 0.3 - 0.3 * 0.001 * 1.005 ** (15 - 20), + "temperature": 10, + } + + self.assertDictAlmostEqual(d2, tank.storage, 16) + + self.assertAlmostEqual( + 0.3 * 0.001 * 1.005 ** (15 - 20), tank.total_decayed["phosphate"] + ) + + def test_queue_push(self): + d1 = {"volume": 5, "phosphate": 0.4, "temperature": 10} + tank = QueueTank(number_of_timesteps=1, capacity=10, initial_storage=d1) + + d2 = {"volume": 1, "phosphate": 0.1, "temperature": 15} + + tank.push_storage(d2) + + d3 = {"volume": 6, "phosphate": 0.5, "temperature": (5 * 10 + 15) / 6} + + self.assertDictAlmostEqual(d3, tank.storage) + self.assertDictAlmostEqual(d1, tank.active_storage) + self.assertDictAlmostEqual(d2, tank.internal_arc.queue[1]) + + tank.push_storage(d2, force=True) + self.assertDictAlmostEqual(d3, tank.active_storage) + + tank.end_timestep() + + d4 = {"volume": 7, "phosphate": 0.6, "temperature": ((5 * 10) + (15 * 2)) / 7} + self.assertDictAlmostEqual(d4, tank.active_storage) + + def test_queue_pull(self): + d1 = {"volume": 5, "phosphate": 0.4, "temperature": 10} + tank = QueueTank(number_of_timesteps=1, capacity=10, initial_storage=d1) + d2 = {"volume": 1, "phosphate": 0.1, "temperature": 15} + reply = tank.push_storage(d2) + + reply = tank.pull_storage({"volume": 6}) + self.assertDictAlmostEqual(d1, reply) + tank.end_timestep() + self.assertDictAlmostEqual(d2, tank.active_storage) + + def test_queue_pull_exact(self): + d1 = {"volume": 5, "phosphate": 0.4, "temperature": 10} + tank = QueueTank(number_of_timesteps=1, capacity=10, initial_storage=d1) + d2 = {"volume": 1, "phosphate": 0.1, "temperature": 15} + reply = tank.push_storage(d2) + + reply = tank.pull_storage_exact( + {"volume": 6, "phosphate": 0.1, "temperature": 10} + ) + + d3 = {"volume": 5, "phosphate": 0.1, "temperature": 10} + self.assertDictAlmostEqual(d3, reply) + + reply = tank.pull_storage_exact( + {"volume": 0, "phosphate": 0.6, "temperature": 10} + ) + d4 = {"volume": 0, "phosphate": 0.3, "temperature": 10} + self.assertDictAlmostEqual(d4, reply, 16) + + def test_decay_queue(self): + node = Node(name="", data_input_dict={("temperature", 1): 15}) + node.t = 1 + d1 = {"volume": 5, "phosphate": 0.4, "temperature": 10} + tank = DecayQueueTank( + number_of_timesteps=1, + capacity=10, + initial_storage=d1, + decays={"phosphate": {"constant": 0.001, "exponent": 1.005}}, + parent=node, + ) + + d2 = {"volume": 1, "phosphate": 0.1, "temperature": 15} + + _ = tank.push_storage(d2) + + tank.end_timestep() + + d4 = { + "volume": 6, + "phosphate": 0.4 + 0.1 * (1 - 0.001 * 1.005 ** (15 - 20)), + "temperature": ((5 * 10) + (15 * 1)) / 6, + } + self.assertDictAlmostEqual(d4, tank.storage, 15) + + +if __name__ == "__main__": + unittest.main() diff --git a/wsimod/nodes/land.py b/wsimod/nodes/land.py index f34fb750..7bcaf86a 100644 --- a/wsimod/nodes/land.py +++ b/wsimod/nodes/land.py @@ -8,8 +8,9 @@ from math import exp, log, log10, sin from wsimod.core import constants -from wsimod.nodes.nodes import DecayTank, Node, ResidenceTank +from wsimod.nodes.nodes import Node from wsimod.nodes.nutrient_pool import NutrientPool +from wsimod.nodes.tanks import DecayTank, ResidenceTank class Land(Node): diff --git a/wsimod/nodes/sewer.py b/wsimod/nodes/sewer.py index af566658..545495c1 100644 --- a/wsimod/nodes/sewer.py +++ b/wsimod/nodes/sewer.py @@ -5,7 +5,8 @@ Converted to totals on 2022-05-03 """ from wsimod.core import constants -from wsimod.nodes.nodes import Node, QueueTank +from wsimod.nodes.nodes import Node +from wsimod.nodes.tanks import QueueTank class Sewer(Node): diff --git a/wsimod/nodes/storage.py b/wsimod/nodes/storage.py index eab68665..2cd75990 100644 --- a/wsimod/nodes/storage.py +++ b/wsimod/nodes/storage.py @@ -6,7 +6,8 @@ from math import exp from wsimod.core import constants -from wsimod.nodes.nodes import DecayQueueTank, DecayTank, Node, QueueTank, Tank +from wsimod.nodes.nodes import Node +from wsimod.nodes.tanks import DecayQueueTank, DecayTank, QueueTank, Tank class Storage(Node): diff --git a/wsimod/nodes/tanks.py b/wsimod/nodes/tanks.py index b2036edb..4806d6ae 100644 --- a/wsimod/nodes/tanks.py +++ b/wsimod/nodes/tanks.py @@ -1,6 +1,6 @@ """Module for defining tanks.""" -from wsimod.arcs import AltQueueArc, DecayArcAlt +from wsimod.arcs.arcs import AltQueueArc, DecayArcAlt from wsimod.core import constants from wsimod.core.core import DecayObj, WSIObj diff --git a/wsimod/nodes/wtw.py b/wsimod/nodes/wtw.py index e40f0a5d..afd6c6a5 100644 --- a/wsimod/nodes/wtw.py +++ b/wsimod/nodes/wtw.py @@ -7,7 +7,8 @@ from typing import Any, Dict from wsimod.core import constants -from wsimod.nodes.nodes import Node, Tank +from wsimod.nodes.nodes import Node +from wsimod.nodes.tanks import Tank class WTW(Node): diff --git a/wsimod/orchestration/model.py b/wsimod/orchestration/model.py index 8dc4e917..4026a325 100644 --- a/wsimod/orchestration/model.py +++ b/wsimod/orchestration/model.py @@ -19,7 +19,8 @@ from wsimod.core import constants from wsimod.core.core import WSIObj from wsimod.nodes.land import ImperviousSurface -from wsimod.nodes.nodes import NODES_REGISTRY, QueueTank, ResidenceTank, Tank +from wsimod.nodes.nodes import NODES_REGISTRY +from wsimod.nodes.tanks import QueueTank, ResidenceTank, Tank os.environ["USE_PYGEOS"] = "0"