Metadata-Version: 2.4
Name: aquaswmm
Version: 0.12.2
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Scientific/Engineering
Requires-Dist: numpy>=1.21 ; extra == 'forecast'
Requires-Dist: eccodes>=2.37 ; extra == 'forecast'
Requires-Dist: eccodeslib ; platform_system != 'Windows' and extra == 'forecast'
Requires-Dist: tomli>=1.1 ; python_full_version < '3.11' and extra == 'forecast'
Provides-Extra: forecast
Summary: EPA SWMM scripting (edit .inp, run, read .out/.rpt) and storm sewer hydraulics: Rational method, Manning, HGL/EGL backwater, HEC-22 inlets
Keywords: swmm,epa-swmm,hydrology,hydraulics,stormwater,drainage,civil-engineering
Author: Michael Flynn
License: GPL-3.0-or-later
Requires-Python: >=3.9
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Homepage, https://aquaswmm.com
Project-URL: Issues, https://github.com/mf4633/stormsewer/issues
Project-URL: Source, https://github.com/mf4633/stormsewer

# aquaswmm

A free, open-source (GPL) Python library for EPA SWMM models and storm sewer
hydraulics: edit `.inp` files by table and column, run the EPA engine, read
`.rpt`/`.out` results, and size pipes with the Rational method, Manning,
standard-step HGL/EGL backwater and HEC-22 inlets. A native extension built
from Rust, with no Python dependencies and no server.

The library is free to use for any purpose. It shares its readers and engine
with the AquaSWMM desktop app, a separate paid product
([aquaswmm.com](https://aquaswmm.com)). Nothing here needs the app or a license.

```sh
pip install aquaswmm
```

## SWMM models: edit, run, read results

The same readers the AquaSWMM desktop app uses, so a script and the app
agree on column names, run validity, and every number read back.

```python
import aquaswmm as aq
import pandas as pd

m = aq.Model.open("site.inp")
pd.DataFrame(m.table("CONDUITS"))          # every row, by column name
m.get("CONDUITS", "C1", "Roughness")       # '0.013'
m.set("CONDUITS", "C1", "Roughness", 0.015)
m.set_option("ROUTING_STEP", "0:00:05")
m.rename("node", "J1", "MH-101")           # and every reference to it
m.validate()                               # the app's model checks

run = m.run("site_n015.inp")               # saves, then runs EPA SWMM
if not run.ok:
    print(run.problem)                     # failed, or blew up (NOT VALID)
run.continuity                             # {'Flow Routing Continuity': 0.015, ...}

res = run.results()                        # or aq.Results("site.out")
pd.DataFrame(res.node_peaks())             # max depth, inflow, flooding
res.link_series("C1", "flow")              # {'time_s': [...], 'value': [...]}
pd.DataFrame(res.froude())                 # Froude at peak flow and max

rep = run.report()                         # or aq.Report("site.rpt")
pd.DataFrame(rep.table("Node Depth Summary"))
```

Model values are strings, exactly as written in the file: a model keeps
every line you did not edit byte for byte, comments and spacing included.
`aq.engines()` lists the EPA SWMM engines found on the machine (set
`AQUASWMM_SWMM_ENGINE_DIR` to add one); `run(..., engine=<id>)` picks
one by the `id` it lists. A run that fails is returned, not raised — check `run.ok`.

Inside the desktop app, Tools → Python Terminal binds the current run as
`m`, `res` and `report` when this package is installed in that Python.

## Site flood forecasts

`aquaswmm.forecast` keeps a SWMM model running continuously so it carries
antecedent conditions (soil moisture, water table, pond levels) from one
day to the next, forces it with NOAA MRMS radar rain and HRRR / GEFS
forecast rain, corrects it with USGS observations through an ensemble
Kalman filter, and reports the probability of exceeding site thresholds.

```sh
pip install "aquaswmm[forecast]"     # numpy + eccodes for GRIB2
```

```python
from aquaswmm.forecast import read_hotstart, spinup, advance

h = read_hotstart("end.hsf", "site.inp")   # SWMM 5.2 hotstart, read against its model
h.variables("groundwater", "S1")           # theta, water_table, flow, max_infil_vol
h.set("node", "Pond", "depth", 3.0, model_units=True)
h.write("edited.hsf")                      # unedited files round-trip byte for byte

s = spinup("site.inp", start, end, rain, runs_dir="runs")   # cold start -> hotstart
s = advance(s, datetime.timedelta(hours=6), rain, runs_dir="runs")
```

The scheduled job is driven by one TOML site file:

```sh
python -m aquaswmm.forecast example > site.toml
python -m aquaswmm.forecast spinup site.toml     # chain observed rain from [spinup] start
python -m aquaswmm.forecast run site.toml        # advance, assimilate, forecast
python -m aquaswmm.forecast hindcast site.toml   # replay past dates, Brier scores
```

`run` writes `forecast.json`, `forecast.csv` and `forecast.html` with each
threshold's probability of exceedance and first-exceedance times. Hotstart
files, continuous runs and the products need only the standard library;
GRIB2 decoding and the filter need the `forecast` extra. The full
description, data sources and limits are in the *Site forecasts* chapter of
the [manual](https://aquaswmm.com/manual/).

## Storm sewer primitives

Float in, float out. Useful for replacing a spreadsheet column.

```python
import aquaswmm as ss

ss.intensity(60, 10, 0.8, 12)          # IDF: i = a/(t+b)^c  ->  5.0607 in/hr
ss.rational_q(0.70, 5.0607, 1.0)       # Q = C i A           ->  3.5425 cfs
ss.manning_capacity(1.25, 0.013, 0.005)  # just-full capacity ->  4.5801 cfs
ss.normal_depth(3.5425, 1.25, 0.013, 0.005)  # -> 0.8254 ft (None if it won't fit)
ss.critical_depth(3.5425, 1.25)
ss.circular_geometry(1.25, 0.8254)
# {'area': 0.8597, 'wetted_perimeter': ..., 'hydraulic_radius': ..., 'top_width': ...}
```

Pass `si=True` for metric: the Manning factor becomes 1.0 and `g` becomes 9.81.

```python
ss.manning_capacity(0.4, 0.013, 0.005, si=True)   # m3/s
```

## Whole networks

```python
import aquaswmm as ss
import pandas as pd

result = ss.analyze_project(ss.demo_project_json())

pipes = pd.DataFrame(result["pipes"])
print(pipes[["id", "design_q", "capacity", "pct_full", "velocity", "hgl_up"]])

nodes = pd.DataFrame(result["nodes"])
print(nodes[nodes.floods])          # anything surcharging to the surface
```

`analyze_file("project.ssproj")` reads a file saved by the desktop app, and
`analyze_ssn(text)` takes the plain-text network format:

```python
ss.analyze_ssn("""
IDF        60 10 0.8
TAILWATER  100.5
MINTC      10

NODE N1   inlet    0    0  104.0  110.0  1.0  0.70  12
NODE OUT  outfall  300  0  102.5  108.5

PIPE P1   N1  OUT  300  1.25  0.013
""")
```

Each pipe dict carries `id`, `from`, `to`, `slope`, `total_ca`, `tc`,
`travel_time`, `intensity`, `design_q`, `capacity`, `pct_full`, `velocity`,
`normal_depth`, `critical_depth`, `hgl_up`, `hgl_dn`, and `surcharged`. Each
node dict carries `id`, `tc`, `rim`, `hgl`, `freeboard`, and `floods`.

## Constants

`K_MANNING_US` (1.486), `K_MANNING_SI` (1.0), `G_US` (32.2), `G_SI` (9.81).

The engine uses **K = 1.486**, the exact unit conversion, matching Autodesk
Hydraflow and HEC-22 rather than the rounded 1.49 some spreadsheets use, a 0.27%
difference in capacity. The [validation page](https://aquaswmm.com/validation.html)
works every number on a reference network by hand.

## What this is not

It computes the published equations; it does not decide whether the Rational
method suits your site, what C to use, or which storm to design for. Those stay
with the engineer sealing the drawing.

## License

GPL-3.0-or-later, like the AquaSWMM engine.

