Metadata-Version: 2.4
Name: aquaswmm
Version: 0.12.0
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
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

Storm sewer hydrology and hydraulics for Python: Rational method, Manning,
standard-step HGL/EGL backwater, and HEC-22 inlets. The same Rust engine that
powers the [StormSewer desktop app](https://github.com/mf4633/stormsewer),
exposed as a native extension — no Python dependencies, no server.

```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.

## 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).

StormSewer uses **K = 1.486**, the exact unit conversion, matching Autodesk Hydraflow and HEC-22 rather than the
1.486 some spreadsheets use — a 0.27% difference in capacity. See
[VALIDATION.md](https://github.com/mf4633/stormsewer/blob/master/VALIDATION.md),
which 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.

