pystorms

A simulation sandbox for the development and evaluation of stormwater control algorithms


Welcome! We’ve developed pystorms with the objective of making the research of stormwater control more accessible. This open-source python package provides a curated collection of diverse stormwater networks and rain events, coupled with a streamlined programming interface and a stormwater simulator to provide a standalone package for developing and evaluating stormwater control algorithms. Please refer to the manuscript for more details on the motivation behind this effort.

Getting Started

pystorms is available on pypi and can be directly installed using pip. It is supported on OSX, Linux, and Windows. pystorms requires Python 3.9 or newer, and installs pyswmm(≥ 1.0.1), swmmio(≥ 0.6.11), numpy(≥ 1.18.4), pandas(≥ 1.0), and PyYAML(≥ 5.3) with it. Please raise an issue on the repository or reach out if you encounter any installation issues.


 pip install pystorms

In pystorms , control of stormwater networks is abstracted as scenarios, named after letters from Greek alphabet.


import pystorms

def controller(state):
    """
    Your control algorithm that generates actions based on state information.
    """
    return actions

env = pystorms.scenarios.theta()
done = False

while not done:
    state = env.state()
    actions = controller(state)
    done = env.step(actions)

Scenario theta is a synthetic example for prototyping control algorithms. Six other scenarios inspired from real-world stormwater are included to provide realistic examples for evaluating stormwater control algorithms. Please refer to the documentation for more details.

What's new in 2.0

Every scenario now takes two keywords. version selects the network: "1" is the scenario as published, and "2" is a harder variant of the same network for theta, alpha, gamma, delta and epsilon. level decides how trustworthy the instrumentation is: "1" reports the true state, "2" adds sensor noise, drift and calibration bias and lets valves stick, and "3" intensifies all of that and adds sensors that drop out. The two are independent, so a controller can be tested on a fixed network while only the measurement quality changes.


env = pystorms.scenarios.theta(version="2", level="3")
done = False

while not done:
    state = env.state()              # noisy, drifting, biased readings
    done = env.step(controller(state))  # stuck valves ignore the command

The faults are drawn from numpy's random state when the scenario is built, so seed it first for a repeatable comparison. The versions and levels page documents the changes to each scenario and what each level does, and the changelog lists everything else that changed.

Version 2 scenarios

Version 2 tightens the objective and modifies the network so that a controller tuned for the original scenario has less room to look good. Version 1 is untouched and stays the default.

ScenarioWhat version 2 changes
thetaFlow threshold halved to 0.25 m³/s. The first basin is half as deep. The event ends a day and a half earlier.
alphaThe five weirs join the five orifices in the action space, for ten controllable assets. Every orifice is widened to the interceptor diameter.
gammaFlow threshold lowered from 4.0 to 3.0. Basins 5 and 9 are removed from the state space, the action space and the performance targets.
deltaOutflow threshold lowered from 12.0 to 0.5. The event runs three days longer with 30 percent more rainfall. The downstream conduit restrictions and the uncontrollable subcatchment flooding are removed, and the routing step is fixed at five seconds so every run has the same number of steps.
epsilonTSS loading threshold lowered to 70 percent of the original. The event runs to the middle of February with 10 percent more rainfall.
beta, zetaNo second version. Asking for one raises ValueError.

The version 2 network is built when the scenario is constructed, by rewriting the shipped SWMM input file. The copy goes to a per user cache directory (PYSTORMS_CACHE, or ~/.cache/pystorms/networks), never into the installed package.

Baseline controllers

Every version 2 scenario ships with two rule based controllers to measure against, plus the uncontrolled response. Their parameters were tuned by Bayesian optimization on level 1 and then evaluated as-is on levels 2 and 3, so the tables below show how much of a controller's benefit survives noisy sensors and sticking valves. The implementations, the tuned parameters, the optimization scripts and the plotting code live in the baseline_controllers directory of the repository.

ScenarioControllerIdea
theta, gamma, epsilonConstant flowEach outlet targets a fixed release rate, chosen by the optimizer. Valve positions follow from the orifice equation and the measured depth.
theta, gamma, epsilonEqual fillingBasins release in proportion to how full they are relative to the network average, so no single basin floods while others sit empty.
alphaStructuralOrifices fully open, weirs fixed at optimized crest settings. No feedback.
alphaConstant flow, equal fillingAs above, over the ten controllable orifices and weirs.
deltaStatic plus ruleFixed positions for the four weirs, plus a depth at which the infiltration basin valve opens.
deltaProportional outflowAdds proportional feedback on the network outflow to the static positions, releasing more when the outflow is far below its threshold and the basins are within their depth bounds.

Performance on the version 2 scenarios, lower is better. Percentages are relative to the uncontrolled response at the same level.

Single seeded fault schedule per level, as used for the manuscript figures. The epsilon rows were computed before the 2.0 fix to the pollutant reading described below, which shifts its numbers by about a tenth of a percent. A controller can do worse than no control at level 3: gamma's constant flow and both delta controllers do, because they keep acting on readings that are no longer true.
thetaLevelUncontrolledConstant flowEqual filling
16.92e+035.45e+03 (−21.2%)5.42e+03 (−21.6%)
26.92e+035.46e+03 (−21.1%)5.47e+03 (−20.9%)
36.92e+036.36e+03 (−8.1%)6.36e+03 (−8.1%)
alphaLevelUncontrolledStructuralConstant flowEqual filling
17.58e+034.19e+03 (−44.7%)3.74e+03 (−50.7%)3.81e+03 (−49.7%)
27.58e+034.19e+03 (−44.7%)3.73e+03 (−50.8%)3.81e+03 (−49.8%)
37.58e+034.19e+03 (−44.7%)3.85e+03 (−49.2%)3.89e+03 (−48.6%)
gammaLevelUncontrolledConstant flowEqual filling
12.68e+051.01e+05 (−62.2%)1.18e+05 (−56.0%)
22.68e+059.69e+04 (−63.8%)1.16e+05 (−56.8%)
32.68e+058.23e+05 (+207.5%)1.36e+05 (−49.0%)
deltaLevelUncontrolledStatic plus ruleProportional outflow
19.94e+042.08e+04 (−79.1%)1.84e+04 (−81.5%)
29.94e+048.34e+04 (−16.1%)5.58e+04 (−43.9%)
39.94e+041.33e+05 (+34.3%)1.45e+05 (+45.7%)
epsilonLevelUncontrolledConstant flowEqual filling
17.35e+046.90e+04 (−6.0%)6.85e+04 (−6.8%)
27.35e+046.98e+04 (−5.0%)6.94e+04 (−5.6%)
37.35e+046.13e+04 (−16.5%)6.15e+04 (−16.3%)

Scenario fixes in 2.0

Preparing the version 2 scenarios turned up a number of problems in the shipped scenarios and in the code that runs them. This table tracks what was wrong, what 2.0 does about it, and whether it changes the numbers a 1.0 script produced. The changelog has the full list, including fixes outside the scenarios.

ScenarioIssue in 1.0Fix in 2.0Effect on results
deltaSeven subcatchments in the network file carried an initial moisture deficit of 4.0. SWMM caps it at 1.0 and rejects the file (ERROR 235), so the scenario could not be built on any current engine.Set to 0.4, reading as a misplaced decimal against the 0.187 and 0.177 on the neighbouring rows.delta runs again. Version 2 already applied the same correction at runtime, so its numbers are unchanged.
epsilonThe pollutant state and the TSS loading objective read the concentration in node 001 rather than in conduit 001, which share an ID.Link pollutants are read from the link.epsilon performance shifts by about a tenth of a percent.
allActions passed as a dict set each valve to its position in the dict (0, 1, 2, ...) instead of the given value.Dict values are applied to the named assets.Only affects callers that used dicts; array and list actions were always correct.
all, level 3A dropped out sensor was zeroed before noise, drift and bias were added, so it reported noise around zero rather than the flat zero the documentation described.The zero is applied last.Level 3 readings during dropouts. The true state and the performance measure were never affected.
allAn integer or unsupported level built the scenario and then crashed inside state(); an unsupported version silently ran version 1.Both keywords are validated when the scenario is built. Integers are accepted; anything undefined raises ValueError.None.
beta, zetaAccepted version="2" and ran version 1 without saying so.Raise ValueError, since no second version exists.None.
gamma, version 2Basins 5 and 9 were removed from the configuration lists while iterating over them, which skipped every following entry. It happened to give the right result only because the skipped entries never matched.Rewritten as a filter.None.
allThe rewritten version 2 networks, and the report and output files SWMM writes on every run, landed inside the installed package, which fails on a read-only install.Written to a per user cache directory instead.None.
allA scenario that was stopped early could not be closed, and under pyswmm 2 the next scenario then failed with MultiSimulationError.env.terminate() and context manager support on every scenario.None.

Jupyter Notebooks

UDM 2022: UDS-RTC 101

Get Involved

If you want to be more involved, please consider the following:

Testimonials

famous lovelace venant vonNeu

About us

pystorms is developed by the open-storm group, a consortium of universities and industry partners developing open-source solutions for smarter stormwater systems.


University of Michigan University of Virginia University of Tennessee, Knoxville Emnet, LLC