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

Cistern patch to allow up to 1yr ddt #135

Merged
merged 4 commits into from
Jun 15, 2023
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
2 changes: 1 addition & 1 deletion nereid/nereid/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__version__ = "0.7.1"
__version__ = "0.8.0"
__author__ = "Austin Orr"
__email__ = "aorr@geosyntec.com"
4 changes: 2 additions & 2 deletions nereid/nereid/src/nomograph/interpolators.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ def _basesurface(self, ax: Optional[Axes] = None, **kwargs: Dict[str, Any]) -> A
ax.tricontourf(self.x_data, self.t_data, self.y_data, levels=255)

t = numpy.linspace(1, numpy.max(self.t_data), 100)
for i, perf in enumerate([0.6, 0.8, 0.9, 0.95, 0.97]):
for i, perf in enumerate([0.2, 0.4, 0.6, 0.8, 0.9, 0.95, 0.97]):
x = [self(t=_t, y=perf) for _t in t]
ax.plot(x, t, c=f"C{i}", label=f"{perf:.0%}")

Expand Down Expand Up @@ -359,7 +359,7 @@ def plot(self, *args: Tuple, **kwargs: Dict[str, Any]) -> Axes:
ax = self.nomo._baseplot(*args, **kwargs) # type: ignore
ax.set_xlabel("size")
ax.set_ylabel("performance")
ax.legend(ncol=2, title="ddt")
ax.legend(loc=6, bbox_to_anchor=(1.01, 0.5), ncol=2, title="ddt")
return ax

def surface_plot(self, *args: Tuple, **kwargs: Dict[str, Any]) -> Axes:
Expand Down
32 changes: 28 additions & 4 deletions nereid/nereid/src/treatment_facility/constructors.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from copy import deepcopy
from typing import Any, Dict, List
from typing import Any, Dict, List, Optional

import pandas

Expand Down Expand Up @@ -329,21 +329,45 @@ def cistern_facility_constructor(
total_volume_cuft: float,
winter_demand_cfs: float,
summer_demand_cfs: float,
winter_dry_weather_flow_cuft_psecond_inflow: Optional[float] = None,
summer_dry_weather_flow_cuft_psecond_inflow: Optional[float] = None,
**kwargs: dict,
) -> Dict[str, Any]:

retention_volume_cuft = total_volume_cuft
winter_dry_weather_flow_cuft_psecond_inflow = (
winter_dry_weather_flow_cuft_psecond_inflow or 0.0
)
summer_dry_weather_flow_cuft_psecond_inflow = (
summer_dry_weather_flow_cuft_psecond_inflow or 0.0
)

winter_demand_cfs_user = winter_demand_cfs
summer_demand_cfs_user = summer_demand_cfs

winter_demand_cfs = max(
0, winter_demand_cfs_user - winter_dry_weather_flow_cuft_psecond_inflow
)

summer_demand_cfs = max(
0, summer_demand_cfs_user - summer_dry_weather_flow_cuft_psecond_inflow
)

winter_demand_cfhr = winter_demand_cfs * 3600
retention_ddt_hr = total_volume_cuft / winter_demand_cfhr
retention_ddt_hr = safe_divide(total_volume_cuft, winter_demand_cfhr)
retention_volume_cuft = total_volume_cuft if retention_ddt_hr > 0 else 0

result = dict(
winter_demand_cfs=winter_demand_cfs,
summer_demand_cfs=summer_demand_cfs,
winter_demand_cfs_user=winter_demand_cfs_user,
summer_demand_cfs_user=summer_demand_cfs_user,
retention_volume_cuft=retention_volume_cuft,
retention_ddt_hr=retention_ddt_hr,
summer_dry_weather_retention_rate_cfs=summer_demand_cfs,
summer_dry_weather_treatment_rate_cfs=0.0,
winter_dry_weather_retention_rate_cfs=winter_demand_cfs,
winter_dry_weather_treatment_rate_cfs=0.0,
node_type="volume_based_facility",
node_type="volume_based_cistern_facility",
)

return result
Expand Down
24 changes: 24 additions & 0 deletions nereid/nereid/src/watershed/simple_facility_capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,27 @@ def compute_simple_facility_volume_capture(
)

return data


def compute_simple_facility_wet_weather_volume_capture(
data: Dict[str, Any]
) -> Dict[str, Any]:
compute_simple_facility_volume_capture(data, "runoff_volume_cuft")
for attr in ["captured", "treated", "retained", "bypassed"]:
data[f"{attr}_pct"] = data[f"runoff_volume_cuft_{attr}_pct"]

return data


def compute_simple_facility_dry_weather_volume_capture(
data: Dict[str, Any],
) -> Dict[str, Any]:
seasons = ["summer", "winter"]
vol_cols = [f"{s}_dry_weather_flow_cuft" for s in seasons] + [
f"{s}_dry_weather_flow_cuft_psecond" for s in seasons
]

for vol_col in vol_cols:
compute_simple_facility_volume_capture(data, vol_col)

return data
39 changes: 21 additions & 18 deletions nereid/nereid/src/watershed/solve_watershed.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
from nereid.src.network.validate import is_valid, validate_network
from nereid.src.nomograph.nomo import load_nomograph_mapping
from nereid.src.tmnt_performance.tasks import effluent_function_map
from nereid.src.treatment_facility.constructors import (
TreatmentFacilityConstructor as TMNTConstructor,
)
from nereid.src.treatment_facility.tasks import initialize_treatment_facilities
from nereid.src.treatment_site.tasks import initialize_treatment_sites
from nereid.src.watershed.dry_weather_loading import (
Expand All @@ -16,7 +19,8 @@
compute_dry_weather_volume_performance,
)
from nereid.src.watershed.simple_facility_capture import (
compute_simple_facility_volume_capture,
compute_simple_facility_dry_weather_volume_capture,
compute_simple_facility_wet_weather_volume_capture,
)
from nereid.src.watershed.treatment_facility_capture import (
compute_volume_capture_with_nomograph,
Expand Down Expand Up @@ -102,6 +106,11 @@ def solve_watershed_loading(
nereid_version = context.get("version", "error: no version info")
cfg_version = context.get("config_date", "error: no config version info")

solve_dw = all(
_ is not None
for _ in [dry_weather_parameters, dry_weather_facility_performance_map]
)

for node in nx.lexicographical_topological_sort(g):
g.nodes[node]["_version"] = nereid_version
g.nodes[node]["_config_version"] = cfg_version
Expand All @@ -114,6 +123,7 @@ def solve_watershed_loading(
nomograph_map=nomograph_map,
dry_weather_parameters=dry_weather_parameters,
dry_weather_facility_performance_map=dry_weather_facility_performance_map,
solve_dw=solve_dw,
)

return
Expand All @@ -130,6 +140,7 @@ def solve_node(
dry_weather_facility_performance_map: Optional[
Mapping[Tuple[str, str], Callable]
] = None,
solve_dw: bool = False,
) -> None:
"""Solve a single node of the graph data structure in place.

Expand Down Expand Up @@ -203,17 +214,11 @@ def solve_node(
data["_is_leaf"] = True
return

node_type = data.get("node_type", "")
node_type = data.get("node_type", None) or "virtual"
predecessors = list(g.predecessors(node))

solve_dw = all(
_ is not None
for _ in [dry_weather_parameters, dry_weather_facility_performance_map]
)

accumulate_wet_weather_loading(g, data, predecessors, wet_weather_parameters)
if solve_dw:
accumulate_dry_weather_loading(g, data, predecessors, dry_weather_parameters)
accumulate_dry_weather_loading(g, data, predecessors, dry_weather_parameters)

if "site_based" in node_type:
# This sequence handles volume capture, load reductions, and also delivers
Expand Down Expand Up @@ -241,10 +246,13 @@ def solve_node(
for _type in ["volume_based", "flow_based", "dry_well", "simple"]
]
):
if "cistern" in node_type:
# run this again to prorate the demand rate & ddt based on the dw inflow.
cistern = TMNTConstructor.cistern_facility_constructor(**data)
data.update(cistern) # this is an in-place update

if "simple" in node_type:
compute_simple_facility_volume_capture(data, "runoff_volume_cuft")
for attr in ["captured", "treated", "retained", "bypassed"]:
data[f"{attr}_pct"] = data[f"runoff_volume_cuft_{attr}_pct"]
compute_simple_facility_wet_weather_volume_capture(data)

elif nomograph_map: # pragma: no branch
compute_volume_capture_with_nomograph(data, nomograph_map)
Expand All @@ -265,13 +273,8 @@ def solve_node(

if solve_dw:
if "simple" in node_type:
seasons = ["summer", "winter"]
vol_cols = [f"{s}_dry_weather_flow_cuft" for s in seasons] + [
f"{s}_dry_weather_flow_cuft_psecond" for s in seasons
]
compute_simple_facility_dry_weather_volume_capture(data)

for vol_col in vol_cols:
compute_simple_facility_volume_capture(data, vol_col)
else:
compute_dry_weather_volume_performance(data)

Expand Down
14 changes: 4 additions & 10 deletions nereid/nereid/src/watershed/treatment_facility_capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,12 @@ def compute_volume_capture_with_nomograph(
data["treatment_volume_cuft"] = data.get("treatment_volume_cuft", 0.0)
data["treatment_ddt_hr"] = data.get("treatment_ddt_hr", 0.0)

if "volume_based_facility" in node_type:

sum_upstream_vol = (
data["retention_volume_cuft_upstream"]
+ data["during_storm_det_volume_cuft_upstream"]
)

if sum_upstream_vol == 0:
data = compute_volume_based_standalone_facility(data, volume_nomo)
if all(v in node_type for v in ["volume_based", "facility"]):
if data["_has_upstream_vol_storage"]:
data = compute_volume_based_nested_facility(data, volume_nomo)

else:
data = compute_volume_based_nested_facility(data, volume_nomo)
data = compute_volume_based_standalone_facility(data, volume_nomo)

# writeup step 2, equation 4. this value is stored, but is
# not used until there is a next-downstream facility.
Expand Down
9 changes: 9 additions & 0 deletions nereid/nereid/src/watershed/wet_weather_loading.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,15 @@ def accumulate_wet_weather_loading(
+ data["during_storm_design_vol_cuft_upstream"]
)

# track if is a nested node. This is used to change strategies
# in the BMP wet weather volume capture solver
_sum_upstream_vol = (
data["retention_volume_cuft_upstream"]
+ data["during_storm_det_volume_cuft_upstream"]
)

data["_has_upstream_vol_storage"] = _sum_upstream_vol > 0

## -- accumulate wet weather pollutant loading
for param in wet_weather_parameters:
load_col = param["load_col"]
Expand Down
81 changes: 81 additions & 0 deletions nereid/nereid/tests/test_src/test_watershed/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,84 @@ def test_treatment_facility_waterbalance(
assert mitigates_peak_flow == (peak_pct > 1e-3), (peak_pct, treatment_results)

return


def test_cistern_facility_waterbalance(
contexts,
simple_3_node_watershed,
treatment_facilities_dict,
):
context = contexts["default"]
facility = deepcopy(treatment_facilities_dict["cistern"])
facility["winter_demand_cfs"] = 0.01
facility["node_id"] = "1"
watershed_request = deepcopy(simple_3_node_watershed)
watershed_request["treatment_facilities"] = [facility]

response_dict = solve_watershed(
watershed=watershed_request,
treatment_pre_validated=False,
context=context,
)

results = response_dict["results"]

treatment_results = [res for res in results if res.get("node_id") == "1"][0]

facility["winter_demand_cfs"] = 0.006
watershed_request["treatment_facilities"] = [facility]

response_dict = solve_watershed(
watershed=watershed_request,
treatment_pre_validated=False,
context=context,
)

results = response_dict["results"]

treatment_results_long = [res for res in results if res.get("node_id") == "1"][0]

facility["winter_demand_cfs"] = 0.002
watershed_request["treatment_facilities"] = [facility]

response_dict = solve_watershed(
watershed=watershed_request,
treatment_pre_validated=False,
context=context,
)

results = response_dict["results"]

treatment_results_xlong = [res for res in results if res.get("node_id") == "1"][0]

# print(
# f"""
# 35 day:
# ddt: {treatment_results['retention_ddt_hr']}
# % ret: {treatment_results['retained_pct']}
# 6 month:
# ddt: {treatment_results_long['retention_ddt_hr']}
# % ret: {treatment_results_long['retained_pct']}
# too long:
# ddt: {treatment_results_xlong['retention_ddt_hr']}
# % ret: {treatment_results_xlong['retained_pct']}
# """
# )

# if there's dry weather inflows, the actual demands is prorated
for r in [treatment_results, treatment_results_long, treatment_results_xlong]:
assert r["winter_demand_cfs"] < r["winter_demand_cfs_user"], r
assert r["summer_demand_cfs"] < r["summer_demand_cfs_user"], r

# long ddts should be greater than short ddts
assert (
treatment_results["retention_ddt_hr"]
< treatment_results_long["retention_ddt_hr"]
), (
treatment_results["retention_ddt_hr"],
treatment_results_long["retention_ddt_hr"],
)

# way long ddts are the same as zero if there are dry weather inflows.
assert 0 == treatment_results_xlong["retention_ddt_hr"], treatment_results_xlong
assert 0 == treatment_results_xlong["retained_pct"], treatment_results_xlong