"""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