FloodGate / core /flood_core /baselines.py
bpHigh's picture
FloodGate v1: live mode, 3D replay, Beam and Opus runs
79c4cf2 verified
Raw History Blame Contribute Delete
3.36 kB
"""Lazy policies and the Corps' own releases, for the references and the board.
hold keep every dam at the release of the step before the window
pass release what comes in (no storage used), as fast as the ramp allows, within the release limits
corps the Nashville District's real releases in the window (tier `real` only; other tiers change the water)
"""
from __future__ import annotations
def hold(task: dict) -> dict[str, list[float]]:
return {d["id"]: [min(max(d["release_prev_cfs"], d["min_release_cfs"]), d["max_release_cfs"])] * task["steps"]
for d in task["dams"]}
def pass_inflow(task: dict) -> dict[str, list[float]]:
out = {}
for d in task["dams"]:
cur, rs = d["release_prev_cfs"], []
for v in d["inflow_cfs"]:
tgt = min(max(v, d["min_release_cfs"]), d["max_release_cfs"])
cur = cur + max(-d["ramp_cfs_per_step"], min(d["ramp_cfs_per_step"], tgt - cur))
rs.append(cur)
out[d["id"]] = rs
return out
def corps(task: dict) -> dict[str, list[float]] | None:
if task["tier"] != "real":
return None
return {d["id"]: list(d["record_release_cfs"]) for d in task["dams"]}
POLICIES = {"hold": hold, "pass": pass_inflow}
def rule(task: dict) -> dict[str, list[float]]:
"""A standard flood-control rule, step by step: release as much as each lake can (to empty its flood pool), but
never push a downstream gauge above its action flow when the water arrives (or above where it already is, when
local runoff alone exceeds it); spill protection first: release more if the lake would overtop."""
from .basin import AF_PER_CFS_STEP, routing_weights
H, T = task["steps"], task["tail_steps"]
q = {p["id"]: list(p["base_cfs"]) for p in task["points"]}
thr = {p["id"]: p["thresholds"]["action"] for p in task["points"]}
rel = {d["id"]: [] for d in task["dams"]}
stor = {d["id"]: d["storage0_af"] for d in task["dams"]}
order = sorted(task["dams"], key=lambda d: -max(task["lags_h"][d["id"]].values()))
for t in range(H):
for d in order:
k = d["id"]
prev = rel[k][-1] if rel[k] else d["release_prev_cfs"]
lo = max(d["min_release_cfs"], prev - d["ramp_cfs_per_step"])
hi = min(d["max_release_cfs"], prev + d["ramp_cfs_per_step"])
# lake target: down to the bottom of the flood pool
want = d["inflow_cfs"][t] + max(0.0, stor[k] - d["bottom_of_flood_af"]) / AF_PER_CFS_STEP
room = hi
for c, lag in task["lags_h"][k].items():
for i, wi in enumerate(routing_weights(lag)):
if t + i < H + T and wi > 0:
cap = max(thr[c], q[c][t + i]) - q[c][t + i]
room = min(room, cap / wi)
r = max(lo, min(hi, want, room))
# never overtop
must = d["inflow_cfs"][t] - (d["top_of_flood_af"] - stor[k]) / AF_PER_CFS_STEP
r = max(r, min(hi, must))
rel[k].append(r)
stor[k] += (d["inflow_cfs"][t] - r) * AF_PER_CFS_STEP
for c, lag in task["lags_h"][k].items():
for i, wi in enumerate(routing_weights(lag)):
if t + i < H + T:
q[c][t + i] += wi * r
return rel
POLICIES["rule"] = rule