""" PySWMM 内水氾濫デモ ===================== 目的 ---- EPA SWMM の入力ファイル(.inp)を PySWMM から実行し、 マンホール越水量、ノード水深、管渠流量をCSVへ出力します。 実務での使い方 -------------- 1. SWMM GUI / swmmio / 自作スクリプトで .inp を作成 2. このスクリプトで複数降雨ケースを自動実行 3. マンホール越水量をCSV化 4. ANUGA等の2次元氾濫解析へ点源として渡す 注意 ---- このサンプルはデモサイト用の最小構成です。 実務では、管渠台帳、マンホール地盤高、排水区、吐口水位、 ポンプ運転条件、キャリブレーション結果を反映してください。 """ from __future__ import annotations import argparse import math from pathlib import Path from typing import Any import pandas as pd def safe_get(obj: Any, name: str, default: float = math.nan) -> float: """PySWMMのバージョン差・属性未定義に備えた安全な属性取得。""" try: value = getattr(obj, name) return float(value() if callable(value) else value) except Exception: return default def create_synthetic_results(out_csv: Path) -> pd.DataFrame: """PySWMMをインストールしていない環境でもページ確認できる簡易結果を作成。""" import numpy as np minutes = np.arange(0, 181, 5) rain = 7 * np.exp(-((minutes - 70) / 30) ** 2) + 3 * np.exp(-((minutes - 105) / 18) ** 2) rows = [] for minute, r in zip(minutes, rain): for node_id, coef, lag in [("J1", 0.05, 0), ("J2", 0.04, 10), ("J3", 0.025, 20), ("J4", 0.015, 25)]: rr = float(np.interp(max(0, minute - lag), minutes, rain)) flooding = max(0.0, coef * (rr - 4.0)) depth = 0.25 + 2.2 * (1 - math.exp(-rr / 7.0)) rows.append({ "elapsed_min": int(minute), "object_type": "node", "object_id": node_id, "depth_m": depth, "flooding_m3s": flooding, "flow_m3s": math.nan, "velocity_ms": math.nan, }) for link_id, coef, lag in [("C1", 0.7, 5), ("C2", 0.55, 10), ("C3", 0.5, 15), ("C4", 0.45, 20)]: rr = float(np.interp(max(0, minute - lag), minutes, rain)) rows.append({ "elapsed_min": int(minute), "object_type": "link", "object_id": link_id, "depth_m": math.nan, "flooding_m3s": math.nan, "flow_m3s": coef * (rr / 7.0) ** 1.2, "velocity_ms": 0.4 + 0.8 * min(1.0, rr / 8.0), }) df = pd.DataFrame(rows) out_csv.parent.mkdir(parents=True, exist_ok=True) df.to_csv(out_csv, index=False) return df def run_pyswmm(inp_path: Path, out_csv: Path) -> pd.DataFrame: """PySWMMでSWMMモデルを実行し、時系列をCSVへ保存。""" try: from pyswmm import Links, Nodes, Simulation except ImportError as exc: raise RuntimeError( "pyswmm が見つかりません。`pip install pyswmm` 後に再実行してください。" ) from exc if not inp_path.exists(): raise FileNotFoundError(f"入力ファイルがありません: {inp_path}") rows: list[dict[str, Any]] = [] with Simulation(str(inp_path)) as sim: nodes = Nodes(sim) links = Links(sim) start_time = None for _ in sim: current_time = sim.current_time if start_time is None: start_time = current_time elapsed_min = int((current_time - start_time).total_seconds() / 60) for node in nodes: rows.append({ "datetime": current_time.isoformat(), "elapsed_min": elapsed_min, "object_type": "node", "object_id": node.nodeid, "depth_m": safe_get(node, "depth"), "head_m": safe_get(node, "head"), "flooding_m3s": safe_get(node, "flooding", 0.0), "total_inflow_m3s": safe_get(node, "total_inflow"), "flow_m3s": math.nan, "velocity_ms": math.nan, }) for link in links: rows.append({ "datetime": current_time.isoformat(), "elapsed_min": elapsed_min, "object_type": "link", "object_id": link.linkid, "depth_m": safe_get(link, "depth"), "head_m": math.nan, "flooding_m3s": math.nan, "total_inflow_m3s": math.nan, "flow_m3s": safe_get(link, "flow"), "velocity_ms": safe_get(link, "velocity"), }) df = pd.DataFrame(rows) out_csv.parent.mkdir(parents=True, exist_ok=True) df.to_csv(out_csv, index=False) return df def summarize_overflow(df: pd.DataFrame, dt_seconds: int = 300) -> pd.DataFrame: """ノード別の総越水量と最大越水流量を集計。""" nodes = df[df["object_type"] == "node"].copy() if "flooding_m3s" not in nodes: return pd.DataFrame() nodes["overflow_volume_m3"] = nodes["flooding_m3s"].fillna(0) * dt_seconds return ( nodes.groupby("object_id", as_index=False) .agg( total_overflow_m3=("overflow_volume_m3", "sum"), max_overflow_m3s=("flooding_m3s", "max"), max_depth_m=("depth_m", "max"), ) .sort_values("total_overflow_m3", ascending=False) ) def main() -> None: parser = argparse.ArgumentParser(description="PySWMM内水氾濫デモを実行します。") parser.add_argument("--inp", default="../data/demo_inland_flood.inp", help="SWMM入力ファイル") parser.add_argument("--out", default="../outputs/pyswmm_timeseries.csv", help="出力CSV") parser.add_argument("--synthetic", action="store_true", help="PySWMMなしで確認用の疑似結果を作成") args = parser.parse_args() base = Path(__file__).resolve().parent inp_path = (base / args.inp).resolve() out_csv = (base / args.out).resolve() if args.synthetic: df = create_synthetic_results(out_csv) else: try: df = run_pyswmm(inp_path, out_csv) except RuntimeError as e: print(e) print("確認用の疑似結果を作成します。") df = create_synthetic_results(out_csv) summary = summarize_overflow(df) summary_csv = out_csv.with_name("overflow_summary.csv") summary.to_csv(summary_csv, index=False) print(f"時系列CSV: {out_csv}") print(f"越水集計CSV: {summary_csv}") print(summary.to_string(index=False)) if __name__ == "__main__": main()