├── .github └── workflows │ ├── publish-to-test-pypi.yml │ └── python-package.yml ├── .gitignore ├── LICENSE ├── README.md ├── pyproject.toml ├── pystorms ├── __init__.py ├── config │ ├── __init__.py │ ├── alpha.yaml │ ├── beta.yaml │ ├── delta.yaml │ ├── epsilon.yaml │ ├── gamma.yaml │ ├── theta.yaml │ └── zeta.yaml ├── environment.py ├── networks │ ├── __init__.py │ ├── alpha.inp │ ├── beta.inp │ ├── delta.inp │ ├── epsilon.inp │ ├── gamma.inp │ ├── theta.inp │ └── zeta.inp ├── scenarios │ ├── __init__.py │ ├── alpha.py │ ├── beta.py │ ├── delta.py │ ├── epsilon.py │ ├── gamma.py │ ├── scenario.py │ ├── theta.py │ └── zeta.py └── utilities.py ├── setup.py ├── tests ├── test_general_functionality.py ├── test_networks.py ├── test_scenarios.py └── test_scenarios2.py └── tutorials ├── BayesianOptimization.ipynb ├── PySWMM_RTC.ipynb ├── RTC101-TestEnvironment.ipynb ├── ReinforcementLearning.ipynb ├── RuleBasedControl.ipynb ├── Scenario_Alpha.ipynb ├── Scenario_Beta.ipynb ├── Scenario_Delta.ipynb ├── Scenario_Epsilon.ipynb ├── Scenario_Gamma.ipynb ├── Scenario_Theta.ipynb ├── Scenario_Zeta.ipynb ├── data ├── BaeOpt.gif ├── epsilon.png ├── gamma_sheet.png ├── mpc_rules_beta.csv ├── theta.png └── theta_sheet.png ├── pystorms_Example.ipynb ├── pystorms_appendix_introduction.pdf ├── pystorms_paper.ipynb ├── pystorms_paper.tex ├── rule_based_controller.py └── scenario_theta.py /.github/workflows/publish-to-test-pypi.yml: -------------------------------------------------------------------------------- 1 | name: Publish Python 🐍 distributions 📦 to PyPI 2 | 3 | on: push 4 | jobs: 5 | build-n-publish: 6 | name: Build and publish Python 🐍 distributions 📦 to PyPI 7 | runs-on: ubuntu-18.04 8 | steps: 9 | - uses: actions/checkout@master 10 | - name: Set up Python 3.9 11 | uses: actions/setup-python@v2 12 | with: 13 | python-version: 3.9 14 | - name: Install pypa/build 15 | run: >- 16 | python -m 17 | pip install 18 | build 19 | --user 20 | - name: Build a binary wheel and a source tarball 21 | run: >- 22 | python -m 23 | build 24 | --sdist 25 | --wheel 26 | --outdir dist/ 27 | . 28 | - name: Publish distribution 📦 to PyPI 29 | if: startsWith(github.ref, 'refs/tags') 30 | uses: pypa/gh-action-pypi-publish@master 31 | with: 32 | password: ${{ secrets.PYPI_API_TOKEN }} 33 | -------------------------------------------------------------------------------- /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: pystorms 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: macOS-latest 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | python-version: [3.9] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v2 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Install dependencies 28 | run: | 29 | brew install libomp 30 | python -m pip install --upgrade pip 31 | python -m pip install . 32 | python -m pip install pytest 33 | - name: Test with pytest 34 | run: | 35 | pytest 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | *.rpt 3 | *.pyc 4 | *.out 5 | venv/ 6 | 7 | # Byte-compiled / optimized / DLL files 8 | __pycache__ 9 | 10 | # Distribution / packaging 11 | .Python 12 | develop-eggs/ 13 | downloads/ 14 | eggs/ 15 | .eggs/ 16 | lib/ 17 | dist/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # Unit test / coverage reports 31 | htmlcov/ 32 | .tox/ 33 | .nox/ 34 | .coverage 35 | .coverage.* 36 | .cache 37 | nosetests.xml 38 | coverage.xml 39 | *.cover 40 | *.py,cover 41 | .hypothesis/ 42 | .pytest_cache/ 43 | cover/ 44 | 45 | 46 | # Jupyter Notebook 47 | .ipynb_checkpoints 48 | 49 | # IPython 50 | profile_default/ 51 | ipython_config.py 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pystorms: simulation sandbox for the evaluation and design of stormwater control algorithms 2 | [![pystorms](https://github.com/kLabUM/pystorms/actions/workflows/python-package.yml/badge.svg?branch=master&event=push)](https://github.com/kLabUM/pystorms/actions/workflows/python-package.yml) 3 | [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) 4 | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/python/black) 5 | 6 | ## Overview 7 | 8 | This library has been developed in an effort to systematize quantitative analysis of stormwater control algorithms. 9 | It is a natural extension of the Open-Storm's mission to open up and ease access into the technical world of smart stormwater systems. 10 | Our initial efforts allowed us to develop open source and free tools for anyone to be able to deploy flood sensors, measure green infrastructure, or even control storm or sewer systems. 11 | Now we have developed a tool to be able to test the performance of algorithms used to coordinate these different sensing and control technologies that have been deployed throughout urban water systems. 12 | 13 | For the motivation behind this effort, we refer the reader to our manuscript [*pystorms*](https://dl.acm.org/citation.cfm?id=3313336). In general, this repo provides a library of `scenarios` that are built to allow for systematic quantitative evaluation of stormwater control algorithms. 14 | 15 | 16 | ## Getting Started 17 | 18 | ### Installation 19 | 20 | **Requirements** 21 | 22 | - PyYAML >= 5.3 23 | - numpy >= 18.4 24 | - pyswmm 25 | 26 | 27 | ```bash 28 | pip install pystorms 29 | ``` 30 | 31 | Please raise an issue on the repository or reach out if you run into any issues installing the package. 32 | 33 | ### Example 34 | 35 | Here is an example implementation on how you would use this library for evaluating the ability of a rule based control in maintaining the flows in a network below a desired threshold. 36 | 37 | ```python 38 | import pystorms 39 | import numpy as np 40 | 41 | # Define your awesome controller 42 | def controller(state): 43 | actions = np.ones(len(state)) 44 | for i in range(0, len(state)): 45 | if state[i] > 0.5: 46 | actions[i] = 1.0 47 | return actions 48 | 49 | 50 | env = pystorms.scenarios.theta() # Initialize scenario 51 | 52 | done = False 53 | while not done: 54 | state = env.state() 55 | actions = controller(state) 56 | done = env.step(actions) 57 | 58 | performance = env.performance() 59 | 60 | ``` 61 | 62 | Detailed documentation can be found on the [webpage](https://www.pystorms.org) 63 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools >= 40.6.0", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | -------------------------------------------------------------------------------- /pystorms/__init__.py: -------------------------------------------------------------------------------- 1 | from pystorms.scenarios import * 2 | from pystorms.utilities import * 3 | from pystorms.environment import * 4 | from pystorms.networks import * 5 | from pystorms.config import * 6 | -------------------------------------------------------------------------------- /pystorms/config/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | # Local Path 4 | HERE = os.path.abspath(os.path.dirname(__file__)) 5 | 6 | 7 | def load_config(name): 8 | r""" returns the path to the desired network. 9 | 10 | Parameters 11 | ---------- 12 | name : str 13 | name of the scenario. *alpha*, *beta*, *gamma*, *delta*, *epsilon*, and *zeta* are valid 14 | keywords 15 | 16 | Returns 17 | ------- 18 | path : str 19 | path to the scenario config 20 | """ 21 | 22 | # Parse the file name 23 | path = os.path.join(HERE, name + ".yaml") 24 | 25 | # Check if network exists 26 | if not (os.path.isfile(path)): 27 | raise ValueError("Undefined Scenario, please refer to the documentation") 28 | 29 | return path 30 | -------------------------------------------------------------------------------- /pystorms/config/alpha.yaml: -------------------------------------------------------------------------------- 1 | # Configuration file for scenario theta 2 | 3 | # name of scearnio 4 | name: alpha 5 | # state definitions 6 | states: 7 | # Depth at each runoff entry point into sewer network 8 | - !!python/tuple 9 | - "J1" 10 | - depthN 11 | - !!python/tuple 12 | - "J2" 13 | - depthN 14 | - !!python/tuple 15 | - "J3" 16 | - depthN 17 | - !!python/tuple 18 | - "J4" 19 | - depthN 20 | - !!python/tuple 21 | - "J5a" 22 | - depthN 23 | - !!python/tuple 24 | - "JC5" 25 | - depthN 26 | - !!python/tuple 27 | - "JC4c" 28 | - depthN 29 | # Depth if the junctions nodes at the sewer inlet into the flow regulators 30 | - !!python/tuple 31 | - "R1" 32 | - depthN 33 | - !!python/tuple 34 | - "R2" 35 | - depthN 36 | - !!python/tuple 37 | - "R3" 38 | - depthN 39 | - !!python/tuple 40 | - "R4" 41 | - depthN 42 | - !!python/tuple 43 | - "R5" 44 | - depthN 45 | # Depth at the CSO outfalls 46 | - !!python/tuple 47 | - "JC1b" 48 | - depthN 49 | - !!python/tuple 50 | - "JC2" 51 | - depthN 52 | - !!python/tuple 53 | - "JC3a" 54 | - depthN 55 | - !!python/tuple 56 | - "JC4c" 57 | - depthN 58 | - !!python/tuple 59 | - "JC5" 60 | - depthN 61 | # Depth at each segment of the stream 62 | - !!python/tuple 63 | - "C1a" 64 | - depthL 65 | - !!python/tuple 66 | - "C1b" 67 | - depthL 68 | - !!python/tuple 69 | - "C2" 70 | - depthL 71 | - !!python/tuple 72 | - "C3a" 73 | - depthL 74 | - !!python/tuple 75 | - "C3b" 76 | - depthL 77 | - !!python/tuple 78 | - "C4a" 79 | - depthL 80 | - !!python/tuple 81 | - "C4b" 82 | - depthL 83 | - !!python/tuple 84 | - "C4c" 85 | - depthL 86 | - !!python/tuple 87 | - "C5a" 88 | - depthL 89 | - !!python/tuple 90 | - "C5b" 91 | - depthL 92 | # Flowrate at final interceptor pipe connected to WWTP 93 | - !!python/tuple 94 | - "I5" 95 | - flow 96 | 97 | # Action space 98 | action_space: 99 | - "Or1" 100 | - "Or2" 101 | - "Or3" 102 | - "Or4" 103 | - "Or5" 104 | 105 | # Performance Targets 106 | performance_targets: 107 | # CSO overflow 108 | - !!python/tuple 109 | - "W1" 110 | - flow 111 | - !!python/tuple 112 | - "W2" 113 | - flow 114 | - !!python/tuple 115 | - "W3" 116 | - flow 117 | - !!python/tuple 118 | - "W4" 119 | - flow 120 | - !!python/tuple 121 | - "W5" 122 | - flow 123 | # flooding 124 | - !!python/tuple 125 | - "J1" 126 | - flooding 127 | - !!python/tuple 128 | - "J2" 129 | - flooding 130 | - !!python/tuple 131 | - "J3" 132 | - flooding 133 | - !!python/tuple 134 | - "J4" 135 | - flooding 136 | - !!python/tuple 137 | - "J5a" 138 | - flooding 139 | - !!python/tuple 140 | - "J5b" 141 | - flooding 142 | - !!python/tuple 143 | - "JC1a" 144 | - flooding 145 | - !!python/tuple 146 | - "JC1b" 147 | - flooding 148 | - !!python/tuple 149 | - "JC2" 150 | - flooding 151 | - !!python/tuple 152 | - "JC3a" 153 | - flooding 154 | - !!python/tuple 155 | - "JC3b" 156 | - flooding 157 | - !!python/tuple 158 | - "JC4a" 159 | - flooding 160 | - !!python/tuple 161 | - "JC4b" 162 | - flooding 163 | - !!python/tuple 164 | - "JC4c" 165 | - flooding 166 | - !!python/tuple 167 | - "JC5" 168 | - flooding 169 | - !!python/tuple 170 | - "JI1" 171 | - flooding 172 | - !!python/tuple 173 | - "JI2" 174 | - flooding 175 | - !!python/tuple 176 | - "JI3a" 177 | - flooding 178 | - !!python/tuple 179 | - "JI3b" 180 | - flooding 181 | - !!python/tuple 182 | - "JI4" 183 | - flooding 184 | - !!python/tuple 185 | - "JI5" 186 | - flooding 187 | - !!python/tuple 188 | - "R1" 189 | - flooding 190 | - !!python/tuple 191 | - "R2" 192 | - flooding 193 | - !!python/tuple 194 | - "R3" 195 | - flooding 196 | - !!python/tuple 197 | - "R4" 198 | - flooding 199 | - !!python/tuple 200 | - "R5" 201 | - flooding -------------------------------------------------------------------------------- /pystorms/config/beta.yaml: -------------------------------------------------------------------------------- 1 | # Configuration file for scenario theta 2 | 3 | # name of scearnio 4 | name: beta 5 | 6 | # state definitions 7 | states: 8 | - !!python/tuple 9 | - "J33" 10 | - depthN 11 | - !!python/tuple 12 | - "J64" 13 | - depthN 14 | - !!python/tuple 15 | - "J98" 16 | - depthN 17 | - !!python/tuple 18 | - "J102" 19 | - depthN 20 | - !!python/tuple 21 | - "OUT0" 22 | - depthN 23 | - !!python/tuple 24 | - "ST0" 25 | - depthN 26 | - !!python/tuple 27 | - "ST2" 28 | - depthN 29 | 30 | # Action space 31 | action_space: 32 | - R2 33 | - P0 34 | - W0 35 | 36 | # Performance Targets 37 | performance_targets: 38 | - !!python/tuple 39 | - J4 40 | - flooding 41 | - !!python/tuple 42 | - J8 43 | - flooding 44 | - !!python/tuple 45 | - J13 46 | - flooding 47 | - !!python/tuple 48 | - J33 49 | - flooding 50 | - !!python/tuple 51 | - J53 52 | - flooding 53 | - !!python/tuple 54 | - J54 55 | - flooding 56 | - !!python/tuple 57 | - J64 58 | - flooding 59 | - !!python/tuple 60 | - J65 61 | - flooding 62 | - !!python/tuple 63 | - J98 64 | - flooding 65 | - !!python/tuple 66 | - J102 67 | - flooding 68 | - !!python/tuple 69 | - J145 70 | - flooding 71 | - !!python/tuple 72 | - J146 73 | - flooding 74 | -------------------------------------------------------------------------------- /pystorms/config/delta.yaml: -------------------------------------------------------------------------------- 1 | # Configuration file for scenario delta 2 | 3 | # name of scearnio 4 | name: delta 5 | # state definitions 6 | states: 7 | - !!python/tuple 8 | - "basin_C" 9 | - depthN 10 | - !!python/tuple 11 | - "basin_S" 12 | - depthN 13 | - !!python/tuple 14 | - "basin_N1" 15 | - depthN 16 | - !!python/tuple 17 | - "basin_N2" 18 | - depthN 19 | - !!python/tuple 20 | - "basin_N3" 21 | - depthN 22 | - !!python/tuple 23 | - "basin_N4" 24 | - depthN 25 | 26 | # Action space 27 | action_space: 28 | - "weir_N3" 29 | - "weir_N2" 30 | - "weir_N1" 31 | - "weir_C" 32 | - "orifice_S" 33 | 34 | # Performance Targets 35 | performance_targets: 36 | # depth 37 | - !!python/tuple 38 | - "basin_C" 39 | - depthN 40 | - !!python/tuple 41 | - "basin_S" 42 | - depthN 43 | - !!python/tuple 44 | - "basin_N1" 45 | - depthN 46 | - !!python/tuple 47 | - "basin_N2" 48 | - depthN 49 | - !!python/tuple 50 | - "basin_N3" 51 | - depthN 52 | # flooding 53 | - !!python/tuple 54 | - "junc_N4sc" 55 | - flooding 56 | - !!python/tuple 57 | - "basin_N4" 58 | - flooding 59 | - !!python/tuple 60 | - "junc_N3sc" 61 | - flooding 62 | - !!python/tuple 63 | - "basin_N3" 64 | - flooding 65 | - !!python/tuple 66 | - "junc_N2sc" 67 | - flooding 68 | - !!python/tuple 69 | - "basin_N2" 70 | - flooding 71 | - !!python/tuple 72 | - "junc_N1sc" 73 | - flooding 74 | - !!python/tuple 75 | - "basin_N1" 76 | - flooding 77 | - !!python/tuple 78 | - "junc_Csc" 79 | - flooding 80 | - !!python/tuple 81 | - "basin_C" 82 | - flooding 83 | - !!python/tuple 84 | - "junc_Ssc" 85 | - flooding 86 | - !!python/tuple 87 | - "basin_S" 88 | - flooding 89 | - !!python/tuple 90 | - "junc_EinflowA" 91 | - flooding 92 | # flow 93 | - !!python/tuple 94 | - "conduit_Eout" 95 | - flow -------------------------------------------------------------------------------- /pystorms/config/epsilon.yaml: -------------------------------------------------------------------------------- 1 | # Configuration file for scenario theta 2 | 3 | # name of scearnio 4 | name: epsilon 5 | # binary 6 | binary: pollutant_support 7 | # state definitions 8 | states: 9 | - !!python/tuple 10 | - "004" 11 | - depthN 12 | - !!python/tuple 13 | - "006" 14 | - depthN 15 | - !!python/tuple 16 | - "011" 17 | - depthN 18 | - !!python/tuple 19 | - "022" 20 | - depthN 21 | - !!python/tuple 22 | - "027" 23 | - depthN 24 | - !!python/tuple 25 | - "030" 26 | - depthN 27 | - !!python/tuple 28 | - "033" 29 | - depthN 30 | - !!python/tuple 31 | - "039" 32 | - depthN 33 | - !!python/tuple 34 | - "044" 35 | - depthN 36 | - !!python/tuple 37 | - "050" 38 | - depthN 39 | - !!python/tuple 40 | - "060" 41 | - depthN 42 | - !!python/tuple 43 | - "001" 44 | - depthN 45 | - !!python/tuple 46 | - "001" 47 | - pollutantL 48 | - TSS 49 | 50 | # Action space 51 | action_space: 52 | - "ISD001" 53 | - "ISD002" 54 | - "ISD003" 55 | - "ISD004" 56 | - "ISD005" 57 | - "ISD006" 58 | - "ISD007" 59 | - "ISD008" 60 | - "ISD009" 61 | - "ISD010" 62 | - "ISD011" 63 | 64 | # Performance Targets 65 | performance_targets: 66 | - !!python/tuple 67 | - "001" 68 | - loading 69 | - !!python/tuple 70 | - "001" 71 | - flow 72 | - !!python/tuple 73 | - "004" 74 | - flooding 75 | - !!python/tuple 76 | - "006" 77 | - flooding 78 | - !!python/tuple 79 | - "011" 80 | - flooding 81 | - !!python/tuple 82 | - "022" 83 | - flooding 84 | - !!python/tuple 85 | - "027" 86 | - flooding 87 | - !!python/tuple 88 | - "030" 89 | - flooding 90 | - !!python/tuple 91 | - "033" 92 | - flooding 93 | - !!python/tuple 94 | - "039" 95 | - flooding 96 | - !!python/tuple 97 | - "044" 98 | - flooding 99 | - !!python/tuple 100 | - "050" 101 | - flooding 102 | - !!python/tuple 103 | - "060" 104 | - flooding 105 | -------------------------------------------------------------------------------- /pystorms/config/gamma.yaml: -------------------------------------------------------------------------------- 1 | # Configuration file for scenario theta 2 | 3 | # name of scearnio 4 | name: gamma 5 | # state definitions 6 | states: 7 | - !!python/tuple 8 | - "1" 9 | - depthN 10 | - !!python/tuple 11 | - "2" 12 | - depthN 13 | - !!python/tuple 14 | - "3" 15 | - depthN 16 | - !!python/tuple 17 | - "4" 18 | - depthN 19 | - !!python/tuple 20 | - "5" 21 | - depthN 22 | - !!python/tuple 23 | - "6" 24 | - depthN 25 | - !!python/tuple 26 | - "7" 27 | - depthN 28 | - !!python/tuple 29 | - "8" 30 | - depthN 31 | - !!python/tuple 32 | - "9" 33 | - depthN 34 | - !!python/tuple 35 | - "10" 36 | - depthN 37 | - !!python/tuple 38 | - "11" 39 | - depthN 40 | # Action space 41 | action_space: 42 | - O1 43 | - O2 44 | - O3 45 | - O4 46 | - O5 47 | - O6 48 | - O7 49 | - O8 50 | - O9 51 | - O10 52 | - O11 53 | # Performance Targets 54 | performance_targets: 55 | - !!python/tuple 56 | - O1 57 | - flow 58 | - !!python/tuple 59 | - O2 60 | - flow 61 | - !!python/tuple 62 | - O3 63 | - flow 64 | - !!python/tuple 65 | - O4 66 | - flow 67 | - !!python/tuple 68 | - O5 69 | - flow 70 | - !!python/tuple 71 | - O6 72 | - flow 73 | - !!python/tuple 74 | - O7 75 | - flow 76 | - !!python/tuple 77 | - O8 78 | - flow 79 | - !!python/tuple 80 | - O9 81 | - flow 82 | - !!python/tuple 83 | - O10 84 | - flow 85 | - !!python/tuple 86 | - O11 87 | - flow 88 | - !!python/tuple 89 | - "1" 90 | - flooding 91 | - !!python/tuple 92 | - "2" 93 | - flooding 94 | - !!python/tuple 95 | - "3" 96 | - flooding 97 | - !!python/tuple 98 | - "4" 99 | - flooding 100 | - !!python/tuple 101 | - "5" 102 | - flooding 103 | - !!python/tuple 104 | - "6" 105 | - flooding 106 | - !!python/tuple 107 | - "7" 108 | - flooding 109 | - !!python/tuple 110 | - "8" 111 | - flooding 112 | - !!python/tuple 113 | - "9" 114 | - flooding 115 | - !!python/tuple 116 | - "10" 117 | - flooding 118 | - !!python/tuple 119 | - "11" 120 | - flooding 121 | - !!python/tuple 122 | - "1" 123 | - depthN 124 | - !!python/tuple 125 | - "2" 126 | - depthN 127 | - !!python/tuple 128 | - "3" 129 | - depthN 130 | - !!python/tuple 131 | - "4" 132 | - depthN 133 | - !!python/tuple 134 | - "5" 135 | - depthN 136 | - !!python/tuple 137 | - "6" 138 | - depthN 139 | - !!python/tuple 140 | - "7" 141 | - depthN 142 | - !!python/tuple 143 | - "8" 144 | - depthN 145 | - !!python/tuple 146 | - "9" 147 | - depthN 148 | - !!python/tuple 149 | - "10" 150 | - depthN 151 | - !!python/tuple 152 | - "11" 153 | - depthN 154 | -------------------------------------------------------------------------------- /pystorms/config/theta.yaml: -------------------------------------------------------------------------------- 1 | # Configuration file for scenario theta 2 | 3 | # name of scearnio 4 | name: theta 5 | # state definitions 6 | states: 7 | - !!python/tuple 8 | - P1 9 | - depthN 10 | - !!python/tuple 11 | - P2 12 | - depthN 13 | # Action space 14 | action_space: 15 | - "1" 16 | - "2" 17 | # Performance Targets 18 | performance_targets: 19 | - !!python/tuple 20 | - "8" 21 | - flow 22 | - !!python/tuple 23 | - P1 24 | - flooding 25 | - !!python/tuple 26 | - P2 27 | - flooding 28 | -------------------------------------------------------------------------------- /pystorms/config/zeta.yaml: -------------------------------------------------------------------------------- 1 | # Configuration file for scenario zeta 2 | 3 | # name of scearnio 4 | name: zeta 5 | # state definitions 6 | states: 7 | - !!python/tuple 8 | - "T1" 9 | - depthN 10 | - !!python/tuple 11 | - "T2" 12 | - depthN 13 | - !!python/tuple 14 | - "T3" 15 | - depthN 16 | - !!python/tuple 17 | - "T4" 18 | - depthN 19 | - !!python/tuple 20 | - "T5" 21 | - depthN 22 | - !!python/tuple 23 | - "T6" 24 | - depthN 25 | 26 | # Action space 27 | action_space: 28 | - "V2" 29 | - "V3" 30 | - "V4" 31 | - "V6" 32 | 33 | # Performance Targets 34 | performance_targets: 35 | # CSOs to river 36 | - !!python/tuple 37 | - "T1" 38 | - flooding 39 | - !!python/tuple 40 | - "T2" 41 | - flooding 42 | - !!python/tuple 43 | - "T3" 44 | - flooding 45 | - !!python/tuple 46 | - "T4" 47 | - flooding 48 | - !!python/tuple 49 | - "T5" 50 | - flooding 51 | - !!python/tuple 52 | - "CSO8" 53 | - flooding 54 | - !!python/tuple 55 | - "CSO10" 56 | - flooding 57 | # CSOs to creek 58 | - !!python/tuple 59 | - "T6" 60 | - flooding 61 | - !!python/tuple 62 | - "CSO7" 63 | - flooding 64 | - !!python/tuple 65 | - "CSO9" 66 | - flooding 67 | # flow to WWTP 68 | - !!python/tuple # Conduit that connects upstream to "Out_to_WWTP" node 69 | - "C14" 70 | - "flow" 71 | # control roughness 72 | - !!python/tuple # flow out of Tank1 73 | - "V1" 74 | - "flow" 75 | - !!python/tuple # flow out of Tank2 76 | - "V2" 77 | - "flow" 78 | - !!python/tuple # flow out of Tank3 79 | - "V3" 80 | - "flow" 81 | - !!python/tuple # flow out of Tank4 82 | - "V4" 83 | - "flow" 84 | - !!python/tuple # flow out of Tank5 85 | - "V5" 86 | - "flow" 87 | - !!python/tuple # flow out of Tank6 88 | - "V6" 89 | - "flow" 90 | -------------------------------------------------------------------------------- /pystorms/environment.py: -------------------------------------------------------------------------------- 1 | """ 2 | Environment abstraction for SWMM. 3 | """ 4 | import numpy as np 5 | import pyswmm.toolkitapi as tkai 6 | from pyswmm.simulation import Simulation 7 | 8 | 9 | class environment: 10 | r"""Environment for controlling the swmm simulation 11 | 12 | This class acts as an interface between swmm's simulation 13 | engine and computational components. This class's methods are defined 14 | as getters and setters for generic stormwater attributes. So that, if need be, this 15 | class can be updated with a different simulation engine, keeping rest of the 16 | workflow stable. 17 | 18 | 19 | Attributes 20 | ---------- 21 | config : dict 22 | dictionary with swmm_ipunt and, action and state space `(ID, attribute)` 23 | ctrl : boolean 24 | if true, config has to be a dict, else config needs to be the path to the input file 25 | binary: str 26 | path to swmm binary; this enables users determine which version of swmm to use 27 | 28 | Methods 29 | ---------- 30 | step 31 | steps the simulation forward by a time step and returns the new state 32 | initial_state 33 | returns the initial state in the stormwater network 34 | terminate 35 | closes the swmm simulation 36 | reset 37 | closes the swmm simulaton and start a new one with the predefined config file. 38 | """ 39 | 40 | def __init__(self, config, ctrl=True, binary=None): 41 | 42 | # control expects users to define the state and action space 43 | # this is required for querying state and setting control actions 44 | self.ctrl = ctrl 45 | if self.ctrl: 46 | # read config from dictionary; 47 | # example config can be found in documentation 48 | # TODO: Add link to config documentation 49 | self.config = config 50 | 51 | # load the swmm object 52 | self.sim = Simulation(self.config["swmm_input"]) 53 | else: 54 | # load the swmm objection based on the inp file path 55 | if type(config) == str: 56 | self.sim = Simulation(INPPATH=config) 57 | else: 58 | raise ValueError(f"Given input file path is not valid {config}") 59 | 60 | # start the swmm simulation 61 | # this reads the inp file and initializes elements in the model 62 | self.sim.start() 63 | 64 | # map class methods to individual class function calls 65 | self.methods = { 66 | "depthN": self._getNodeDepth, 67 | "depthL": self._getLinkDepth, 68 | "volumeN": self._getNodeVolume, 69 | "volumeL": self._getLinkVolume, 70 | "flow": self._getLinkFlow, 71 | "flooding": self._getNodeFlooding, 72 | "inflow": self._getNodeInflow, 73 | "pollutantN": self._getNodePollutant, 74 | "pollutantL": self._getLinkPollutant, 75 | "simulation_time": self._getCurrentSimulationDateTime, 76 | } 77 | 78 | def _state(self): 79 | r""" 80 | Query the stormwater network states based on the config file. 81 | """ 82 | if self.ctrl: 83 | state = [] 84 | for _temp in self.config["states"]: 85 | ID = _temp[0] 86 | attribute = _temp[1] 87 | 88 | if attribute == "pollutantN" or attribute == "pollutantL": 89 | pollutant_index = _temp[2] 90 | state.append(self.methods[attribute](ID, pollutant_index)) 91 | else: 92 | state.append(self.methods[attribute](ID)) 93 | 94 | state = np.asarray(state) 95 | return state 96 | else: 97 | print("State config not defined! \n ctrl is defined as False") 98 | return np.array([]) 99 | 100 | def step(self, actions=None): 101 | r""" 102 | Implements the control action and forwards 103 | the simulation by a step. 104 | 105 | Parameters: 106 | ---------- 107 | actions : list or array of dict 108 | actions to take as an array (1 x n) 109 | 110 | Returns: 111 | ------- 112 | new_state : array 113 | next state 114 | done : boolean 115 | event termination indicator 116 | """ 117 | 118 | if (self.ctrl) and (actions is not None): 119 | # implement the actions based on type of argument passed 120 | # if actions are an array or a list 121 | if type(actions) == list or type(actions) == np.ndarray: 122 | for asset, valve_position in zip(self.config["action_space"], actions): 123 | self._setValvePosition(asset, valve_position) 124 | elif type(actions) == dict: 125 | for valve_position, asset in enumerate(actions): 126 | self._setValvePosition(asset, valve_position) 127 | else: 128 | raise ValueError( 129 | "actions must be dict or list or np.ndarray \n got{}".format( 130 | type(actions) 131 | ) 132 | ) 133 | 134 | # take the step ! 135 | time = self.sim._model.swmm_step() 136 | done = False if time > 0 else True 137 | return done 138 | 139 | def reset(self): 140 | r""" 141 | Resets the simulation and returns the initial state 142 | 143 | Returns 144 | ------- 145 | initial_state : array 146 | initial state in the network 147 | 148 | """ 149 | self.terminate() 150 | 151 | # Start the next simulation 152 | self.sim._model.swmm_open() 153 | self.sim._model.swmm_start() 154 | 155 | # get the state 156 | state = self._state() 157 | return state 158 | 159 | def terminate(self): 160 | r""" 161 | Terminates the simulation 162 | """ 163 | self.sim._model.swmm_end() 164 | self.sim._model.swmm_close() 165 | 166 | def initial_state(self): 167 | r""" 168 | Get the initial state in the stormwater network 169 | 170 | Returns 171 | ------- 172 | initial_state : array 173 | initial state in the network 174 | """ 175 | return self._state() 176 | 177 | # ------ Node Parameters ---------------------------------------------- 178 | def _getNodeDepth(self, ID): 179 | return self.sim._model.getNodeResult(ID, tkai.NodeResults.newDepth.value) 180 | 181 | def _getNodeFlooding(self, ID): 182 | return self.sim._model.getNodeResult(ID, tkai.NodeResults.overflow.value) 183 | 184 | def _getNodeLosses(self, ID): 185 | return self.sim._model.getNodeResult(ID, tkai.NodeResults.losses.value) 186 | 187 | def _getNodeVolume(self, ID): 188 | return self.sim._model.getNodeResult(ID, tkai.NodeResults.newVolume.value) 189 | 190 | def _getNodeInflow(self, ID): 191 | return self.sim._model.getNodeResult(ID, tkai.NodeResults.totalinflow.value) 192 | 193 | def _setInflow(self, ID, value): 194 | return self.sim._model.setNodeInflow(ID, value) 195 | 196 | def _getNodePollutant(self, ID, pollutant_name=None): 197 | pollut_quantity = self.sim._model.getNodePollut(ID, tkai.NodePollut.nodeQual) 198 | pollut_id = self.sim._model.getObjectIDList(tkai.ObjectType.POLLUT.value) 199 | pollutants = {pollut_id[i]: pollut_quantity[i] for i in range(0, len(pollut_id))} 200 | if pollutant_name is None: 201 | return pollutants 202 | else: 203 | return pollutants[pollutant_name] 204 | 205 | # ------ Valve modifications ------------------------------------------- 206 | def _getValvePosition(self, ID): 207 | return self.sim._model.getLinkResult(ID, tkai.LinkResults.setting.value) 208 | 209 | def _setValvePosition(self, ID, valve): 210 | self.sim._model.setLinkSetting(ID, valve) 211 | 212 | # ------ Link modifications -------------------------------------------- 213 | 214 | def _getLinkPollutant(self, ID, pollutant_name=None): 215 | pollut_quantity = self.sim._model.getNodePollut(ID, tkai.LinkPollut.linkQual) 216 | pollut_id = self.sim._model.getObjectIDList(tkai.ObjectType.POLLUT.value) 217 | pollutants = {pollut_id[i]: pollut_quantity[i] for i in range(0, len(pollut_id))} 218 | if pollutant_name is None: 219 | return pollutants 220 | else: 221 | return pollutants[pollutant_name] 222 | 223 | def _getLinkDepth(self, ID): 224 | return self.sim._model.getLinkResult(ID, tkai.LinkResults.newDepth.value) 225 | 226 | def _getLinkVolume(self, ID): 227 | return self.sim._model.getLinkResult(ID, tkai.LinkResults.newVolume.value) 228 | 229 | def _getLinkFlow(self, ID): 230 | return self.sim._model.getLinkResult(ID, tkai.LinkResults.newFlow.value) 231 | 232 | # ------- Obtain the current simulation time to compute the timestep ---------- 233 | def _getCurrentSimulationDateTime(self): 234 | r""" 235 | Get the current time of the simulation for this timestep. 236 | Can be used to compute the current timestep. 237 | 238 | Returns 239 | ------- 240 | :return: current simulation datetime 241 | :rtype: datetime 242 | """ 243 | return self.sim._model.getCurrentSimulationTime() 244 | 245 | def _getInitialSimulationDateTime(self): 246 | r""" 247 | Get the initial datetime of the simulation. 248 | Can be used to compute the initial timestep. 249 | 250 | Returns 251 | ------- 252 | :return: initial simulation datetime 253 | :rtype: datetime 254 | """ 255 | return self.sim._model.getSimulationDateTime( 256 | tkai.SimulationTime.StartDateTime.value 257 | ) 258 | -------------------------------------------------------------------------------- /pystorms/networks/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | # Local Path 4 | HERE = os.path.abspath(os.path.dirname(__file__)) 5 | 6 | 7 | def load_network(name): 8 | r""" returns the path to the desired network. 9 | 10 | Parameters 11 | ---------- 12 | name : str 13 | name of the network. *alpha*, *beta*, *gamma*, *delta*, *epsilon*, and *zeta* are valid 14 | keywords 15 | 16 | Returns 17 | ------- 18 | path : str 19 | path to the network 20 | """ 21 | 22 | # Parse the file name 23 | path = os.path.join(HERE, name + ".inp") 24 | 25 | # Check if network exists 26 | if not (os.path.isfile(path)): 27 | raise ValueError("Undefined Network, please refer to the documentation") 28 | 29 | return path 30 | -------------------------------------------------------------------------------- /pystorms/networks/theta.inp: -------------------------------------------------------------------------------- 1 | [TITLE] 2 | ;;Project Title/Notes 3 | 4 | [OPTIONS] 5 | ;;Option Value 6 | FLOW_UNITS CMS 7 | INFILTRATION HORTON 8 | FLOW_ROUTING DYNWAVE 9 | LINK_OFFSETS DEPTH 10 | MIN_SLOPE 0 11 | ALLOW_PONDING NO 12 | SKIP_STEADY_STATE NO 13 | 14 | START_DATE 02/25/2018 15 | START_TIME 00:00:00 16 | REPORT_START_DATE 02/25/2018 17 | REPORT_START_TIME 00:00:00 18 | END_DATE 02/28/2018 19 | END_TIME 06:00:00 20 | SWEEP_START 01/01 21 | SWEEP_END 12/31 22 | DRY_DAYS 0 23 | REPORT_STEP 00:15:00 24 | WET_STEP 00:05:00 25 | DRY_STEP 01:00:00 26 | ROUTING_STEP 0:00:30 27 | 28 | INERTIAL_DAMPING PARTIAL 29 | NORMAL_FLOW_LIMITED BOTH 30 | FORCE_MAIN_EQUATION H-W 31 | VARIABLE_STEP 0.75 32 | LENGTHENING_STEP 0 33 | MIN_SURFAREA 1.14 34 | MAX_TRIALS 8 35 | HEAD_TOLERANCE 0.0015 36 | SYS_FLOW_TOL 5 37 | LAT_FLOW_TOL 5 38 | MINIMUM_STEP 0.5 39 | THREADS 1 40 | 41 | [EVAPORATION] 42 | ;;Data Source Parameters 43 | ;;-------------- ---------------- 44 | CONSTANT 0.0 45 | DRY_ONLY NO 46 | 47 | [RAINGAGES] 48 | ;;Name Format Interval SCF Source 49 | ;;-------------- --------- ------ ------ ---------- 50 | 1 INTENSITY 1:00 1.0 TIMESERIES T 51 | 52 | [SUBCATCHMENTS] 53 | ;;Name Rain Gage Outlet Area %Imperv Width %Slope CurbLen SnowPack 54 | ;;-------------- ---------------- ---------------- -------- -------- -------- -------- -------- ---------------- 55 | SC1 1 P1 100 25 500 0.5 0 56 | 3 1 P2 100 25 500 0.5 0 57 | 58 | [SUBAREAS] 59 | ;;Subcatchment N-Imperv N-Perv S-Imperv S-Perv PctZero RouteTo PctRouted 60 | ;;-------------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- 61 | SC1 0.01 0.1 0.05 0.05 25 OUTLET 62 | 3 0.01 0.1 0.05 0.05 25 OUTLET 63 | 64 | [INFILTRATION] 65 | ;;Subcatchment MaxRate MinRate Decay DryTime MaxInfil 66 | ;;-------------- ---------- ---------- ---------- ---------- ---------- 67 | SC1 3.0 0.5 4 7 0 68 | 3 3.0 0.5 4 7 0 69 | 70 | [JUNCTIONS] 71 | ;;Name Elevation MaxDepth InitDepth SurDepth Aponded 72 | ;;-------------- ---------- ---------- ---------- ---------- ---------- 73 | P1J 95 0 0 0 0 74 | P2J 95 0 0 0 0 75 | 76 | [OUTFALLS] 77 | ;;Name Elevation Type Stage Data Gated Route To 78 | ;;-------------- ---------- ---------- ---------------- -------- ---------------- 79 | O 80 FREE NO 80 | 81 | [DIVIDERS] 82 | ;;Name Elevation Diverted Link Type Parameters 83 | ;;-------------- ---------- ---------------- ---------- ---------- 84 | PJ3 90 8 CUTOFF 0 0 0 0 0 85 | 86 | [STORAGE] 87 | ;;Name Elev. MaxDepth InitDepth Shape Curve Name/Params N/A Fevap Psi Ksat IMD 88 | ;;-------------- -------- ---------- ----------- ---------- ---------------------------- -------- -------- -------- -------- 89 | P1 100 2 0 FUNCTIONAL 1000 0 0 0 0 90 | P2 95 2 0 FUNCTIONAL 1000 0 0 0 0 91 | 92 | [CONDUITS] 93 | ;;Name From Node To Node Length Roughness InOffset OutOffset InitFlow MaxFlow 94 | ;;-------------- ---------------- ---------------- ---------- ---------- ---------- ---------- ---------- ---------- 95 | 7 P1J PJ3 100 0.01 0 0 0 0 96 | 8 PJ3 O 100 0.01 0 0 0 0 97 | 9 P2J PJ3 400 0.01 0 0 0 0 98 | 99 | [ORIFICES] 100 | ;;Name From Node To Node Type Offset Qcoeff Gated CloseTime 101 | ;;-------------- ---------------- ---------------- ------------ ---------- ---------- -------- ---------- 102 | 1 P1 P1J BOTTOM 0 1 NO 0 103 | 2 P2 P2J BOTTOM 0 1 NO 0 104 | 105 | [XSECTIONS] 106 | ;;Link Shape Geom1 Geom2 Geom3 Geom4 Barrels Culvert 107 | ;;-------------- ------------ ---------------- ---------- ---------- ---------- ---------- ---------- 108 | 7 PARABOLIC 1 1 0 0 1 109 | 8 PARABOLIC 1 1 0 0 1 110 | 9 PARABOLIC 1 1 0 0 1 111 | 1 RECT_CLOSED 1 1 0 0 112 | 2 RECT_CLOSED 1 1 0 0 113 | 114 | [CURVES] 115 | ;;Name Type X-Value Y-Value 116 | ;;-------------- ---------- ---------- ---------- 117 | S Storage 0.0 100 118 | S 0.25 100 119 | S 0.5 100 120 | S 1 100 121 | 122 | [TIMESERIES] 123 | ;;Name Date Time Value 124 | ;;-------------- ---------- ---------- ---------- 125 | T 00:00 0.0 126 | T 01:00 1.2 127 | T 02:00 2.3 128 | T 03:00 3.2 129 | T 04:00 3.1 130 | T 05:00 3.1 131 | T 06:00 2.1 132 | T 07:00 1.1 133 | T 08:00 0.1 134 | T 09:00 0.0 135 | 136 | 137 | [REPORT] 138 | ;;Reporting Options 139 | INPUT NO 140 | CONTROLS NO 141 | SUBCATCHMENTS ALL 142 | NODES ALL 143 | LINKS ALL 144 | 145 | [TAGS] 146 | 147 | [MAP] 148 | DIMENSIONS 0.000 0.000 10000.000 10000.000 149 | Units None 150 | 151 | [COORDINATES] 152 | ;;Node X-Coord Y-Coord 153 | ;;-------------- ------------------ ------------------ 154 | P1J 3443.396 5294.811 155 | P2J 5783.582 6019.900 156 | O 8576.642 1952.555 157 | PJ3 5534.826 4402.985 158 | P1 2325.871 5733.831 159 | P2 5820.896 6492.537 160 | 161 | [VERTICES] 162 | ;;Link X-Coord Y-Coord 163 | ;;-------------- ------------------ ------------------ 164 | 165 | [Polygons] 166 | ;;Subcatchment X-Coord Y-Coord 167 | ;;-------------- ------------------ ------------------ 168 | SC1 -3220.544 7497.777 169 | SC1 -3220.544 7497.777 170 | SC1 -3342.002 7052.433 171 | SC1 -3402.731 4785.226 172 | SC1 1050.711 4724.497 173 | SC1 1070.954 7457.291 174 | SC1 -3301.516 7477.534 175 | 3 3500.000 9751.244 176 | 3 3313.433 7649.254 177 | 3 7144.279 7562.189 178 | 3 7194.030 9689.055 179 | 3 3475.124 9751.244 180 | 181 | [SYMBOLS] 182 | ;;Gage X-Coord Y-Coord 183 | ;;-------------- ------------------ ------------------ 184 | 1 2828.467 9598.540 185 | 186 | -------------------------------------------------------------------------------- /pystorms/scenarios/__init__.py: -------------------------------------------------------------------------------- 1 | from .scenario import scenario 2 | from .alpha import alpha 3 | from .beta import beta 4 | from .epsilon import epsilon 5 | from .gamma import gamma 6 | from .theta import theta 7 | from .delta import delta 8 | from .zeta import zeta 9 | -------------------------------------------------------------------------------- /pystorms/scenarios/alpha.py: -------------------------------------------------------------------------------- 1 | from pystorms.environment import environment 2 | from pystorms.networks import load_network 3 | from pystorms.config import load_config 4 | from pystorms.scenarios import scenario 5 | from pystorms.utilities import threshold, exponentialpenalty 6 | import yaml 7 | 8 | 9 | class alpha(scenario): 10 | r"""Alpha Scenario 11 | 12 | Separated stormwater network driven by a 2-year storm event. 13 | Scenario is adapted from SWMM Apps tutorial Example 8. 14 | 15 | Parameters 16 | ---------- 17 | config : yaml configuration file 18 | physical attributes of the network. 19 | 20 | Methods 21 | ---------- 22 | step: implement actions, progress simulation by a timestep, and compute performance metric 23 | 24 | Notes 25 | ----- 26 | Objective is the following: 27 | 1. To minimize the combined sewer overflow of the system 28 | 2. To avoid flooding 29 | 30 | Performance is measured as the following: 31 | 1. First, any combined sewer overflow, 32 | 2. Second, any flooding in the network 33 | 34 | """ 35 | 36 | def __init__(self): 37 | # Network configuration 38 | self.config = yaml.load(open(load_config("alpha"), "r"), yaml.FullLoader) 39 | self.config["swmm_input"] = load_network(self.config["name"]) 40 | 41 | # Create the environment based on the physical parameters 42 | self.env = environment(self.config, ctrl=True) 43 | 44 | # Create an object for storing the data points 45 | self.data_log = { 46 | "performance_measure": [], 47 | "simulation_time": [], 48 | "flow": {}, 49 | "volume": {}, 50 | "flooding": {}, 51 | "simulation_time": [], 52 | } 53 | 54 | # Data logger for storing _performance data 55 | for ID, attribute in self.config["performance_targets"]: 56 | self.data_log[attribute][ID] = [] 57 | 58 | def step(self, actions=None, log=True): 59 | # Implement the actions and take a step forward 60 | done = self.env.step(actions) 61 | 62 | # Temp variables 63 | __performance = 0.0 64 | 65 | # Determine current timestep in simulation by 66 | # obtaining the differeence between the current time 67 | # and previous time, and converting to seconds 68 | __currentsimtime = self.env._getCurrentSimulationDateTime() 69 | 70 | if len(self.data_log["simulation_time"]) > 1: 71 | __prevsimtime = self.data_log["simulation_time"][-1] 72 | else: 73 | __prevsimtime = self.env._getInitialSimulationDateTime() 74 | 75 | __timestep = (__currentsimtime - __prevsimtime).total_seconds() 76 | 77 | # Log the flows in the networks 78 | if log: 79 | self._logger() 80 | 81 | # Estimate the performance 82 | for ID, attribute in self.config["performance_targets"]: 83 | # compute penalty for flooding 84 | if attribute == "flooding": 85 | __flood = self.env.methods[attribute](ID) 86 | if __flood > 0.0: 87 | __performance += __flood * (10 ** 6) 88 | # compute penalty for CSO overflow 89 | # TODO - decide if we want to have a penalty for negative flow 90 | if attribute == "flow": 91 | __flow = self.env.methods[attribute](ID) 92 | __volume = __timestep * __flow 93 | __performance += threshold(value=__volume, target=0.0, scaling=1.0) 94 | 95 | # Record the _performance 96 | self.data_log["performance_measure"].append(__performance) 97 | 98 | # Terminate the simulation 99 | if done: 100 | self.env.terminate() 101 | 102 | return done 103 | -------------------------------------------------------------------------------- /pystorms/scenarios/beta.py: -------------------------------------------------------------------------------- 1 | from pystorms.environment import environment 2 | from pystorms.networks import load_network 3 | from pystorms.config import load_config 4 | from pystorms.scenarios import scenario 5 | from pystorms.utilities import threshold 6 | import yaml 7 | 8 | 9 | class beta(scenario): 10 | r"""beta Scenario 11 | 12 | Stormwater network with tidal downstream fluctuations 13 | 14 | Parameters 15 | ---------- 16 | config : yaml file 17 | 18 | Methods 19 | ---------- 20 | step: implement actions, progress simulation by a timestep, and compute performance metric 21 | 22 | Notes 23 | ---------- 24 | Objective : Minimize flooding across most sensitive locations 25 | 26 | """ 27 | 28 | def __init__(self): 29 | # Network configuration 30 | self.config = yaml.load(open(load_config("beta"), "r"), yaml.FullLoader) 31 | self.config["swmm_input"] = load_network(self.config["name"]) 32 | 33 | # Create the environment based on the physical parameters defined in the config file 34 | self.env = environment(self.config, ctrl=True) 35 | 36 | # Create an object for storing data 37 | self.data_log = { 38 | "performance_measure": [], 39 | "simulation_time": [], 40 | "flooding": {}, 41 | } 42 | 43 | # Data logger for storing _performance data 44 | for ID, attribute in self.config["performance_targets"]: 45 | self.data_log[attribute][ID] = [] 46 | 47 | def step(self, actions=None, log=True): 48 | # Implement the action and take a step forward 49 | done = self.env.step(actions) 50 | 51 | # Initialize the time step temporary performance value 52 | __performance = 0.0 # temporary variable 53 | 54 | # Determine current timestep in simulation by 55 | # obtaining the differeence between the current time 56 | # and previous time, and converting to seconds 57 | __currentsimtime = self.env._getCurrentSimulationDateTime() 58 | 59 | if len(self.data_log["simulation_time"]) > 1: 60 | __prevsimtime = self.data_log["simulation_time"][-1] 61 | else: 62 | __prevsimtime = self.env._getInitialSimulationDateTime() 63 | 64 | __timestep = (__currentsimtime - __prevsimtime).total_seconds() 65 | 66 | # Log the flows in the networks 67 | if log: 68 | self._logger() 69 | 70 | # cycle through performance targets 71 | for ID, attribute in self.config["performance_targets"]: 72 | if attribute == "flooding": 73 | __floodrate = self.env.methods[attribute](ID) 74 | if __floodrate > 0.0: 75 | __floodvol = ( 76 | __floodrate * __timestep * 7.48 77 | ) # convert the flooding rate to flooding volume in gallons (1 ft^3 is 7.48 gallons) 78 | else: 79 | __floodvol = 0.0 80 | __performance += __floodvol 81 | 82 | # Record the _performormance 83 | self.data_log["performance_measure"].append(__performance) 84 | 85 | # # Log the simulation time 86 | # self.data_log["simulation_time"].append(__currentsimtime) 87 | 88 | # Terminate the simulation 89 | if done: 90 | self.env.terminate() 91 | 92 | return done 93 | -------------------------------------------------------------------------------- /pystorms/scenarios/delta.py: -------------------------------------------------------------------------------- 1 | from pystorms.environment import environment 2 | from pystorms.networks import load_network 3 | from pystorms.config import load_config 4 | from pystorms.scenarios import scenario 5 | from pystorms.utilities import threshold, exponentialpenalty 6 | import yaml 7 | 8 | 9 | class delta(scenario): 10 | r"""Delta Scenario 11 | 12 | Separated stormwater network driven by a idealized event. 13 | 14 | Parameters 15 | ---------- 16 | config : yaml configuration file 17 | physical attributes of the network. 18 | 19 | Methods 20 | ---------- 21 | step: implement actions, progress simulation by a timestep, and compute performance metric 22 | 23 | Notes 24 | ----- 25 | Objective is the following: 26 | 1. To maintain levels of three detention ponds within a range of depths, and 27 | 2. To maintain the network outflow below a threshold. 28 | 29 | Performance is measured as the following: 30 | 1. First, deviation of depth above/below the "desired" depth range for the three detention ponds, 31 | 2. Second, deviation of depth above/below "maximum/minimum" depth ranges for the three detention ponds 32 | and one other infiltration pond, 33 | 3. Any flooding through the network, and 34 | 4. Any deviation above the threshold of the outflow. 35 | 36 | """ 37 | 38 | def __init__(self): 39 | # Network configuration 40 | self.config = yaml.load(open(load_config("delta"), "r"), yaml.FullLoader) 41 | self.config["swmm_input"] = load_network(self.config["name"]) 42 | 43 | self.threshold = 12.0 44 | 45 | self.depth_thresholds = { 46 | "basin_C": (5.7, 2.21, 3.8, 3.28), 47 | "basin_S": (6.55, 9.5), 48 | "basin_N1": (5.92, 2.11, 5.8, 5.2), 49 | "basin_N2": (6.59, 4.04, 5.04, 4.44), 50 | "basin_N3": (11.99, 5.28, 5.92, 5.32), 51 | } 52 | 53 | # Additional penalty definition 54 | self.max_penalty = 10 ** 6 55 | 56 | # Create the environment based on the physical parameters 57 | self.env = environment(self.config, ctrl=True) 58 | 59 | # Create an object for storing the data points 60 | self.data_log = { 61 | "performance_measure": [], 62 | "depthN": {}, 63 | "flow": {}, 64 | "flooding": {}, 65 | "simulation_time": [] 66 | } 67 | 68 | # Data logger for storing _performance data 69 | for ID, attribute in self.config["performance_targets"]: 70 | self.data_log[attribute][ID] = [] 71 | 72 | def step(self, actions=None, log=True): 73 | # Implement the actions and take a step forward 74 | done = self.env.step(actions) 75 | 76 | # Log the flows in the networks 77 | if log: 78 | self._logger() 79 | 80 | # Estimate the performance 81 | __performance = 0.0 82 | 83 | for ID, attribute in self.config["performance_targets"]: 84 | # compute penalty for flooding 85 | if attribute == "flooding": 86 | __flood = self.env.methods[attribute](ID) 87 | if __flood > 0.0: 88 | __performance += 10 ** 6 89 | # compute penalty for flow out of network above threshold 90 | if attribute == "flow": 91 | __flow = self.env.methods[attribute](ID) 92 | __performance += threshold( 93 | value=__flow, target=self.threshold, scaling=10.0 94 | ) 95 | # compute penalty for depth at basins above/below predefined ranges 96 | if attribute == "depthN": 97 | depth = self.env.methods[attribute](ID) 98 | temp = self.depth_thresholds[ID] 99 | if ID == "basin_S": 100 | if depth > temp[1]: # flooding value 101 | __performance += 10 ** 6 102 | elif depth > temp[0]: 103 | __temp = (depth - temp[0]) / (temp[1] - temp[0]) 104 | __performance += exponentialpenalty( 105 | value=__temp, max_penalty=self.max_penalty, scaling=1.0 106 | ) 107 | else: 108 | __performance += 0.0 109 | else: 110 | if depth > temp[0] or depth < temp[1]: # flooding value + fish dead 111 | __performance += 10 ** 6 112 | elif depth > temp[2]: 113 | __temp = (depth - temp[2]) / (temp[0] - temp[2]) 114 | __performance += exponentialpenalty( 115 | value=__temp, max_penalty=self.max_penalty, scaling=1.0 116 | ) 117 | elif depth < temp[3]: 118 | __temp = (temp[3] - depth) / (temp[3] - temp[1]) 119 | __performance += exponentialpenalty( 120 | value=__temp, max_penalty=self.max_penalty, scaling=1.0 121 | ) 122 | else: 123 | __performance += 0.0 124 | 125 | # Record the _performance 126 | self.data_log["performance_measure"].append(__performance) 127 | 128 | # Terminate the simulation 129 | if done: 130 | self.env.terminate() 131 | 132 | return done 133 | -------------------------------------------------------------------------------- /pystorms/scenarios/epsilon.py: -------------------------------------------------------------------------------- 1 | from pystorms.environment import environment 2 | from pystorms.networks import load_network 3 | from pystorms.config import load_config 4 | from pystorms.scenarios import scenario 5 | from pystorms.utilities import threshold 6 | import yaml 7 | 8 | 9 | class epsilon(scenario): 10 | r"""Epsilon Scenario 11 | 12 | Stormwater network with control structures in pipes 13 | 14 | Parameters 15 | ---------- 16 | config : yaml file 17 | 18 | Methods 19 | ---------- 20 | step: implement actions, progress simulation by a timestep, and compute performance metric 21 | 22 | Notes 23 | ---------- 24 | Objective : Route flows to maintain constant outflow at the outlet 25 | 26 | """ 27 | 28 | def __init__(self): 29 | # Network configuration 30 | self.config = yaml.load(open(load_config("epsilon"), "r"), yaml.FullLoader) 31 | self.config["swmm_input"] = load_network(self.config["name"]) 32 | 33 | # Dry weather TSS loading, measured at the outlet of the network 34 | self._performormance_threshold = 1.075 # Kg/sec 35 | 36 | # Create the env based on the config file 37 | self.env = environment(self.config, ctrl=True, binary=self.config["binary"]) 38 | 39 | # Create an object for storing data 40 | self.data_log = { 41 | "performance_measure": [], 42 | "loading": {}, 43 | "pollutantL": {}, 44 | "flow": {}, 45 | "flooding": {}, 46 | "simulation_time": [] 47 | } 48 | 49 | # Data logger for storing _performormance data 50 | for ID, attribute in self.config["performance_targets"]: 51 | self.data_log[attribute][ID] = [] 52 | 53 | def step(self, actions=None, log=True): 54 | # Implement the action and take a step forward 55 | done = self.env.step(actions) 56 | 57 | # Log the flows in the networks 58 | if log: 59 | self._logger() 60 | 61 | # Estimate the _performormance 62 | __performance = 0.0 # temporary variable 63 | 64 | for ID, attribute in self.config["performance_targets"]: 65 | if attribute == "flooding": 66 | flood = self.env.methods[attribute](ID) 67 | if flood > 0.0: 68 | __performance += 10 ** 9 69 | elif attribute == "loading": 70 | pollutantLoading = ( 71 | self.env.methods["pollutantL"](ID, 'TSS') 72 | * self.env.methods["flow"](ID) 73 | * 28.3168 74 | / (10 ** 6) 75 | ) 76 | __performance += threshold(pollutantLoading, self._performormance_threshold) 77 | 78 | # Record the _performormance 79 | self.data_log["performance_measure"].append(__performance) 80 | 81 | # Terminate the simulation 82 | if done: 83 | self.env.terminate() 84 | 85 | return done 86 | 87 | def _logger(self): 88 | # Log all the _performormance values; 89 | # additionally, other components can be added here 90 | for ID, attribute in self.config["performance_targets"]: 91 | if attribute == "loading": 92 | pollutantLoading = ( 93 | self.env.methods["pollutantL"](ID, 'TSS') 94 | * self.env.methods["flow"](ID) 95 | * 28.3168 96 | / (10 ** 6) 97 | ) 98 | self.data_log[attribute][ID].append(pollutantLoading) 99 | else: 100 | self.data_log[attribute][ID].append(self.env.methods[attribute](ID)) 101 | -------------------------------------------------------------------------------- /pystorms/scenarios/gamma.py: -------------------------------------------------------------------------------- 1 | from pystorms.environment import environment 2 | from pystorms.utilities import threshold 3 | from pystorms.networks import load_network 4 | from pystorms.config import load_config 5 | from pystorms.scenarios import scenario 6 | import yaml 7 | 8 | 9 | class gamma(scenario): 10 | r"""Gamma Scenario 11 | 12 | Separated stormwater network driven by a 25 year 6 hour event. 13 | 14 | Parameters 15 | ---------- 16 | config : yaml file 17 | 18 | Methods 19 | ---------- 20 | step: implement actions, progress simulation by a timestep, and compute performance metric 21 | 22 | Notes 23 | ----- 24 | Objective : Route flows though the network such that they are below a threshold. 25 | """ 26 | 27 | def __init__(self): 28 | # Network configuration 29 | self.config = yaml.load(open(load_config("gamma"), "r"), yaml.FullLoader) 30 | self.config["swmm_input"] = load_network(self.config["name"]) 31 | 32 | # Common threhold for the network, can be done independently 33 | self._performormance_threshold = 4.0 34 | 35 | # Create the environment based on the physical parameters 36 | self.env = environment(self.config, ctrl=True) 37 | 38 | # Create an object for storing the data points 39 | self.data_log = { 40 | "performance_measure": [], 41 | "flow": {}, 42 | "flooding": {}, 43 | "depthN": {}, 44 | "simulation_time": [], 45 | } 46 | 47 | # Data logger for storing _performormance data 48 | for ID, attribute in self.config["performance_targets"]: 49 | self.data_log[attribute][ID] = [] 50 | 51 | def step(self, actions=None, log=True): 52 | # Implement the actions and take a step forward 53 | done = self.env.step(actions) 54 | 55 | # Log the flows in the networks 56 | if log: 57 | self._logger() 58 | 59 | # Estimate the _performormance 60 | __performance = 0.0 # temp variable 61 | 62 | for ID, attribute in self.config["performance_targets"]: 63 | if attribute == "flooding": 64 | __flood = self.env.methods[attribute](ID) 65 | if __flood > 0.0: 66 | __performance += 10 ** 6 67 | elif attribute == "flow": 68 | __target = self._performormance_threshold 69 | __performance += threshold( 70 | self.env.methods[attribute](ID), __target, scaling=1.0 71 | ) 72 | # Check for water in the last timestep 73 | elif done and attribute == "depthN": 74 | __depth = self.env.methods[attribute](ID) 75 | if __depth > 0.10: 76 | __performance += 7 * 10 ** 5 77 | 78 | # Record the _performormance 79 | self.data_log["performance_measure"].append(__performance) 80 | 81 | # Terminate the simulation 82 | if done: 83 | self.env.terminate() 84 | 85 | return done 86 | -------------------------------------------------------------------------------- /pystorms/scenarios/scenario.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import numpy as np 3 | from pystorms.utilities import perf_metrics 4 | 5 | 6 | # Create a abstract class to force scenario class definition 7 | class scenario(abc.ABC): 8 | @abc.abstractmethod 9 | # Specific to the scenario 10 | def step(self, actions=None, log=True): 11 | pass 12 | 13 | def _logger(self): 14 | for attribute in self.data_log.keys(): 15 | if attribute not in ["performance_measure", "simulation_time"]: 16 | for element in self.data_log[attribute].keys(): 17 | self.data_log[attribute][element].append( 18 | self.env.methods[attribute](element) 19 | ) 20 | elif attribute == 'simulation_time': 21 | self.data_log[attribute].append(self.env.methods[attribute]()) 22 | 23 | def state(self): 24 | return self.env._state() 25 | 26 | def performance(self, metric="cumulative"): 27 | return perf_metrics(self.data_log["performance_measure"], metric) 28 | 29 | def save(self, path=None): 30 | if path is None: 31 | path = "{0}/data_{1}.npy".format("./", self.config["name"]) 32 | return np.save(path, self.data_log) 33 | -------------------------------------------------------------------------------- /pystorms/scenarios/theta.py: -------------------------------------------------------------------------------- 1 | from pystorms.environment import environment 2 | from pystorms.networks import load_network 3 | from pystorms.config import load_config 4 | from pystorms.scenarios import scenario 5 | from pystorms.utilities import threshold 6 | import yaml 7 | 8 | 9 | class theta(scenario): 10 | r"""Theta Scenario 11 | 12 | Separated stormwater network driven by a idealized event. 13 | 14 | Parameters 15 | ---------- 16 | config : yaml configuration file 17 | physical attributes of the network. 18 | 19 | Methods 20 | ---------- 21 | step: implement actions, progress simulation by a timestep, and compute performance metric 22 | 23 | Notes 24 | ----- 25 | Performance is measured as the deviation from the threshold. 26 | 27 | """ 28 | 29 | def __init__(self): 30 | # Network configuration 31 | self.config = yaml.load(open(load_config("theta"), "r"), yaml.FullLoader) 32 | self.config["swmm_input"] = load_network(self.config["name"]) 33 | 34 | self.threshold = 0.5 35 | 36 | # Create the environment based on the physical parameters 37 | self.env = environment(self.config, ctrl=True) 38 | 39 | # Create an object for storing the data points 40 | self.data_log = { 41 | "performance_measure": [], 42 | "flow": {}, 43 | "flooding": {}, 44 | "simulation_time": [] 45 | } 46 | 47 | # Data logger for storing _performance data 48 | for ID, attribute in self.config["performance_targets"]: 49 | self.data_log[attribute][ID] = [] 50 | 51 | def step(self, actions=None, log=True): 52 | # Implement the actions and take a step forward 53 | done = self.env.step(actions) 54 | 55 | # Log the flows in the networks 56 | if log: 57 | self._logger() 58 | 59 | # Estimate the performance 60 | __performance = 0.0 61 | 62 | for ID, attribute in self.config["performance_targets"]: 63 | if attribute == "flooding": 64 | __flood = self.env.methods[attribute](ID) 65 | if __flood > 0.0: 66 | __performance += 10 ** 6 67 | if attribute == "flow": 68 | __flow = self.env.methods[attribute](ID) 69 | __performance = threshold( 70 | value=__flow, target=self.threshold, scaling=10.0 71 | ) 72 | 73 | # Record the _performance 74 | self.data_log["performance_measure"].append(__performance) 75 | 76 | # Terminate the simulation 77 | if done: 78 | self.env.terminate() 79 | 80 | return done 81 | -------------------------------------------------------------------------------- /pystorms/scenarios/zeta.py: -------------------------------------------------------------------------------- 1 | from pystorms.environment import environment 2 | from pystorms.networks import load_network 3 | from pystorms.config import load_config 4 | from pystorms.scenarios import scenario 5 | from pystorms.utilities import threshold, exponentialpenalty 6 | 7 | import yaml 8 | 9 | 10 | class zeta(scenario): 11 | r"""Zeta Scenario 12 | 13 | Separated stormwater network driven by a idealized event. 14 | 15 | Parameters 16 | ---------- 17 | config : yaml configuration file 18 | physical attributes of the network. 19 | 20 | Methods 21 | ---------- 22 | step: 23 | 24 | Notes 25 | ----- 26 | Objectives are the following: 27 | 1. Minimization of accumulated CSO volume 28 | 2. Minimization of CSO to the creek more than the river 29 | 3. Maximizing flow to the WWTP 30 | 4. Minimizing roughness of control. 31 | 32 | Performance is measured as the following: 33 | 1. *2 for CSO volume (doubled for flow into the creek) 34 | 2. *(−1) for the flow to the WWTP 35 | 3. *(0.01) for the roughness of the control 36 | 37 | """ 38 | 39 | def __init__(self): 40 | # Network configuration 41 | self.config = yaml.load(open(load_config("zeta"), "r"), yaml.FullLoader) 42 | self.config["swmm_input"] = load_network(self.config["name"]) 43 | 44 | # Create the environment based on the physical parameters 45 | self.env = environment(self.config, ctrl=True) 46 | 47 | self.penalty_weight = { 48 | "T1": 1, 49 | "T2": 1, 50 | "T3": 1, 51 | "T4": 1, 52 | "T5": 1, 53 | "T6": 2, #creek 54 | "CSO7": 2, #creek 55 | "CSO8": 1, 56 | "CSO9": 2, #creek 57 | "CSO10": 1, 58 | } 59 | 60 | # Create an object for storing the data points 61 | self.data_log = { 62 | "performance_measure": [], 63 | "simulation_time": [], 64 | "flow": {}, 65 | "flooding": {}, 66 | "depthN": {}, 67 | } 68 | 69 | # Data logger for storing _performance data 70 | for ID, attribute in self.config["performance_targets"]: 71 | self.data_log[attribute][ID] = [] 72 | 73 | def step(self, actions=None, log=True): 74 | # Implement the actions and take a step forward 75 | done = self.env.step(actions) 76 | 77 | # Initialize temporary variables 78 | __performance = 0.0 # 79 | 80 | # Determine current timestep in simulation by 81 | # obtaining the differeence between the current time 82 | # and previous time, and converting to seconds 83 | __currentsimtime = self.env._getCurrentSimulationDateTime() 84 | 85 | if len(self.data_log["simulation_time"]) > 1: 86 | __prevsimtime = self.data_log["simulation_time"][-1] 87 | else: 88 | __prevsimtime = self.env._getInitialSimulationDateTime() 89 | 90 | __timestep = (__currentsimtime - __prevsimtime).total_seconds() 91 | 92 | # Log the flows in the networks 93 | if log: 94 | self._logger() 95 | 96 | for ID, attribute in self.config["performance_targets"]: 97 | if attribute == "flooding": # compute penalty for CSO overflow 98 | __floodrate = self.env.methods[attribute](ID) # flooding rate 99 | __volume = __floodrate * __timestep # flooding volume 100 | __weight = self.penalty_weight[ID] 101 | else: # compute reward for flow to WWTP, and penalty for change in flow from previous timestep due to control (i.e. throttle flow) 102 | __flowrate = self.env.methods[attribute](ID) 103 | if ID == "C14": # conduit connected to "Out_to_WWTP" node 104 | __volume = __flowrate * __timestep 105 | __weight = -0.1 106 | else: 107 | if len(self.data_log[attribute][ID]) > 1: 108 | __prevflowrate = self.data_log[attribute][ID][-2] 109 | else: 110 | __prevflowrate = __flowrate 111 | __throttleflowrate = abs(__prevflowrate - __flowrate) 112 | __volume = __throttleflowrate * __timestep 113 | __weight = 0.01 114 | __performance += __volume * __weight 115 | 116 | # Record the _performance 117 | self.data_log["performance_measure"].append(__performance) 118 | 119 | # # Log the simulation time 120 | # self.data_log["simulation_time"].append(__currentsimtime) 121 | 122 | # Terminate the simulation 123 | if done: 124 | self.env.terminate() 125 | 126 | return done 127 | -------------------------------------------------------------------------------- /pystorms/utilities.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def append_rainfall( 5 | network_source, 6 | rainfall_source, 7 | destination="./temp_network.inp", 8 | event_name="TestRain", 9 | ): 10 | r""" 11 | 12 | Appends rainfall timeseries from a pandas time series to the swmm input file by creating 13 | a new copy of the network. 14 | 15 | Parameters 16 | ---------- 17 | network_source 18 | Path to swmm input file 19 | rainfall_source 20 | Pandas.Series of timestamped rainfall 21 | destination 22 | Path to make the copy of the swmm file 23 | event_name 24 | Name of the Event 25 | 26 | Returns 27 | ------- 28 | path 29 | Path to the swmm inp file with Rainfall 30 | """ 31 | path_to_file = destination 32 | with open(network_source) as source: 33 | with open(path_to_file, "w") as destination: 34 | for line in source: 35 | destination.write(line) 36 | destination.write("\n") 37 | destination.write("[TIMESERIES] \n") 38 | destination.write(";;Name Date Time Value \n") 39 | destination.write(";;-------------- ---------- ---------- ---------- \n") 40 | for i in range(0, len(rainfall_source)): 41 | destination.write( 42 | "{0: <14} {1}/{2}/{3:<6} {4}:{5}:{6: <3} {7} \n".format( 43 | event_name, 44 | rainfall_source.index[i].month, 45 | rainfall_source.index[i].day, 46 | rainfall_source.index[i].year, 47 | rainfall_source.index[i].hour, 48 | rainfall_source.index[i].minute, 49 | rainfall_source.index[i].second, 50 | rainfall_source[i], 51 | ) 52 | ) 53 | return path_to_file 54 | 55 | 56 | def perf_metrics(performance_measure, metric): 57 | r""" 58 | 59 | Compute the performance statistics for the performance metrics 60 | 61 | Parameters 62 | ---------- 63 | performance_measure 64 | data whose stats have to be computed 65 | metric 66 | stat to be computed 67 | 68 | Returns 69 | ------- 70 | stat 71 | desired metric for data 72 | 73 | """ 74 | 75 | if len(performance_measure) < 1: 76 | raise ValueError("Run step; performance metrics have not been computed") 77 | if metric == "mean": 78 | return sum(performance_measure) / len(performance_measure) 79 | elif metric == "cumulative": 80 | return sum(performance_measure) 81 | elif metric == "median": 82 | return np.median(performance_measure) 83 | elif metric == "maximum": 84 | return np.max(performance_measure) 85 | elif metric == "minimum": 86 | return np.min(performance_measure) 87 | elif metric == "recent": 88 | return performance_measure[-1] 89 | else: 90 | raise ValueError( 91 | "mean, cumulative,median, maximum, minimum, and recent are the only valid metrics" 92 | ) 93 | 94 | 95 | def threshold(value, target=0.10, scaling=1.0): 96 | r""" 97 | 98 | measure the deviation from flow 99 | 100 | Parameters 101 | ---------- 102 | value 103 | value whose deviation should be measured 104 | target 105 | threshold value 106 | scaling 107 | scaling factor 108 | 109 | Returns 110 | ------- 111 | performace 112 | diviation scale 113 | 114 | """ 115 | performance = 0.0 116 | if value <= target: 117 | performance += 0.0 118 | else: 119 | performance += (value - target) * scaling 120 | return performance 121 | 122 | 123 | def exponentialpenalty(value, max_penalty=10 ** 6, scaling=1.0): 124 | r""" 125 | 126 | measure the deviation from flow 127 | 128 | Parameters 129 | ---------- 130 | value 131 | value between 0 - 1 that represents a fraction of a value within a given range 132 | max_penalty 133 | the penalty at the upper end of the range, 134 | i.e. if value=1, then performance=max_penalty, and 135 | if value=0, then performance=0 136 | scaling 137 | scaling factor to increase penalty magnitude (should stay at default 1.0) 138 | 139 | Returns 140 | ------- 141 | performace 142 | exponential penalty scale 143 | 144 | """ 145 | performance = 0.0 146 | performance += scaling * (((max_penalty + 1) ** value) - 1) 147 | return performance 148 | 149 | 150 | def _to_dataframe(data_log): 151 | r""" 152 | 153 | convert environment data log to dataframe 154 | 155 | Parameters 156 | ---------- 157 | data_log 158 | dict with simulation time and data 159 | 160 | Returns: 161 | ------- 162 | data_dataframe 163 | dict of dataframes 164 | """ 165 | data_dataframe = {} 166 | time_index = data_log['simulation_time'] 167 | for attribute in data_log.keys(): 168 | if attribute != 'simulation_time': 169 | data_dataframe[attribute] = pd.DataFrame(index=time_index, 170 | data=data_log[attribute]) 171 | return dataframe 172 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | setup( 7 | name="pystorms", 8 | version="1.0.0", 9 | description="Simulation sandbox for stormwater control algorithms", 10 | author="Abhiram Mullapudi, Sara C. Troutman, Sara Rimer, Branko Kerkez", 11 | author_email="abhiramm@umich.edu, stroutm@umich.edu", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/kLabUM/pystorms", 15 | packages=['pystorms'], 16 | package_data={ 17 | "pystorms": [ 18 | "networks/*.inp", 19 | "networks/*.py", 20 | "event_drivers/*.npy", 21 | "config/*.yaml", 22 | "config/*.py", 23 | "scenarios/*.py", 24 | ] 25 | }, 26 | classifiers=[ 27 | "Programming Language :: Python :: 3", 28 | "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", 29 | "Operating System :: OS Independent", 30 | ], 31 | install_requires=[ 32 | "numpy>=1.18.4", 33 | "pyswmm>=1.0.1", 34 | "pyyaml>=5.3" 35 | ], 36 | ) 37 | -------------------------------------------------------------------------------- /tests/test_general_functionality.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pystorms 3 | import numpy as np 4 | import datetime 5 | 6 | 7 | def test_save(): 8 | # pick a scenario 9 | env = pystorms.scenarios.theta() 10 | # run and create the data log 11 | done = False 12 | while not done: 13 | done = env.step() 14 | # save the data 15 | env.save(path="./temp.npy") 16 | # read the saved data 17 | data = np.load("./temp.npy", allow_pickle=True).item() 18 | assert len(data["performance_measure"]) == len(env.data_log["performance_measure"]) 19 | os.remove("./temp.npy") 20 | 21 | 22 | def test_save1(): 23 | # pick a scenario 24 | env = pystorms.scenarios.theta() 25 | # run and create the data log 26 | done = False 27 | while not done: 28 | done = env.step() 29 | # save the data 30 | env.save() 31 | data = np.load("./data_theta.npy", allow_pickle=True).item() 32 | assert len(data["performance_measure"]) == len(env.data_log["performance_measure"]) 33 | os.remove("./data_theta.npy") 34 | 35 | 36 | def test_environment_simulation_time(): 37 | env = pystorms.scenarios.theta() 38 | done = False 39 | while not done: 40 | done = env.step() 41 | 42 | assert type(env.data_log["simulation_time"][0]) == datetime.datetime 43 | assert len(env.data_log["simulation_time"]) == len( 44 | env.data_log["performance_measure"] 45 | ) 46 | -------------------------------------------------------------------------------- /tests/test_networks.py: -------------------------------------------------------------------------------- 1 | import pystorms 2 | import pytest 3 | 4 | 5 | def test_load_network(): 6 | network = pystorms.networks.load_network("alpha") 7 | assert "inp" == network[-3:] 8 | network = pystorms.networks.load_network("beta") 9 | assert "inp" == network[-3:] 10 | network = pystorms.networks.load_network("gamma") 11 | assert "inp" == network[-3:] 12 | network = pystorms.networks.load_network("epsilon") 13 | assert "inp" == network[-3:] 14 | network = pystorms.networks.load_network("theta") 15 | assert "inp" == network[-3:] 16 | network = pystorms.networks.load_network("delta") 17 | assert "inp" == network[-3:] 18 | network = pystorms.networks.load_network("zeta") 19 | assert "inp" == network[-3:] 20 | with pytest.raises(ValueError): 21 | pystorms.networks.load_network("purushotham") 22 | -------------------------------------------------------------------------------- /tests/test_scenarios.py: -------------------------------------------------------------------------------- 1 | import pystorms 2 | import numpy as np 3 | import pytest 4 | 5 | 6 | def test_alpha(): 7 | """ 8 | Tests for Alpha Scenario 9 | """ 10 | # Initalize your environment 11 | env = pystorms.scenarios.alpha() 12 | done = False 13 | steps = 0 14 | while not done: 15 | state = env.state() 16 | # Check if performance measure is raising error 17 | if steps < 1: 18 | with pytest.raises(ValueError): 19 | env.performance() 20 | done = env.step() 21 | steps += 1 22 | # Check for length of state being returned 23 | assert len(state) == 28 24 | # Check if data log is working 25 | assert len(env.data_log["performance_measure"]) == steps 26 | 27 | 28 | def test_beta(): 29 | """ 30 | Tests for Beta Scenario 31 | """ 32 | # Initalize your environment 33 | env = pystorms.scenarios.beta() 34 | done = False 35 | steps = 0 36 | while not done: 37 | state = env.state() 38 | # Check if performance measure is raising error 39 | if steps < 1: 40 | with pytest.raises(ValueError): 41 | env.performance() 42 | done = env.step() 43 | steps += 1 44 | # Check for length of state being returned 45 | assert len(state) == 7 46 | # Check if data log is working 47 | assert len(env.data_log["performance_measure"]) == steps 48 | 49 | 50 | def test_gamma(): 51 | """ 52 | Tests for Gamma Scenario 53 | """ 54 | # Initalize your environment 55 | env = pystorms.scenarios.gamma() 56 | done = False 57 | steps = 0 58 | while not done: 59 | state = env.state() 60 | # Check if performance measure is raising error 61 | if steps < 1: 62 | with pytest.raises(ValueError): 63 | env.performance() 64 | done = env.step() 65 | steps += 1 66 | # Check for length of state being returned 67 | assert len(state) == 11 68 | # Check if data log is working 69 | assert len(env.data_log["performance_measure"]) == steps 70 | 71 | # All valves closed - remanent water in basins 72 | env = pystorms.scenarios.gamma() 73 | done = False 74 | while not done: 75 | done = env.step(np.zeros(11)) 76 | assert env.performance() > 10 ** 5 77 | 78 | 79 | def test_theta(): 80 | """ 81 | Tests for Theta Scenario 82 | """ 83 | # Initalize your environment 84 | env = pystorms.scenarios.theta() 85 | done = False 86 | steps = 0 87 | while not done: 88 | state = env.state() 89 | # Check if performance measure is raising error 90 | if steps < 1: 91 | with pytest.raises(ValueError): 92 | env.performance() 93 | done = env.step() 94 | steps += 1 95 | # Check for length of state being returned 96 | assert len(state) == 2 97 | # Check if data log is working 98 | assert len(env.data_log["performance_measure"]) == steps 99 | 100 | 101 | def test_epsilon(): 102 | """ 103 | Tests for Epsilon Scenario 104 | """ 105 | # Initalize your environment 106 | env = pystorms.scenarios.epsilon() 107 | done = False 108 | steps = 0 109 | while not done: 110 | state = env.state() 111 | # Check if performance measure is raising error 112 | if steps < 1: 113 | with pytest.raises(ValueError): 114 | env.performance() 115 | done = env.step() 116 | steps += 1 117 | # Check for length of state being returned 118 | assert len(state) == 13 119 | # Check if data log is working 120 | assert len(env.data_log["performance_measure"]) == steps 121 | -------------------------------------------------------------------------------- /tests/test_scenarios2.py: -------------------------------------------------------------------------------- 1 | import pystorms 2 | import numpy as np 3 | import pytest 4 | 5 | 6 | def test_delta(): 7 | """ 8 | Tests for Delta Scenario 9 | """ 10 | # Initalize your environment 11 | env = pystorms.scenarios.delta() 12 | done = False 13 | steps = 0 14 | while not done: 15 | state = env.state() 16 | # Check if performance measure is raising error 17 | if steps < 1: 18 | with pytest.raises(ValueError): 19 | env.performance() 20 | done = env.step() 21 | steps += 1 22 | # Check for length of state being returned 23 | assert len(state) == 6 24 | # Check if data log is working 25 | assert len(env.data_log["performance_measure"]) == steps 26 | 27 | 28 | def test_zeta(): 29 | """ 30 | Tests for Zeta Scenario 31 | """ 32 | # Initalize your environment 33 | env = pystorms.scenarios.zeta() 34 | done = False 35 | steps = 0 36 | while not done: 37 | state = env.state() 38 | # Check if performance measure is raising error 39 | if steps < 1: 40 | with pytest.raises(ValueError): 41 | env.performance() 42 | done = env.step() 43 | steps += 1 44 | # Check for length of state being returned 45 | assert len(state) == 6 46 | # Check if data log is working 47 | assert len(env.data_log["performance_measure"]) == steps 48 | -------------------------------------------------------------------------------- /tutorials/RTC101-TestEnvironment.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "15c90eb3-85d8-40dc-a9c2-d43f2c779afd", 6 | "metadata": {}, 7 | "source": [ 8 | "# UDS-RTC101: Test environment\n", 9 | "This notebook is for testing Google Colab on your browser. " 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": null, 15 | "id": "50f82c8b-3a96-4b25-8af2-fc09bef5c37d", 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "!pip install pyswmm pystorms numpy matplotlib" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 1, 25 | "id": "2a6e387d-96e8-4700-9a99-8185e81fc577", 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [ 29 | "import pyswmm\n", 30 | "import pystorms\n", 31 | "import numpy as np\n", 32 | "import matplotlib.pyplot as plt" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": 2, 38 | "id": "3222658b-344b-4c81-866d-a6e4f61c8863", 39 | "metadata": {}, 40 | "outputs": [ 41 | { 42 | "data": { 43 | "text/plain": [ 44 | "'1.0.1'" 45 | ] 46 | }, 47 | "execution_count": 2, 48 | "metadata": {}, 49 | "output_type": "execute_result" 50 | } 51 | ], 52 | "source": [ 53 | "pyswmm.__version__" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": 3, 59 | "id": "b0617b76-a190-4f05-b98f-e31d02e6fc35", 60 | "metadata": {}, 61 | "outputs": [ 62 | { 63 | "data": { 64 | "text/plain": [ 65 | "'0.6.0'" 66 | ] 67 | }, 68 | "execution_count": 3, 69 | "metadata": {}, 70 | "output_type": "execute_result" 71 | } 72 | ], 73 | "source": [ 74 | "pystorms.__version__" 75 | ] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "execution_count": 4, 80 | "id": "7b8811fc-d6b0-49c6-8b37-28c34c5d62d3", 81 | "metadata": {}, 82 | "outputs": [ 83 | { 84 | "data": { 85 | "text/plain": [ 86 | "[]" 87 | ] 88 | }, 89 | "execution_count": 4, 90 | "metadata": {}, 91 | "output_type": "execute_result" 92 | }, 93 | { 94 | "data": { 95 | "image/png": "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\n", 96 | "text/plain": [ 97 | "
" 98 | ] 99 | }, 100 | "metadata": { 101 | "needs_background": "light" 102 | }, 103 | "output_type": "display_data" 104 | } 105 | ], 106 | "source": [ 107 | "x = np.linspace(0, 2, 100) * np.pi\n", 108 | "y = np.sin(x)\n", 109 | "plt.plot(x, y)" 110 | ] 111 | } 112 | ], 113 | "metadata": { 114 | "kernelspec": { 115 | "display_name": "pystorms-tutorial", 116 | "language": "python", 117 | "name": "pystorms-tutorial" 118 | }, 119 | "language_info": { 120 | "codemirror_mode": { 121 | "name": "ipython", 122 | "version": 3 123 | }, 124 | "file_extension": ".py", 125 | "mimetype": "text/x-python", 126 | "name": "python", 127 | "nbconvert_exporter": "python", 128 | "pygments_lexer": "ipython3", 129 | "version": "3.9.10" 130 | } 131 | }, 132 | "nbformat": 4, 133 | "nbformat_minor": 5 134 | } 135 | -------------------------------------------------------------------------------- /tutorials/ReinforcementLearning.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 12, 6 | "id": "7cfed399-fbc3-4c08-80a9-f20ab11a5e24", 7 | "metadata": {}, 8 | "outputs": [ 9 | { 10 | "name": "stderr", 11 | "output_type": "stream", 12 | "text": [ 13 | "/Users/pluto/Archive/dev/pystorms/pystorms/utilities.py:169: SyntaxWarning: \"is not\" with a literal. Did you mean \"!=\"?\n", 14 | " if attribute is not 'simulation_time':\n" 15 | ] 16 | } 17 | ], 18 | "source": [ 19 | "import torch\n", 20 | "import random\n", 21 | "import pystorms\n", 22 | "import numpy as np\n", 23 | "import matplotlib.pyplot as plt\n", 24 | "from collections import namedtuple, deque" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 2, 30 | "id": "b32ab9de-9a68-4027-8e65-03dbca6c7c4c", 31 | "metadata": {}, 32 | "outputs": [], 33 | "source": [ 34 | "class ReplayMemory():\n", 35 | " \"\"\"\n", 36 | " Container for storing state transitions\n", 37 | " \n", 38 | " ...\n", 39 | " \n", 40 | " Attributes\n", 41 | " ----------\n", 42 | " name : str\n", 43 | " name of the namedtuple\n", 44 | " colums : tuple of str\n", 45 | " name of the colums being stored\n", 46 | " capcity : int\n", 47 | " length of the replay memory\n", 48 | " \n", 49 | " Methods\n", 50 | " -------\n", 51 | " push(*args)\n", 52 | " Add state transitions objects into the container\n", 53 | " sample(batch_size=32)\n", 54 | " Randomly select from the memory\n", 55 | " \"\"\"\n", 56 | " def __init__(self,\n", 57 | " name = 'Transition',\n", 58 | " columns = ('state', 'action', 'next_state', 'reward'),\n", 59 | " capacity = 2):\n", 60 | " \n", 61 | " self.memory = deque([], maxlen=capacity)\n", 62 | " # namedtuple acts as the container for storing named objects\n", 63 | " self.container = namedtuple(name, columns)\n", 64 | " \n", 65 | " def push(self, *args):\n", 66 | " self.memory.append(self.container(*args))\n", 67 | " \n", 68 | " def sample(self, batch_size=32):\n", 69 | " return random.sample(self.memory, batch_size)\n", 70 | " \n", 71 | " def __len__(self):\n", 72 | " return len(self.memory)" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": 11, 78 | "id": "d2a28c0f-8728-436a-9f66-77df826babae", 79 | "metadata": {}, 80 | "outputs": [], 81 | "source": [ 82 | "class AgentSmith(torch.nn.Module):\n", 83 | " \"\"\"\n", 84 | " Feed forward neural network\n", 85 | " \n", 86 | " \"\"\"\n", 87 | " def __init__(self,\n", 88 | " layers=[((5, 5), 'ReLU'),\n", 89 | " ((5, 5), 'SeLU')]):\n", 90 | " \n", 91 | " # Dark magic to inherent things\n", 92 | " super(AgentSmith, self).__init__()\n", 93 | " \n", 94 | " # Neural network is stacked as a list\n", 95 | " self.network = []\n", 96 | " for layer in layers:\n", 97 | " input_dims = layer[0][0]\n", 98 | " output_dims = layer[0][1]\n", 99 | " activation_function = layer[1]\n", 100 | " self.network.append(torch.nn.Linear(in_features=input_dims,\n", 101 | " out_features=output_dims))\n", 102 | " self.network.append(getattr(torch.nn, activation_function))\n", 103 | " \n", 104 | " def forward(x):\n", 105 | " for layer in self.network:\n", 106 | " x = layer(x)" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": 16, 112 | "id": "e5732baf-8e72-46c3-bd99-511dd96e3282", 113 | "metadata": {}, 114 | "outputs": [ 115 | { 116 | "name": "stdout", 117 | "output_type": "stream", 118 | "text": [ 119 | "\n", 120 | " o Retrieving project data" 121 | ] 122 | } 123 | ], 124 | "source": [ 125 | "# create a new environment\n", 126 | "env = pystorms.scenarios.theta()\n", 127 | "done = False\n", 128 | "# replay memory\n", 129 | "replay_memory = ReplayMemory(capacity=10000)" 130 | ] 131 | }, 132 | { 133 | "cell_type": "code", 134 | "execution_count": 19, 135 | "id": "29862465-7427-406a-b9f5-9b46b7b24b0b", 136 | "metadata": {}, 137 | "outputs": [ 138 | { 139 | "name": "stdout", 140 | "output_type": "stream", 141 | "text": [ 142 | "> \u001b[0;32m/var/folders/jd/5m0pjc1x1tl88mk0lny0tgy80000gn/T/ipykernel_2484/1833492756.py\u001b[0m(34)\u001b[0;36m\u001b[0;34m()\u001b[0m\n", 143 | "\u001b[0;32m 30 \u001b[0;31m \u001b[0;32mimport\u001b[0m \u001b[0mpdb\u001b[0m\u001b[0;34m;\u001b[0m \u001b[0mpdb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_trace\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 144 | "\u001b[0m\u001b[0;32m 31 \u001b[0;31m\u001b[0;34m\u001b[0m\u001b[0m\n", 145 | "\u001b[0m\u001b[0;32m 32 \u001b[0;31m\u001b[0;34m\u001b[0m\u001b[0m\n", 146 | "\u001b[0m\u001b[0;32m 33 \u001b[0;31m \u001b[0;31m# set the new state to the old state\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 147 | "\u001b[0m\u001b[0;32m---> 34 \u001b[0;31m \u001b[0mstate\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnext_state\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 148 | "\u001b[0m\n" 149 | ] 150 | }, 151 | { 152 | "name": "stdin", 153 | "output_type": "stream", 154 | "text": [ 155 | "ipdb> batch\n" 156 | ] 157 | }, 158 | { 159 | "name": "stdout", 160 | "output_type": "stream", 161 | "text": [ 162 | "Transition(state=(array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.])), action=([1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]), next_state=(array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.])), reward=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0))\n" 163 | ] 164 | }, 165 | { 166 | "name": "stdin", 167 | "output_type": "stream", 168 | "text": [ 169 | "ipdb> len(batch)\n" 170 | ] 171 | }, 172 | { 173 | "name": "stdout", 174 | "output_type": "stream", 175 | "text": [ 176 | "4\n" 177 | ] 178 | }, 179 | { 180 | "name": "stdin", 181 | "output_type": "stream", 182 | "text": [ 183 | "ipdb> transitions\n" 184 | ] 185 | }, 186 | { 187 | "name": "stdout", 188 | "output_type": "stream", 189 | "text": [ 190 | "[Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0), Transition(state=array([0., 0.]), action=[1.0, 1.0], next_state=array([0., 0.]), reward=0.0)]\n" 191 | ] 192 | }, 193 | { 194 | "name": "stdin", 195 | "output_type": "stream", 196 | "text": [ 197 | "ipdb> state_batch = torch.cat(batch.state)\n" 198 | ] 199 | }, 200 | { 201 | "name": "stdout", 202 | "output_type": "stream", 203 | "text": [ 204 | "*** TypeError: expected Tensor as element 0 in argument 0, but got numpy.ndarray\n" 205 | ] 206 | }, 207 | { 208 | "name": "stdin", 209 | "output_type": "stream", 210 | "text": [ 211 | "ipdb> batch\n" 212 | ] 213 | }, 214 | { 215 | "name": "stdout", 216 | "output_type": "stream", 217 | "text": [ 218 | "Transition(state=(array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.])), action=([1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]), next_state=(array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.])), reward=(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0))\n" 219 | ] 220 | }, 221 | { 222 | "name": "stdin", 223 | "output_type": "stream", 224 | "text": [ 225 | "ipdb> batch.state\n" 226 | ] 227 | }, 228 | { 229 | "name": "stdout", 230 | "output_type": "stream", 231 | "text": [ 232 | "(array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]), array([0., 0.]))\n" 233 | ] 234 | }, 235 | { 236 | "name": "stdin", 237 | "output_type": "stream", 238 | "text": [ 239 | "ipdb> reward_batch = torch.cat(batch.reward)\n" 240 | ] 241 | }, 242 | { 243 | "name": "stdout", 244 | "output_type": "stream", 245 | "text": [ 246 | "*** TypeError: expected Tensor as element 0 in argument 0, but got float\n" 247 | ] 248 | }, 249 | { 250 | "name": "stdin", 251 | "output_type": "stream", 252 | "text": [ 253 | "ipdb> exit()\n" 254 | ] 255 | }, 256 | { 257 | "ename": "BdbQuit", 258 | "evalue": "", 259 | "output_type": "error", 260 | "traceback": [ 261 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 262 | "\u001b[0;31mBdbQuit\u001b[0m Traceback (most recent call last)", 263 | "\u001b[0;32m/var/folders/jd/5m0pjc1x1tl88mk0lny0tgy80000gn/T/ipykernel_2484/1833492756.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 32\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 33\u001b[0m \u001b[0;31m# set the new state to the old state\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 34\u001b[0;31m \u001b[0mstate\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnext_state\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", 264 | "\u001b[0;32m/var/folders/jd/5m0pjc1x1tl88mk0lny0tgy80000gn/T/ipykernel_2484/1833492756.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 32\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 33\u001b[0m \u001b[0;31m# set the new state to the old state\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 34\u001b[0;31m \u001b[0mstate\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnext_state\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", 265 | "\u001b[0;32m/opt/homebrew/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/bdb.py\u001b[0m in \u001b[0;36mtrace_dispatch\u001b[0;34m(self, frame, event, arg)\u001b[0m\n\u001b[1;32m 86\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;31m# None\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 87\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mevent\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m'line'\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 88\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdispatch_line\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mframe\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 89\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mevent\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m'call'\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 90\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdispatch_call\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mframe\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0marg\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 266 | "\u001b[0;32m/opt/homebrew/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/bdb.py\u001b[0m in \u001b[0;36mdispatch_line\u001b[0;34m(self, frame)\u001b[0m\n\u001b[1;32m 111\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstop_here\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mframe\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbreak_here\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mframe\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 112\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0muser_line\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mframe\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 113\u001b[0;31m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mquitting\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mBdbQuit\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 114\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrace_dispatch\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 115\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", 267 | "\u001b[0;31mBdbQuit\u001b[0m: " 268 | ] 269 | } 270 | ], 271 | "source": [ 272 | "# get the inital state\n", 273 | "state = env.state()\n", 274 | "train = False\n", 275 | "while not done:\n", 276 | " # get the action based on the current state\n", 277 | " action = [1.0, 1.0]#controller(state)\n", 278 | " \n", 279 | " # implement the action\n", 280 | " done = env.step(action)\n", 281 | " \n", 282 | " # get the reward based on your previous action\n", 283 | " reward = env.performance()\n", 284 | " \n", 285 | " # get the next state if simulation has not been terminated\n", 286 | " if not done:\n", 287 | " next_state = env.state()\n", 288 | " else:\n", 289 | " next_state = None\n", 290 | " \n", 291 | " # store the state transitions in the memory\n", 292 | " replay_memory.push(state, action, next_state, reward)\n", 293 | " \n", 294 | " if replay_memory.__len__() > 32:\n", 295 | " train = True\n", 296 | "\n", 297 | " if train:\n", 298 | " # sample from the batch\n", 299 | " transitions = replay_memory.sample(batch_size=32)\n", 300 | " batch = replay_memory.container(*zip(*transitions))\n", 301 | " \n", 302 | " non_final_mask = torch.tensor(tuple(map(lambda s: s is not None, batch.next_state)), dtype=torch.bool)\n", 303 | " import pdb; pdb.set_trace()\n", 304 | " \n", 305 | " \n", 306 | " # set the new state to the old state\n", 307 | " state = next_state" 308 | ] 309 | }, 310 | { 311 | "cell_type": "code", 312 | "execution_count": 22, 313 | "id": "3294dc0c-3fd3-4278-a61f-0549e25482b1", 314 | "metadata": {}, 315 | "outputs": [ 316 | { 317 | "ename": "TypeError", 318 | "evalue": "expected Tensor as element 0 in argument 0, but got numpy.ndarray", 319 | "output_type": "error", 320 | "traceback": [ 321 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 322 | "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", 323 | "\u001b[0;32m/var/folders/jd/5m0pjc1x1tl88mk0lny0tgy80000gn/T/ipykernel_2484/1024343405.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mtorch\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mbatch\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstate\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", 324 | "\u001b[0;31mTypeError\u001b[0m: expected Tensor as element 0 in argument 0, but got numpy.ndarray" 325 | ] 326 | } 327 | ], 328 | "source": [ 329 | "torch.cat(batch.state)" 330 | ] 331 | }, 332 | { 333 | "cell_type": "code", 334 | "execution_count": 30, 335 | "id": "33395137-76df-4fca-a97e-8bdcaec7bab9", 336 | "metadata": {}, 337 | "outputs": [ 338 | { 339 | "data": { 340 | "text/plain": [ 341 | "torch.Size([32, 2])" 342 | ] 343 | }, 344 | "execution_count": 30, 345 | "metadata": {}, 346 | "output_type": "execute_result" 347 | } 348 | ], 349 | "source": [ 350 | "torch.tensor(np.asarray(batch.state)).shape" 351 | ] 352 | }, 353 | { 354 | "cell_type": "code", 355 | "execution_count": null, 356 | "id": "3d63e4cb-b313-4d98-ae76-f6bb0ae8e5cc", 357 | "metadata": {}, 358 | "outputs": [], 359 | "source": [] 360 | } 361 | ], 362 | "metadata": { 363 | "kernelspec": { 364 | "display_name": "pystorms", 365 | "language": "python", 366 | "name": "pystorms" 367 | }, 368 | "language_info": { 369 | "codemirror_mode": { 370 | "name": "ipython", 371 | "version": 3 372 | }, 373 | "file_extension": ".py", 374 | "mimetype": "text/x-python", 375 | "name": "python", 376 | "nbconvert_exporter": "python", 377 | "pygments_lexer": "ipython3", 378 | "version": "3.9.9" 379 | } 380 | }, 381 | "nbformat": 4, 382 | "nbformat_minor": 5 383 | } 384 | -------------------------------------------------------------------------------- /tutorials/RuleBasedControl.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 11, 6 | "id": "0ffb32fa-a351-4a6f-b69e-91a326969fbc", 7 | "metadata": {}, 8 | "outputs": [ 9 | { 10 | "ename": "ModuleNotFoundError", 11 | "evalue": "No module named 'pandas'", 12 | "output_type": "error", 13 | "traceback": [ 14 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 15 | "\u001b[0;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", 16 | "\u001b[0;32m/var/folders/jd/5m0pjc1x1tl88mk0lny0tgy80000gn/T/ipykernel_64241/3980316843.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mpystorms\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mnumpy\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0;32mimport\u001b[0m \u001b[0mpandas\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mpd\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mmatplotlib\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpyplot\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mplt\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 17 | "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'pandas'" 18 | ] 19 | } 20 | ], 21 | "source": [ 22 | "import pystorms\n", 23 | "import numpy as np\n", 24 | "import pandas as pd\n", 25 | "import matplotlib.pyplot as plt" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": 4, 31 | "id": "4d90ee83-9fbd-47a3-807d-92e3eb535240", 32 | "metadata": {}, 33 | "outputs": [ 34 | { 35 | "name": "stdout", 36 | "output_type": "stream", 37 | "text": [ 38 | "\n", 39 | " o Retrieving project data" 40 | ] 41 | } 42 | ], 43 | "source": [ 44 | "env = pystorms.scenarios.theta()\n", 45 | "done = False" 46 | ] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": 5, 51 | "id": "db359081-6aae-49dd-a4b0-7c50218d0e5c", 52 | "metadata": {}, 53 | "outputs": [], 54 | "source": [ 55 | "while not done:\n", 56 | " state = env.state()\n", 57 | " done = env.step()" 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": 8, 63 | "id": "217f9ed2-65bf-4000-9267-3edf08cd7f57", 64 | "metadata": {}, 65 | "outputs": [], 66 | "source": [ 67 | "sim_time = env.data_log['simulation_time']" 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": 10, 73 | "id": "bec0cf1b-9e9b-4a50-9188-a8bd0cb9ce23", 74 | "metadata": {}, 75 | "outputs": [ 76 | { 77 | "data": { 78 | "text/plain": [ 79 | "dict_keys(['performance_measure', 'flow', 'flooding', 'simulation_time'])" 80 | ] 81 | }, 82 | "execution_count": 10, 83 | "metadata": {}, 84 | "output_type": "execute_result" 85 | } 86 | ], 87 | "source": [ 88 | "flows = " 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": 12, 94 | "id": "e83716e9-0f6e-4101-84dd-f9e85ddd6917", 95 | "metadata": {}, 96 | "outputs": [ 97 | { 98 | "name": "stdout", 99 | "output_type": "stream", 100 | "text": [ 101 | "Collecting pandas\n", 102 | " Downloading pandas-1.3.5.tar.gz (4.7 MB)\n", 103 | " |████████████████████████████████| 4.7 MB 4.1 MB/s \n", 104 | "\u001b[?25h Installing build dependencies ... \u001b[?25ldone\n", 105 | "\u001b[?25h Getting requirements to build wheel ... \u001b[?25ldone\n", 106 | "\u001b[?25h Preparing metadata (pyproject.toml) ... \u001b[?25ldone\n", 107 | "\u001b[?25hRequirement already satisfied: numpy>=1.20.0 in /Users/pluto/Archive/dev/pystorms/venv/lib/python3.9/site-packages (from pandas) (1.22.0)\n", 108 | "Requirement already satisfied: python-dateutil>=2.7.3 in /Users/pluto/Archive/dev/pystorms/venv/lib/python3.9/site-packages (from pandas) (2.8.2)\n", 109 | "Collecting pytz>=2017.3\n", 110 | " Using cached pytz-2021.3-py2.py3-none-any.whl (503 kB)\n", 111 | "Requirement already satisfied: six>=1.5 in /Users/pluto/Archive/dev/pystorms/venv/lib/python3.9/site-packages (from python-dateutil>=2.7.3->pandas) (1.16.0)\n", 112 | "Building wheels for collected packages: pandas\n", 113 | " Building wheel for pandas (pyproject.toml) ... \u001b[?25ldone\n", 114 | "\u001b[?25h Created wheel for pandas: filename=pandas-1.3.5-cp39-cp39-macosx_12_0_arm64.whl size=10245242 sha256=de3160b2375a5530b1693a2369daafad04a4ddfed249b785b5751497494ae4df\n", 115 | " Stored in directory: /Users/pluto/Library/Caches/pip/wheels/46/1f/09/be8c6f216f000b48aaef3009dc7017707a1b18ef30ba548b8d\n", 116 | "Successfully built pandas\n", 117 | "Installing collected packages: pytz, pandas\n", 118 | "Successfully installed pandas-1.3.5 pytz-2021.3\n" 119 | ] 120 | } 121 | ], 122 | "source": [ 123 | "!pip install pandas" 124 | ] 125 | }, 126 | { 127 | "cell_type": "code", 128 | "execution_count": null, 129 | "id": "9eb11104-d3a9-4a91-84be-f918676865db", 130 | "metadata": {}, 131 | "outputs": [], 132 | "source": [] 133 | } 134 | ], 135 | "metadata": { 136 | "kernelspec": { 137 | "display_name": "pystorms-tutorial", 138 | "language": "python", 139 | "name": "pystorms-tutorial" 140 | }, 141 | "language_info": { 142 | "codemirror_mode": { 143 | "name": "ipython", 144 | "version": 3 145 | }, 146 | "file_extension": ".py", 147 | "mimetype": "text/x-python", 148 | "name": "python", 149 | "nbconvert_exporter": "python", 150 | "pygments_lexer": "ipython3", 151 | "version": "3.9.9" 152 | } 153 | }, 154 | "nbformat": 4, 155 | "nbformat_minor": 5 156 | } 157 | -------------------------------------------------------------------------------- /tutorials/Scenario_Beta.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "toc": true 7 | }, 8 | "source": [ 9 | "

Table of Contents

\n", 10 | "" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": {}, 16 | "source": [ 17 | "# Set up the environment" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": 1, 23 | "metadata": { 24 | "ExecuteTime": { 25 | "end_time": "2021-07-20T20:56:15.267204Z", 26 | "start_time": "2021-07-20T20:56:14.518045Z" 27 | } 28 | }, 29 | "outputs": [], 30 | "source": [ 31 | "# Import pystorms \n", 32 | "import pystorms \n", 33 | "# Import other Python libraries\n", 34 | "import numpy as np\n", 35 | "import pandas as pd\n", 36 | "from datetime import timedelta" 37 | ] 38 | }, 39 | { 40 | "cell_type": "markdown", 41 | "metadata": {}, 42 | "source": [ 43 | "# Overview of Scenario Beta" 44 | ] 45 | }, 46 | { 47 | "cell_type": "markdown", 48 | "metadata": { 49 | "ExecuteTime": { 50 | "end_time": "2021-07-20T20:57:17.662535Z", 51 | "start_time": "2021-07-20T20:57:17.658402Z" 52 | } 53 | }, 54 | "source": [ 55 | "The `config.yaml` file lists the specifics for `Scenario Beta`. Is is developed based on a Model-Predictive Control method developed and published by [Sadler et al. (2020)](https://doi.org/10.1016/j.jhydrol.2020.124571), and subsequently made available with the [HydroShare collaborative environment](https://doi.org/10.4211/hs.5148675c6a5841e686a3b6aec67a38ee)." 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "## Uncontrolled Case" 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "metadata": {}, 68 | "source": [ 69 | "We run `Scenario Beta` without any control. In this Scenario, we have three control assets: an orifice (`R2`), a pump (`P0`), and a weir (`W0`). For the uncontrolled case, the settings for these three assets are set as follows:" 70 | ] 71 | }, 72 | { 73 | "cell_type": "markdown", 74 | "metadata": {}, 75 | "source": [ 76 | "|Control asset |SWMM implementation | Uncontrolled setting | \n", 77 | "|--------------|--------------------|----------------------|\n", 78 | "|Orifice 3 |`R2` | 1.0 |\n", 79 | "|Pump 1 |`P0` | 0.0 |\n", 80 | "|Weir 1 |`W0` | 1.0 |" 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": 2, 86 | "metadata": { 87 | "ExecuteTime": { 88 | "end_time": "2021-07-20T21:05:18.890869Z", 89 | "start_time": "2021-07-20T21:04:55.647083Z" 90 | } 91 | }, 92 | "outputs": [ 93 | { 94 | "name": "stdout", 95 | "output_type": "stream", 96 | "text": [ 97 | "\n", 98 | " o Retrieving project data" 99 | ] 100 | } 101 | ], 102 | "source": [ 103 | "# run the base uncontrolled instance\n", 104 | "env_uncontrolled = pystorms.scenarios.beta()\n", 105 | "done = False\n", 106 | "actions = np.array([1.0, 0.0, 1.0])\n", 107 | "while not done:\n", 108 | " done = env_uncontrolled.step(actions)\n", 109 | "uncontrolled_perf = sum(env_uncontrolled.data_log[\"performance_measure\"])" 110 | ] 111 | }, 112 | { 113 | "cell_type": "markdown", 114 | "metadata": { 115 | "ExecuteTime": { 116 | "end_time": "2021-07-20T21:05:18.904169Z", 117 | "start_time": "2021-07-20T21:05:18.900018Z" 118 | } 119 | }, 120 | "source": [ 121 | "The performance for this case is computed to be the following:" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": 3, 127 | "metadata": { 128 | "ExecuteTime": { 129 | "end_time": "2021-07-20T21:05:52.277007Z", 130 | "start_time": "2021-07-20T21:05:52.273680Z" 131 | } 132 | }, 133 | "outputs": [ 134 | { 135 | "name": "stdout", 136 | "output_type": "stream", 137 | "text": [ 138 | "2971770.621866018\n" 139 | ] 140 | } 141 | ], 142 | "source": [ 143 | "print(uncontrolled_perf)" 144 | ] 145 | }, 146 | { 147 | "cell_type": "markdown", 148 | "metadata": {}, 149 | "source": [ 150 | "## MPC Case" 151 | ] 152 | }, 153 | { 154 | "cell_type": "markdown", 155 | "metadata": {}, 156 | "source": [ 157 | "The MPC case has built a controller that is based on the actual time of the simulation. The settings are available in the `mpc_rules_beta.csv` file." 158 | ] 159 | }, 160 | { 161 | "cell_type": "code", 162 | "execution_count": 4, 163 | "metadata": { 164 | "ExecuteTime": { 165 | "end_time": "2021-07-20T21:14:36.305358Z", 166 | "start_time": "2021-07-20T21:14:36.292208Z" 167 | } 168 | }, 169 | "outputs": [ 170 | { 171 | "data": { 172 | "text/html": [ 173 | "
\n", 174 | "\n", 187 | "\n", 188 | " \n", 189 | " \n", 190 | " \n", 191 | " \n", 192 | " \n", 193 | " \n", 194 | " \n", 195 | " \n", 196 | " \n", 197 | " \n", 198 | " \n", 199 | " \n", 200 | " \n", 201 | " \n", 202 | " \n", 203 | " \n", 204 | " \n", 205 | " \n", 206 | " \n", 207 | " \n", 208 | " \n", 209 | " \n", 210 | " \n", 211 | " \n", 212 | " \n", 213 | " \n", 214 | " \n", 215 | " \n", 216 | " \n", 217 | " \n", 218 | " \n", 219 | " \n", 220 | " \n", 221 | " \n", 222 | " \n", 223 | " \n", 224 | " \n", 225 | " \n", 226 | " \n", 227 | " \n", 228 | " \n", 229 | " \n", 230 | " \n", 231 | " \n", 232 | " \n", 233 | " \n", 234 | " \n", 235 | " \n", 236 | " \n", 237 | " \n", 238 | " \n", 239 | " \n", 240 | " \n", 241 | " \n", 242 | " \n", 243 | " \n", 244 | " \n", 245 | " \n", 246 | " \n", 247 | " \n", 248 | " \n", 249 | " \n", 250 | " \n", 251 | " \n", 252 | " \n", 253 | " \n", 254 | " \n", 255 | " \n", 256 | " \n", 257 | " \n", 258 | " \n", 259 | " \n", 260 | " \n", 261 | " \n", 262 | " \n", 263 | " \n", 264 | " \n", 265 | " \n", 266 | " \n", 267 | " \n", 268 | " \n", 269 | " \n", 270 | " \n", 271 | " \n", 272 | " \n", 273 | " \n", 274 | " \n", 275 | " \n", 276 | " \n", 277 | " \n", 278 | " \n", 279 | " \n", 280 | " \n", 281 | " \n", 282 | " \n", 283 | " \n", 284 | " \n", 285 | " \n", 286 | " \n", 287 | " \n", 288 | "
datetimesimtime (hr)R2P0W0
02016-10-08 00:00:000.001.000OFF1.000
12016-10-08 00:15:000.251.000OFF1.000
22016-10-08 00:30:000.500.714OFF0.000
32016-10-08 00:45:000.751.000OFF0.857
42016-10-08 01:00:001.000.571ON0.571
..................
602016-10-08 15:00:0015.000.000OFF0.000
612016-10-08 15:15:0015.250.000OFF0.000
622016-10-08 15:30:0015.500.000OFF0.571
632016-10-08 15:45:0015.750.143ON0.000
642016-10-08 16:00:0016.000.000ON0.000
\n", 289 | "

65 rows × 5 columns

\n", 290 | "
" 291 | ], 292 | "text/plain": [ 293 | " datetime simtime (hr) R2 P0 W0\n", 294 | "0 2016-10-08 00:00:00 0.00 1.000 OFF 1.000\n", 295 | "1 2016-10-08 00:15:00 0.25 1.000 OFF 1.000\n", 296 | "2 2016-10-08 00:30:00 0.50 0.714 OFF 0.000\n", 297 | "3 2016-10-08 00:45:00 0.75 1.000 OFF 0.857\n", 298 | "4 2016-10-08 01:00:00 1.00 0.571 ON 0.571\n", 299 | ".. ... ... ... ... ...\n", 300 | "60 2016-10-08 15:00:00 15.00 0.000 OFF 0.000\n", 301 | "61 2016-10-08 15:15:00 15.25 0.000 OFF 0.000\n", 302 | "62 2016-10-08 15:30:00 15.50 0.000 OFF 0.571\n", 303 | "63 2016-10-08 15:45:00 15.75 0.143 ON 0.000\n", 304 | "64 2016-10-08 16:00:00 16.00 0.000 ON 0.000\n", 305 | "\n", 306 | "[65 rows x 5 columns]" 307 | ] 308 | }, 309 | "execution_count": 4, 310 | "metadata": {}, 311 | "output_type": "execute_result" 312 | } 313 | ], 314 | "source": [ 315 | "# obtain the controller details from the .csv file with the control settings and corresponding simulation time\n", 316 | "mpc_df = pd.read_csv(\"mpc_rules_beta.csv\")\n", 317 | "mpc_df['datetime'] = pd.to_datetime(mpc_df['datetime'])\n", 318 | "mpc_df\n", 319 | "#" 320 | ] 321 | }, 322 | { 323 | "cell_type": "code", 324 | "execution_count": 5, 325 | "metadata": {}, 326 | "outputs": [], 327 | "source": [ 328 | "# convert the dataframe of rules to an easily readable dictionary \n", 329 | "mpc_df['P0'].replace({\"ON\":1.0, \"OFF\":0.0}, inplace=True)\n", 330 | "mpc_df.drop(columns=[\"simtime (hr)\"], inplace=True)\n", 331 | "mpc_datetimes = mpc_df['datetime'].to_list()\n", 332 | "mpc_controller = mpc_df.set_index('datetime').to_dict()" 333 | ] 334 | }, 335 | { 336 | "cell_type": "code", 337 | "execution_count": 6, 338 | "metadata": {}, 339 | "outputs": [ 340 | { 341 | "name": "stdout", 342 | "output_type": "stream", 343 | "text": [ 344 | "\n", 345 | " o Retrieving project data" 346 | ] 347 | } 348 | ], 349 | "source": [ 350 | "# initialize the scenario, and obtain the initial controller settings\n", 351 | "env_mpc = pystorms.scenarios.beta()\n", 352 | "controller_datetime = env_mpc.env.getInitialSimulationDateTime()\n", 353 | "actions = np.array([mpc_controller['R2'][controller_datetime], \n", 354 | " mpc_controller['P0'][controller_datetime], \n", 355 | " mpc_controller['W0'][controller_datetime]])\n", 356 | "done = False" 357 | ] 358 | }, 359 | { 360 | "cell_type": "code", 361 | "execution_count": 7, 362 | "metadata": { 363 | "ExecuteTime": { 364 | "end_time": "2021-07-20T21:25:57.066700Z", 365 | "start_time": "2021-07-20T21:25:21.394381Z" 366 | } 367 | }, 368 | "outputs": [], 369 | "source": [ 370 | "# Run the simulation\n", 371 | "while not done:\n", 372 | " sim_datetime = env_mpc.env.getCurrentSimulationDateTime()\n", 373 | " if (sim_datetime >= controller_datetime) and (controller_datetime in mpc_datetimes):\n", 374 | " actions = np.array([mpc_controller['R2'][controller_datetime], \n", 375 | " mpc_controller['P0'][controller_datetime], \n", 376 | " mpc_controller['W0'][controller_datetime]])\n", 377 | " controller_datetime += timedelta(minutes=15)\n", 378 | " done = env_mpc.step(actions)\n", 379 | "mpc_perf = sum(env_mpc.data_log[\"performance_measure\"])" 380 | ] 381 | }, 382 | { 383 | "cell_type": "code", 384 | "execution_count": 8, 385 | "metadata": {}, 386 | "outputs": [ 387 | { 388 | "name": "stdout", 389 | "output_type": "stream", 390 | "text": [ 391 | "2925689.498831413\n" 392 | ] 393 | } 394 | ], 395 | "source": [ 396 | "print(mpc_perf)\n", 397 | "#" 398 | ] 399 | }, 400 | { 401 | "cell_type": "markdown", 402 | "metadata": {}, 403 | "source": [ 404 | "## Results" 405 | ] 406 | }, 407 | { 408 | "cell_type": "markdown", 409 | "metadata": {}, 410 | "source": [ 411 | "The results are summarized in the following table:" 412 | ] 413 | }, 414 | { 415 | "cell_type": "markdown", 416 | "metadata": {}, 417 | "source": [ 418 | "| |Performance | \n", 419 | "|----------------------|---------|\n", 420 | "|Uncontrolled instance | 2971770 | \n", 421 | "|MPC controller | 2925689 |" 422 | ] 423 | }, 424 | { 425 | "cell_type": "markdown", 426 | "metadata": {}, 427 | "source": [ 428 | "As expected, the MPC controller performance better than the uncontrolled instance." 429 | ] 430 | }, 431 | { 432 | "cell_type": "markdown", 433 | "metadata": {}, 434 | "source": [ 435 | "## Configuration details for Beta" 436 | ] 437 | }, 438 | { 439 | "cell_type": "markdown", 440 | "metadata": {}, 441 | "source": [ 442 | "```yaml\n", 443 | "name: beta\n", 444 | "\n", 445 | "states:\n", 446 | " - !!python/tuple \n", 447 | " - \"J33\"\n", 448 | " - depthN\n", 449 | " - !!python/tuple\n", 450 | " - \"J64\"\n", 451 | " - depthN\n", 452 | " - !!python/tuple \n", 453 | " - \"J98\"\n", 454 | " - depthN\n", 455 | " - !!python/tuple\n", 456 | " - \"J102\"\n", 457 | " - depthN\n", 458 | " - !!python/tuple \n", 459 | " - \"OUT0\"\n", 460 | " - depthN\n", 461 | " - !!python/tuple\n", 462 | " - \"ST0\"\n", 463 | " - depthN\n", 464 | " - !!python/tuple \n", 465 | " - \"ST2\"\n", 466 | " - depthN\n", 467 | "\n", 468 | "action_space:\n", 469 | " - R2\n", 470 | " - P0\n", 471 | " - W0\n", 472 | "\n", 473 | "performance_targets:\n", 474 | " - !!python/tuple\n", 475 | " - J4\n", 476 | " - flooding\n", 477 | " - !!python/tuple\n", 478 | " - J8\n", 479 | " - flooding\n", 480 | " - !!python/tuple\n", 481 | " - J13\n", 482 | " - flooding \n", 483 | " - !!python/tuple\n", 484 | " - J33\n", 485 | " - flooding\n", 486 | " - !!python/tuple\n", 487 | " - J53\n", 488 | " - flooding\n", 489 | " - !!python/tuple\n", 490 | " - J54\n", 491 | " - flooding \n", 492 | " - !!python/tuple\n", 493 | " - J64\n", 494 | " - flooding\n", 495 | " - !!python/tuple\n", 496 | " - J65\n", 497 | " - flooding\n", 498 | " - !!python/tuple\n", 499 | " - J98\n", 500 | " - flooding \n", 501 | " - !!python/tuple\n", 502 | " - J102\n", 503 | " - flooding\n", 504 | " - !!python/tuple\n", 505 | " - J145\n", 506 | " - flooding\n", 507 | " - !!python/tuple\n", 508 | " - J146\n", 509 | " - flooding\n", 510 | "```" 511 | ] 512 | }, 513 | { 514 | "cell_type": "code", 515 | "execution_count": null, 516 | "metadata": {}, 517 | "outputs": [], 518 | "source": [] 519 | } 520 | ], 521 | "metadata": { 522 | "hide_input": false, 523 | "kernelspec": { 524 | "display_name": "Python 3 (ipykernel)", 525 | "language": "python", 526 | "name": "python3" 527 | }, 528 | "language_info": { 529 | "codemirror_mode": { 530 | "name": "ipython", 531 | "version": 3 532 | }, 533 | "file_extension": ".py", 534 | "mimetype": "text/x-python", 535 | "name": "python", 536 | "nbconvert_exporter": "python", 537 | "pygments_lexer": "ipython3", 538 | "version": "3.9.13" 539 | }, 540 | "toc": { 541 | "base_numbering": 1, 542 | "nav_menu": {}, 543 | "number_sections": false, 544 | "sideBar": false, 545 | "skip_h1_title": true, 546 | "title_cell": "Table of Contents", 547 | "title_sidebar": "Contents", 548 | "toc_cell": true, 549 | "toc_position": {}, 550 | "toc_section_display": true, 551 | "toc_window_display": false 552 | }, 553 | "varInspector": { 554 | "cols": { 555 | "lenName": 16, 556 | "lenType": 16, 557 | "lenVar": 40 558 | }, 559 | "kernels_config": { 560 | "python": { 561 | "delete_cmd_postfix": "", 562 | "delete_cmd_prefix": "del ", 563 | "library": "var_list.py", 564 | "varRefreshCmd": "print(var_dic_list())" 565 | }, 566 | "r": { 567 | "delete_cmd_postfix": ") ", 568 | "delete_cmd_prefix": "rm(", 569 | "library": "var_list.r", 570 | "varRefreshCmd": "cat(var_dic_list()) " 571 | } 572 | }, 573 | "types_to_exclude": [ 574 | "module", 575 | "function", 576 | "builtin_function_or_method", 577 | "instance", 578 | "_Feature" 579 | ], 580 | "window_display": false 581 | } 582 | }, 583 | "nbformat": 4, 584 | "nbformat_minor": 4 585 | } 586 | -------------------------------------------------------------------------------- /tutorials/Scenario_Epsilon.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Scenario Epsilon\n", 8 | "\n", 9 | "In this example, we evaluate the use of bayesian optimization for controlling loads to the treatment plant." 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "outputs": [], 16 | "source": [ 17 | "import pystorms\n", 18 | "import numpy as np\n", 19 | "import matplotlib.pyplot as plt\n", 20 | "from GPyOpt.methods import BayesianOptimization" 21 | ], 22 | "metadata": { 23 | "collapsed": false, 24 | "pycharm": { 25 | "name": "#%%\n" 26 | } 27 | } 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": 2, 32 | "metadata": {}, 33 | "outputs": [], 34 | "source": [ 35 | "# RC parms for pertty plots\n", 36 | "plt.rcParams.update({'font.size': 14})\n", 37 | "plt.style.use('seaborn-whitegrid')\n", 38 | "plt.style.use('seaborn-dark-palette')" 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": null, 44 | "metadata": {}, 45 | "outputs": [], 46 | "source": [ 47 | "# Objective Function\n", 48 | "def f_loss(x):\n", 49 | " # GypOpt uses 2d array\n", 50 | " # pystorms requies 1d array\n", 51 | " x = x.flatten()\n", 52 | "\n", 53 | " # Initialize the scenario\n", 54 | " env = pystorms.scenarios.epsilon()\n", 55 | " done = False\n", 56 | " \n", 57 | " while not done:\n", 58 | " done = env.step(x)\n", 59 | " \n", 60 | " loss = env.performance()\n", 61 | " return loss" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": null, 67 | "metadata": {}, 68 | "outputs": [], 69 | "source": [ 70 | "# Read the parsed args\n", 71 | "random_seed = 42\n", 72 | "number_iter = 5\n", 73 | "\n", 74 | "# Set the random seed\n", 75 | "np.random.seed(random_seed)\n", 76 | "\n", 77 | "# Create the domain\n", 78 | "domain = []\n", 79 | "for i in range(1, 11):\n", 80 | " domain.append({\"name\": \"var_\" + str(i), \"type\": \"continuous\", \"domain\": (0.0, 1.0)})\n", 81 | "\n", 82 | "\n", 83 | "myBopt = BayesianOptimization(\n", 84 | " f=f_loss, domain=domain,\n", 85 | " model_type=\"GP\",\n", 86 | " acquisition_type=\"EI\",\n", 87 | ")\n", 88 | "\n", 89 | "myBopt.run_optimization(\n", 90 | " max_iter=number_iter,\n", 91 | " verbosity=True,\n", 92 | " eps=0.005,\n", 93 | ")" 94 | ] 95 | } 96 | ], 97 | "metadata": { 98 | "hide_input": false, 99 | "kernelspec": { 100 | "display_name": "pystorms-tutorial", 101 | "language": "python", 102 | "name": "pystorms-tutorial" 103 | }, 104 | "language_info": { 105 | "codemirror_mode": { 106 | "name": "ipython", 107 | "version": 3 108 | }, 109 | "file_extension": ".py", 110 | "mimetype": "text/x-python", 111 | "name": "python", 112 | "nbconvert_exporter": "python", 113 | "pygments_lexer": "ipython3", 114 | "version": "3.9.10" 115 | }, 116 | "toc": { 117 | "base_numbering": 1, 118 | "nav_menu": {}, 119 | "number_sections": false, 120 | "sideBar": false, 121 | "skip_h1_title": true, 122 | "title_cell": "Table of Contents", 123 | "title_sidebar": "Contents", 124 | "toc_cell": true, 125 | "toc_position": {}, 126 | "toc_section_display": true, 127 | "toc_window_display": false 128 | }, 129 | "varInspector": { 130 | "cols": { 131 | "lenName": 16, 132 | "lenType": 16, 133 | "lenVar": 40 134 | }, 135 | "kernels_config": { 136 | "python": { 137 | "delete_cmd_postfix": "", 138 | "delete_cmd_prefix": "del ", 139 | "library": "var_list.py", 140 | "varRefreshCmd": "print(var_dic_list())" 141 | }, 142 | "r": { 143 | "delete_cmd_postfix": ") ", 144 | "delete_cmd_prefix": "rm(", 145 | "library": "var_list.r", 146 | "varRefreshCmd": "cat(var_dic_list()) " 147 | } 148 | }, 149 | "types_to_exclude": [ 150 | "module", 151 | "function", 152 | "builtin_function_or_method", 153 | "instance", 154 | "_Feature" 155 | ], 156 | "window_display": false 157 | } 158 | }, 159 | "nbformat": 4, 160 | "nbformat_minor": 4 161 | } -------------------------------------------------------------------------------- /tutorials/Scenario_Zeta.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "toc": true 7 | }, 8 | "source": [ 9 | "

Table of Contents

\n", 10 | "
    " 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 1, 16 | "metadata": { 17 | "ExecuteTime": { 18 | "end_time": "2021-07-21T17:41:17.314069Z", 19 | "start_time": "2021-07-21T17:41:02.743767Z" 20 | } 21 | }, 22 | "outputs": [], 23 | "source": [ 24 | "# Import python libraries \n", 25 | "import pystorms \n", 26 | "# Python Scientific Computing Stack\n", 27 | "import numpy as np\n", 28 | "import pandas as pd\n", 29 | "# Plotting tools\n", 30 | "import seaborn as sns\n", 31 | "from matplotlib import pyplot as plt" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": 2, 37 | "metadata": { 38 | "ExecuteTime": { 39 | "end_time": "2021-07-21T17:41:24.323584Z", 40 | "start_time": "2021-07-21T17:41:24.318699Z" 41 | } 42 | }, 43 | "outputs": [], 44 | "source": [ 45 | "# set seaborn figure preferences and colors\n", 46 | "sns.set_context(\"notebook\", font_scale=1.5, rc={\"lines.linewidth\": 2.5})\n", 47 | "sns.set_style(\"darkgrid\")\n", 48 | "colorpalette = sns.color_palette(\"colorblind\")\n", 49 | "colors_hex = colorpalette.as_hex()" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": 3, 55 | "metadata": { 56 | "ExecuteTime": { 57 | "end_time": "2021-07-21T17:41:26.202444Z", 58 | "start_time": "2021-07-21T17:41:26.199470Z" 59 | } 60 | }, 61 | "outputs": [], 62 | "source": [ 63 | "plt.rcParams['figure.figsize'] = [20, 15]\n", 64 | "plt.rcParams['figure.dpi'] = 100" 65 | ] 66 | }, 67 | { 68 | "cell_type": "markdown", 69 | "metadata": {}, 70 | "source": [ 71 | "# Uncontrolled Case" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": 4, 77 | "metadata": { 78 | "ExecuteTime": { 79 | "end_time": "2021-07-21T17:43:07.948813Z", 80 | "start_time": "2021-07-21T17:43:04.405599Z" 81 | } 82 | }, 83 | "outputs": [ 84 | { 85 | "name": "stdout", 86 | "output_type": "stream", 87 | "text": [ 88 | "\n", 89 | " o Retrieving project data" 90 | ] 91 | } 92 | ], 93 | "source": [ 94 | "################################################################################\n", 95 | "# Uncontrolled performance - control asset settings are set to all ones\n", 96 | "################################################################################\n", 97 | "env_uncontrolled = pystorms.scenarios.zeta()\n", 98 | "actions = np.array([1.0, 1.0, 1.0, 1.0])\n", 99 | "done = False\n", 100 | "while not done:\n", 101 | " done = env_uncontrolled.step(actions)\n", 102 | "uncontrolled_perf = sum(env_uncontrolled.data_log[\"performance_measure\"])" 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": 5, 108 | "metadata": { 109 | "ExecuteTime": { 110 | "end_time": "2021-07-21T17:43:08.711546Z", 111 | "start_time": "2021-07-21T17:43:08.708261Z" 112 | } 113 | }, 114 | "outputs": [ 115 | { 116 | "name": "stdout", 117 | "output_type": "stream", 118 | "text": [ 119 | "74023.79897010184\n" 120 | ] 121 | } 122 | ], 123 | "source": [ 124 | "print(uncontrolled_perf)" 125 | ] 126 | }, 127 | { 128 | "cell_type": "markdown", 129 | "metadata": {}, 130 | "source": [ 131 | "# Control example - BC Control" 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": 6, 137 | "metadata": { 138 | "ExecuteTime": { 139 | "end_time": "2021-07-21T17:43:10.897508Z", 140 | "start_time": "2021-07-21T17:43:10.893447Z" 141 | } 142 | }, 143 | "outputs": [], 144 | "source": [ 145 | "def bc_control(depths, actions): \n", 146 | " if depths[0] >= 0:\n", 147 | " actions[0] = 0.2366\n", 148 | " actions[1] = 0.6508\n", 149 | " actions[2] = 0.3523\n", 150 | " actions[3] = 0.4303\n", 151 | " return actions" 152 | ] 153 | }, 154 | { 155 | "cell_type": "code", 156 | "execution_count": 7, 157 | "metadata": { 158 | "ExecuteTime": { 159 | "end_time": "2021-07-21T17:43:16.187891Z", 160 | "start_time": "2021-07-21T17:43:12.244877Z" 161 | } 162 | }, 163 | "outputs": [ 164 | { 165 | "name": "stdout", 166 | "output_type": "stream", 167 | "text": [ 168 | "\n", 169 | " o Retrieving project data" 170 | ] 171 | } 172 | ], 173 | "source": [ 174 | "################################################################################\n", 175 | "# Controlled performance - BC Control implementation\n", 176 | "################################################################################\n", 177 | "env_controllerBC = pystorms.scenarios.zeta()\n", 178 | "done = False\n", 179 | "currentactions = np.ones(4)\n", 180 | "while not done:\n", 181 | " state = env_controllerBC.state()\n", 182 | " newactions = bc_control(state, currentactions)\n", 183 | " done = env_controllerBC.step(newactions)\n", 184 | " currentactions = newactions\n", 185 | "controllerBC_perf = sum(env_controllerBC.data_log[\"performance_measure\"])" 186 | ] 187 | }, 188 | { 189 | "cell_type": "code", 190 | "execution_count": 8, 191 | "metadata": { 192 | "ExecuteTime": { 193 | "end_time": "2021-07-21T17:43:17.088722Z", 194 | "start_time": "2021-07-21T17:43:17.085346Z" 195 | } 196 | }, 197 | "outputs": [ 198 | { 199 | "name": "stdout", 200 | "output_type": "stream", 201 | "text": [ 202 | "72450.93989818185\n" 203 | ] 204 | } 205 | ], 206 | "source": [ 207 | "print(controllerBC_perf)" 208 | ] 209 | }, 210 | { 211 | "cell_type": "markdown", 212 | "metadata": {}, 213 | "source": [ 214 | "# Control example - EFD Control" 215 | ] 216 | }, 217 | { 218 | "cell_type": "code", 219 | "execution_count": 9, 220 | "metadata": { 221 | "ExecuteTime": { 222 | "end_time": "2021-07-21T17:43:19.117345Z", 223 | "start_time": "2021-07-21T17:43:19.108846Z" 224 | } 225 | }, 226 | "outputs": [], 227 | "source": [ 228 | "def efd_control(depths):\n", 229 | " TANKmax = np.argmax(depths) # index 0 is Tank1; 1 is Tank2; 2 is Tank3; 3 is Tank4; index 4 is Tank5; 5 is Tank6\n", 230 | " TANKmin = np.argmin(depths) # index 0 is Tank1; 1 is Tank2; 2 is Tank3; 3 is Tank4; index 4 is Tank5; 5 is Tank6\n", 231 | " #\n", 232 | " V2setting = 0.2366\n", 233 | " V3setting = 0.6508\n", 234 | " V4setting = 0.3523\n", 235 | " V6setting = 0.4303\n", 236 | " # Rule EFD01\n", 237 | " if (depths < 1).all():\n", 238 | " pass\n", 239 | " elif TANKmax == 1:\n", 240 | " V2setting = 1.0\n", 241 | " if TANKmin == 2:\n", 242 | " V3setting = 0.3159\n", 243 | " elif TANKmin == 3:\n", 244 | " V4setting = 0.1894\n", 245 | " else:\n", 246 | " V6setting = 0.1687\n", 247 | " elif TANKmax == 2:\n", 248 | " V3setting = 1.0\n", 249 | " if TANKmin == 1:\n", 250 | " V2setting = 0.1075\n", 251 | " elif TANKmin == 3:\n", 252 | " V4setting = 0.1894\n", 253 | " else:\n", 254 | " V6setting = 0.1687\n", 255 | " elif TANKmax == 3:\n", 256 | " V4setting = 1.0\n", 257 | " if TANKmin == 1:\n", 258 | " V2setting = 0.1075\n", 259 | " elif TANKmin == 2:\n", 260 | " V3setting = 0.3159\n", 261 | " else:\n", 262 | " V6setting = 0.1687\n", 263 | " else:\n", 264 | " V6setting = 1.0\n", 265 | " if TANKmin == 1:\n", 266 | " V2setting = 0.1075\n", 267 | " elif TANKmin == 2:\n", 268 | " V3setting = 0.3159\n", 269 | " else:\n", 270 | " V4setting = 0.1894\n", 271 | " newsettings = np.array([V2setting, V3setting, V4setting, V6setting])\n", 272 | " return newsettings" 273 | ] 274 | }, 275 | { 276 | "cell_type": "code", 277 | "execution_count": 10, 278 | "metadata": { 279 | "ExecuteTime": { 280 | "end_time": "2021-07-21T17:43:23.978765Z", 281 | "start_time": "2021-07-21T17:43:19.756583Z" 282 | } 283 | }, 284 | "outputs": [ 285 | { 286 | "name": "stdout", 287 | "output_type": "stream", 288 | "text": [ 289 | "\n", 290 | " o Retrieving project data" 291 | ] 292 | } 293 | ], 294 | "source": [ 295 | "################################################################################\n", 296 | "# Controlled performance - EFD Control implementation\n", 297 | "################################################################################\n", 298 | "env_controllerEFD = pystorms.scenarios.zeta()\n", 299 | "done = False\n", 300 | "while not done:\n", 301 | " state = env_controllerEFD.state()\n", 302 | " actions = efd_control(state)\n", 303 | " done = env_controllerEFD.step(actions)\n", 304 | "controllerEFD_perf = sum(env_controllerEFD.data_log[\"performance_measure\"])" 305 | ] 306 | }, 307 | { 308 | "cell_type": "code", 309 | "execution_count": 11, 310 | "metadata": { 311 | "ExecuteTime": { 312 | "end_time": "2021-07-21T17:43:24.006783Z", 313 | "start_time": "2021-07-21T17:43:24.003633Z" 314 | } 315 | }, 316 | "outputs": [ 317 | { 318 | "name": "stdout", 319 | "output_type": "stream", 320 | "text": [ 321 | "72280.01583298284\n" 322 | ] 323 | } 324 | ], 325 | "source": [ 326 | "print(controllerEFD_perf)" 327 | ] 328 | }, 329 | { 330 | "cell_type": "code", 331 | "execution_count": null, 332 | "metadata": {}, 333 | "outputs": [], 334 | "source": [] 335 | }, 336 | { 337 | "cell_type": "code", 338 | "execution_count": null, 339 | "metadata": {}, 340 | "outputs": [], 341 | "source": [] 342 | }, 343 | { 344 | "cell_type": "markdown", 345 | "metadata": {}, 346 | "source": [ 347 | "```\n", 348 | "# Configuration file for scenario zeta\n", 349 | "\n", 350 | "# name of scearnio\n", 351 | "name: zeta\n", 352 | "# state definitions\n", 353 | "states:\n", 354 | " - !!python/tuple\n", 355 | " - \"T1\"\n", 356 | " - depthN\n", 357 | " - !!python/tuple\n", 358 | " - \"T2\"\n", 359 | " - depthN\n", 360 | " - !!python/tuple\n", 361 | " - \"T3\"\n", 362 | " - depthN\n", 363 | " - !!python/tuple\n", 364 | " - \"T4\"\n", 365 | " - depthN\n", 366 | " - !!python/tuple\n", 367 | " - \"T5\"\n", 368 | " - depthN\n", 369 | " - !!python/tuple\n", 370 | " - \"T6\"\n", 371 | " - depthN\n", 372 | "\n", 373 | "# Action space\n", 374 | "action_space:\n", 375 | " - \"V2\"\n", 376 | " - \"V3\"\n", 377 | " - \"V4\"\n", 378 | " - \"V6\"\n", 379 | "\n", 380 | "# Performance Targets\n", 381 | "performance_targets:\n", 382 | " # CSOs to river\n", 383 | " - !!python/tuple\n", 384 | " - \"T1\"\n", 385 | " - flooding\n", 386 | " - !!python/tuple\n", 387 | " - \"T2\"\n", 388 | " - flooding\n", 389 | " - !!python/tuple\n", 390 | " - \"T3\"\n", 391 | " - flooding\n", 392 | " - !!python/tuple\n", 393 | " - \"T4\"\n", 394 | " - flooding\n", 395 | " - !!python/tuple\n", 396 | " - \"T5\"\n", 397 | " - flooding\n", 398 | " - !!python/tuple\n", 399 | " - \"CSO8\"\n", 400 | " - flooding\n", 401 | " - !!python/tuple\n", 402 | " - \"CSO10\"\n", 403 | " - flooding\n", 404 | " # CSOs to creek\n", 405 | " - !!python/tuple\n", 406 | " - \"T6\"\n", 407 | " - flooding\n", 408 | " - !!python/tuple\n", 409 | " - \"CSO7\"\n", 410 | " - flooding\n", 411 | " - !!python/tuple\n", 412 | " - \"CSO9\"\n", 413 | " - flooding\n", 414 | " # flow to WWTP\n", 415 | " - !!python/tuple # Conduit that connects upstream to \"Out_to_WWTP\" node\n", 416 | " - \"C14\"\n", 417 | " - \"flow\"\n", 418 | " # control roughness\n", 419 | " - !!python/tuple # flow out of CSO7\n", 420 | " - \"C5\"\n", 421 | " - \"flow\"\n", 422 | " - !!python/tuple # flow out of CSO8\n", 423 | " - \"C15\"\n", 424 | " - \"flow\"\n", 425 | " - !!python/tuple # flow out of CSO9\n", 426 | " - \"C8\"\n", 427 | " - \"flow\"\n", 428 | " - !!python/tuple # flow out of CSO10\n", 429 | " - \"C21\"\n", 430 | " - \"flow\"\n", 431 | " - !!python/tuple # flow out of Tank1\n", 432 | " - \"V1\"\n", 433 | " - \"flow\"\n", 434 | " - !!python/tuple # flow out of Tank2\n", 435 | " - \"V2\"\n", 436 | " - \"flow\"\n", 437 | " - !!python/tuple # flow out of Tank3\n", 438 | " - \"V3\"\n", 439 | " - \"flow\"\n", 440 | " - !!python/tuple # flow out of Tank4\n", 441 | " - \"V4\"\n", 442 | " - \"flow\"\n", 443 | " - !!python/tuple # flow out of Tank5\n", 444 | " - \"V5\"\n", 445 | " - \"flow\"\n", 446 | " - !!python/tuple # flow out of Tank6\n", 447 | " - \"V6\"\n", 448 | " - \"flow\"\n", 449 | "```" 450 | ] 451 | }, 452 | { 453 | "cell_type": "code", 454 | "execution_count": null, 455 | "metadata": {}, 456 | "outputs": [], 457 | "source": [] 458 | } 459 | ], 460 | "metadata": { 461 | "hide_input": false, 462 | "kernelspec": { 463 | "display_name": "Python 3 (ipykernel)", 464 | "language": "python", 465 | "name": "python3" 466 | }, 467 | "language_info": { 468 | "codemirror_mode": { 469 | "name": "ipython", 470 | "version": 3 471 | }, 472 | "file_extension": ".py", 473 | "mimetype": "text/x-python", 474 | "name": "python", 475 | "nbconvert_exporter": "python", 476 | "pygments_lexer": "ipython3", 477 | "version": "3.9.10" 478 | }, 479 | "toc": { 480 | "base_numbering": 1, 481 | "nav_menu": {}, 482 | "number_sections": false, 483 | "sideBar": false, 484 | "skip_h1_title": true, 485 | "title_cell": "Table of Contents", 486 | "title_sidebar": "Contents", 487 | "toc_cell": true, 488 | "toc_position": {}, 489 | "toc_section_display": true, 490 | "toc_window_display": false 491 | }, 492 | "varInspector": { 493 | "cols": { 494 | "lenName": 16, 495 | "lenType": 16, 496 | "lenVar": 40 497 | }, 498 | "kernels_config": { 499 | "python": { 500 | "delete_cmd_postfix": "", 501 | "delete_cmd_prefix": "del ", 502 | "library": "var_list.py", 503 | "varRefreshCmd": "print(var_dic_list())" 504 | }, 505 | "r": { 506 | "delete_cmd_postfix": ") ", 507 | "delete_cmd_prefix": "rm(", 508 | "library": "var_list.r", 509 | "varRefreshCmd": "cat(var_dic_list()) " 510 | } 511 | }, 512 | "types_to_exclude": [ 513 | "module", 514 | "function", 515 | "builtin_function_or_method", 516 | "instance", 517 | "_Feature" 518 | ], 519 | "window_display": false 520 | } 521 | }, 522 | "nbformat": 4, 523 | "nbformat_minor": 4 524 | } 525 | -------------------------------------------------------------------------------- /tutorials/data/BaeOpt.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kLabUM/pystorms/0ae4382a78a0bcad39fece03112bed0192d6dea9/tutorials/data/BaeOpt.gif -------------------------------------------------------------------------------- /tutorials/data/epsilon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kLabUM/pystorms/0ae4382a78a0bcad39fece03112bed0192d6dea9/tutorials/data/epsilon.png -------------------------------------------------------------------------------- /tutorials/data/gamma_sheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kLabUM/pystorms/0ae4382a78a0bcad39fece03112bed0192d6dea9/tutorials/data/gamma_sheet.png -------------------------------------------------------------------------------- /tutorials/data/mpc_rules_beta.csv: -------------------------------------------------------------------------------- 1 | datetime,simtime (hr),R2,P0,W0 2 | 10/8/16 0:00,0,1,OFF,1 3 | 10/8/16 0:15,0.25,1,OFF,1 4 | 10/8/16 0:30,0.5,0.714,OFF,0 5 | 10/8/16 0:45,0.75,1,OFF,0.857 6 | 10/8/16 1:00,1,0.571,ON,0.571 7 | 10/8/16 1:15,1.25,0.429,ON,0.571 8 | 10/8/16 1:30,1.5,1,ON,0.571 9 | 10/8/16 1:45,1.75,1,ON,0 10 | 10/8/16 2:00,2,1,ON,0.286 11 | 10/8/16 2:15,2.25,0.286,ON,0.429 12 | 10/8/16 2:30,2.5,0.286,OFF,0.714 13 | 10/8/16 2:45,2.75,0.429,ON,0.857 14 | 10/8/16 3:00,3,0.714,ON,0.857 15 | 10/8/16 3:15,3.25,0.714,ON,0.857 16 | 10/8/16 3:30,3.5,0.143,OFF,0.857 17 | 10/8/16 3:45,3.75,0.429,OFF,0.143 18 | 10/8/16 4:00,4,0.286,ON,0.714 19 | 10/8/16 4:15,4.25,0.429,OFF,0.857 20 | 10/8/16 4:30,4.5,0.429,ON,0.286 21 | 10/8/16 4:45,4.75,0.143,OFF,0.857 22 | 10/8/16 5:00,5,0.286,OFF,0.714 23 | 10/8/16 5:15,5.25,0.429,ON,0.143 24 | 10/8/16 5:30,5.5,0.429,ON,0.571 25 | 10/8/16 5:45,5.75,0,ON,1 26 | 10/8/16 6:00,6,0.286,OFF,0.571 27 | 10/8/16 6:15,6.25,0.429,ON,0 28 | 10/8/16 6:30,6.5,0.429,ON,0.286 29 | 10/8/16 6:45,6.75,0.143,ON,0.143 30 | 10/8/16 7:00,7,0.714,OFF,0.286 31 | 10/8/16 7:15,7.25,0.429,OFF,0.714 32 | 10/8/16 7:30,7.5,0.429,OFF,0.714 33 | 10/8/16 7:45,7.75,0.429,OFF,0 34 | 10/8/16 8:00,8,0.286,OFF,1 35 | 10/8/16 8:15,8.25,0.286,OFF,0.286 36 | 10/8/16 8:30,8.5,0.429,OFF,1 37 | 10/8/16 8:45,8.75,0,OFF,0.857 38 | 10/8/16 9:00,9,0.286,OFF,0.286 39 | 10/8/16 9:15,9.25,0.429,OFF,1 40 | 10/8/16 9:30,9.5,1,ON,0.857 41 | 10/8/16 9:45,9.75,1,ON,0.286 42 | 10/8/16 10:00,10,0.714,ON,0.857 43 | 10/8/16 10:15,10.25,0.857,OFF,1 44 | 10/8/16 10:30,10.5,1,ON,0.429 45 | 10/8/16 10:45,10.75,0,ON,0.714 46 | 10/8/16 11:00,11,0,ON,0.714 47 | 10/8/16 11:15,11.25,0,OFF,0.714 48 | 10/8/16 11:30,11.5,0,ON,0.429 49 | 10/8/16 11:45,11.75,0,OFF,1 50 | 10/8/16 12:00,12,0,OFF,0.857 51 | 10/8/16 12:15,12.25,0,ON,0.714 52 | 10/8/16 12:30,12.5,0.429,ON,0.857 53 | 10/8/16 12:45,12.75,1,ON,1 54 | 10/8/16 13:00,13,0.143,ON,0.286 55 | 10/8/16 13:15,13.25,0,ON,1 56 | 10/8/16 13:30,13.5,0,OFF,1 57 | 10/8/16 13:45,13.75,0,ON,1 58 | 10/8/16 14:00,14,0,ON,0.714 59 | 10/8/16 14:15,14.25,0,OFF,0 60 | 10/8/16 14:30,14.5,0,ON,0 61 | 10/8/16 14:45,14.75,0.143,OFF,0 62 | 10/8/16 15:00,15,0,OFF,0 63 | 10/8/16 15:15,15.25,0,OFF,0 64 | 10/8/16 15:30,15.5,0,OFF,0.571 65 | 10/8/16 15:45,15.75,0.143,ON,0 66 | 10/8/16 16:00,16,0,ON,0 -------------------------------------------------------------------------------- /tutorials/data/theta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kLabUM/pystorms/0ae4382a78a0bcad39fece03112bed0192d6dea9/tutorials/data/theta.png -------------------------------------------------------------------------------- /tutorials/data/theta_sheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kLabUM/pystorms/0ae4382a78a0bcad39fece03112bed0192d6dea9/tutorials/data/theta_sheet.png -------------------------------------------------------------------------------- /tutorials/pystorms_Example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "8dfb941b-7a51-42fe-ba54-0ecadda2523b", 6 | "metadata": {}, 7 | "source": [ 8 | "# ⛈️ ⛈️ Control of stormwater systems using pystorms ⛈️ ⛈️\n", 9 | "\n", 10 | "## Objective\n", 11 | "This notebook aims to help the users get started with pystorms for prototyping and evaluating the performance of control algorithms.\n", 12 | "\n", 13 | "## pystorms\n", 14 | "pystorms is a curated collection of stormwater networks developed for prototyping and evaluating stormwater control algorithms. It uses pyswmm as its backend for interacting with the EPA-SWMM's simulation engine." 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "id": "15bb4176-c5c3-4812-ba46-432193c4937f", 21 | "metadata": {}, 22 | "outputs": [], 23 | "source": [ 24 | "# Google Colab might need to reinstall pystorms\n", 25 | "!pip install pystorms" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": 1, 31 | "id": "776b4589-8fd3-4bc3-ae27-a4ab7299dbd9", 32 | "metadata": {}, 33 | "outputs": [], 34 | "source": [ 35 | "import pystorms\n", 36 | "import matplotlib.pyplot as plt" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": 2, 42 | "id": "7f47c770-0bc1-4e30-8c7a-d3332175994c", 43 | "metadata": {}, 44 | "outputs": [], 45 | "source": [ 46 | "# RC parms for pertty plots\n", 47 | "plt.rcParams.update({'font.size': 14})\n", 48 | "plt.style.use('seaborn-whitegrid')\n", 49 | "plt.style.use('seaborn-dark-palette')" 50 | ] 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "id": "7561d574-22c5-4310-80f8-eed80ef36b74", 55 | "metadata": {}, 56 | "source": [ 57 | "In this example, we would be using scenario theta\n", 58 | "\n", 59 | "![theta](./theta_sheet.png)\n", 60 | "\n", 61 | "### Model IDs in Scenario Theta\n", 62 | "```yaml\n", 63 | "# name of scearnio \n", 64 | "name: theta\n", 65 | "# state definitions\n", 66 | "states:\n", 67 | " - !!python/tuple \n", 68 | " - P1\n", 69 | " - depthN\n", 70 | " - !!python/tuple\n", 71 | " - P2\n", 72 | " - depthN\n", 73 | "# Action space \n", 74 | "action_space:\n", 75 | " - \"1\"\n", 76 | " - \"2\"\n", 77 | "# Performance Targets\n", 78 | "performance_targets:\n", 79 | " - !!python/tuple\n", 80 | " - \"8\"\n", 81 | " - flow\n", 82 | " - !!python/tuple\n", 83 | " - P1\n", 84 | " - flooding\n", 85 | " - !!python/tuple\n", 86 | " - P2\n", 87 | " - flooding\n", 88 | "```" 89 | ] 90 | }, 91 | { 92 | "cell_type": "markdown", 93 | "id": "7a2d997a-8157-4a0e-9dd1-3193608c5806", 94 | "metadata": {}, 95 | "source": [ 96 | "### Simulating control using pystorms" 97 | ] 98 | }, 99 | { 100 | "cell_type": "code", 101 | "execution_count": 47, 102 | "id": "cff3217f-d853-44e9-a800-1073bc8f6592", 103 | "metadata": {}, 104 | "outputs": [ 105 | { 106 | "name": "stdout", 107 | "output_type": "stream", 108 | "text": [ 109 | "\n", 110 | " o Retrieving project data" 111 | ] 112 | } 113 | ], 114 | "source": [ 115 | "env = pystorms.scenarios.theta()\n", 116 | "done = False" 117 | ] 118 | }, 119 | { 120 | "cell_type": "markdown", 121 | "id": "1bb808b8-7273-4626-94eb-7863bc4688b6", 122 | "metadata": {}, 123 | "source": [ 124 | "pystorms abstacts the control of stormwater networks as scenarios. Each scenario comprises of a stormwater network, an event driver, a set of states and controllable assets, and a performance metric.\n", 125 | "\n", 126 | "```python\n", 127 | "env = pystorms.scenarios.theta()\n", 128 | "```\n", 129 | "\n", 130 | "The above command initalizes the stormwater control scenario and starts the simulation." 131 | ] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "execution_count": 48, 136 | "id": "e942ccd9-5503-4366-84e3-69e17de5fdcc", 137 | "metadata": {}, 138 | "outputs": [], 139 | "source": [ 140 | "while not done:\n", 141 | " # query the current state of the simulation\n", 142 | " state = env.state()\n", 143 | " \n", 144 | " # Initialize random actions\n", 145 | " actions = [1.00, 1.00]\n", 146 | " \n", 147 | " # set the actions and progress the simulation\n", 148 | " done = env.step(actions)" 149 | ] 150 | }, 151 | { 152 | "cell_type": "markdown", 153 | "id": "6f7f1150-325d-447f-b6de-3d63bb108833", 154 | "metadata": {}, 155 | "source": [ 156 | "pystorms also computes the performance metric as it runs through the simulation." 157 | ] 158 | }, 159 | { 160 | "cell_type": "code", 161 | "execution_count": 49, 162 | "id": "52db7bb3-83e0-4060-8f8b-20fdf2b68ad2", 163 | "metadata": {}, 164 | "outputs": [ 165 | { 166 | "data": { 167 | "text/plain": [ 168 | "1630.3422288715237" 169 | ] 170 | }, 171 | "execution_count": 49, 172 | "metadata": {}, 173 | "output_type": "execute_result" 174 | } 175 | ], 176 | "source": [ 177 | "env.performance()" 178 | ] 179 | }, 180 | { 181 | "cell_type": "code", 182 | "execution_count": 19, 183 | "id": "7ced1a00-006e-44b6-a4d4-a9130f3bb041", 184 | "metadata": {}, 185 | "outputs": [ 186 | { 187 | "data": { 188 | "text/plain": [ 189 | "dict_keys(['performance_measure', 'flow', 'flooding', 'simulation_time'])" 190 | ] 191 | }, 192 | "execution_count": 19, 193 | "metadata": {}, 194 | "output_type": "execute_result" 195 | } 196 | ], 197 | "source": [ 198 | "env.data_log.keys()" 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "execution_count": 22, 204 | "id": "06390a87-fe2c-4e62-a47d-1bb42f4ad8eb", 205 | "metadata": {}, 206 | "outputs": [ 207 | { 208 | "data": { 209 | "text/plain": [ 210 | "dict_keys(['8'])" 211 | ] 212 | }, 213 | "execution_count": 22, 214 | "metadata": {}, 215 | "output_type": "execute_result" 216 | } 217 | ], 218 | "source": [ 219 | "env.data_log['flow'].keys()" 220 | ] 221 | }, 222 | { 223 | "cell_type": "code", 224 | "execution_count": 25, 225 | "id": "81701440-61a3-4737-84ca-bc4dbd697a13", 226 | "metadata": {}, 227 | "outputs": [ 228 | { 229 | "data": { 230 | "text/plain": [ 231 | "[4.4933228890095494e-05,\n", 232 | " 4.492085410367944e-05,\n", 233 | " 4.490848420155321e-05,\n", 234 | " 4.4896119169411195e-05,\n", 235 | " 4.4883759016013713e-05,\n", 236 | " 4.487140375564894e-05,\n", 237 | " 4.485905339890831e-05,\n", 238 | " 4.484670795361076e-05,\n", 239 | " 4.483436742480517e-05,\n", 240 | " 4.482484695924919e-05]" 241 | ] 242 | }, 243 | "execution_count": 25, 244 | "metadata": {}, 245 | "output_type": "execute_result" 246 | } 247 | ], 248 | "source": [ 249 | "env.data_log['flow']['8'][-10:]" 250 | ] 251 | }, 252 | { 253 | "cell_type": "markdown", 254 | "id": "b6a1cc8a-f418-4271-b217-30d47036704b", 255 | "metadata": {}, 256 | "source": [ 257 | "### Pro Tip\n", 258 | "data_log is structured so that you can easily export it into pandas for post-processing. " 259 | ] 260 | }, 261 | { 262 | "cell_type": "code", 263 | "execution_count": 17, 264 | "id": "9a84992e-9ece-498b-a4ee-40fde1f3d56e", 265 | "metadata": {}, 266 | "outputs": [], 267 | "source": [ 268 | "import pandas as pd\n", 269 | "flows = pd.DataFrame(index=env.data_log['simulation_time'], data=env.data_log['flow'])\n", 270 | "flooding = pd.DataFrame(index=env.data_log['simulation_time'], data=env.data_log['flooding'])" 271 | ] 272 | }, 273 | { 274 | "cell_type": "code", 275 | "execution_count": 18, 276 | "id": "b4705e44-d94f-438b-9654-08c68b7d3cd9", 277 | "metadata": {}, 278 | "outputs": [ 279 | { 280 | "data": { 281 | "text/html": [ 282 | "
    \n", 283 | "\n", 296 | "\n", 297 | " \n", 298 | " \n", 299 | " \n", 300 | " \n", 301 | " \n", 302 | " \n", 303 | " \n", 304 | " \n", 305 | " \n", 306 | " \n", 307 | " \n", 308 | " \n", 309 | " \n", 310 | " \n", 311 | " \n", 312 | " \n", 313 | " \n", 314 | " \n", 315 | " \n", 316 | " \n", 317 | " \n", 318 | " \n", 319 | " \n", 320 | " \n", 321 | " \n", 322 | " \n", 323 | " \n", 324 | " \n", 325 | " \n", 326 | " \n", 327 | " \n", 328 | " \n", 329 | " \n", 330 | " \n", 331 | " \n", 332 | " \n", 333 | " \n", 334 | " \n", 335 | " \n", 336 | " \n", 337 | " \n", 338 | " \n", 339 | " \n", 340 | " \n", 341 | " \n", 342 | " \n", 343 | " \n", 344 | " \n", 345 | " \n", 346 | " \n", 347 | " \n", 348 | " \n", 349 | " \n", 350 | " \n", 351 | " \n", 352 | " \n", 353 | " \n", 354 | " \n", 355 | " \n", 356 | " \n", 357 | " \n", 358 | " \n", 359 | " \n", 360 | " \n", 361 | "
    P1P2
    2018-02-25 00:00:010.00.0
    2018-02-25 00:00:310.00.0
    2018-02-25 00:01:010.00.0
    2018-02-25 00:01:310.00.0
    2018-02-25 00:02:010.00.0
    .........
    2018-02-28 05:58:140.00.0
    2018-02-28 05:58:440.00.0
    2018-02-28 05:59:140.00.0
    2018-02-28 05:59:440.00.0
    2018-02-28 06:00:000.00.0
    \n", 362 | "

    12578 rows × 2 columns

    \n", 363 | "
    " 364 | ], 365 | "text/plain": [ 366 | " P1 P2\n", 367 | "2018-02-25 00:00:01 0.0 0.0\n", 368 | "2018-02-25 00:00:31 0.0 0.0\n", 369 | "2018-02-25 00:01:01 0.0 0.0\n", 370 | "2018-02-25 00:01:31 0.0 0.0\n", 371 | "2018-02-25 00:02:01 0.0 0.0\n", 372 | "... ... ...\n", 373 | "2018-02-28 05:58:14 0.0 0.0\n", 374 | "2018-02-28 05:58:44 0.0 0.0\n", 375 | "2018-02-28 05:59:14 0.0 0.0\n", 376 | "2018-02-28 05:59:44 0.0 0.0\n", 377 | "2018-02-28 06:00:00 0.0 0.0\n", 378 | "\n", 379 | "[12578 rows x 2 columns]" 380 | ] 381 | }, 382 | "execution_count": 18, 383 | "metadata": {}, 384 | "output_type": "execute_result" 385 | } 386 | ], 387 | "source": [ 388 | "flooding" 389 | ] 390 | }, 391 | { 392 | "cell_type": "markdown", 393 | "id": "abc13bd5-a3af-468e-bb73-e86a0127023b", 394 | "metadata": {}, 395 | "source": [ 396 | "### Exercise 1\n", 397 | "1. Implement a rule based controller for scenario theta that uses the state information. In this sceanario states are the depths in the basins P1 and P2 at every timestep." 398 | ] 399 | }, 400 | { 401 | "cell_type": "code", 402 | "execution_count": null, 403 | "id": "bc1348e5-bdff-45d1-8cc5-153860676bfd", 404 | "metadata": {}, 405 | "outputs": [], 406 | "source": [ 407 | "env = pystorms.scenarios.theta()\n", 408 | "done = False\n", 409 | "\n", 410 | "while not done:\n", 411 | " # query the current state of the simulation\n", 412 | " state = env.state()\n", 413 | " \n", 414 | " # your control logic goes here\n", 415 | " \n", 416 | " \n", 417 | " # set the actions and progress the simulation\n", 418 | " done = env.step()" 419 | ] 420 | }, 421 | { 422 | "cell_type": "markdown", 423 | "id": "3960fe10-3ce2-4574-b332-5e5316d31b3e", 424 | "metadata": {}, 425 | "source": [ 426 | "2. Plot the controlled and uncontrolled flows going into the outlet.\n", 427 | "\n", 428 | "Note: outlet flows are logged in `env.data_log`" 429 | ] 430 | }, 431 | { 432 | "cell_type": "markdown", 433 | "id": "6a98d255-d6a2-4a4d-a3af-5f819cd63b01", 434 | "metadata": {}, 435 | "source": [ 436 | "### Exercise 2\n", 437 | "\n", 438 | "Implement a rule based to controller that achieves a performance of less that 1000. Peformance metric in scenario theta is computed based on the flooding and flows going into the outlet.\n", 439 | "Uncontrolled performance(i.e., when the valves are completly open) is 1630.34\n", 440 | "\n", 441 | "Tip 💡: Make sure your rule-based controller is not causing flooding in the basins as it tries to achive the desired performance." 442 | ] 443 | }, 444 | { 445 | "cell_type": "markdown", 446 | "id": "9b694642-7cf8-4094-ab6b-a0eb0d8b34f3", 447 | "metadata": {}, 448 | "source": [ 449 | "### Bonus Question 💁\n", 450 | "\n", 451 | "Implement a controller that achieves a performance metric of less than 500." 452 | ] 453 | } 454 | ], 455 | "metadata": { 456 | "kernelspec": { 457 | "display_name": "pystorms-tutorial", 458 | "language": "python", 459 | "name": "pystorms-tutorial" 460 | }, 461 | "language_info": { 462 | "codemirror_mode": { 463 | "name": "ipython", 464 | "version": 3 465 | }, 466 | "file_extension": ".py", 467 | "mimetype": "text/x-python", 468 | "name": "python", 469 | "nbconvert_exporter": "python", 470 | "pygments_lexer": "ipython3", 471 | "version": "3.9.13" 472 | } 473 | }, 474 | "nbformat": 4, 475 | "nbformat_minor": 5 476 | } 477 | -------------------------------------------------------------------------------- /tutorials/pystorms_appendix_introduction.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kLabUM/pystorms/0ae4382a78a0bcad39fece03112bed0192d6dea9/tutorials/pystorms_appendix_introduction.pdf -------------------------------------------------------------------------------- /tutorials/pystorms_paper.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "bfa19710-b275-4807-a411-1e2c3e817b00", 6 | "metadata": {}, 7 | "source": [ 8 | "# Getting started with pystorms\n", 9 | "\n", 10 | "pystorms works on Windows, OSX, and Linux based operating systems. It requires Python 3.6+ and can be installed via `pip`" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "id": "d6e1ff46-15f1-4a59-a577-7f2771491ab5", 16 | "metadata": {}, 17 | "source": [ 18 | "```bash\n", 19 | "pip install pystorms\n", 20 | "```\n", 21 | "\n", 22 | "If you haven't installed Python packages before, please refer to https://packaging.python.org/en/latest/tutorials/installing-packages/" 23 | ] 24 | }, 25 | { 26 | "cell_type": "markdown", 27 | "id": "f5446a3d-66b5-4ac3-9993-aa01513e30ee", 28 | "metadata": {}, 29 | "source": [ 30 | "## Simulating a stormwater control scenario\n", 31 | "\n", 32 | "`pystorms` has seven scenarios that can used for evaluating and prototyping stormwater control algorithms. This example demonstrates how `scenario theta` for testing a rule-based control algorithm." 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": 1, 38 | "id": "d2d8b9fb-676c-4b1f-bb2d-d64cb86b1912", 39 | "metadata": {}, 40 | "outputs": [ 41 | { 42 | "name": "stdout", 43 | "output_type": "stream", 44 | "text": [ 45 | "\n", 46 | " o Retrieving project data\n", 47 | " o Retrieving project data\n", 48 | "\n", 49 | "Peformance of the uncontrolled scenario theta: 1630.3422288715237\n", 50 | "Peformance of the controller on scenario theta: 1125.8162370076384\n" 51 | ] 52 | } 53 | ], 54 | "source": [ 55 | "import pystorms\n", 56 | "\n", 57 | "\n", 58 | "def rule_based_controller(depths):\n", 59 | " \"\"\"\n", 60 | " Determines control actions based on depths in the basin\n", 61 | "\n", 62 | " Parameters\n", 63 | " ----------\n", 64 | " depths : numpy.ndarray\n", 65 | " Depth in the basins of the stormwater network at current timestep.\n", 66 | "\n", 67 | " Returns\n", 68 | " -------\n", 69 | " actions : numpy.ndarray\n", 70 | " Gate positions to set at the outlets of basins in the stormwater network at current timestep.\n", 71 | "\n", 72 | "\n", 73 | " Examples\n", 74 | " --------\n", 75 | " >>> depths = [1.5, 0.25]\n", 76 | " >>> rule_based_controller(depths)\n", 77 | " [0.5, 1.0]\n", 78 | " \"\"\"\n", 79 | " actions = [1.0, 1.0]\n", 80 | " # gate positions in SWMM are between 0.0 to 1.0\n", 81 | " # 0.0 being completely closed and 1.0 is fully open\n", 82 | " for basin_index in range(0, len(depths)):\n", 83 | " if depths[basin_index] > 0.5:\n", 84 | " actions[basin_index] = 0.5\n", 85 | " else:\n", 86 | " actions[basin_index] = 0.0\n", 87 | " return actions\n", 88 | "\n", 89 | "scenario_theta_uncontrolled = pystorms.scenarios.theta()\n", 90 | "done = False\n", 91 | "while not done:\n", 92 | " # done gets set to True once the simulation ends else it is set to False\n", 93 | " # if no argument is passed to the step function, it sets the gate positions to completely open\n", 94 | " done = scenario_theta_uncontrolled.step()\n", 95 | "\n", 96 | " \n", 97 | "scenario_theta_controlled = pystorms.scenarios.theta()\n", 98 | "done = False\n", 99 | "while not done:\n", 100 | " # get the current state in the stormwater network\n", 101 | " # in this scenario, state is the depth in the two controlled basins of the stormwater network\n", 102 | " state = scenario_theta_controlled.state()\n", 103 | " \n", 104 | " # determine the gate positions to set at the outlets of the two controlled basins\n", 105 | " actions = rule_based_controller(depths=state)\n", 106 | " \n", 107 | " # set the gate positions and progress the simulation\n", 108 | " # done gets set to True once the simulation ends else it is set to False\n", 109 | " done = scenario_theta_controlled.step(actions)\n", 110 | " \n", 111 | "# performance of the control algorithm for scenario theta can be queried using the performance function call\n", 112 | "print(f\"\\n\\nPeformance of the uncontrolled scenario theta: {scenario_theta_uncontrolled.performance()}\")\n", 113 | "print(f\"Peformance of the controller on scenario theta: {scenario_theta_controlled.performance()}\")" 114 | ] 115 | }, 116 | { 117 | "cell_type": "markdown", 118 | "id": "57563f4e-000a-4580-96f2-288c3537cbd5", 119 | "metadata": {}, 120 | "source": [ 121 | "### `pystorms` API explained" 122 | ] 123 | }, 124 | { 125 | "cell_type": "markdown", 126 | "id": "93d6135a-adb6-4576-95bc-ff048a3f4d5a", 127 | "metadata": {}, 128 | "source": [ 129 | "```python\n", 130 | " = pystorms.scenarios.()\n", 131 | "```\n", 132 | "\n", 133 | "`pystorms` treats each scenario as a class. The seven scenarios in pystorms can be invoked by replacing the scenario name by `theta`, `alpha`, `beta`, `gamma`, `delta`, and `epsilon`. Once the above statement is invoked, it will intialize the scenario, start the stormwater simulation, and hand the control over to the user. Users can then use this to class object to control the simulation." 134 | ] 135 | }, 136 | { 137 | "cell_type": "markdown", 138 | "id": "9fdead38-2551-4b3c-9297-d7117bf88322", 139 | "metadata": {}, 140 | "source": [ 141 | "```python\n", 142 | "state = .state()\n", 143 | "```\n", 144 | "\n", 145 | "`pystorms` scenario class object's `state` method can be used to query state of the stormwater network. This is a `numpy.ndarray`. The attributes in this array can be found in the scenario `.yaml` file. Scenario theta's configuration yaml is below.\n", 146 | "\n", 147 | "```yaml\n", 148 | "# Configuration file for scenario theta \n", 149 | "# name of scearnio \n", 150 | "name: theta\n", 151 | "# state definitions\n", 152 | "states:\n", 153 | " - !!python/tuple \n", 154 | " - P1\n", 155 | " - depthN\n", 156 | " - !!python/tuple\n", 157 | " - P2\n", 158 | " - depthN\n", 159 | "# Action space \n", 160 | "action_space:\n", 161 | " - \"1\"\n", 162 | " - \"2\"\n", 163 | "# Performance Targets\n", 164 | "performance_targets:\n", 165 | " - !!python/tuple\n", 166 | " - \"8\"\n", 167 | " - flow\n", 168 | " - !!python/tuple\n", 169 | " - P1\n", 170 | " - flooding\n", 171 | " - !!python/tuple\n", 172 | " - P2\n", 173 | " - flooding\n", 174 | "```\n", 175 | "\n", 176 | "`state` contains node depth in basin `P1` and node depth in basin `P2` as the first and second elements in the state array. " 177 | ] 178 | }, 179 | { 180 | "cell_type": "markdown", 181 | "id": "87dd8567-60bf-45ae-84af-4f04fc435656", 182 | "metadata": {}, 183 | "source": [ 184 | "```python\n", 185 | "done = .step(actions)\n", 186 | "```\n", 187 | "\n", 188 | "`step(actions)` function implements the control actions, if actions are passed as an argument or else sets the controlled gates to completely open, and progresses the simulation one timestep. If the simulation ends it returns True or else it returns False. " 189 | ] 190 | } 191 | ], 192 | "metadata": { 193 | "kernelspec": { 194 | "display_name": "pystorms", 195 | "language": "python", 196 | "name": "pystorms" 197 | }, 198 | "language_info": { 199 | "codemirror_mode": { 200 | "name": "ipython", 201 | "version": 3 202 | }, 203 | "file_extension": ".py", 204 | "mimetype": "text/x-python", 205 | "name": "python", 206 | "nbconvert_exporter": "python", 207 | "pygments_lexer": "ipython3", 208 | "version": "3.9.13" 209 | } 210 | }, 211 | "nbformat": 4, 212 | "nbformat_minor": 5 213 | } 214 | -------------------------------------------------------------------------------- /tutorials/rule_based_controller.py: -------------------------------------------------------------------------------- 1 | import pystorms 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | 6 | def controller(state): 7 | actions = np.zeros((2)) 8 | for i in range(0, 2): 9 | if state[i] > 0.50: 10 | actions[i] = 0.50 11 | else: 12 | actions[i] = 0.00 13 | return actions 14 | env = pystorms.scenarios.theta() 15 | done = False 16 | 17 | while not done: 18 | state = env.state() 19 | actions = controller(state) 20 | done = env.step(actions) 21 | 22 | print(env.performance()) 23 | -------------------------------------------------------------------------------- /tutorials/scenario_theta.py: -------------------------------------------------------------------------------- 1 | import pystorms 2 | import numpy as np 3 | import pandas as pd 4 | import matplotlib.pyplot as plt 5 | 6 | 7 | def controller( 8 | depths: np.ndarray, 9 | tolerance: float = 0.50, 10 | LAMBDA: float = 0.50, 11 | MAX_DEPTH: float = 2.0, 12 | ) -> np.ndarray: 13 | """ 14 | Implementation of equal-filling controller 15 | 16 | Parameters 17 | ---------- 18 | depths: np.ndarray 19 | depths in the basins being controlled 20 | tolerance: float 21 | parameter to control oscillations in the actions 22 | LAMBDA: float 23 | parameter for tuning controller's response 24 | MAX_DEPTH: float 25 | max depth in the basins to compute filling degree 26 | 27 | Returns 28 | ------- 29 | actions: np.ndarray 30 | control actions 31 | """ 32 | 33 | # Compute the filling degree 34 | f = depths / MAX_DEPTH 35 | 36 | # Estimate the average filling degree 37 | f_mean = np.mean(f) 38 | 39 | # Compute psi 40 | N = len(depths) 41 | psi = np.zeros(N) 42 | for i in range(0, N): 43 | psi[i] = f[i] - f_mean 44 | if psi[i] < 0.0 - tolerance: 45 | psi[i] = 0.0 46 | elif psi[i] >= 0.0 - tolerance and psi[i] <= 0.0 + tolerance: 47 | psi[i] = f_mean 48 | 49 | # Assign valve positions 50 | actions = np.zeros(N) 51 | for i in range(0, N): 52 | if depths[i] > 0.0: 53 | k = 1.0 / np.sqrt(2 * 9.81 * depths[i]) 54 | action = k * LAMBDA * psi[i] / np.sum(psi) 55 | actions[i] = min(1.0, action) 56 | return actions 57 | 58 | 59 | def rule_based_controller(state): 60 | actions = np.zeros(2) 61 | actions = state/2.0 62 | return actions 63 | 64 | 65 | # Configure matplotlib style to make pretty plots 66 | plt.style.use("seaborn-v0_8-whitegrid") 67 | 68 | # Run simulation with gates open 69 | env_uncontrolled = pystorms.scenarios.theta() 70 | 71 | # Update the datalog to append states 72 | env_uncontrolled.data_log["depthN"] = {} 73 | env_uncontrolled.data_log["depthN"]['P1'] = [] 74 | env_uncontrolled.data_log["depthN"]['P2'] = [] 75 | 76 | done = False 77 | while not done: 78 | done = env_uncontrolled.step() 79 | 80 | # convert flows as a dataframe for easy plotting and timeseries handling 81 | uncontrolled_flows = pd.DataFrame.from_dict(env_uncontrolled.data_log["flow"]) 82 | uncontrolled_flows.index = env_uncontrolled.data_log["simulation_time"] 83 | uncontrolled_flows = uncontrolled_flows.resample("15min").mean() 84 | uncontrolled_flows = uncontrolled_flows.rename(columns={"8": "Uncontrolled"}) 85 | 86 | uncontrolled_depth = pd.DataFrame.from_dict(env_uncontrolled.data_log["depthN"]) 87 | uncontrolled_depth.index = env_uncontrolled.data_log["simulation_time"] 88 | uncontrolled_depth = uncontrolled_depth.resample("15min").mean() 89 | 90 | 91 | # Controlled simulation 92 | env_controlled = pystorms.scenarios.theta() 93 | done = False 94 | 95 | # Update the datalog to append states 96 | env_controlled.data_log["depthN"] = {} 97 | env_controlled.data_log["depthN"]['P1'] = [] 98 | env_controlled.data_log["depthN"]['P2'] = [] 99 | 100 | while not done: 101 | state = env_controlled.state() 102 | # Note the difference between controlled and uncontrolled simulation 103 | actions = controller(state) 104 | done = env_controlled.step(actions) 105 | 106 | controlled_flows = pd.DataFrame.from_dict(env_controlled.data_log["flow"]) 107 | controlled_flows.index = env_controlled.data_log["simulation_time"] 108 | controlled_flows = controlled_flows.resample("15min").mean() 109 | controlled_flows = controlled_flows.rename(columns={"8": "Controlled"}) 110 | 111 | controlled_depth = pd.DataFrame.from_dict(env_controlled.data_log["depthN"]) 112 | controlled_depth.index = env_controlled.data_log["simulation_time"] 113 | controlled_depth = controlled_depth.resample("15min").mean() 114 | 115 | # Rule based controller 116 | env_rule_controlled = pystorms.scenarios.theta() 117 | done = False 118 | 119 | # Update the datalog to append states 120 | env_rule_controlled.data_log["depthN"] = {} 121 | env_rule_controlled.data_log["depthN"]['P1'] = [] 122 | env_rule_controlled.data_log["depthN"]['P2'] = [] 123 | 124 | while not done: 125 | state = env_rule_controlled.state() 126 | actions = rule_based_controller(state) 127 | done = env_rule_controlled.step(actions) 128 | 129 | env_rule_controlled_flows = pd.DataFrame.from_dict(env_rule_controlled.data_log["flow"]) 130 | env_rule_controlled_flows.index = env_rule_controlled.data_log["simulation_time"] 131 | env_rule_controlled_flows = env_rule_controlled_flows.resample("15min").mean() 132 | env_rule_controlled_flows = env_rule_controlled_flows.rename(columns={"8": "Rule-baseed Controller"}) 133 | 134 | env_rule_controlled_depth = pd.DataFrame.from_dict(env_rule_controlled.data_log["depthN"]) 135 | env_rule_controlled_depth.index = env_rule_controlled.data_log["simulation_time"] 136 | env_rule_controlled_depth = env_rule_controlled_depth.resample("15min").mean() 137 | 138 | 139 | fig, ax = plt.subplots(1, 3, sharey=True) 140 | controlled_depth[['P1']].plot(ax=ax[0]) 141 | uncontrolled_depth[['P1']].plot(ax=ax[0]) 142 | env_rule_controlled_depth[['P1']].plot(ax=ax[0]) 143 | 144 | controlled_depth[['P2']].plot(ax=ax[1]) 145 | uncontrolled_depth[['P2']].plot(ax=ax[1]) 146 | env_rule_controlled_depth[['P2']].plot(ax=ax[1]) 147 | 148 | controlled_flows.plot(ax=ax[2]) 149 | uncontrolled_flows.plot(ax=ax[2]) 150 | env_rule_controlled_flows.plot(ax=ax[2]) 151 | 152 | fig.set_size_inches(8.0, 3.0) 153 | plt.savefig("scenario_theta.svg", dpi=1000) 154 | --------------------------------------------------------------------------------