Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply overrides in Storage and its superclasses #81

Merged
merged 21 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 116 additions & 2 deletions tests/test_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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()
5 changes: 5 additions & 0 deletions tests/test_tanks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -14,6 +15,10 @@


class MyTestClass(TestCase):
def setUp(self):
""""""
constants.set_simple_pollutants()

def assertDictAlmostEqual(self, d1, d2, accuracy=19):
"""

Expand Down
137 changes: 126 additions & 11 deletions wsimod/nodes/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see that these aren't used - but I guess they were used in CatchWat? Are we likely to need them again? If so, maybe just copy this excerpt into #2

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably not as they are for the solids and we haven't decided which sedimentation-resuspension model we are going to integrate ... plus these parameters are not performing very well in previous case studies

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 = []
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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.

Expand Down
Loading