├── .gitattributes ├── .github └── workflows │ └── build-wheels.yml ├── .gitignore ├── .gitmodules ├── README.md ├── epanet-module ├── LICENSE ├── README.md ├── epamodule.py ├── example2.inp ├── example2.py ├── example3.inp ├── outbin.py ├── test_outbin.py └── tester.py ├── epanet_python ├── appveyor.yml ├── before_build.bat ├── epanet_python │ ├── output │ │ ├── epanet │ │ │ └── output │ │ │ │ ├── __init__.py │ │ │ │ └── output.i │ │ ├── setup.py │ │ └── tests │ │ │ ├── data │ │ │ └── net1.out │ │ │ └── test_output.py │ └── toolkit │ │ ├── epanet │ │ └── toolkit │ │ │ ├── __init__.py │ │ │ └── toolkit.i │ │ ├── setup.py │ │ └── tests │ │ ├── data │ │ ├── Net1.inp │ │ └── test_warnings.inp │ │ └── test_toolkit.py ├── requirements.txt ├── setup.cfg ├── setup.py └── tox.ini └── owa-epanet ├── CMakeLists.txt ├── LICENSE ├── MANIFEST.in ├── README.md ├── build_owa-epanet.bat ├── packages └── epanet │ └── __init__.py ├── pyproject.toml ├── scripts └── clean.sh ├── setup.py ├── test ├── Pipfile ├── README.md ├── __init__.py ├── data │ ├── __init__.py │ └── example_1.inp ├── test-lib.py └── test_owa_epanet.py └── wrapper ├── output.i └── toolkit.i /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.sh text eol=lf 3 | -------------------------------------------------------------------------------- /.github/workflows/build-wheels.yml: -------------------------------------------------------------------------------- 1 | name: Build wheels 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build_wheels: 8 | name: Build wheel for cp${{ matrix.python }}-${{ matrix.platform_id }} 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | os: [ windows-latest, ubuntu-latest, macos-latest ] 14 | python: [ 36, 37, 38, 39, 310] 15 | bitness: [ 32, 64 ] 16 | include: 17 | # Run 32 and 64 bit version in parallel for Linux and Windows 18 | - os: windows-latest 19 | bitness: 64 20 | platform_id: win_amd64 21 | - os: windows-latest 22 | bitness: 32 23 | platform_id: win32 24 | - os: ubuntu-latest 25 | bitness: 64 26 | platform_id: manylinux_x86_64 27 | - os: macos-latest 28 | bitness: 64 29 | platform_id: macosx_x86_64 30 | exclude: 31 | - os: macos-latest 32 | bitness: 32 33 | # This build was broken on OpenMP so is excluded for now 34 | - os: ubuntu-latest 35 | bitness: 32 36 | 37 | steps: 38 | - name: Checkout repo 39 | uses: actions/checkout@v2 40 | with: 41 | submodules: true 42 | 43 | - name: Install Python 44 | uses: actions/setup-python@v2 45 | 46 | - uses: ilammy/msvc-dev-cmd@v1 47 | if: startsWith(matrix.os, 'windows') 48 | 49 | - name: Install cibuildwheel 50 | run: python -m pip install cibuildwheel==2.3.1 51 | 52 | - name: Build wheels 53 | run: python -m cibuildwheel --output-dir wheelhouse owa-epanet 54 | env: 55 | CIBW_BUILD: cp${{ matrix.python }}-${{ matrix.platform_id }} 56 | CIBW_BEFORE_ALL_LINUX: git submodule update --init && yum install swig -y 57 | CIBW_BEFORE_ALL_WINDOWS: git submodule update && choco install swig 58 | CIBW_BEFORE_ALL_MACOS: git submodule update && brew install swig ninja libomp 59 | CIBW_BEFORE_BUILD: pip install scikit-build==0.11.1 cmake==3.18.4 60 | CIBW_BUILD_VERBOSITY: 1 61 | CIBW_TEST_COMMAND: pytest {package} 62 | CIBW_BEFORE_TEST: pip install scikit-build==0.11.1 cmake==3.18.4 63 | CIBW_TEST_REQUIRES: pytest 64 | CIBW_TEST_SKIP: "*-win32 *-manylinux_i686" 65 | 66 | - name: Store artifacts 67 | uses: actions/upload-artifact@v2 68 | with: 69 | path: ./wheelhouse/*.whl 70 | 71 | 72 | build_sdist: 73 | name: Build source distribution 74 | runs-on: ubuntu-latest 75 | steps: 76 | - uses: actions/checkout@v2 77 | with: 78 | submodules: true 79 | 80 | - name: Fix submodules 81 | run: | 82 | cd owa-epanet 83 | git submodule update 84 | 85 | - uses: actions/setup-python@v2 86 | name: Install Python 87 | with: 88 | python-version: '3.8' 89 | 90 | - name: Install dependencies 91 | run: | 92 | sudo apt update 93 | sudo apt install swig -y 94 | pip install scikit-build==0.11.1 cmake==3.18.4 95 | 96 | - name: Build sdist 97 | run: | 98 | cd owa-epanet 99 | python setup.py sdist 100 | 101 | - uses: actions/upload-artifact@v2 102 | with: 103 | path: owa-epanet/dist/*.tar.gz 104 | 105 | upload_pypi: 106 | needs: [ build_wheels, build_sdist ] 107 | runs-on: ubuntu-latest 108 | 109 | steps: 110 | - uses: actions/download-artifact@v2 111 | with: 112 | name: artifact 113 | path: dist 114 | 115 | - uses: pypa/gh-action-pypi-publish@v1.4.2 116 | with: 117 | user: __token__ 118 | password: ${{ secrets.PYPI_API_TOKEN }} 119 | skip_existing: true 120 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | 59 | # PyCharm 60 | .idea/ 61 | 62 | # Eclipse 63 | .settings/ 64 | .project 65 | .pydevproject 66 | 67 | # Testing 68 | .pytest_cache/ 69 | epanet_python/toolkit/tests/data/test.* 70 | epanet_python/toolkit/tests/data/test_reopen.inp 71 | epanet_python/toolkit/tests/data/test_warnings.rpt 72 | 73 | # Wrapped libraries 74 | *.h 75 | *.dll 76 | *.lib 77 | buildlib/ 78 | 79 | # SWIG wrappers 80 | output.py 81 | output_wrap.c 82 | toolkit.py 83 | toolkit_wrap.c 84 | MANIFEST 85 | owa-epanet/dist/ 86 | owa-epanet/_skbuild/ 87 | 88 | *.so 89 | *.dylib 90 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "owa-epanet/EPANET"] 2 | path = owa-epanet/EPANET 3 | url = https://github.com/OpenWaterAnalytics/EPANET.git 4 | branch = dev 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # epanet-python 2 | The home for Python packages related to the EPANET engine. 3 | 4 | 5 | ## Contents 6 | * owa-epanet - the thinnest of SWIG-generated wrappers for EPANET. Updated frequently alongside OWA:EPANET. 7 | * epanet-module - A ctypes based wrapper for the EPANET Toolkit (with support for reading EPANET binary output files). 8 | * epanet-python - SWIG based wrappers for the EPANET Toolkit and Output libraries. Deprecated. 9 | 10 | 11 | ## Motivation 12 | These Python wrappers for EPANET are available to developers interested in building higher level functionality on top. 13 | 14 | ## Which wrapper to use 15 | This depends on user preference. Using a ctypes may have certain advantages. Alternatively, starting with an auto-generated python API wrapper is a good foundation for building more abstractions, and SWIG-wrapping means that the Python package gets automatically updated whenever new core features are available. 16 | 17 | ## Contributing 18 | There are many ways for those interested in contributing to participate - providing software development support, helping with documentation, finding bugs, or contributing feature requests. Feel free to get involved! Just open an issue or chat with others on the [community forum](http://community.wateranalytics.org). 19 | -------------------------------------------------------------------------------- /epanet-module/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Open Water Analytics 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /epanet-module/README.md: -------------------------------------------------------------------------------- 1 | # epanet-python 2 | python wrapper for epanet library 3 | Works with both Python 2.7 and 3.x 4 | 5 | ## epamodule 6 | A Python module that permits Epanet toolkit library calls from pure Python programs. 7 | Dropped interface generated by ctypesgen from epanet2.h (beacause not working with 2.x and 3.x) 8 | 9 | Example2.py is example2.c from toolkit help rewritten in Python as example of epamodule use. 10 | 11 | ## outbin 12 | Python module for reading EpanetBinaryOutputFiles in OO mode 13 | See help of class EpanetOutBin. 14 | At present it is a self contained pure Py module: a partial rewriting is planned 15 | in a near future for use of outputapi C library. 16 | -------------------------------------------------------------------------------- /epanet-module/epamodule.py: -------------------------------------------------------------------------------- 1 | """Python EpanetToolkit interface 2 | 3 | added function ENsimtime""" 4 | 5 | 6 | import ctypes 7 | import platform 8 | import datetime 9 | 10 | _plat= platform.system() 11 | if _plat=='Linux': 12 | _lib = ctypes.CDLL("libepanet2.so.2") 13 | elif _plat=='Windows': 14 | try: 15 | # if epanet2.dll compiled with __cdecl (as in OpenWaterAnalytics) 16 | _lib = ctypes.CDLL("epamodule\epanet2.dll") 17 | _lib.ENgetversion(ctypes.byref(ctypes.c_int())) 18 | except ValueError: 19 | # if epanet2.dll compiled with __stdcall (as in EPA original DLL) 20 | try: 21 | _lib = ctypes.windll.epanet2 22 | _lib.ENgetversion(ctypes.byref(ctypes.c_int())) 23 | except ValueError: 24 | raise Exception("epanet2.dll not suitable") 25 | 26 | else: 27 | Exception('Platform '+ _plat +' unsupported (not yet)') 28 | 29 | 30 | _current_simulation_time= ctypes.c_long() 31 | 32 | _max_label_len= 32 33 | _err_max_char= 80 34 | 35 | 36 | 37 | 38 | 39 | def ENepanet(nomeinp, nomerpt='', nomebin='', vfunc=None): 40 | """Runs a complete EPANET simulation. 41 | 42 | Arguments: 43 | nomeinp: name of the input file 44 | nomerpt: name of an output report file 45 | nomebin: name of an optional binary output file 46 | vfunc : pointer to a user-supplied function which accepts a character string as its argument.""" 47 | if vfunc is not None: 48 | CFUNC = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_char_p) 49 | callback= CFUNC(vfunc) 50 | else: 51 | callback= None 52 | ierr= _lib.ENepanet(ctypes.c_char_p(nomeinp.encode()), 53 | ctypes.c_char_p(nomerpt.encode()), 54 | ctypes.c_char_p(nomebin.encode()), 55 | callback) 56 | if ierr!=0: raise ENtoolkitError(ierr) 57 | 58 | 59 | def ENopen(nomeinp, nomerpt='', nomebin=''): 60 | """Opens the Toolkit to analyze a particular distribution system 61 | 62 | Arguments: 63 | nomeinp: name of the input file 64 | nomerpt: name of an output report file 65 | nomebin: name of an optional binary output file 66 | """ 67 | ierr= _lib.ENopen(ctypes.c_char_p(nomeinp.encode()), 68 | ctypes.c_char_p(nomerpt.encode()), 69 | ctypes.c_char_p(nomebin.encode())) 70 | if ierr!=0: 71 | raise ENtoolkitError(ierr) 72 | 73 | 74 | def ENclose(): 75 | """Closes down the Toolkit system (including all files being processed)""" 76 | ierr= _lib.ENclose() 77 | if ierr!=0: raise ENtoolkitError(ierr) 78 | 79 | 80 | def ENgetnodeindex(nodeid): 81 | """Retrieves the index of a node with a specified ID. 82 | 83 | Arguments: 84 | nodeid: node ID label""" 85 | j= ctypes.c_int() 86 | ierr= _lib.ENgetnodeindex(ctypes.c_char_p(nodeid.encode()), ctypes.byref(j)) 87 | if ierr!=0: raise ENtoolkitError(ierr) 88 | return j.value 89 | 90 | 91 | def ENgetnodeid(index): 92 | """Retrieves the ID label of a node with a specified index. 93 | 94 | Arguments: 95 | index: node index""" 96 | label = ctypes.create_string_buffer(_max_label_len) 97 | ierr= _lib.ENgetnodeid(index, ctypes.byref(label)) 98 | if ierr!=0: raise ENtoolkitError(ierr) 99 | return label.value 100 | 101 | def ENsettitle(line1,line2,line3): 102 | """Set inp file title 103 | 104 | Arguments: 105 | line1: line 1 of 3 inp title lines 106 | line2: line 2 of 3 inp title lines 107 | line3: line 3 of 3 inp title lines""" 108 | ierr = _lib.ENsettitle(ctypes.c_char_p(line1.encode()), 109 | ctypes.c_char_p(line2.encode()), 110 | ctypes.c_char_p(line3.encode())) 111 | if ierr!=0: raise ENtoolkitError(ierr) 112 | 113 | 114 | def ENsetflowunits(units_code): 115 | """Set flow units 116 | 117 | Arguments: 118 | units_code: int code for unit type: 119 | EN_CFS 0 cubic feet per second 120 | EN_GPM 1 gallons per minute 121 | EN_MGD 2 million gallons per day 122 | EN_IMGD 3 Imperial mgd 123 | EN_AFD 4 acre-feet per day 124 | EN_LPS 5 liters per second 125 | EN_LPM 6 liters per minute 126 | EN_MLD 7 million liters per day 127 | EN_CMH 8 cubic meters per hour 128 | EN_CMD 9 cubic meters per day 129 | """ 130 | ierr = _lib.ENsetflowunits(ctypes.c_int(units_code)) 131 | if ierr != 0: raise ENtoolkitError(ierr) 132 | 133 | 134 | def ENgetnodetype(index): 135 | """Retrieves the node-type code for a specific node. 136 | 137 | Arguments: 138 | index: node index""" 139 | j= ctypes.c_int() 140 | ierr= _lib.ENgetnodetype(index, ctypes.byref(j)) 141 | if ierr!=0: raise ENtoolkitError(ierr) 142 | return j.value 143 | 144 | 145 | def ENgetnodevalue(index, paramcode): 146 | """Retrieves the value of a specific node parameter. 147 | 148 | Arguments: 149 | index: node index 150 | paramcode: Node parameter codes consist of the following constants: 151 | EN_ELEVATION Elevation 152 | EN_BASEDEMAND ** Base demand 153 | EN_PATTERN ** Demand pattern index 154 | EN_EMITTER Emitter coeff. 155 | EN_INITQUAL Initial quality 156 | EN_SOURCEQUAL Source quality 157 | EN_SOURCEPAT Source pattern index 158 | EN_SOURCETYPE Source type (See note below) 159 | EN_TANKLEVEL Initial water level in tank 160 | EN_DEMAND * Actual demand 161 | EN_HEAD * Hydraulic head 162 | EN_PRESSURE * Pressure 163 | EN_QUALITY * Actual quality 164 | EN_SOURCEMASS * Mass flow rate per minute of a chemical source 165 | * computed values) 166 | ** primary demand category is last on demand list 167 | 168 | The following parameter codes apply only to storage tank nodes: 169 | EN_INITVOLUME Initial water volume 170 | EN_MIXMODEL Mixing model code (see below) 171 | EN_MIXZONEVOL Inlet/Outlet zone volume in a 2-compartment tank 172 | EN_TANKDIAM Tank diameter 173 | EN_MINVOLUME Minimum water volume 174 | EN_VOLCURVE Index of volume versus depth curve (0 if none assigned) 175 | EN_MINLEVEL Minimum water level 176 | EN_MAXLEVEL Maximum water level 177 | EN_MIXFRACTION Fraction of total volume occupied by the inlet/outlet zone in a 2-compartment tank 178 | EN_TANK_KBULK Bulk reaction rate coefficient""" 179 | j= ctypes.c_float() 180 | ierr= _lib.ENgetnodevalue(index, paramcode, ctypes.byref(j)) 181 | if ierr!=0: raise ENtoolkitError(ierr) 182 | return j.value 183 | 184 | 185 | ##------ 186 | def ENgetlinkindex(linkid): 187 | """Retrieves the index of a link with a specified ID. 188 | 189 | Arguments: 190 | linkid: link ID label""" 191 | j= ctypes.c_int() 192 | ierr= _lib.ENgetlinkindex(ctypes.c_char_p(linkid.encode()), ctypes.byref(j)) 193 | if ierr!=0: raise ENtoolkitError(ierr) 194 | return j.value 195 | 196 | 197 | def ENgetlinkid(index): 198 | """Retrieves the ID label of a link with a specified index. 199 | 200 | Arguments: 201 | index: link index""" 202 | label = ctypes.create_string_buffer(_max_label_len) 203 | ierr= _lib.ENgetlinkid(index, ctypes.byref(label)) 204 | if ierr!=0: raise ENtoolkitError(ierr) 205 | return label.value 206 | 207 | 208 | def ENgetlinktype(index): 209 | """Retrieves the link-type code for a specific link. 210 | 211 | Arguments: 212 | index: link index""" 213 | j= ctypes.c_int() 214 | ierr= _lib.ENgetlinktype(index, ctypes.byref(j)) 215 | if ierr!=0: raise ENtoolkitError(ierr) 216 | return j.value 217 | 218 | 219 | def ENgetlinknodes(index): 220 | """Retrieves the indexes of the end nodes of a specified link. 221 | 222 | Arguments: 223 | index: link index""" 224 | j1= ctypes.c_int() 225 | j2= ctypes.c_int() 226 | ierr= _lib.ENgetlinknodes(index,ctypes.byref(j1),ctypes.byref(j2)) 227 | if ierr!=0: raise ENtoolkitError(ierr) 228 | return j1.value,j2.value 229 | 230 | def ENgetlinkvalue(index, paramcode): 231 | """Retrieves the value of a specific link parameter. 232 | 233 | Arguments: 234 | index: link index 235 | paramcode: Link parameter codes consist of the following constants: 236 | EN_DIAMETER Diameter 237 | EN_LENGTH Length 238 | EN_ROUGHNESS Roughness coeff. 239 | EN_MINORLOSS Minor loss coeff. 240 | EN_INITSTATUS Initial link status (0 = closed, 1 = open) 241 | EN_INITSETTING Roughness for pipes, initial speed for pumps, initial setting for valves 242 | EN_KBULK Bulk reaction coeff. 243 | EN_KWALL Wall reaction coeff. 244 | EN_FLOW * Flow rate 245 | EN_VELOCITY * Flow velocity 246 | EN_HEADLOSS * Head loss 247 | EN_STATUS * Actual link status (0 = closed, 1 = open) 248 | EN_SETTING * Roughness for pipes, actual speed for pumps, actual setting for valves 249 | EN_ENERGY * Energy expended in kwatts 250 | * computed values""" 251 | j= ctypes.c_float() 252 | ierr= _lib.ENgetlinkvalue(index, paramcode, ctypes.byref(j)) 253 | if ierr!=0: raise ENtoolkitError(ierr) 254 | return j.value 255 | #------ 256 | 257 | def ENgetpatternid(index): 258 | """Retrieves the ID label of a particular time pattern. 259 | 260 | Arguments: 261 | index: pattern index""" 262 | label = ctypes.create_string_buffer(_max_label_len) 263 | ierr= _lib.ENgetpatternid(index, ctypes.byref(label)) 264 | if ierr!=0: raise ENtoolkitError(ierr) 265 | return label.value 266 | 267 | def ENgetpatternindex(patternid): 268 | """Retrieves the index of a particular time pattern. 269 | 270 | Arguments: 271 | id: pattern ID label""" 272 | j= ctypes.c_int() 273 | ierr= _lib.ENgetpatternindex(ctypes.c_char_p(patternid.encode()), ctypes.byref(j)) 274 | if ierr!=0: raise ENtoolkitError(ierr) 275 | return j.value 276 | 277 | 278 | def ENgetpatternlen(index): 279 | """Retrieves the number of time periods in a specific time pattern. 280 | 281 | Arguments: 282 | index:pattern index""" 283 | j= ctypes.c_int() 284 | ierr= _lib.ENgetpatternlen(index, ctypes.byref(j)) 285 | if ierr!=0: raise ENtoolkitError(ierr) 286 | return j.value 287 | 288 | 289 | def ENgetpatternvalue( index, period): 290 | """Retrieves the multiplier factor for a specific time period in a time pattern. 291 | 292 | Arguments: 293 | index: time pattern index 294 | period: period within time pattern""" 295 | j= ctypes.c_float() 296 | ierr= _lib.ENgetpatternvalue(index, period, ctypes.byref(j)) 297 | if ierr!=0: raise ENtoolkitError(ierr) 298 | return j.value 299 | 300 | 301 | 302 | def ENgetcount(countcode): 303 | """Retrieves the number of network components of a specified type. 304 | 305 | Arguments: 306 | countcode: component code EN_NODECOUNT 307 | EN_TANKCOUNT 308 | EN_LINKCOUNT 309 | EN_PATCOUNT 310 | EN_CURVECOUNT 311 | EN_CONTROLCOUNT""" 312 | j= ctypes.c_int() 313 | ierr= _lib.ENgetcount(countcode, ctypes.byref(j)) 314 | if ierr!=0: raise ENtoolkitError(ierr) 315 | return j.value 316 | 317 | 318 | def ENgetflowunits(): 319 | """Retrieves a code number indicating the units used to express all flow rates.""" 320 | j= ctypes.c_int() 321 | ierr= _lib.ENgetflowunits(ctypes.byref(j)) 322 | if ierr!=0: raise ENtoolkitError(ierr) 323 | return j.value 324 | 325 | 326 | def ENgettimeparam(paramcode): 327 | """Retrieves the value of a specific analysis time parameter. 328 | Arguments: 329 | paramcode: EN_DURATION 330 | EN_HYDSTEP 331 | EN_QUALSTEP 332 | EN_PATTERNSTEP 333 | EN_PATTERNSTART 334 | EN_REPORTSTEP 335 | EN_REPORTSTART 336 | EN_RULESTEP 337 | EN_STATISTIC 338 | EN_PERIODS""" 339 | j= ctypes.c_int() 340 | ierr= _lib.ENgettimeparam(paramcode, ctypes.byref(j)) 341 | if ierr!=0: raise ENtoolkitError(ierr) 342 | return j.value 343 | 344 | def ENgetqualtype(): 345 | """Retrieves the type of water quality analysis called for 346 | returns qualcode: Water quality analysis codes are as follows: 347 | EN_NONE 0 No quality analysis 348 | EN_CHEM 1 Chemical analysis 349 | EN_AGE 2 Water age analysis 350 | EN_TRACE 3 Source tracing 351 | tracenode: index of node traced in a source tracing 352 | analysis (value will be 0 when qualcode 353 | is not EN_TRACE)""" 354 | qualcode= ctypes.c_int() 355 | tracenode= ctypes.c_int() 356 | ierr= _lib.ENgetqualtype(ctypes.byref(qualcode), 357 | ctypes.byref(tracenode)) 358 | if ierr!=0: raise ENtoolkitError(ierr) 359 | return qualcode.value, tracenode.value 360 | 361 | 362 | 363 | #-------Retrieving other network information-------- 364 | def ENgetcontrol(cindex): 365 | """Retrieves the parameters of a simple control statement. 366 | Arguments: 367 | cindex: control statement index 368 | ctype: control type code EN_LOWLEVEL (Low Level Control) 369 | EN_HILEVEL (High Level Control) 370 | EN_TIMER (Timer Control) 371 | EN_TIMEOFDAY (Time-of-Day Control) 372 | lindex: index of link being controlled 373 | setting: value of the control setting 374 | nindex: index of controlling node 375 | level: value of controlling water level or pressure for level controls 376 | or of time of control action (in seconds) for time-based controls""" 377 | #int ENgetcontrol(int cindex, int* ctype, int* lindex, float* setting, int* nindex, float* level ) 378 | ctype = ctypes.c_int() 379 | lindex = ctypes.c_int() 380 | setting = ctypes.c_float() 381 | nindex = ctypes.c_int() 382 | level = ctypes.c_float() 383 | 384 | ierr= _lib.ENgetcontrol(ctypes.c_int(cindex), ctypes.byref(ctype), 385 | ctypes.byref(lindex), ctypes.byref(setting), 386 | ctypes.byref(nindex), ctypes.byref(level) ) 387 | if ierr!=0: raise ENtoolkitError(ierr) 388 | return ctype.value, lindex.value, setting.value, nindex.value, level.value 389 | 390 | 391 | def ENgetoption(optioncode): 392 | """Retrieves the value of a particular analysis option. 393 | 394 | Arguments: 395 | optioncode: EN_TRIALS 396 | EN_ACCURACY 397 | EN_TOLERANCE 398 | EN_EMITEXPON 399 | EN_DEMANDMULT""" 400 | j= ctypes.c_float() 401 | ierr= _lib.ENgetoption(optioncode, ctypes.byref(j)) 402 | if ierr!=0: raise ENtoolkitError(ierr) 403 | return j.value 404 | 405 | def ENgetversion(): 406 | """Retrieves the current version number of the Toolkit.""" 407 | j= ctypes.c_int() 408 | ierr= _lib.ENgetversion(ctypes.byref(j)) 409 | if ierr!=0: raise ENtoolkitError(ierr) 410 | return j.value 411 | 412 | 413 | 414 | #---------Setting new values for network parameters------------- 415 | def ENsetcontrol(cindex, ctype, lindex, setting, nindex, level ): 416 | """Sets the parameters of a simple control statement. 417 | Arguments: 418 | cindex: control statement index 419 | ctype: control type code EN_LOWLEVEL (Low Level Control) 420 | EN_HILEVEL (High Level Control) 421 | EN_TIMER (Timer Control) 422 | EN_TIMEOFDAY (Time-of-Day Control) 423 | lindex: index of link being controlled 424 | setting: value of the control setting 425 | nindex: index of controlling node 426 | level: value of controlling water level or pressure for level controls 427 | or of time of control action (in seconds) for time-based controls""" 428 | #int ENsetcontrol(int cindex, int* ctype, int* lindex, float* setting, int* nindex, float* level ) 429 | ierr= _lib.ENsetcontrol(ctypes.c_int(cindex), ctypes.c_int(ctype), 430 | ctypes.c_int(lindex), ctypes.c_float(setting), 431 | ctypes.c_int(nindex), ctypes.c_float(level) ) 432 | if ierr!=0: raise ENtoolkitError(ierr) 433 | 434 | 435 | def ENsetnodevalue(index, paramcode, value): 436 | """Sets the value of a parameter for a specific node. 437 | Arguments: 438 | index: node index 439 | paramcode: Node parameter codes consist of the following constants: 440 | EN_ELEVATION Elevation 441 | EN_BASEDEMAND ** Base demand 442 | EN_PATTERN ** Demand pattern index 443 | EN_EMITTER Emitter coeff. 444 | EN_INITQUAL Initial quality 445 | EN_SOURCEQUAL Source quality 446 | EN_SOURCEPAT Source pattern index 447 | EN_SOURCETYPE Source type (See note below) 448 | EN_TANKLEVEL Initial water level in tank 449 | ** primary demand category is last on demand list 450 | The following parameter codes apply only to storage tank nodes 451 | EN_TANKDIAM Tank diameter 452 | EN_MINVOLUME Minimum water volume 453 | EN_MINLEVEL Minimum water level 454 | EN_MAXLEVEL Maximum water level 455 | EN_MIXMODEL Mixing model code 456 | EN_MIXFRACTION Fraction of total volume occupied by the inlet/outlet 457 | EN_TANK_KBULK Bulk reaction rate coefficient 458 | value:parameter value""" 459 | ierr= _lib.ENsetnodevalue(ctypes.c_int(index), ctypes.c_int(paramcode), ctypes.c_float(value)) 460 | if ierr!=0: raise ENtoolkitError(ierr) 461 | 462 | 463 | def ENsetlinkvalue(index, paramcode, value): 464 | """Sets the value of a parameter for a specific link. 465 | Arguments: 466 | index: link index 467 | paramcode: Link parameter codes consist of the following constants: 468 | EN_DIAMETER Diameter 469 | EN_LENGTH Length 470 | EN_ROUGHNESS Roughness coeff. 471 | EN_MINORLOSS Minor loss coeff. 472 | EN_INITSTATUS * Initial link status (0 = closed, 1 = open) 473 | EN_INITSETTING * Roughness for pipes, initial speed for pumps, initial setting for valves 474 | EN_KBULK Bulk reaction coeff. 475 | EN_KWALL Wall reaction coeff. 476 | EN_STATUS * Actual link status (0 = closed, 1 = open) 477 | EN_SETTING * Roughness for pipes, actual speed for pumps, actual setting for valves 478 | * Use EN_INITSTATUS and EN_INITSETTING to set the design value for a link's status or setting that 479 | exists prior to the start of a simulation. Use EN_STATUS and EN_SETTING to change these values while 480 | a simulation is being run (within the ENrunH - ENnextH loop). 481 | 482 | value:parameter value""" 483 | ierr= _lib.ENsetlinkvalue(ctypes.c_int(index), 484 | ctypes.c_int(paramcode), 485 | ctypes.c_float(value)) 486 | if ierr!=0: raise ENtoolkitError(ierr) 487 | 488 | 489 | def ENaddpattern(patternid): 490 | """Adds a new time pattern to the network. 491 | Arguments: 492 | id: ID label of pattern""" 493 | ierr= _lib.ENaddpattern(ctypes.c_char_p(patternid.encode())) 494 | if ierr!=0: raise ENtoolkitError(ierr) 495 | 496 | 497 | def ENsetpattern(index, factors): 498 | """Sets all of the multiplier factors for a specific time pattern. 499 | Arguments: 500 | index: time pattern index 501 | factors: multiplier factors list for the entire pattern""" 502 | # int ENsetpattern( int index, float* factors, int nfactors ) 503 | nfactors= len(factors) 504 | cfactors_type= ctypes.c_float* nfactors 505 | cfactors= cfactors_type() 506 | for i in range(nfactors): 507 | cfactors[i]= float(factors[i] ) 508 | ierr= _lib.ENsetpattern(ctypes.c_int(index), cfactors, ctypes.c_int(nfactors) ) 509 | if ierr!=0: raise ENtoolkitError(ierr) 510 | 511 | 512 | def ENsetpatternvalue( index, period, value): 513 | """Sets the multiplier factor for a specific period within a time pattern. 514 | Arguments: 515 | index: time pattern index 516 | period: period within time pattern 517 | value: multiplier factor for the period""" 518 | #int ENsetpatternvalue( int index, int period, float value ) 519 | ierr= _lib.ENsetpatternvalue( ctypes.c_int(index), 520 | ctypes.c_int(period), 521 | ctypes.c_float(value) ) 522 | if ierr!=0: raise ENtoolkitError(ierr) 523 | 524 | 525 | 526 | def ENsetqualtype(qualcode, chemname, chemunits, tracenode): 527 | """Sets the type of water quality analysis called for. 528 | Arguments: 529 | qualcode: water quality analysis code 530 | chemname: name of the chemical being analyzed 531 | chemunits: units that the chemical is measured in 532 | tracenode: ID of node traced in a source tracing analysis """ 533 | ierr= _lib.ENsetqualtype( ctypes.c_int(qualcode), 534 | ctypes.c_char_p(chemname.encode()), 535 | ctypes.c_char_p(chemunits.encode()), 536 | ctypes.c_char_p(tracenode.encode())) 537 | if ierr!=0: raise ENtoolkitError(ierr) 538 | 539 | 540 | def ENsettimeparam(paramcode, timevalue): 541 | """Sets the value of a time parameter. 542 | Arguments: 543 | paramcode: time parameter code EN_DURATION 544 | EN_HYDSTEP 545 | EN_QUALSTEP 546 | EN_PATTERNSTEP 547 | EN_PATTERNSTART 548 | EN_REPORTSTEP 549 | EN_REPORTSTART 550 | EN_RULESTEP 551 | EN_STATISTIC 552 | EN_PERIODS 553 | timevalue: value of time parameter in seconds 554 | The codes for EN_STATISTIC are: 555 | EN_NONE none 556 | EN_AVERAGE averaged 557 | EN_MINIMUM minimums 558 | EN_MAXIMUM maximums 559 | EN_RANGE ranges""" 560 | ierr= _lib.ENsettimeparam(ctypes.c_int(paramcode), ctypes.c_int(timevalue)) 561 | if ierr!=0: raise ENtoolkitError(ierr) 562 | 563 | 564 | def ENsetoption( optioncode, value): 565 | """Sets the value of a particular analysis option. 566 | 567 | Arguments: 568 | optioncode: option code EN_TRIALS 569 | EN_ACCURACY 570 | EN_TOLERANCE 571 | EN_EMITEXPON 572 | EN_DEMANDMULT 573 | value: option value""" 574 | ierr= _lib.ENsetoption(ctypes.c_int(optioncode), ctypes.c_float(value)) 575 | if ierr!=0: raise ENtoolkitError(ierr) 576 | 577 | 578 | #----- Saving and using hydraulic analysis results files ------- 579 | def ENsavehydfile(fname): 580 | """Saves the current contents of the binary hydraulics file to a file.""" 581 | ierr= _lib.ENsavehydfile(ctypes.c_char_p(fname.encode())) 582 | if ierr!=0: raise ENtoolkitError(ierr) 583 | 584 | def ENusehydfile(fname): 585 | """Uses the contents of the specified file as the current binary hydraulics file""" 586 | ierr= _lib.ENusehydfile(ctypes.c_char_p(fname.encode())) 587 | if ierr!=0: raise ENtoolkitError(ierr) 588 | 589 | 590 | 591 | #----------Running a hydraulic analysis -------------------------- 592 | def ENsolveH(): 593 | """Runs a complete hydraulic simulation with results 594 | for all time periods written to the binary Hydraulics file.""" 595 | ierr= _lib.ENsolveH() 596 | if ierr!=0: raise ENtoolkitError(ierr) 597 | 598 | 599 | def ENopenH(): 600 | """Opens the hydraulics analysis system""" 601 | ierr= _lib.ENopenH() 602 | if ierr != 0: raise ENtoolkitError(ierr) 603 | 604 | 605 | def ENinitH(flag=None): 606 | """Initializes storage tank levels, link status and settings, 607 | and the simulation clock time prior 608 | to running a hydraulic analysis. 609 | 610 | flag EN_NOSAVE [+EN_SAVE] [+EN_INITFLOW] """ 611 | ierr= _lib.ENinitH(flag) 612 | if ierr!=0: raise ENtoolkitError(ierr) 613 | 614 | 615 | def ENrunH(): 616 | """Runs a single period hydraulic analysis, 617 | retrieving the current simulation clock time t""" 618 | ierr= _lib.ENrunH(ctypes.byref(_current_simulation_time)) 619 | if ierr>=100: 620 | raise ENtoolkitError(ierr) 621 | elif ierr>0: 622 | return ENgeterror(ierr) 623 | 624 | 625 | def ENsimtime(): 626 | """retrieves the current simulation time t as datetime.timedelta instance""" 627 | return datetime.timedelta(seconds= _current_simulation_time.value ) 628 | 629 | def ENnextH(): 630 | """Determines the length of time until the next hydraulic event occurs in an extended period 631 | simulation.""" 632 | _deltat= ctypes.c_long() 633 | ierr= _lib.ENnextH(ctypes.byref(_deltat)) 634 | if ierr!=0: raise ENtoolkitError(ierr) 635 | return _deltat.value 636 | 637 | 638 | def ENcloseH(): 639 | """Closes the hydraulic analysis system, freeing all allocated memory.""" 640 | ierr= _lib.ENcloseH() 641 | if ierr!=0: raise ENtoolkitError(ierr) 642 | 643 | #-------------------------------------------- 644 | 645 | #----------Running a quality analysis -------------------------- 646 | def ENsolveQ(): 647 | """Runs a complete water quality simulation with results 648 | at uniform reporting intervals written to EPANET's binary Output file.""" 649 | ierr= _lib.ENsolveQ() 650 | if ierr!=0: raise ENtoolkitError(ierr) 651 | 652 | 653 | def ENopenQ(): 654 | """Opens the water quality analysis system""" 655 | ierr= _lib.ENopenQ() 656 | 657 | 658 | def ENinitQ(flag=None): 659 | """Initializes water quality and the simulation clock 660 | time prior to running a water quality analysis. 661 | 662 | flag EN_NOSAVE | EN_SAVE """ 663 | ierr= _lib.ENinitQ(flag) 664 | if ierr!=0: raise ENtoolkitError(ierr) 665 | 666 | def ENrunQ(): 667 | """Makes available the hydraulic and water quality results 668 | that occur at the start of the next time period of a water quality analysis, 669 | where the start of the period is returned in t.""" 670 | ierr= _lib.ENrunQ(ctypes.byref(_current_simulation_time)) 671 | if ierr>=100: 672 | raise ENtoolkitError(ierr) 673 | elif ierr>0: 674 | return ENgeterror(ierr) 675 | 676 | def ENnextQ(): 677 | """Advances the water quality simulation 678 | to the start of the next hydraulic time period.""" 679 | _deltat= ctypes.c_long() 680 | ierr= _lib.ENnextQ(ctypes.byref(_deltat)) 681 | if ierr!=0: raise ENtoolkitError(ierr) 682 | return _deltat.value 683 | 684 | 685 | def ENstepQ(): 686 | """Advances the water quality simulation one water quality time step. 687 | The time remaining in the overall simulation is returned in tleft.""" 688 | tleft= ctypes.c_long() 689 | ierr= _lib.ENnextQ(ctypes.byref(tleft)) 690 | if ierr!=0: raise ENtoolkitError(ierr) 691 | return tleft.value 692 | 693 | def ENcloseQ(): 694 | """Closes the water quality analysis system, 695 | freeing all allocated memory.""" 696 | ierr= _lib.ENcloseQ() 697 | if ierr!=0: raise ENtoolkitError(ierr) 698 | #-------------------------------------------- 699 | 700 | 701 | 702 | 703 | 704 | def ENsaveH(): 705 | """Transfers results of a hydraulic simulation 706 | from the binary Hydraulics file to the binary 707 | Output file, where results are only reported at 708 | uniform reporting intervals.""" 709 | ierr= _lib.ENsaveH() 710 | if ierr!=0: raise ENtoolkitError(ierr) 711 | 712 | 713 | def ENsaveinpfile(fname): 714 | """Writes all current network input data to a file 715 | using the format of an EPANET input file.""" 716 | ierr= _lib.ENsaveinpfile( ctypes.c_char_p(fname.encode())) 717 | if ierr!=0: raise ENtoolkitError(ierr) 718 | 719 | 720 | def ENreport(): 721 | """Writes a formatted text report on simulation results 722 | to the Report file.""" 723 | ierr= _lib.ENreport() 724 | if ierr!=0: raise ENtoolkitError(ierr) 725 | 726 | def ENresetreport(): 727 | """Clears any report formatting commands 728 | 729 | that either appeared in the [REPORT] section of the 730 | EPANET Input file or were issued with the 731 | ENsetreport function""" 732 | ierr= _lib.ENresetreport() 733 | if ierr!=0: raise ENtoolkitError(ierr) 734 | 735 | def ENsetreport(command): 736 | """Issues a report formatting command. 737 | 738 | Formatting commands are the same as used in the 739 | [REPORT] section of the EPANET Input file.""" 740 | ierr= _lib.ENsetreport(ctypes.c_char_p(command.encode())) 741 | if ierr!=0: raise ENtoolkitError(ierr) 742 | 743 | def ENsetstatusreport(statuslevel): 744 | """Sets the level of hydraulic status reporting. 745 | 746 | statuslevel: level of status reporting 747 | 0 - no status reporting 748 | 1 - normal reporting 749 | 2 - full status reporting""" 750 | ierr= _lib.ENsetstatusreport(ctypes.c_int(statuslevel)) 751 | if ierr!=0: raise ENtoolkitError(ierr) 752 | 753 | def ENgeterror(errcode): 754 | """Retrieves the text of the message associated with a particular error or warning code.""" 755 | errmsg= ctypes.create_string_buffer(_err_max_char) 756 | _lib.ENgeterror( errcode,ctypes.byref(errmsg), _err_max_char ) 757 | return errmsg.value.decode() 758 | 759 | def ENwriteline(line ): 760 | """Writes a line of text to the EPANET report file.""" 761 | ierr= _lib.ENwriteline(ctypes.c_char_p(line.encode() )) 762 | if ierr!=0: raise ENtoolkitError(ierr) 763 | 764 | 765 | class ENtoolkitError(Exception): 766 | def __init__(self, ierr): 767 | self.warning= ierr < 100 768 | self.args= (ierr,) 769 | self.message= ENgeterror(ierr) 770 | if self.message=='' and ierr!=0: 771 | self.message='ENtoolkit Undocumented Error '+str(ierr)+': look at text.h in epanet sources' 772 | def __str__(self): 773 | return self.message 774 | 775 | 776 | #------ functions added from OpenWaterAnalytics ---------------------------------- 777 | # functions not present in original Epanet2 toolkit from US EPA 778 | # it may change in future versions 779 | #---------------------------------------------------------------------------------- 780 | if hasattr(_lib,"ENgetcurve"): 781 | def ENgetcurve(curveIndex): 782 | curveid = ctypes.create_string_buffer(_max_label_len) 783 | nValues = ctypes.c_int() 784 | xValues= ctypes.POINTER(ctypes.c_float)() 785 | yValues= ctypes.POINTER(ctypes.c_float)() 786 | ierr= _lib.ENgetcurve(curveIndex, 787 | ctypes.byref(curveid), 788 | ctypes.byref(nValues), 789 | ctypes.byref(xValues), 790 | ctypes.byref(yValues) 791 | ) 792 | # strange behavior of ENgetcurve: it returns also curveID 793 | # better split in two distinct functions .... 794 | if ierr!=0: raise ENtoolkitError(ierr) 795 | curve= [] 796 | for i in range(nValues.value): 797 | curve.append( (xValues[i],yValues[i]) ) 798 | return curve 799 | 800 | def ENgetcurveid(curveIndex): 801 | curveid = ctypes.create_string_buffer(_max_label_len) 802 | nValues = ctypes.c_int() 803 | xValues= ctypes.POINTER(ctypes.c_float)() 804 | yValues= ctypes.POINTER(ctypes.c_float)() 805 | ierr= _lib.ENgetcurve(curveIndex, 806 | ctypes.byref(curveid), 807 | ctypes.byref(nValues), 808 | ctypes.byref(xValues), 809 | ctypes.byref(yValues) 810 | ) 811 | # strange behavior of ENgetcurve: it returns also curveID 812 | # better split in two distinct functions .... 813 | if ierr!=0: raise ENtoolkitError(ierr) 814 | return curveid.value 815 | 816 | #-----end of functions added from OpenWaterAnalytics ---------------------------------- 817 | 818 | 819 | EN_ELEVATION = 0 # /* Node parameters */ 820 | EN_BASEDEMAND = 1 821 | EN_PATTERN = 2 822 | EN_EMITTER = 3 823 | EN_INITQUAL = 4 824 | EN_SOURCEQUAL = 5 825 | EN_SOURCEPAT = 6 826 | EN_SOURCETYPE = 7 827 | EN_TANKLEVEL = 8 828 | EN_DEMAND = 9 829 | EN_HEAD = 10 830 | EN_PRESSURE = 11 831 | EN_QUALITY = 12 832 | EN_SOURCEMASS = 13 833 | EN_INITVOLUME = 14 834 | EN_MIXMODEL = 15 835 | EN_MIXZONEVOL = 16 836 | 837 | EN_TANKDIAM = 17 838 | EN_MINVOLUME = 18 839 | EN_VOLCURVE = 19 840 | EN_MINLEVEL = 20 841 | EN_MAXLEVEL = 21 842 | EN_MIXFRACTION = 22 843 | EN_TANK_KBULK = 23 844 | 845 | EN_DIAMETER = 0 # /* Link parameters */ 846 | EN_LENGTH = 1 847 | EN_ROUGHNESS = 2 848 | EN_MINORLOSS = 3 849 | EN_INITSTATUS = 4 850 | EN_INITSETTING = 5 851 | EN_KBULK = 6 852 | EN_KWALL = 7 853 | EN_FLOW = 8 854 | EN_VELOCITY = 9 855 | EN_HEADLOSS = 10 856 | EN_STATUS = 11 857 | EN_SETTING = 12 858 | EN_ENERGY = 13 859 | 860 | EN_DURATION = 0 # /* Time parameters */ 861 | EN_HYDSTEP = 1 862 | EN_QUALSTEP = 2 863 | EN_PATTERNSTEP = 3 864 | EN_PATTERNSTART = 4 865 | EN_REPORTSTEP = 5 866 | EN_REPORTSTART = 6 867 | EN_RULESTEP = 7 868 | EN_STATISTIC = 8 869 | EN_PERIODS = 9 870 | 871 | EN_NODECOUNT = 0 # /* Component counts */ 872 | EN_TANKCOUNT = 1 873 | EN_LINKCOUNT = 2 874 | EN_PATCOUNT = 3 875 | EN_CURVECOUNT = 4 876 | EN_CONTROLCOUNT = 5 877 | 878 | EN_JUNCTION = 0 # /* Node types */ 879 | EN_RESERVOIR = 1 880 | EN_TANK = 2 881 | 882 | EN_CVPIPE = 0 # /* Link types */ 883 | EN_PIPE = 1 884 | EN_PUMP = 2 885 | EN_PRV = 3 886 | EN_PSV = 4 887 | EN_PBV = 5 888 | EN_FCV = 6 889 | EN_TCV = 7 890 | EN_GPV = 8 891 | 892 | EN_NONE = 0 # /* Quality analysis types */ 893 | EN_CHEM = 1 894 | EN_AGE = 2 895 | EN_TRACE = 3 896 | 897 | EN_CONCEN = 0 # /* Source quality types */ 898 | EN_MASS = 1 899 | EN_SETPOINT = 2 900 | EN_FLOWPACED = 3 901 | 902 | EN_CFS = 0 # /* Flow units types */ 903 | EN_GPM = 1 904 | EN_MGD = 2 905 | EN_IMGD = 3 906 | EN_AFD = 4 907 | EN_LPS = 5 908 | EN_LPM = 6 909 | EN_MLD = 7 910 | EN_CMH = 8 911 | EN_CMD = 9 912 | 913 | EN_TRIALS = 0 # /* Misc. options */ 914 | EN_ACCURACY = 1 915 | EN_TOLERANCE = 2 916 | EN_EMITEXPON = 3 917 | EN_DEMANDMULT = 4 918 | 919 | EN_LOWLEVEL = 0 # /* Control types */ 920 | EN_HILEVEL = 1 921 | EN_TIMER = 2 922 | EN_TIMEOFDAY = 3 923 | 924 | EN_AVERAGE = 1 # /* Time statistic types. */ 925 | EN_MINIMUM = 2 926 | EN_MAXIMUM = 3 927 | EN_RANGE = 4 928 | 929 | EN_MIX1 = 0 # /* Tank mixing models */ 930 | EN_MIX2 = 1 931 | EN_FIFO = 2 932 | EN_LIFO = 3 933 | 934 | EN_NOSAVE = 0 # /* Save-results-to-file flag */ 935 | EN_SAVE = 1 936 | EN_INITFLOW = 10 # /* Re-initialize flow flag */ 937 | 938 | 939 | 940 | FlowUnits= { EN_CFS :"cfs" , 941 | EN_GPM :"gpm" , 942 | EN_MGD :"a-f/d" , 943 | EN_IMGD:"mgd" , 944 | EN_AFD :"Imgd" , 945 | EN_LPS :"L/s" , 946 | EN_LPM :"Lpm" , 947 | EN_MLD :"m3/h" , 948 | EN_CMH :"m3/d" , 949 | EN_CMD :"ML/d" } 950 | -------------------------------------------------------------------------------- /epanet-module/example2.inp: -------------------------------------------------------------------------------- 1 | [TITLE] 2 | EPANET Example Network 2 3 | Example of modeling a 55-hour fluoride tracer study. 4 | Measured fluoride data is contained in the file Net2-FL.dat 5 | and should be registered with the project to produce a 6 | Calibration Report (select Calibration Data from the Project 7 | menu). 8 | 9 | [JUNCTIONS] 10 | ;ID Elev Demand Pattern 11 | 1 50 -694.4 2 ; 12 | 2 100 8 ; 13 | 3 60 14 ; 14 | 4 60 8 ; 15 | 5 100 8 ; 16 | 6 125 5 ; 17 | 7 160 4 ; 18 | 8 110 9 ; 19 | 9 180 14 ; 20 | 10 130 5 ; 21 | 11 185 34.78 ; 22 | 12 210 16 ; 23 | 13 210 2 ; 24 | 14 200 2 ; 25 | 15 190 2 ; 26 | 16 150 20 ; 27 | 17 180 20 ; 28 | 18 100 20 ; 29 | 19 150 5 ; 30 | 20 170 19 ; 31 | 21 150 16 ; 32 | 22 200 10 ; 33 | 23 230 8 ; 34 | 24 190 11 ; 35 | 25 230 6 ; 36 | 27 130 8 ; 37 | 28 110 0 ; 38 | 29 110 7 ; 39 | 30 130 3 ; 40 | 31 190 17 ; 41 | 32 110 17 ; 42 | 33 180 1.5 ; 43 | 34 190 1.5 ; 44 | 35 110 0 ; 45 | 36 110 1 ; 46 | 47 | [RESERVOIRS] 48 | ;ID Head Pattern 49 | 50 | [TANKS] 51 | ;ID Elevation InitLevel MinLevel MaxLevel Diameter MinVol VolCurve 52 | 26 235 56.7 50 70 50 0 ; 53 | 54 | [PIPES] 55 | ;ID Node1 Node2 Length Diameter Roughness MinorLoss Status 56 | 1 1 2 2400 12 100 0 Open ; 57 | 2 2 5 800 12 100 0 Open ; 58 | 3 2 3 1300 8 100 0 Open ; 59 | 4 3 4 1200 8 100 0 Open ; 60 | 5 4 5 1000 12 100 0 Open ; 61 | 6 5 6 1200 12 100 0 Open ; 62 | 7 6 7 2700 12 100 0 Open ; 63 | 8 7 8 1200 12 140 0 Open ; 64 | 9 7 9 400 12 100 0 Open ; 65 | 10 8 10 1000 8 140 0 Open ; 66 | 11 9 11 700 12 100 0 Open ; 67 | 12 11 12 1900 12 100 0 Open ; 68 | 13 12 13 600 12 100 0 Open ; 69 | 14 13 14 400 12 100 0 Open ; 70 | 15 14 15 300 12 100 0 Open ; 71 | 16 13 16 1500 8 100 0 Open ; 72 | 17 15 17 1500 8 100 0 Open ; 73 | 18 16 17 600 8 100 0 Open ; 74 | 19 17 18 700 12 100 0 Open ; 75 | 20 18 32 350 12 100 0 Open ; 76 | 21 16 19 1400 8 100 0 Open ; 77 | 22 14 20 1100 12 100 0 Open ; 78 | 23 20 21 1300 8 100 0 Open ; 79 | 24 21 22 1300 8 100 0 Open ; 80 | 25 20 22 1300 8 100 0 Open ; 81 | 26 24 23 600 12 100 0 Open ; 82 | 27 15 24 250 12 100 0 Open ; 83 | 28 23 25 300 12 100 0 Open ; 84 | 29 25 26 200 12 100 0 Open ; 85 | 30 25 31 600 12 100 0 Open ; 86 | 31 31 27 400 8 100 0 Open ; 87 | 32 27 29 400 8 100 0 Open ; 88 | 34 29 28 700 8 100 0 Open ; 89 | 35 22 33 1000 8 100 0 Open ; 90 | 36 33 34 400 8 100 0 Open ; 91 | 37 32 19 500 8 100 0 Open ; 92 | 38 29 35 500 8 100 0 Open ; 93 | 39 35 30 1000 8 100 0 Open ; 94 | 40 28 35 700 8 100 0 Open ; 95 | 41 28 36 300 8 100 0 Open ; 96 | 97 | [PUMPS] 98 | ;ID Node1 Node2 Parameters 99 | 100 | [VALVES] 101 | ;ID Node1 Node2 Diameter Type Setting MinorLoss 102 | 103 | [TAGS] 104 | 105 | [DEMANDS] 106 | ;Junction Demand Pattern Category 107 | 108 | [STATUS] 109 | ;ID Status/Setting 110 | 111 | [PATTERNS] 112 | ;ID Multipliers 113 | ;Demand Pattern 114 | 1 1.26 1.04 .97 .97 .89 1.19 115 | 1 1.28 .67 .67 1.34 2.46 .97 116 | 1 .92 .68 1.43 .61 .31 .78 117 | 1 .37 .67 1.26 1.56 1.19 1.26 118 | 1 .6 1.1 1.03 .73 .88 1.06 119 | 1 .99 1.72 1.12 1.34 1.12 .97 120 | 1 1.04 1.15 .91 .61 .68 .46 121 | 1 .51 .74 1.12 1.34 1.26 .97 122 | 1 .82 1.37 1.03 .81 .88 .81 123 | 1 .81 124 | ;Pump Station Outflow Pattern 125 | 2 .96 .96 .96 .96 .96 .96 126 | 2 .62 0 0 0 0 0 127 | 2 .8 1 1 1 1 .15 128 | 2 0 0 0 0 0 0 129 | 2 .55 .92 .92 .92 .92 .9 130 | 2 .9 .45 0 0 0 0 131 | 2 0 .7 1 1 1 1 132 | 2 .2 0 0 0 0 0 133 | 2 0 .74 .92 .92 .92 .92 134 | 2 .92 135 | ;Pump Station Fluoride Pattern 136 | 3 .98 1.02 1.05 .99 .64 .46 137 | 3 .35 .35 .35 .35 .35 .35 138 | 3 .17 .17 .13 .13 .13 .15 139 | 3 .15 .15 .15 .15 .15 .15 140 | 3 .15 .12 .1 .08 .11 .09 141 | 3 .09 .08 .08 .08 .08 .08 142 | 3 .08 .09 .07 .07 .09 .09 143 | 3 .09 .09 .09 .09 .09 .09 144 | 3 .09 .08 .35 .72 .82 .92 145 | 3 1 146 | 147 | [CURVES] 148 | ;ID X-Value Y-Value 149 | 150 | [CONTROLS] 151 | 152 | [RULES] 153 | 154 | [ENERGY] 155 | Global Efficiency 75 156 | Global Price 0.0 157 | Demand Charge 0.0 158 | 159 | [EMITTERS] 160 | ;Junction Coefficient 161 | 162 | [QUALITY] 163 | ;Node InitQual 164 | 1 1.0 165 | 2 1.0 166 | 3 1.0 167 | 4 1.0 168 | 5 1.0 169 | 6 1.0 170 | 7 1.0 171 | 8 1.0 172 | 9 1.0 173 | 10 1.0 174 | 11 1.0 175 | 12 1.0 176 | 13 1.0 177 | 14 1.0 178 | 15 1.0 179 | 16 1.0 180 | 17 1.0 181 | 18 1.0 182 | 19 1.0 183 | 20 1.0 184 | 21 1.0 185 | 22 1.0 186 | 23 1.0 187 | 24 1.0 188 | 25 1.0 189 | 27 1.0 190 | 28 1.0 191 | 29 1.0 192 | 30 1.0 193 | 31 1.0 194 | 32 1.0 195 | 33 1.0 196 | 34 1.0 197 | 35 1.0 198 | 36 1.0 199 | 26 1.0 200 | 201 | [SOURCES] 202 | ;Node Type Quality Pattern 203 | 1 CONCEN 1.0 3 204 | 205 | [REACTIONS] 206 | ;Type Pipe/Tank Coefficient 207 | 208 | 209 | [REACTIONS] 210 | Order Bulk 1 211 | Order Tank 1 212 | Order Wall 1 213 | Global Bulk 0.0 214 | Global Wall 0.0 215 | Limiting Potential 0.0 216 | Roughness Correlation 0.0 217 | 218 | [MIXING] 219 | ;Tank Model 220 | 221 | [TIMES] 222 | Duration 55:00 223 | Hydraulic Timestep 1:00 224 | Quality Timestep 0:05 225 | Pattern Timestep 1:00 226 | Pattern Start 0:00 227 | Report Timestep 1:00 228 | Report Start 0:00 229 | Start ClockTime 8 am 230 | Statistic None 231 | 232 | [REPORT] 233 | Status No 234 | Summary No 235 | Page 0 236 | 237 | [OPTIONS] 238 | Units GPM 239 | Headloss H-W 240 | Specific Gravity 1.0 241 | Viscosity 1.0 242 | Trials 40 243 | Accuracy 0.001 244 | CHECKFREQ 2 245 | MAXCHECK 10 246 | DAMPLIMIT 0 247 | Unbalanced Continue 10 248 | Pattern 1 249 | Demand Multiplier 1.0 250 | Emitter Exponent 0.5 251 | Quality Fluoride mg/L 252 | Diffusivity 1.0 253 | Tolerance 0.01 254 | 255 | [COORDINATES] 256 | ;Node X-Coord Y-Coord 257 | 1 21.00 4.00 258 | 2 19.00 20.00 259 | 3 11.00 21.00 260 | 4 14.00 28.00 261 | 5 19.00 25.00 262 | 6 28.00 23.00 263 | 7 36.00 39.00 264 | 8 38.00 30.00 265 | 9 36.00 42.00 266 | 10 37.00 23.00 267 | 11 37.00 49.00 268 | 12 39.00 60.00 269 | 13 38.00 64.00 270 | 14 38.00 66.00 271 | 15 37.00 69.00 272 | 16 27.00 65.00 273 | 17 27.00 69.00 274 | 18 23.00 68.00 275 | 19 21.00 59.00 276 | 20 45.00 68.00 277 | 21 51.00 62.00 278 | 22 54.00 69.00 279 | 23 35.00 74.00 280 | 24 37.00 71.00 281 | 25 35.00 76.00 282 | 27 39.00 87.00 283 | 28 49.00 85.00 284 | 29 42.00 86.00 285 | 30 47.00 80.00 286 | 31 37.00 80.00 287 | 32 23.00 64.00 288 | 33 56.00 73.00 289 | 34 56.00 77.00 290 | 35 43.00 81.00 291 | 36 53.00 87.00 292 | 26 33.00 76.00 293 | 294 | [VERTICES] 295 | ;Link X-Coord Y-Coord 296 | 297 | [LABELS] 298 | ;X-Coord Y-Coord Label & Anchor Node 299 | 24.00 7.00 "Pump" 300 | 24.00 4.00 "Station" 301 | 26.76 77.42 "Tank" 302 | 303 | [BACKDROP] 304 | DIMENSIONS 8.75 -0.15 58.25 91.15 305 | UNITS None 306 | FILE 307 | OFFSET 0.00 0.00 308 | 309 | [END] 310 | -------------------------------------------------------------------------------- /epanet-module/example2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """This example illustrates how the Toolkit could be used 3 | to develop a hydrant rating curve used in fire flow studies. 4 | This curve shows the amount of flow available at a node in 5 | the system as a function of pressure. 6 | 7 | The curve is generated by running a number of steady state 8 | hydraulic analyses with the node of interest subjected to a 9 | different demand in each analysis. 10 | 11 | For this example we assume that the ID label of the node of 12 | interest is MyNode and that N different demand levels stored 13 | in the array Demands need to be examined. 14 | The corresponding pressures will be returned. 15 | To keep the code more readable no exception handling is made.""" 16 | 17 | import epamodule as em 18 | 19 | def HydrantRating( MyNode, Demands): 20 | #Open the EPANET toolkit & hydraulics solver 21 | em.ENopen("example2.inp", "example2.rpt") 22 | em.ENopenH() 23 | 24 | # Get the index of the node of interest 25 | nodeindex= em.ENgetnodeindex(MyNode); 26 | 27 | rating= [] 28 | # Iterate over all demands 29 | for dem in Demands: 30 | em.ENsetnodevalue(nodeindex, em.EN_BASEDEMAND, dem) 31 | em.ENinitH(em.EN_NOSAVE) 32 | em.ENrunH() 33 | pressure= em.ENgetnodevalue(nodeindex, em.EN_PRESSURE) 34 | rating.append(pressure) 35 | 36 | # Close hydraulics solver & toolkit */ 37 | em.ENcloseH() 38 | em.ENclose() 39 | return rating 40 | 41 | if __name__=='__main__': 42 | print (HydrantRating('3', [0.0, 10.0, 20.0, 50.0, 100.0] )) 43 | -------------------------------------------------------------------------------- /epanet-module/example3.inp: -------------------------------------------------------------------------------- 1 | [TITLE] 2 | EPANET Example Network 3 3 | Example showing how the percent of Lake water in a dual-source 4 | system changes over time. 5 | 6 | [JUNCTIONS] 7 | ;ID Elev Demand Pattern 8 | 10 147 0 ; 9 | 15 32 1 3 ; 10 | 20 129 0 ; 11 | 35 12.5 1 4 ; 12 | 40 131.9 0 ; 13 | 50 116.5 0 ; 14 | 60 0 0 ; 15 | 601 0 0 ; 16 | 61 0 0 ; 17 | 101 42 189.95 ; 18 | 103 43 133.2 ; 19 | 105 28.5 135.37 ; 20 | 107 22 54.64 ; 21 | 109 20.3 231.4 ; 22 | 111 10 141.94 ; 23 | 113 2 20.01 ; 24 | 115 14 52.1 ; 25 | 117 13.6 117.71 ; 26 | 119 2 176.13 ; 27 | 120 0 0 ; 28 | 121 -2 41.63 ; 29 | 123 11 1 2 ; 30 | 125 11 45.6 ; 31 | 127 56 17.66 ; 32 | 129 51 0 ; 33 | 131 6 42.75 ; 34 | 139 31 5.89 ; 35 | 141 4 9.85 ; 36 | 143 -4.5 6.2 ; 37 | 145 1 27.63 ; 38 | 147 18.5 8.55 ; 39 | 149 16 27.07 ; 40 | 151 33.5 144.48 ; 41 | 153 66.2 44.17 ; 42 | 157 13.1 51.79 ; 43 | 159 6 41.32 ; 44 | 161 4 15.8 ; 45 | 163 5 9.42 ; 46 | 164 5 0 ; 47 | 166 -2 2.6 ; 48 | 167 -5 14.56 ; 49 | 169 -5 0 ; 50 | 171 -4 39.34 ; 51 | 173 -4 0 ; 52 | 177 8 58.17 ; 53 | 179 8 0 ; 54 | 181 8 0 ; 55 | 183 11 0 ; 56 | 184 16 0 ; 57 | 185 16 25.65 ; 58 | 187 12.5 0 ; 59 | 189 4 107.92 ; 60 | 191 25 81.9 ; 61 | 193 18 71.31 ; 62 | 195 15.5 0 ; 63 | 197 23 17.04 ; 64 | 199 -2 119.32 ; 65 | 201 0.1 44.61 ; 66 | 203 2 1 5 ; 67 | 204 21 0 ; 68 | 205 21 65.36 ; 69 | 206 1 0 ; 70 | 207 9 69.39 ; 71 | 208 16 0 ; 72 | 209 -2 0.87 ; 73 | 211 7 8.67 ; 74 | 213 7 13.94 ; 75 | 215 7 92.19 ; 76 | 217 6 24.22 ; 77 | 219 4 41.32 ; 78 | 225 8 22.8 ; 79 | 229 10.5 64.18 ; 80 | 231 5 16.48 ; 81 | 237 14 15.61 ; 82 | 239 13 44.61 ; 83 | 241 13 0 ; 84 | 243 14 4.34 ; 85 | 247 18 70.38 ; 86 | 249 18 0 ; 87 | 251 30 24.16 ; 88 | 253 36 54.52 ; 89 | 255 27 40.39 ; 90 | 257 17 0 ; 91 | 259 25 0 ; 92 | 261 0 0 ; 93 | 263 0 0 ; 94 | 265 0 0 ; 95 | 267 21 0 ; 96 | 269 0 0 ; 97 | 271 6 0 ; 98 | 273 8 0 ; 99 | 275 10 0 ; 100 | 101 | [RESERVOIRS] 102 | ;ID Head Pattern 103 | River 220.0 ; 104 | Lake 167.0 ; 105 | 106 | [TANKS] 107 | ;ID Elevation InitLevel MinLevel MaxLevel Diameter MinVol VolCurve 108 | 1 131.9 13.1 .1 32.1 85 0 ; 109 | 2 116.5 23.5 6.5 40.3 50 0 ; 110 | 3 129.0 29.0 4.0 35.5 164 0 ; 111 | 112 | [PIPES] 113 | ;ID Node1 Node2 Length Diameter Roughness MinorLoss Status 114 | 20 3 20 99 99 199 0 Open ; 115 | 40 1 40 99 99 199 0 Open ; 116 | 50 2 50 99 99 199 0 Open ; 117 | 60 River 60 1231 24 140 0 Open ; 118 | 101 10 101 14200 18 110 0 Open ; 119 | 103 101 103 1350 16 130 0 Open ; 120 | 105 101 105 2540 12 130 0 Open ; 121 | 107 105 107 1470 12 130 0 Open ; 122 | 109 103 109 3940 16 130 0 Open ; 123 | 111 109 111 2000 12 130 0 Open ; 124 | 112 115 111 1160 12 130 0 Open ; 125 | 113 111 113 1680 12 130 0 Open ; 126 | 114 115 113 2000 8 130 0 Open ; 127 | 115 107 115 1950 8 130 0 Open ; 128 | 116 113 193 1660 12 130 0 Open ; 129 | 117 263 105 2725 12 130 0 Open ; 130 | 119 115 117 2180 12 130 0 Open ; 131 | 120 119 120 730 12 130 0 Open ; 132 | 121 120 117 1870 12 130 0 Open ; 133 | 122 121 120 2050 8 130 0 Open ; 134 | 123 121 119 2000 30 141 0 Open ; 135 | 125 123 121 1500 30 141 0 Open ; 136 | 129 121 125 930 24 130 0 Open ; 137 | 131 125 127 3240 24 130 0 Open ; 138 | 133 20 127 785 20 130 0 Open ; 139 | 135 127 129 900 24 130 0 Open ; 140 | 137 129 131 6480 16 130 0 Open ; 141 | 145 129 139 2750 8 130 0 Open ; 142 | 147 139 141 2050 8 130 0 Open ; 143 | 149 143 141 1400 8 130 0 Open ; 144 | 151 15 143 1650 8 130 0 Open ; 145 | 153 145 141 3510 12 130 0 Open ; 146 | 155 147 145 2200 12 130 0 Open ; 147 | 159 147 149 880 12 130 0 Open ; 148 | 161 149 151 1020 8 130 0 Open ; 149 | 163 151 153 1170 12 130 0 Open ; 150 | 169 125 153 4560 8 130 0 Open ; 151 | 171 119 151 3460 12 130 0 Open ; 152 | 173 119 157 2080 30 141 0 Open ; 153 | 175 157 159 2910 30 141 0 Open ; 154 | 177 159 161 2000 30 141 0 Open ; 155 | 179 161 163 430 30 141 0 Open ; 156 | 180 163 164 150 14 130 0 Open ; 157 | 181 164 166 490 14 130 0 Open ; 158 | 183 265 169 590 30 141 0 Open ; 159 | 185 167 169 60 8 130 0 Open ; 160 | 186 187 204 99.9 8 130 0 Open ; 161 | 187 169 171 1270 30 141 0 Open ; 162 | 189 171 173 50 30 141 0 Open ; 163 | 191 271 171 760 24 130 0 Open ; 164 | 193 35 181 30 24 130 0 Open ; 165 | 195 181 177 30 12 130 0 Open ; 166 | 197 177 179 30 12 130 0 Open ; 167 | 199 179 183 210 12 130 0 Open ; 168 | 201 40 179 1190 12 130 0 Open ; 169 | 202 185 184 99.9 8 130 0 Open ; 170 | 203 183 185 510 8 130 0 Open ; 171 | 204 184 205 4530. 12 130 0 Open ; 172 | 205 204 185 1325. 12 130 0 Open ; 173 | 207 189 183 1350 12 130 0 Open ; 174 | 209 189 187 500 8 130 0 Open ; 175 | 211 169 269 646 12 130 0 Open ; 176 | 213 191 187 2560 12 130 0 Open ; 177 | 215 267 189 1230 12 130 0 Open ; 178 | 217 191 193 520 12 130 0 Open ; 179 | 219 193 195 360 12 130 0 Open ; 180 | 221 161 195 2300 8 130 0 Open ; 181 | 223 197 191 1150 12 130 0 Open ; 182 | 225 111 197 2790 12 130 0 Open ; 183 | 229 173 199 4000 24 141 0 Open ; 184 | 231 199 201 630 24 141 0 Open ; 185 | 233 201 203 120 24 130 0 Open ; 186 | 235 199 273 725 12 130 0 Open ; 187 | 237 205 207 1200 12 130 0 Open ; 188 | 238 207 206 450 12 130 0 Open ; 189 | 239 275 207 1430 12 130 0 Open ; 190 | 240 206 208 510 12 130 0 Open ; 191 | 241 208 209 885 12 130 0 Open ; 192 | 243 209 211 1210 16 130 0 Open ; 193 | 245 211 213 990 16 130 0 Open ; 194 | 247 213 215 4285 16 130 0 Open ; 195 | 249 215 217 1660 16 130 0 Open ; 196 | 251 217 219 2050 14 130 0 Open ; 197 | 257 217 225 1560 12 130 0 Open ; 198 | 261 213 229 2200 8 130 0 Open ; 199 | 263 229 231 1960 12 130 0 Open ; 200 | 269 211 237 2080 12 130 0 Open ; 201 | 271 237 229 790 8 130 0 Open ; 202 | 273 237 239 510 12 130 0 Open ; 203 | 275 239 241 35 12 130 0 Open ; 204 | 277 241 243 2200 12 130 0 Open ; 205 | 281 241 247 445 10 130 0 Open ; 206 | 283 239 249 430 12 130 0 Open ; 207 | 285 247 249 10 12 130 0 Open ; 208 | 287 247 255 1390 10 130 0 Open ; 209 | 289 50 255 925 10 130 0 Open ; 210 | 291 255 253 1100 10 130 0 Open ; 211 | 293 255 251 1100 8 130 0 Open ; 212 | 295 249 251 1450 12 130 0 Open ; 213 | 297 120 257 645 8 130 0 Open ; 214 | 299 257 259 350 8 130 0 Open ; 215 | 301 259 263 1400 8 130 0 Open ; 216 | 303 257 261 1400 8 130 0 Open ; 217 | 305 117 261 645 12 130 0 Open ; 218 | 307 261 263 350 12 130 0 Open ; 219 | 309 265 267 1580 8 130 0 Open ; 220 | 311 193 267 1170 12 130 0 Open ; 221 | 313 269 189 646 12 130 0 Open ; 222 | 315 181 271 260 24 130 0 Open ; 223 | 317 273 275 2230 8 130 0 Open ; 224 | 319 273 205 645 12 130 0 Open ; 225 | 321 163 265 1200 30 141 0 Open ; 226 | 323 201 275 300 12 130 0 Open ; 227 | 325 269 271 1290 8 130 0 Open ; 228 | 329 61 123 45500 30 140 0 Open ; 229 | 330 60 601 1 30 140 0 Closed ; 230 | 333 601 61 1 30 140 0 Open ; 231 | 232 | [PUMPS] 233 | ;ID Node1 Node2 Parameters 234 | 10 Lake 10 HEAD 1 ; 235 | 335 60 61 HEAD 2 ; 236 | 237 | [VALVES] 238 | ;ID Node1 Node2 Diameter Type Setting MinorLoss 239 | 240 | [TAGS] 241 | 242 | [DEMANDS] 243 | ;Junction Demand Pattern Category 244 | 245 | [STATUS] 246 | ;ID Status/Setting 247 | 10 Closed 248 | 249 | [PATTERNS] 250 | ;ID Multipliers 251 | ;General Default Demand Pattern 252 | 1 1.34 1.94 1.46 1.44 .76 .92 253 | 1 .85 1.07 .96 1.1 1.08 1.19 254 | 1 1.16 1.08 .96 .83 .79 .74 255 | 1 .64 .64 .85 .96 1.24 1.67 256 | ;Demand Pattern for Node 123 257 | 2 0 0 0 0 0 1219 258 | 2 0 0 0 1866 1836 1818 259 | 2 1818 1822 1822 1817 1824 1816 260 | 2 1833 1817 1830 1814 1840 1859 261 | ;Demand Pattern for Node 15 262 | 3 620 620 620 620 620 360 263 | 3 360 0 0 0 0 360 264 | 3 360 360 360 360 0 0 265 | 3 0 0 0 0 360 360 266 | ;Demand Pattern for Node 35 267 | 4 1637 1706 1719 1719 1791 1819 268 | 4 1777 1842 1815 1825 1856 1801 269 | 4 1819 1733 1664 1620 1613 1620 270 | 4 1616 1647 1627 1627 1671 1668 271 | ;Demand Pattern for Node 203 272 | 5 4439 4531 4511 4582 4531 4582 273 | 5 4572 4613 4643 4643 4592 4613 274 | 5 4531 4521 4449 4439 4449 4460 275 | 5 4439 4419 4368 4399 4470 4480 276 | 277 | [CURVES] 278 | ;ID X-Value Y-Value 279 | ;PUMP: Pump Curve for Pump 10 (Lake Source) 280 | 1 0 104. 281 | 1 2000. 92. 282 | 1 4000. 63. 283 | ;PUMP: Pump Curve for Pump 335 (River Source) 284 | 2 0 200. 285 | 2 8000. 138. 286 | 2 14000. 86. 287 | 288 | [CONTROLS] 289 | ;Lake source operates only part of the day 290 | Link 10 OPEN AT TIME 1 291 | Link 10 CLOSED AT TIME 15 292 | 293 | ;Pump 335 controlled by level in Tank 1 294 | ;When pump is closed, bypass pipe is opened 295 | Link 335 OPEN IF Node 1 BELOW 17.1 296 | Link 335 CLOSED IF Node 1 ABOVE 19.1 297 | Link 330 CLOSED IF Node 1 BELOW 17.1 298 | Link 330 OPEN IF Node 1 ABOVE 19.1 299 | 300 | 301 | [RULES] 302 | 303 | [ENERGY] 304 | Global Efficiency 75 305 | Global Price 0.0 306 | Demand Charge 0.0 307 | 308 | [EMITTERS] 309 | ;Junction Coefficient 310 | 311 | [QUALITY] 312 | ;Node InitQual 313 | 10 2 314 | 15 2 315 | 20 2 316 | 35 2 317 | 40 2 318 | 50 2 319 | 60 2 320 | 601 2 321 | 61 2 322 | 101 2 323 | 103 2 324 | 105 2 325 | 107 2 326 | 109 2 327 | 111 2 328 | 113 2 329 | 115 2 330 | 117 2 331 | 119 2 332 | 120 2 333 | 121 2 334 | 123 2 335 | 125 2 336 | 127 2 337 | 129 2 338 | 131 2 339 | 139 2 340 | 141 2 341 | 143 2 342 | 145 2 343 | 147 2 344 | 149 2 345 | 151 2 346 | 153 2 347 | 157 2 348 | 159 2 349 | 161 2 350 | 163 2 351 | 164 2 352 | 166 2 353 | 167 2 354 | 169 2 355 | 171 2 356 | 173 2 357 | 179 2 358 | 181 2 359 | 183 2 360 | 184 2 361 | 185 2 362 | 187 2 363 | 189 2 364 | 191 2 365 | 193 2 366 | 195 2 367 | 197 2 368 | 199 2 369 | 201 2 370 | 203 2 371 | 204 2 372 | 205 2 373 | 206 2 374 | 207 2 375 | 208 2 376 | 209 2 377 | 211 2 378 | 213 2 379 | 215 2 380 | 217 2 381 | 219 2 382 | 225 2 383 | 229 2 384 | 231 2 385 | 237 2 386 | 239 2 387 | 241 2 388 | 243 2 389 | 247 2 390 | 249 2 391 | 251 2 392 | 253 2 393 | 255 2 394 | 257 2 395 | 259 2 396 | 261 2 397 | 263 2 398 | 265 2 399 | 267 2 400 | 269 2 401 | 271 2 402 | 273 2 403 | 275 2 404 | 1 2 405 | 2 2 406 | 3 2 407 | 408 | [SOURCES] 409 | ;Node Type Quality Pattern 410 | River CONCEN 2 411 | Lake CONCEN 4 412 | 413 | [REACTIONS] 414 | ;Type Pipe/Tank Coefficient 415 | 416 | 417 | [REACTIONS] 418 | Order Bulk 1 419 | Order Tank 1 420 | Order Wall 1 421 | Global Bulk 0.0 422 | Global Wall 0.0 423 | Limiting Potential 0.0 424 | Roughness Correlation 0.0 425 | 426 | [MIXING] 427 | ;Tank Model 428 | 429 | [TIMES] 430 | Duration 24:00 431 | Hydraulic Timestep 1:00 432 | Quality Timestep 0:05 433 | Pattern Timestep 1:00 434 | Pattern Start 0:00 435 | Report Timestep 1:00 436 | Report Start 0:00 437 | Start ClockTime 12 am 438 | Statistic None 439 | 440 | [REPORT] 441 | Status Yes 442 | Summary No 443 | Page 0 444 | 445 | [OPTIONS] 446 | Units GPM 447 | Headloss H-W 448 | Specific Gravity 1.0 449 | Viscosity 1.0 450 | Trials 40 451 | Accuracy 0.001 452 | CHECKFREQ 2 453 | MAXCHECK 10 454 | DAMPLIMIT 0 455 | Unbalanced Continue 10 456 | Pattern 1 457 | Demand Multiplier 1.0 458 | Emitter Exponent 0.5 459 | Quality Chemical mg/L 460 | Diffusivity 1.0 461 | Tolerance 0.01 462 | 463 | [COORDINATES] 464 | ;Node X-Coord Y-Coord 465 | 10 9.00 27.85 466 | 15 38.68 23.76 467 | 20 29.44 26.91 468 | 35 25.46 10.52 469 | 40 27.02 9.81 470 | 50 33.01 3.01 471 | 60 23.90 29.94 472 | 601 23.00 29.49 473 | 61 23.71 29.03 474 | 101 13.81 22.94 475 | 103 12.96 21.31 476 | 105 16.97 21.28 477 | 107 18.45 20.46 478 | 109 17.64 18.92 479 | 111 20.21 17.53 480 | 113 22.04 16.61 481 | 115 20.98 19.18 482 | 117 21.69 21.28 483 | 119 23.70 22.76 484 | 120 22.08 23.10 485 | 121 23.54 25.50 486 | 123 23.37 27.31 487 | 125 24.59 25.64 488 | 127 29.29 26.40 489 | 129 30.32 26.39 490 | 131 37.89 29.55 491 | 139 33.28 24.54 492 | 141 35.68 23.08 493 | 143 37.47 21.97 494 | 145 33.02 19.29 495 | 147 30.24 20.38 496 | 149 29.62 20.74 497 | 151 28.29 21.39 498 | 153 28.13 22.63 499 | 157 24.85 20.16 500 | 159 23.12 17.50 501 | 161 25.10 15.28 502 | 163 25.39 14.98 503 | 164 25.98 15.14 504 | 166 26.48 15.13 505 | 167 25.88 12.98 506 | 169 25.68 12.74 507 | 171 26.65 11.80 508 | 173 26.87 11.59 509 | 179 25.71 10.40 510 | 181 25.72 10.74 511 | 183 25.45 10.18 512 | 184 25.15 9.52 513 | 185 25.01 9.67 514 | 187 23.64 11.04 515 | 189 24.15 11.37 516 | 191 22.10 14.07 517 | 193 22.88 14.35 518 | 195 23.18 14.72 519 | 197 20.97 15.18 520 | 199 29.42 8.44 521 | 201 30.89 8.57 522 | 203 31.14 8.89 523 | 204 23.80 10.90 524 | 205 29.20 6.46 525 | 206 31.66 6.64 526 | 207 31.00 6.61 527 | 208 32.54 6.81 528 | 209 33.76 6.59 529 | 211 34.20 5.54 530 | 213 35.26 6.16 531 | 215 39.95 8.73 532 | 217 42.11 8.67 533 | 219 44.86 9.32 534 | 225 43.53 7.38 535 | 229 36.16 3.49 536 | 231 38.38 2.54 537 | 237 35.37 3.08 538 | 239 35.76 2.31 539 | 241 35.87 2.11 540 | 243 37.04 0.00 541 | 247 35.02 2.05 542 | 249 35.02 1.81 543 | 251 34.15 1.10 544 | 253 32.17 1.88 545 | 255 33.51 2.45 546 | 257 21.17 23.32 547 | 259 20.80 23.40 548 | 261 20.79 21.45 549 | 263 20.32 21.57 550 | 265 25.39 13.60 551 | 267 23.38 12.95 552 | 269 25.03 12.14 553 | 271 25.97 11.00 554 | 273 29.16 7.38 555 | 275 31.07 8.29 556 | River 24.15 31.06 557 | Lake 8.00 27.53 558 | 1 27.46 9.84 559 | 2 32.99 3.45 560 | 3 29.41 27.27 561 | 562 | [VERTICES] 563 | ;Link X-Coord Y-Coord 564 | 565 | [LABELS] 566 | ;X-Coord Y-Coord Label & Anchor Node 567 | 8.00 29.42 "LAKE" 568 | 25.00 31.10 "RIVER" 569 | 570 | [BACKDROP] 571 | DIMENSIONS 6.16 -1.55 46.70 32.61 572 | UNITS None 573 | FILE 574 | OFFSET 0.00 0.00 575 | 576 | [END] 577 | -------------------------------------------------------------------------------- /epanet-module/outbin.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | import struct 3 | from io import SEEK_END 4 | 5 | MAGICNUMBER= 516114521 6 | MAXFNAME= 259 #/* Max. # characters in file name */ 7 | MAXMSG = 79 #/* Max. # characters in message text */ 8 | MAXID = 31 #/* Max. # characters in ID name */ 9 | INTSIZE = 4 10 | REAL4SIZE = 4 11 | 12 | 13 | _pressureunits= {0:"psi" 14 | ,1:"meters" 15 | ,2:"kPa"} 16 | 17 | 18 | _statistics= {0: "" #none (report time series) 19 | ,1: "time-averaged values" 20 | ,2: "minimum values" 21 | ,3: "maximum values" 22 | ,4: "ranges"} 23 | 24 | _flowunits= {0 : "cubic feet/second" 25 | ,1 : "gallons/minute" 26 | ,2 : "million gallons/day" 27 | ,3 : "Imperial million gallons/day" 28 | ,4 : "acre-ft/day" 29 | ,5 : "liters/second" 30 | ,6 : "liters/minute" 31 | ,7 : "megaliters/day" 32 | ,8 : "cubic meters/hour" 33 | ,9 : "cubic meters/day" 34 | } 35 | 36 | _waterquality= { 0: "" #none 37 | ,1: "chemical" 38 | ,2: "age" 39 | ,3: "source trace" 40 | } 41 | 42 | _status= {0 : "closed (max. head exceeded for pump)" 43 | ,1 : "temporarily closed" 44 | ,2 : "closed" 45 | ,3 : "open" 46 | ,4 : "active (partially open)" 47 | ,5 : "open (max. flow exceeded for pump)" 48 | ,6 : "open (flow setting not met for FCV)" 49 | ,7 : "open (pressure setting not met for PRV or PSV)"} 50 | 51 | _linktype= {0: "PipeCV" 52 | ,1: "Pipe" 53 | ,2: "Pump" 54 | ,3: "PRV" 55 | ,4: "PSV" 56 | ,5: "PBV" 57 | ,6: "FCV" 58 | ,7: "TCV" 59 | ,8: "GPV"} 60 | 61 | 62 | 63 | 64 | 65 | class OutBinNode(object): 66 | def __init__(self, outfile, ordinal): 67 | self.outfile= outfile 68 | self.ordinal= ordinal 69 | self._type= "JUNCTION" 70 | def _read_timeserie(self, varindex): 71 | return self.outfile._node_timeserie(self.ordinal, varindex) 72 | @property 73 | def ID(self): 74 | "node ID" 75 | return self.outfile.nodeID(self.ordinal+1) 76 | @property 77 | def nodetype(self): 78 | "node type" 79 | return self._type 80 | @property 81 | def demand(self): 82 | "node Demand" 83 | return self._read_timeserie(varindex=0) 84 | @property 85 | def head(self): 86 | "node Head" 87 | return self._read_timeserie(varindex=1) 88 | @property 89 | def pressure(self): 90 | "node Pressure" 91 | return self._read_timeserie(varindex=2) 92 | @property 93 | def quality(self): 94 | "node Water Quality" 95 | return self._read_timeserie(varindex=3) 96 | 97 | class OutBinLink(object): 98 | def __init__(self, outfile, ordinal): 99 | self.outfile= outfile 100 | self.ordinal= ordinal 101 | def _read_timeserie(self, varindex): 102 | return self.outfile._link_timeserie(self.ordinal, 103 | varindex) 104 | @property 105 | def ID(self): 106 | "link ID" 107 | return self.outfile.linkID(self.ordinal+1) 108 | @property 109 | def linktype(self): 110 | "link type" 111 | return self.outfile.linktype(self.ordinal+1) 112 | @property 113 | def flow(self): 114 | "link flow" 115 | return self._read_timeserie(varindex=0) 116 | @property 117 | def velocity(self): 118 | "link velocity" 119 | return self._read_timeserie(varindex=1) 120 | @property 121 | def headloss(self): 122 | """Headloss per 1000 Units of Length 123 | 124 | (total head for pumps and head loss for valves)""" 125 | return self._read_timeserie(varindex=2) 126 | @property 127 | def quality(self): 128 | "link Average Water Quality" 129 | return self._read_timeserie(varindex=3) 130 | @property 131 | def status(self): 132 | "status" 133 | return [_status[int(x)] for x in self._read_timeserie(varindex=4)] 134 | @property 135 | def setting(self): 136 | """link setting 137 | 138 | Roughness coeff. for Pipes, 139 | Speed for Pumps 140 | Setting for Valves""" 141 | return self._read_timeserie(varindex=5) 142 | @property 143 | def reactionrate(self): 144 | "link Reaction Rate" 145 | return self._read_timeserie(varindex=6) 146 | @property 147 | def friction(self): 148 | "link Friction Factor" 149 | return self._read_timeserie(varindex=7) 150 | 151 | 152 | class EpanetOutBin(object): 153 | """Class wrapper for Epanet binary output file 154 | 155 | example code: 156 | >>> import EpanetOutBin 157 | >>> with EpanetOutBin("Net1.bin") as a: 158 | ... print a.nodes.keys() 159 | ... print a.nodes['10'].demand 160 | ... print a.flowunits 161 | ... 162 | [u'11', u'10', u'13', u'12', u'21', u'22', u'23', u'32', u'31', u'2', u'9'] 163 | [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, 164 | 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] 165 | gallons/minute 166 | 167 | alternative use open/close: 168 | >>> import EpanetOutBin 169 | >>> a= EpanetOutBin("Net1.bin") 170 | >>> a.open() 171 | >>> a.nodes['10'].demand 172 | [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, 173 | 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] 174 | >>> a.close()""" 175 | 176 | def __init__(self, nomefilebin): 177 | self.filename= nomefilebin 178 | 179 | def open(self): 180 | f= open(self.filename,"rb") 181 | self._file= f 182 | 183 | # Prolog Section 184 | Prolog = struct.unpack("15i",f.read(15*INTSIZE)) 185 | if (Prolog[ 0] != MAGICNUMBER): 186 | raise Exception("{0} is not a Epanet binary output file".format(nomefilebin)) 187 | 188 | self.CODEVERSION =Prolog[ 1] 189 | self._Nnodes =Prolog[ 2] 190 | self._Ntanks =Prolog[ 3] 191 | self._Nlinks =Prolog[ 4] 192 | self._Npumps =Prolog[ 5] 193 | self._Nvalves =Prolog[ 6] 194 | Tstatflag =Prolog[11] 195 | Rstart =Prolog[12] 196 | Rstep =Prolog[13] 197 | Dur =Prolog[14] 198 | 199 | f.seek(-3*INTSIZE, SEEK_END) 200 | self._Nperiods, warn, magic = struct.unpack("3i",f.read(3*INTSIZE)) 201 | if magic != MAGICNUMBER : 202 | raise Exception("File {0} corrupted or incomplete".format(nomefilebin)) 203 | 204 | 205 | self.prolog_start= 0 206 | prolog_length = 15*INTSIZE 207 | prolog_length+= 3*(MAXMSG+1) #title 208 | prolog_length+= 2*(MAXFNAME+1) #filenames 209 | prolog_length+= 2*(MAXID+1) #ChemName QUALITY.Units 210 | prolog_length+= self._Nnodes*(MAXID+1) #nodenames 211 | prolog_length+= self._Nlinks*(MAXID+1) #linknames 212 | prolog_length+= self._Nlinks*INTSIZE* 3 #N1, N2, Type 213 | prolog_length+= self._Ntanks*(INTSIZE+ REAL4SIZE) # Tank[i].Node+ Tank[i].A 214 | prolog_length+= self._Nnodes*(REAL4SIZE) #node elevations 215 | prolog_length+= self._Nlinks*REAL4SIZE*2#link lengths & diameters 216 | 217 | self.energy_start= self.prolog_start+ prolog_length 218 | energy_length = 7*REAL4SIZE*self._Npumps 219 | energy_length+=REAL4SIZE 220 | 221 | self.dynamic_start= self.energy_start+ energy_length 222 | self.dynamic_step = 4*REAL4SIZE*self._Nnodes 223 | self.dynamic_step+= 8*REAL4SIZE*self._Nlinks 224 | dynamic_lenght= self.dynamic_step* self._Nperiods 225 | 226 | self.epilog_start= self.dynamic_start+dynamic_lenght 227 | 228 | self._nodes= {} 229 | for i in range(self._Nnodes): 230 | node= OutBinNode(self, i) 231 | self._nodes[node.ID]= node 232 | 233 | for i in range(self._Ntanks): 234 | area= self.tankarea(i) 235 | if area > 0.0: 236 | tipo= "TANK" 237 | else: 238 | tipo= "RESERVOIR" 239 | nodo= self._nodes[self.tanknode(i)] 240 | nodo._type= tipo 241 | nodo.area= area 242 | 243 | self._links= {} 244 | for i in range(self._Nlinks): 245 | link= OutBinLink(self, i) 246 | self._links[link.ID]= link 247 | 248 | 249 | def close(self): 250 | self._file.close() 251 | 252 | def __enter__(self): 253 | self.open() 254 | return self 255 | 256 | def __exit__(self ,type, value, traceback): 257 | self._file.close() 258 | return False 259 | 260 | 261 | 262 | @property 263 | def nodes(self): 264 | "nodes dictionary (node ID as key)" 265 | return self._nodes 266 | 267 | @property 268 | def links(self): 269 | "links dictionary (link ID as key)" 270 | return self._links 271 | 272 | 273 | @property 274 | def quality(self): 275 | "Water Quality" 276 | addr= self.prolog_start+ 7*INTSIZE 277 | self._file.seek(addr) 278 | i= struct.unpack("i",self._file.read(INTSIZE))[0] 279 | return _waterquality[i] 280 | 281 | @property 282 | def tracenode(self): 283 | "Node ID for Source Tracing" 284 | addr= self.prolog_start+ 8*INTSIZE 285 | self._file.seek(addr) 286 | i= struct.unpack("i",self._file.read(INTSIZE))[0] 287 | if i>0: 288 | return self.nodeID(i) 289 | 290 | @property 291 | def flowunits(self): 292 | "Flow Units" 293 | addr= self.prolog_start+ 9*INTSIZE 294 | self._file.seek(addr) 295 | i= struct.unpack("i",self._file.read(INTSIZE))[0] 296 | return _flowunits[i] 297 | 298 | @property 299 | def pressureunits(self): 300 | "Pressure Units " 301 | addr= self.prolog_start+ 10*INTSIZE 302 | self._file.seek(addr) 303 | i= struct.unpack("i",self._file.read(INTSIZE))[0] 304 | return _pressureunits[i] 305 | 306 | @property 307 | def statistics(self): 308 | "Time Statistics " 309 | addr= self.prolog_start+ 11*INTSIZE 310 | self._file.seek(addr) 311 | i= struct.unpack("i",self._file.read(INTSIZE))[0] 312 | return _statistics[i] 313 | 314 | 315 | @property 316 | def title(self): 317 | "Problem Title" 318 | addr= self.prolog_start+ 15*INTSIZE 319 | self._file.seek(addr) 320 | stri = self._file.read(MAXMSG+1).decode().strip("\x00")+"\n" 321 | stri+= self._file.read(MAXMSG+1).decode().strip("\x00")+"\n" 322 | stri+= self._file.read(MAXMSG+1).decode().strip("\x00") 323 | return stri 324 | 325 | @property 326 | def inputfilename(self): 327 | "Name of inputfile" 328 | addr= self.prolog_start+ 15*INTSIZE+ 3*(MAXMSG+1) 329 | self._file.seek(addr) 330 | return self._file.read(MAXFNAME+1).decode().strip("\x00") 331 | 332 | @property 333 | def reportfilename(self): 334 | "Name of reportfile" 335 | addr= self.prolog_start+ 15*INTSIZE+ 3*(MAXMSG+1)+ (MAXFNAME+1) 336 | self._file.seek(addr) 337 | return self._file.read(MAXFNAME+1).decode().strip("\x00") 338 | 339 | @property 340 | def chemicalname(self): 341 | "Name of Chemical" 342 | addr = self.prolog_start+ 15*INTSIZE 343 | addr+= 3*(MAXMSG+1)+ 2*(MAXFNAME+1) 344 | self._file.seek(addr) 345 | return self._file.read(MAXID+1).decode().strip("\x00") 346 | 347 | @property 348 | def chemicalunits(self): 349 | "Chemical Concentration Units " 350 | addr = self.prolog_start+ 15*INTSIZE 351 | addr+= 3*(MAXMSG+1)+ 2*(MAXFNAME+1) 352 | addr+= MAXID+1 353 | self._file.seek(addr) 354 | return self._file.read(MAXID+1).decode().strip("\x00") 355 | 356 | 357 | @property 358 | def energy(self): 359 | "Peak Energy Usage (kw-hrs)" 360 | addr= self.energy_start+ 7*REAL4SIZE*self._Npumps 361 | self._file.seek(addr) 362 | return struct.unpack("f",self._file.read(REAL4SIZE))[0] 363 | @property 364 | def bulk_rrate(self): 365 | "Average bulk reaction rate (mass/hr)" 366 | addr= self.epilog_start 367 | self._file.seek(addr) 368 | return struct.unpack("f",self._file.read(REAL4SIZE))[0] 369 | @property 370 | def wall_rrate(self): 371 | "Average wall reaction rate (mass/hr)" 372 | addr= self.epilog_start+ REAL4SIZE 373 | self._file.seek(addr) 374 | return struct.unpack("f",self._file.read(REAL4SIZE))[0] 375 | @property 376 | def tank_rrate(self): 377 | "Average tank reaction rate (mass/hr)" 378 | addr= self.epilog_start+ 2*REAL4SIZE 379 | self._file.seek(addr) 380 | return struct.unpack("f",self._file.read(REAL4SIZE))[0] 381 | @property 382 | def source_inflowrate(self): 383 | "Average source inflow rate (mass/hr)" 384 | addr= self.epilog_start+ 3*REAL4SIZE 385 | self._file.seek(addr) 386 | return struct.unpack("f",self._file.read(REAL4SIZE))[0] 387 | @property 388 | def warning(self): 389 | "Warning Flag: True if warnings were generated" 390 | addr= self.epilog_start+ 4*REAL4SIZE+ INTSIZE 391 | self._file.seek(addr) 392 | return struct.unpack("i",self._file.read(INTSIZE))[0]==1 393 | 394 | def _node_timeserie(self, ordinal, varindex): 395 | addr = self.dynamic_start 396 | addr+= varindex*REAL4SIZE*self._Nnodes 397 | addr+= ordinal* REAL4SIZE 398 | return self._read_timeserie(addr) 399 | 400 | def _link_timeserie(self, ordinal, varindex): 401 | addr = self.dynamic_start 402 | addr+= 4*REAL4SIZE*self._Nnodes 403 | addr+= varindex*REAL4SIZE*self._Nlinks 404 | addr+= ordinal* REAL4SIZE 405 | return self._read_timeserie(addr) 406 | 407 | def _read_timeserie(self, addr): 408 | x=[] 409 | for i in range(self._Nperiods): 410 | self._file.seek(addr) 411 | val= struct.unpack("f",self._file.read(REAL4SIZE))[0] 412 | x.append(val) 413 | addr+= self.dynamic_step 414 | return x 415 | 416 | 417 | def _read_ID(self, addr): 418 | self._file.seek(addr) 419 | return struct.unpack("{0}s".format(MAXID+1),self._file.read(MAXID+1))[0].decode().strip("\x00") 420 | 421 | def nodeID(self, index): 422 | addr = self.prolog_start 423 | addr+= 15*INTSIZE+3*(MAXMSG+1)+2*(MAXFNAME+1)+ 2*(MAXID+1) 424 | addr+= (index-1)* (MAXID+1) 425 | return self._read_ID(addr) 426 | 427 | def linkID(self, index): 428 | addr = self.prolog_start 429 | addr+= 15*INTSIZE+3*(MAXMSG+1)+2*(MAXFNAME+1)+ 2*(MAXID+1) 430 | addr+= self._Nnodes* (MAXID+1) 431 | addr+= (index-1)* (MAXID+1) 432 | return self._read_ID(addr) 433 | 434 | def linktype(self, index): 435 | addr = self.prolog_start 436 | addr+= 15*INTSIZE+3*(MAXMSG+1)+2*(MAXFNAME+1)+ 2*(MAXID+1) 437 | addr+= self._Nnodes* (MAXID+1) 438 | addr+= self._Nlinks* (MAXID+1) 439 | addr+= 2* self._Nlinks* INTSIZE 440 | addr+= (index-1)* INTSIZE 441 | self._file.seek(addr) 442 | val= struct.unpack("i",self._file.read(INTSIZE))[0] 443 | return _linktype[val] 444 | 445 | def tanknode(self, ordinal): 446 | addr = self.prolog_start 447 | addr+= 15*INTSIZE+3*(MAXMSG+1)+2*(MAXFNAME+1)+ 2*(MAXID+1) 448 | addr+= self._Nnodes* (MAXID+1) 449 | addr+= self._Nlinks* (MAXID+1) 450 | addr+= 3* self._Nlinks* INTSIZE 451 | addr+= ordinal* INTSIZE 452 | self._file.seek(addr) 453 | index= struct.unpack("i",self._file.read(INTSIZE))[0] 454 | return self.nodeID(index) 455 | 456 | def tankarea(self, ordinal): 457 | addr = self.prolog_start 458 | addr+= 15*INTSIZE+3*(MAXMSG+1)+2*(MAXFNAME+1)+ 2*(MAXID+1) 459 | addr+= self._Nnodes* (MAXID+1) 460 | addr+= self._Nlinks* (MAXID+1) 461 | addr+= 3* self._Nlinks* INTSIZE 462 | addr+= self._Ntanks* INTSIZE 463 | addr+= ordinal* REAL4SIZE 464 | self._file.seek(addr) 465 | return struct.unpack("f",self._file.read(REAL4SIZE))[0] 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | -------------------------------------------------------------------------------- /epanet-module/test_outbin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import print_function 3 | 4 | from outbin import EpanetOutBin 5 | with EpanetOutBin("example3.bin") as a: 6 | name= '217' 7 | if name in a.nodes: 8 | node= a.nodes[name] 9 | print (node.ID) 10 | print (node.demand) 11 | print (a.flowunits) 12 | else: 13 | print ("node {0} not found".format(name)) -------------------------------------------------------------------------------- /epanet-module/tester.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import print_function 3 | import epamodule as em 4 | def out(s): 5 | print (s.decode()) 6 | 7 | nomeinp= "example3.inp" 8 | nomeout= "example3.txt" 9 | nomebin= "example3.bin" 10 | em.ENepanet(nomeinp, nomeout, nomebin, vfunc=out) -------------------------------------------------------------------------------- /epanet_python/appveyor.yml: -------------------------------------------------------------------------------- 1 | 2 | 3 | environment: 4 | passphrase: 5 | secure: En1qcj1/l2B3Nov+vOHlfRrFZk00bg1HPRouD64Gzfw= 6 | CIBW_SKIP: cp27-* cp34-* cp35-* *-win32 7 | CIBW_BEFORE_BUILD: pip install six 8 | CIBW_TEST_COMMAND: pytest {project}\\tests 9 | CIBW_TEST_REQUIRES: pytest numpy 10 | matrix: 11 | - PYTHON: "C:\\Python36-x64" 12 | 13 | 14 | install: 15 | - choco install swig 16 | - ps: iex ((New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/appveyor/secure-file/master/install.ps1')) 17 | - appveyor-tools\secure-file -decrypt .coveralls.yml.enc -secret %passphrase% 18 | - "%PYTHON%\\python.exe -m pip install wheel tox" 19 | 20 | 21 | build: off 22 | 23 | 24 | test_script: 25 | - before_build.bat 26 | - "%PYTHON%\\python.exe -m tox -v" 27 | 28 | after_test: 29 | - "%PYTHON%\\python.exe -m pip install cibuildwheel==0.10.1" 30 | - "%PYTHON%\\python.exe -m cibuildwheel --output-dir wheelhouse .\\epanet_python\\output" 31 | - "%PYTHON%\\python.exe -m cibuildwheel --output-dir wheelhouse .\\epanet_python\\toolkit" 32 | 33 | artifacts: 34 | - path: "wheelhouse\\*.whl" 35 | name: Wheels 36 | 37 | cache: 38 | - C:\ProgramData\chocolatey\bin -> appveyor.yml 39 | - C:\ProgramData\chocolatey\lib -> appveyor.yml 40 | - C:\projects\epanet-python\.tox -> tox.ini 41 | -------------------------------------------------------------------------------- /epanet_python/before_build.bat: -------------------------------------------------------------------------------- 1 | :: 2 | :: before_build.bat - Prepares for epanet toolkit and output module builds 3 | :: 4 | :: Date Created: 12/12/2018 5 | :: 6 | :: Author: Michael E. Tryby 7 | :: US EPA - ORD/NRMRL 8 | :: 9 | :: Requires: 10 | :: git 11 | :: CMake 12 | :: Visual Studio Build Tools 13 | :: SWIG 14 | :: 15 | :: Note: 16 | :: This script must be located at the root of the project folder 17 | :: in order to work correctly. 18 | :: 19 | 20 | 21 | :: Determine project path and strip trailing \ from path 22 | set "PROJECT_PATH=%~dp0" 23 | IF %PROJECT_PATH:~-1%==\ set "PROJECT_PATH=%PROJECT_PATH:~0,-1%" 24 | 25 | set TOOLKIT_PATH=\epanet_python\toolkit\epanet\toolkit 26 | set OUTPUT_PATH=\epanet_python\output\epanet\output 27 | 28 | 29 | mkdir buildlib 30 | cd buildlib 31 | git clone --branch=dev https://github.com/OpenWaterAnalytics/EPANET.git 32 | cd epanet 33 | 34 | 35 | mkdir buildprod 36 | cd buildprod 37 | cmake -G"Visual Studio 14 2015 Win64" -DBUILD_PY_LIB=ON -DBUILD_TESTS=OFF .. 38 | cmake --build . --config Release 39 | 40 | 41 | copy /Y .\bin\Release\epanet_py.dll %PROJECT_PATH%\%TOOLKIT_PATH% 42 | copy /Y .\lib\Release\epanet_py.lib %PROJECT_PATH%\%TOOLKIT_PATH% 43 | copy /Y ..\include\*.h %PROJECT_PATH%\%TOOLKIT_PATH% 44 | 45 | copy /Y .\bin\Release\epanet-output.dll %PROJECT_PATH%\%OUTPUT_PATH% 46 | copy /Y .\lib\Release\epanet-output.lib %PROJECT_PATH%\%OUTPUT_PATH% 47 | copy /Y ..\src\outfile\include\*.h %PROJECT_PATH%\%OUTPUT_PATH% 48 | 49 | 50 | :: Generate swig wrappers 51 | cd %PROJECT_PATH%\%TOOLKIT_PATH% 52 | swig -python -py3 toolkit.i 53 | cd %PROJECT_PATH%\%OUTPUT_PATH% 54 | swig -python -py3 output.i 55 | 56 | 57 | :: Return to project root 58 | cd %PROJECT_PATH% 59 | -------------------------------------------------------------------------------- /epanet_python/epanet_python/output/epanet/output/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # 4 | # __init__.py - EPANET output package 5 | # 6 | # Date Created: August 15, 2018 7 | # 8 | # Author: Michael E. Tryby 9 | # US EPA - ORD/NRMRL 10 | # 11 | 12 | ''' 13 | A low level pythonic API for the epanet-output dll using SWIG. 14 | ''' 15 | 16 | 17 | __author__ = "Michael Tryby" 18 | __copyright__ = "None" 19 | __credits__ = "Maurizio Cingi" 20 | __license__ = "CC0 1.0 Universal" 21 | 22 | __version__ = "0.2.0" 23 | __date__ = "August 15, 2018" 24 | 25 | __maintainer__ = "Michael Tryby" 26 | __email__ = "tryby.michael@epa.gov" 27 | __status = "Development" 28 | 29 | 30 | from enum import Enum, auto 31 | 32 | from epanet.output import output as oapi 33 | 34 | 35 | class Units(Enum): 36 | FLOW_RATE = auto() 37 | HYD_HEAD = auto() 38 | PRESSURE = auto() 39 | CONCEN = auto() 40 | VELOCITY = auto() 41 | HEADLOSS = auto() 42 | RX_RATE = auto() 43 | UNITLESS = auto() 44 | NONE = auto() 45 | 46 | class RxUnits(Enum): 47 | MGH = auto() 48 | UGH = auto() 49 | 50 | 51 | class OutputMetadata(): 52 | ''' 53 | Simple attribute name and unit lookup. 54 | ''' 55 | 56 | _unit_labels_us_ = { 57 | Units.HYD_HEAD: "ft", 58 | Units.VELOCITY: "ft/sec", 59 | Units.HEADLOSS: "ft/1000ft", 60 | Units.UNITLESS: "unitless", 61 | Units.NONE: "", 62 | 63 | RxUnits.MGH: "mg/hr", 64 | RxUnits.UGH: "ug/hr", 65 | 66 | oapi.FlowUnits.CFS: "cu ft/s", 67 | oapi.FlowUnits.GPM: "gal/min", 68 | oapi.FlowUnits.MGD: "M gal/day", 69 | oapi.FlowUnits.IMGD: "M Imp gal/day", 70 | oapi.FlowUnits.AFD: "ac ft/day", 71 | 72 | oapi.PressUnits.PSI: "psi", 73 | 74 | oapi.QualUnits.NONE: "", 75 | oapi.QualUnits.MGL: "mg/L", 76 | oapi.QualUnits.UGL: "ug/L", 77 | oapi.QualUnits.HOURS: "hrs", 78 | oapi.QualUnits.PRCNT: "%"} 79 | 80 | _unit_labels_si_ = { 81 | Units.HYD_HEAD: "m", 82 | Units.VELOCITY: "m/sec", 83 | Units.HEADLOSS: "m/Km", 84 | Units.UNITLESS: "unitless", 85 | Units.NONE: "", 86 | 87 | RxUnits.MGH: "mg/hr", 88 | RxUnits.UGH: "ug/hr", 89 | 90 | oapi.FlowUnits.LPS: "L/sec", 91 | oapi.FlowUnits.LPM: "L/min", 92 | oapi.FlowUnits.MLD: "M L/day", 93 | oapi.FlowUnits.CMH: "cu m/hr", 94 | oapi.FlowUnits.CMD: "cu m/day", 95 | 96 | oapi.PressUnits.MTR: "meters", 97 | oapi.PressUnits.KPA: "kPa", 98 | 99 | oapi.QualUnits.NONE: "", 100 | oapi.QualUnits.MGL: "mg/L", 101 | oapi.QualUnits.UGL: "ug/L", 102 | oapi.QualUnits.HOURS: "hrs", 103 | oapi.QualUnits.PRCNT: "%"} 104 | 105 | 106 | def __init__(self, output_handle): 107 | 108 | self.units = list() 109 | # If outputhandle not initialized use default settings 110 | if output_handle == None: 111 | self.units = [oapi.FlowUnits.GPM.value, 112 | oapi.PressUnits.PSI.value, 113 | oapi.QualUnits.NONE.value] 114 | # Else quary the output api for unit settings 115 | else: 116 | for u in oapi.Units: 117 | self.units.append(oapi.getunits(output_handle, u)) 118 | 119 | # Convert unit settings to enums 120 | self._flow = oapi.FlowUnits(self.units[0]) 121 | self._press = oapi.PressUnits(self.units[1]) 122 | self._qual = oapi.QualUnits(self.units[2]) 123 | 124 | # Determine unit system from flow setting 125 | if self._flow.value <= oapi.FlowUnits.AFD.value: 126 | self._unit_labels = type(self)._unit_labels_us_ 127 | else: 128 | self._unit_labels = type(self)._unit_labels_si_ 129 | 130 | # Determine mass units from quality settings 131 | if self._qual == oapi.QualUnits.MGL: 132 | self._rx_rate = RxUnits.MGH 133 | elif self._qual == oapi.QualUnits.UGL: 134 | self._rx_rate = RxUnits.UGH 135 | else: 136 | self._rx_rate = Units.NONE 137 | 138 | 139 | self._metadata = { 140 | oapi.NodeAttribute.DEMAND: ("Demand", self._unit_labels[self._flow]), 141 | oapi.NodeAttribute.HEAD: ("Head", self._unit_labels[Units.HYD_HEAD]), 142 | oapi.NodeAttribute.PRESSURE: ("Pressure", self._unit_labels[self._press]), 143 | oapi.NodeAttribute.QUALITY: ("Quality", self._unit_labels[self._qual]), 144 | 145 | oapi.LinkAttribute.FLOW: ("Flow", self._unit_labels[self._flow]), 146 | oapi.LinkAttribute.VELOCITY: ("Velocity", self._unit_labels[Units.VELOCITY]), 147 | oapi.LinkAttribute.HEADLOSS: ("Unit Headloss", self._unit_labels[Units.HEADLOSS]), 148 | oapi.LinkAttribute.AVG_QUALITY: ("Quality", self._unit_labels[self._qual]), 149 | oapi.LinkAttribute.STATUS: ("Status", self._unit_labels[Units.NONE]), 150 | oapi.LinkAttribute.SETTING: ("Setting", self._unit_labels[Units.NONE]), 151 | oapi.LinkAttribute.RX_RATE: ("Reaction Rate", self._unit_labels[self._rx_rate]), 152 | oapi.LinkAttribute.FRCTN_FCTR: ("Friction Factor", self._unit_labels[Units.UNITLESS]) 153 | } 154 | 155 | 156 | def get_attribute_metadata(self, attribute): 157 | ''' 158 | Takes an attribute enum and returns a tuple with name and units. 159 | ''' 160 | return self._metadata[attribute] 161 | 162 | 163 | # Units of Measurement 164 | # 165 | # Units US Customary SI Metric 166 | # Concentration mg/L mg/L 167 | # ug/L ug/L 168 | # Demand flow flow 169 | # Diameter in mm 170 | # Efficiency percent percent 171 | # Elevation feet meters 172 | # Emitter Coefficient flow/(psi)^1/2 flow/(meters)^1/2 173 | # Energy kilowatt-hours kilowatt-hours 174 | # Flow CFS LPS 175 | # GPM LPM 176 | # MGD MLD 177 | # IMGD CMH 178 | # AFD CMD 179 | # Friction Factor unitless unitless 180 | # Hydraulic Head feet meters 181 | # Length feet meters 182 | # Minor Loss Coefficient unitless unitless 183 | # Power horsepower kilowatts 184 | # Pressure psi meters 185 | # Reaction Coeff (Bulk) 1/day (1st-order) 1/day 186 | # Reaction Coeff (Wall) mass/L/day (0th-order) mass/L/day 187 | # ft/day (1st-order) meters/day (1st-order) 188 | # Roughness Coeff 10^-3 feet (DW) mm (DW) 189 | # unitless unitless 190 | # Source Mass Injection mass/min mass/min 191 | # Velocity feet/sec meters/sec 192 | # Volume cubic feet cubic meters 193 | # Water Age hours hours 194 | -------------------------------------------------------------------------------- /epanet_python/epanet_python/output/epanet/output/output.i: -------------------------------------------------------------------------------- 1 | /* 2 | * epanet_output.i - SWIG interface description file for EPANET Output API 3 | * 4 | * Created: 9/20/2017 5 | * 6 | * Author: Michael E. Tryby 7 | * US EPA - ORD/NRMRL 8 | * 9 | */ 10 | %module(package="epanet") output 11 | %{ 12 | #include "epanet_output.h" 13 | 14 | #define SWIG_FILE_WITH_INIT 15 | %} 16 | 17 | %include "typemaps.i" 18 | 19 | /* DEFINE AND TYPEDEF MUST BE INCLUDED */ 20 | 21 | typedef void* ENR_Handle; 22 | 23 | typedef enum { 24 | ENR_node = 1, 25 | ENR_link = 2 26 | } ENR_ElementType; 27 | 28 | typedef enum { 29 | ENR_flowUnits = 1, 30 | ENR_pressUnits = 2, 31 | ENR_qualUnits = 3 32 | } ENR_Units; 33 | 34 | typedef enum { 35 | ENR_CFS = 0, 36 | ENR_GPM = 1, 37 | ENR_MGD = 2, 38 | ENR_IMGD = 3, 39 | ENR_AFD = 4, 40 | ENR_LPS = 5, 41 | ENR_LPM = 6, 42 | ENR_MLD = 7, 43 | ENR_CMH = 8, 44 | ENR_CMD = 9 45 | } ENR_FlowUnits; 46 | 47 | typedef enum { 48 | ENR_PSI = 0, 49 | ENR_MTR = 1, 50 | ENR_KPA = 2 51 | } ENR_PressUnits; 52 | 53 | typedef enum { 54 | ENR_NONE = 0, 55 | ENR_MGL = 1, 56 | ENR_UGL = 2, 57 | ENR_HOURS = 3, 58 | ENR_PRCNT = 4 59 | } ENR_QualUnits; 60 | 61 | /* 62 | typedef enum { 63 | ENR_nodeCount = 1, 64 | ENR_tankCount = 2, 65 | ENR_linkCount = 3, 66 | ENR_pumpCount = 4, 67 | ENR_valveCount = 5 68 | } ENR_ElementCount; 69 | */ 70 | 71 | typedef enum { 72 | ENR_reportStart = 1, 73 | ENR_reportStep = 2, 74 | ENR_simDuration = 3, 75 | ENR_numPeriods = 4 76 | }ENR_Time; 77 | 78 | typedef enum { 79 | ENR_demand = 1, 80 | ENR_head = 2, 81 | ENR_pressure = 3, 82 | ENR_quality = 4 83 | } ENR_NodeAttribute; 84 | 85 | typedef enum { 86 | ENR_flow = 1, 87 | ENR_velocity = 2, 88 | ENR_headloss = 3, 89 | ENR_avgQuality = 4, 90 | ENR_status = 5, 91 | ENR_setting = 6, 92 | ENR_rxRate = 7, 93 | ENR_frctnFctr = 8 94 | } ENR_LinkAttribute; 95 | 96 | #ifdef WINDOWS 97 | #ifdef __cplusplus 98 | #define DLLEXPORT __declspec(dllexport) __cdecl 99 | #else 100 | #define DLLEXPORT __declspec(dllexport) __stdcall 101 | #endif 102 | #else 103 | #define DLLEXPORT 104 | #endif 105 | 106 | /* TYPEMAPS FOR OPAQUE POINTER */ 107 | /* Used for functions that output a new opaque pointer */ 108 | %typemap(in, numinputs=0) ENR_Handle* p_handle_out (ENR_Handle retval) 109 | { 110 | /* OUTPUT in */ 111 | retval = NULL; 112 | $1 = &retval; 113 | } 114 | /* used for functions that take in an opaque pointer (or NULL) 115 | and return a (possibly) different pointer */ 116 | %typemap(argout) ENR_Handle* p_handle_out 117 | { 118 | /* OUTPUT argout */ 119 | %append_output(SWIG_NewPointerObj(SWIG_as_voidptr(retval$argnum), $1_descriptor, 0)); 120 | } 121 | %typemap(in) ENR_Handle* p_handle_inout (ENR_Handle retval) 122 | { 123 | /* INOUT in */ 124 | SWIG_ConvertPtr(obj0,SWIG_as_voidptrptr(&retval), 0, 0); 125 | $1 = &retval; 126 | } 127 | /* No need for special IN typemap for opaque pointers, it works anyway */ 128 | 129 | 130 | 131 | /* TYPEMAP FOR IGNORING INT ERROR CODE RETURN VALUE */ 132 | %typemap(out) int { 133 | $result = Py_None; 134 | Py_INCREF($result); 135 | } 136 | 137 | /* TYPEMAPS FOR INT ARGUMENT AS RETURN VALUE */ 138 | %typemap(in, numinputs=0) int* int_out (int temp) { 139 | $1 = &temp; 140 | } 141 | %typemap(argout) int* int_out { 142 | %append_output(PyInt_FromLong(*$1)); 143 | } 144 | 145 | /* TYPEMAP FOR MEMORY MANAGEMENT AND ENCODING OF STRINGS */ 146 | %typemap(in, numinputs=0)char** string_out (char* temp), int* slen (int temp){ 147 | $1 = &temp; 148 | } 149 | %typemap(argout)(char** string_out, int* slen) { 150 | if (*$1) { 151 | PyObject* o; 152 | o = PyUnicode_FromStringAndSize(*$1, *$2); 153 | 154 | $result = SWIG_Python_AppendOutput($result, o); 155 | free(*$1); 156 | } 157 | } 158 | 159 | /* TYPEMAPS FOR MEMORY MANAGEMNET OF FLOAT ARRAYS */ 160 | %typemap(in, numinputs=0)float** float_out (float* temp), int* int_dim (int temp){ 161 | $1 = &temp; 162 | } 163 | %typemap(argout) (float** float_out, int* int_dim) { 164 | if (*$1) { 165 | PyObject *o = PyList_New(*$2); 166 | int i; 167 | float* temp = *$1; 168 | for(i=0; i<*$2; i++) { 169 | PyList_SetItem(o, i, PyFloat_FromDouble((double)temp[i])); 170 | } 171 | $result = SWIG_Python_AppendOutput($result, o); 172 | free(*$1); 173 | } 174 | } 175 | 176 | /* TYPEMAPS FOR MEMORY MANAGEMENT OF INT ARRAYS */ 177 | %typemap(in, numinputs=0)int** int_out (long* temp), int* int_dim (int temp){ 178 | $1 = &temp; 179 | } 180 | %typemap(argout) (int** int_out, int* int_dim) { 181 | if (*$1) { 182 | PyObject *o = PyList_New(*$2); 183 | int i; 184 | long* temp = *$1; 185 | for(i=0; i<*$2; i++) { 186 | PyList_SetItem(o, i, PyInt_FromLong(temp[i])); 187 | } 188 | $result = SWIG_Python_AppendOutput($result, o); 189 | free(*$1); 190 | } 191 | } 192 | 193 | /* TYPEMAP FOR ENUMERATED TYPES */ 194 | %typemap(in) EnumeratedType (int val, int ecode = 0) { 195 | if (PyObject_HasAttrString($input,"value")) { 196 | PyObject* o; 197 | o = PyObject_GetAttrString($input, "value"); 198 | ecode = SWIG_AsVal_int(o, &val); 199 | } 200 | else { 201 | SWIG_exception_fail(SWIG_ArgError(ecode), "in method '" "$symname" "', argument " "$argnum"" of type '" "$ltype""'"); 202 | } 203 | 204 | $1 = ($1_type)(val); 205 | } 206 | %apply EnumeratedType {ENR_ElementType, ENR_Units, ENR_Time, ENR_NodeAttribute, ENR_LinkAttribute} 207 | 208 | 209 | /* RENAME FUNCTIONS PYTHON STYLE */ 210 | %rename("%(regex:/^\w+_([a-zA-Z]+)/\L\\1/)s") ""; 211 | 212 | /* GENERATES DOCUMENTATION */ 213 | %feature("autodoc", "2"); 214 | 215 | 216 | /* INSERTS CUSTOM EXCEPTION HANDLING IN WRAPPER */ 217 | %exception 218 | { 219 | char* err_msg; 220 | ENR_clearError(arg1); 221 | $function 222 | if (ENR_checkError(arg1, &err_msg)) 223 | { 224 | PyErr_SetString(PyExc_Exception, err_msg); 225 | SWIG_fail; 226 | } 227 | } 228 | /* INSERT EXCEPTION HANDLING FOR THESE FUNCTIONS */ 229 | int DLLEXPORT ENR_open(ENR_Handle p_handle, const char* path); 230 | 231 | int DLLEXPORT ENR_getVersion(ENR_Handle p_handle, int* int_out); 232 | int DLLEXPORT ENR_getNetSize(ENR_Handle p_handle, int** int_out, int* int_dim); 233 | int DLLEXPORT ENR_getUnits(ENR_Handle p_handle, ENR_Units t_enum, int* int_out); 234 | int DLLEXPORT ENR_getTimes(ENR_Handle p_handle, ENR_Time t_enum, int* int_out); 235 | int DLLEXPORT ENR_getElementName(ENR_Handle p_handle, ENR_ElementType t_enum, 236 | int elementIndex, char** string_out, int* slen); 237 | int DLLEXPORT ENR_getEnergyUsage(ENR_Handle p_handle, int pumpIndex, 238 | int* int_out, float** float_out, int* int_dim); 239 | int DLLEXPORT ENR_getNetReacts(ENR_Handle p_handle, float** float_out, int* int_dim); 240 | 241 | int DLLEXPORT ENR_getNodeSeries(ENR_Handle p_handle_in, int nodeIndex, ENR_NodeAttribute t_enum, 242 | int startPeriod, int endPeriod, float** float_out, int* int_dim); 243 | int DLLEXPORT ENR_getLinkSeries(ENR_Handle p_handle_in, int linkIndex, ENR_LinkAttribute t_enum, 244 | int startPeriod, int endPeriod, float** float_out, int* int_dim); 245 | 246 | int DLLEXPORT ENR_getNodeAttribute(ENR_Handle p_handle, int periodIndex, 247 | ENR_NodeAttribute t_enum, float** float_out, int* int_dim); 248 | int DLLEXPORT ENR_getLinkAttribute(ENR_Handle p_handle, int periodIndex, 249 | ENR_LinkAttribute t_enum, float** float_out, int* int_dim); 250 | 251 | int DLLEXPORT ENR_getNodeResult(ENR_Handle p_handle_in, int periodIndex, 252 | int nodeIndex, float** float_out, int* int_dim); 253 | int DLLEXPORT ENR_getLinkResult(ENR_Handle p_handle_in, int periodIndex, 254 | int linkIndex, float** float_out, int* int_dim); 255 | 256 | %exception; 257 | 258 | /* NO EXCEPTION HANDLING FOR THESE FUNCTIONS */ 259 | int DLLEXPORT ENR_init(ENR_Handle* p_handle_out); 260 | int DLLEXPORT ENR_close(ENR_Handle* p_handle_inout); 261 | void DLLEXPORT ENR_free(void** array); 262 | 263 | void DLLEXPORT ENR_clearError(ENR_Handle p_handle); 264 | int DLLEXPORT ENR_checkError(ENR_Handle p_handle, char** msg_buffer); 265 | 266 | 267 | /* CODE ADDED DIRECTLY TO SWIGGED INTERFACE MODULE */ 268 | %pythoncode%{ 269 | import enum 270 | 271 | class ElementType(enum.Enum): 272 | NODE = ENR_node 273 | LINK = ENR_link 274 | 275 | class Units(enum.Enum): 276 | FLOW = ENR_flowUnits 277 | PRESS = ENR_pressUnits 278 | QUAL = ENR_qualUnits 279 | 280 | class FlowUnits(enum.Enum): 281 | CFS = ENR_CFS 282 | GPM = ENR_GPM 283 | MGD = ENR_MGD 284 | IMGD = ENR_IMGD 285 | AFD = ENR_AFD 286 | LPS = ENR_LPS 287 | LPM = ENR_LPM 288 | MLD = ENR_MLD 289 | CMH = ENR_CMH 290 | CMD = ENR_CMD 291 | 292 | class PressUnits(enum.Enum): 293 | PSI = ENR_PSI 294 | MTR = ENR_MTR 295 | KPA = ENR_KPA 296 | 297 | class QualUnits(enum.Enum): 298 | NONE = ENR_NONE 299 | MGL = ENR_MGL 300 | UGL = ENR_UGL 301 | HOURS = ENR_HOURS 302 | PRCNT = ENR_PRCNT 303 | 304 | class Time(enum.Enum): 305 | REPORT_START = ENR_reportStart 306 | REPORT_STEP = ENR_reportStep 307 | SIM_DURATION = ENR_simDuration 308 | NUM_PERIODS = ENR_numPeriods 309 | 310 | class NodeAttribute(enum.Enum): 311 | DEMAND = ENR_demand 312 | HEAD = ENR_head 313 | PRESSURE = ENR_pressure 314 | QUALITY = ENR_quality 315 | 316 | class LinkAttribute(enum.Enum): 317 | FLOW = ENR_flow 318 | VELOCITY = ENR_velocity 319 | HEADLOSS = ENR_headloss 320 | AVG_QUALITY = ENR_avgQuality 321 | STATUS = ENR_status 322 | SETTING = ENR_setting 323 | RX_RATE = ENR_rxRate 324 | FRCTN_FCTR = ENR_frctnFctr 325 | %} 326 | -------------------------------------------------------------------------------- /epanet_python/epanet_python/output/setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # setup.py 4 | # 5 | # Created: 9/20/2017 6 | # Author: Michael E. Tryby 7 | # US EPA - ORD/NRMRL 8 | # 9 | # Setup up script for en_outputapi python extension 10 | # 11 | # Requires: 12 | # Platform C language compiler 13 | # Python packages: numpy 14 | # 15 | 16 | #try: 17 | from setuptools import setup, Extension 18 | from setuptools.command.build_ext import build_ext 19 | 20 | #except ImportError: 21 | # from distutils.core import setup, Extension 22 | # from distutils.command.build_ext import build_ext 23 | 24 | 25 | microlib_name = 'epanet.output' 26 | 27 | setup( 28 | name = 'epanet.output', 29 | version = "0.2.0.dev0", 30 | ext_modules = [ 31 | Extension("epanet.output._output", 32 | sources = ['epanet/output/output_wrap.c'], 33 | include_dirs = ['epanet/output'], 34 | libraries = ['epanet-output'], 35 | library_dirs = ['epanet/output'], 36 | language = 'C' 37 | ) 38 | ], 39 | 40 | # tox can't find swmm module at test time unless namespace is declared 41 | namespace_packages=['epanet'], 42 | 43 | packages = {'epanet.output'}, 44 | py_modules = ['output'], 45 | package_data = {'epanet.output':['*epanet-output.dll', '*epanet-output.so']}, 46 | 47 | zip_safe=False, 48 | ) 49 | -------------------------------------------------------------------------------- /epanet_python/epanet_python/output/tests/data/net1.out: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenWaterAnalytics/epanet-python/f71869a908928a5fe274a3fdf2a0e0ad8c703e2f/epanet_python/epanet_python/output/tests/data/net1.out -------------------------------------------------------------------------------- /epanet_python/epanet_python/output/tests/test_output.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | import numpy as np 5 | 6 | from epanet.output import OutputMetadata 7 | from epanet.output import output as oapi 8 | 9 | 10 | DATA_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'data') 11 | OUTPUT_FILE_EXAMPLE1 = os.path.join(DATA_PATH, 'net1.out') 12 | 13 | 14 | @pytest.fixture() 15 | def handle(request): 16 | _handle = oapi.init() 17 | oapi.open(_handle, OUTPUT_FILE_EXAMPLE1) 18 | 19 | def close(): 20 | oapi.close(_handle) 21 | 22 | request.addfinalizer(close) 23 | return _handle 24 | 25 | 26 | def test_outputmetadata_handle(handle): 27 | 28 | om = OutputMetadata(handle) 29 | 30 | ref = { 31 | oapi.NodeAttribute.DEMAND: ("Demand", "gal/min"), 32 | oapi.NodeAttribute.HEAD: ("Head", "ft"), 33 | oapi.NodeAttribute.PRESSURE: ("Pressure", "psi"), 34 | oapi.NodeAttribute.QUALITY: ("Quality", "mg/L"), 35 | 36 | oapi.LinkAttribute.FLOW: ("Flow", "gal/min"), 37 | oapi.LinkAttribute.VELOCITY: ("Velocity", "ft/sec"), 38 | oapi.LinkAttribute.HEADLOSS: ("Unit Headloss", "ft/1000ft"), 39 | oapi.LinkAttribute.AVG_QUALITY: ("Quality", "mg/L"), 40 | oapi.LinkAttribute.STATUS: ("Status", ""), 41 | oapi.LinkAttribute.SETTING: ("Setting", ""), 42 | oapi.LinkAttribute.RX_RATE: ("Reaction Rate", "mg/hr"), 43 | oapi.LinkAttribute.FRCTN_FCTR: ("Friction Factor", "unitless")} 44 | 45 | for attr in oapi.NodeAttribute: 46 | temp = om.get_attribute_metadata(attr) 47 | assert temp == ref[attr] 48 | 49 | for attr in oapi.LinkAttribute: 50 | temp = om.get_attribute_metadata(attr) 51 | assert temp == ref[attr] 52 | 53 | 54 | def test_getnetsize(handle): 55 | # node, tank, link, pump, valve 56 | ref_array = np.array([11, 2, 13, 1, 0]) 57 | 58 | netsize_list = oapi.getnetsize(handle) 59 | assert len(netsize_list) == 5 60 | 61 | assert np.array_equal(netsize_list, ref_array) 62 | 63 | 64 | def test_getnodeSeries(handle): 65 | 66 | ref_array = np.array( 67 | [119.25731, 68 | 120.45029, 69 | 121.19854, 70 | 122.00622, 71 | 122.37414, 72 | 122.8122, 73 | 122.82034, 74 | 122.90379, 75 | 123.40434, 76 | 123.81807]) 77 | 78 | array = oapi.getnodeseries(handle, 2, oapi.NodeAttribute.PRESSURE, 0, 10) 79 | assert len(array) == 10 80 | 81 | assert np.allclose(array, ref_array) 82 | 83 | 84 | def test_getlinkseries(handle): 85 | pass 86 | 87 | def test_getnodeattribute(handle): 88 | ref_array = np.array([ 1., 0.44407997, 0.43766347, 0.42827705, 0.41342604, 89 | 0.42804748, 0.44152543, 0.40502965, 0.38635802, 1., 0.96745253]) 90 | 91 | array = oapi.getnodeattribute(handle, 1, oapi.NodeAttribute.QUALITY) 92 | assert len(array) == 11 93 | assert np.allclose(array, ref_array) 94 | 95 | 96 | def test_getlinkattribute(handle): 97 | ref_array = np.array([ 1848.58117676, 1220.42736816, 130.11161804, 98 | 187.68930054, 119.88839722, 40.46448898, -748.58111572, 478.15377808, 99 | 191.73458862, 30.11160851, 140.4644928, 59.53551483, 1848.58117676]) 100 | 101 | array = oapi.getlinkattribute(handle, 1, oapi.LinkAttribute.FLOW) 102 | assert len(array) == 13 103 | assert np.allclose(array, ref_array) 104 | 105 | 106 | def test_getnoderesult(handle): 107 | pass 108 | 109 | def test_getlinkresult(handle): 110 | pass 111 | -------------------------------------------------------------------------------- /epanet_python/epanet_python/toolkit/epanet/toolkit/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from epanet.toolkit import toolkit 3 | -------------------------------------------------------------------------------- /epanet_python/epanet_python/toolkit/epanet/toolkit/toolkit.i: -------------------------------------------------------------------------------- 1 | /* 2 | * epanet_toolkit.i - SWIG interface description file for EPANET toolkit 3 | * 4 | * Created: 11/27/2017 5 | * Author: Michael E. Tryby 6 | * US EPA - ORD/NRMRL 7 | * 8 | * Build command: 9 | * $ swig -I../include -python -py3 epanet_toolkit.i 10 | * 11 | */ 12 | 13 | %module(package="epanet") toolkit 14 | 15 | 16 | %include "typemaps.i" 17 | %include "cstring.i" 18 | 19 | 20 | %{ 21 | #include "epanet_py.h" 22 | 23 | #define SWIG_FILE_WITH_INIT 24 | %} 25 | 26 | 27 | // Opaque pointer to project 28 | typedef void *Handle; 29 | 30 | 31 | %include "epanet2_enums.h" 32 | 33 | 34 | /* TYPEMAPS FOR OPAQUE POINTER */ 35 | /* Used for functions that output a new opaque pointer */ 36 | %typemap(in, numinputs=0) Handle *ph_out (Handle retval) { 37 | /* OUTPUT in */ 38 | retval = NULL; 39 | $1 = &retval; 40 | } 41 | /* used for functions that take in an opaque pointer (or NULL) 42 | and return a (possibly) different pointer */ 43 | %typemap(argout) Handle *ph_out, Handle *ph_inout { 44 | /* OUTPUT argout */ 45 | %append_output(SWIG_NewPointerObj(SWIG_as_voidptr(retval$argnum), $1_descriptor, 0)); 46 | } 47 | %typemap(in) Handle *ph_inout (Handle retval) { 48 | /* INOUT in */ 49 | SWIG_ConvertPtr(obj0,SWIG_as_voidptrptr(&retval), 0, 0); 50 | $1 = &retval; 51 | } 52 | /* No need for special IN typemap for opaque pointers, it works anyway */ 53 | 54 | 55 | /* TYPEMAP FOR IGNORING INT ERROR CODE RETURN VALUE */ 56 | %typemap(out) int { 57 | $result = Py_None; 58 | Py_INCREF($result); 59 | } 60 | 61 | 62 | /* TYPEMAP FOR ENUMERATED TYPES */ 63 | %typemap(in) EnumeratedType (int val, int ecode = 0) { 64 | if (PyObject_HasAttrString($input,"value")) { 65 | PyObject* o; 66 | o = PyObject_GetAttrString($input, "value"); 67 | ecode = SWIG_AsVal_int(o, &val); 68 | } 69 | else { 70 | SWIG_exception_fail(SWIG_ArgError(ecode), "in method '" "$symname" "', argument " "$argnum"" of type '" "$ltype""'"); 71 | } 72 | 73 | $1 = ($1_type)(val); 74 | } 75 | %apply EnumeratedType {EN_NodeProperty, EN_LinkProperty, EN_TimeParameter, 76 | EN_AnalysisStatistic, EN_CountType, EN_NodeType, EN_LinkType, EN_QualityType, 77 | EN_SourceType, EN_HeadLossType, EN_FlowUnits, EN_DemandModel, EN_Option, 78 | EN_ControlType, EN_StatisticType, EN_MixingModel, EN_InitHydOption, EN_PumpType, 79 | EN_CurveType, EN_ActionCodeType, EN_RuleObject, EN_RuleVariable, 80 | EN_RuleOperator, EN_RuleStatus, EN_StatusReport}; 81 | 82 | 83 | /* MARK FUNCTIONS AS ALLOCATING AND DEALLOCATING MEMORY */ 84 | %newobject proj_create; 85 | %delobject proj_delete; 86 | 87 | 88 | /* GENERATES DOCUMENTATION */ 89 | %feature("autodoc", "2"); 90 | 91 | 92 | /* MACRO FOR RETURNING A BOUNDED LENGTH STRING */ 93 | %cstring_bounded_output(char *id_out, EN_MAXID); 94 | %cstring_bounded_output(char *msg_out, EN_MAXMSG); 95 | 96 | 97 | /* INSERTS CUSTOM EXCEPTION HANDLING IN WRAPPER */ 98 | %exception 99 | { 100 | int err_code; 101 | char* err_msg; 102 | 103 | err_clear(arg1); 104 | 105 | $function 106 | 107 | err_code = err_check(arg1, &err_msg); 108 | if ( err_code > 10) 109 | { 110 | PyErr_SetString(PyExc_Exception, err_msg); 111 | toolkit_free((void **)&err_msg); 112 | SWIG_fail; 113 | } 114 | else if (err_code > 0) 115 | { 116 | PyErr_WarnEx(PyExc_Warning, err_msg, 2); 117 | toolkit_free((void **)&err_msg); 118 | } 119 | } 120 | 121 | 122 | /* INSERT EXCEPTION HANDLING FOR THESE FUNCTIONS */ 123 | 124 | int proj_run(Handle ph, const char *input_path, const char *report_path, const char *output_path); 125 | int proj_init(Handle ph, const char *rptFile, const char *outFile, EN_FlowUnits unitsType, EN_HeadLossType headLossType); 126 | int proj_open(Handle ph, const char *inpFile, const char *rptFile, const char *binOutFile); 127 | //int proj_gettitle(Handle ph, char *line1, char *line2, char *line3); 128 | //int proj_settitle(Handle ph, const char *line1, const char *line2, const char *line3); 129 | int proj_getcount(Handle ph, EN_CountType code, int *OUTPUT); 130 | int proj_savefile(Handle ph, const char *filename); 131 | int proj_close(Handle ph); 132 | 133 | 134 | int hydr_solve(Handle ph); 135 | int hydr_save(Handle ph); 136 | int hydr_open(Handle ph); 137 | int hydr_init(Handle ph, EN_InitHydOption saveFlag); 138 | int hydr_run(Handle ph, long *OUTPUT); 139 | int hydr_next(Handle ph, long *OUTPUT); 140 | int hydr_close(Handle ph); 141 | int hydr_savefile(Handle ph, char *filename); 142 | int hydr_usefile(Handle ph, char *filename); 143 | 144 | 145 | int qual_solve(Handle ph); 146 | int qual_open(Handle ph); 147 | int qual_init(Handle ph, EN_InitHydOption saveFlag); 148 | int qual_run(Handle ph, long *OUTPUT); 149 | int qual_next(Handle ph, long *OUTPUT); 150 | int qual_step(Handle ph, long *OUTPUT); 151 | int qual_close(Handle ph); 152 | 153 | 154 | int rprt_writeline(Handle ph, char *line); 155 | int rprt_writeresults(Handle ph); 156 | int rprt_reset(Handle ph); 157 | int rprt_set(Handle ph, char *reportCommand); 158 | int rprt_setlevel(Handle ph, EN_StatusReport code); 159 | int rprt_anlysstats(Handle ph, EN_AnalysisStatistic code, double *OUTPUT ); 160 | 161 | 162 | int anlys_getoption(Handle ph, EN_Option code, double *OUTPUT); 163 | int anlys_setoption(Handle ph, EN_Option code, double value); 164 | int anlys_getflowunits(Handle ph, int *OUTPUT); 165 | int anlys_setflowunits(Handle ph, EN_FlowUnits code); 166 | int anlys_gettimeparam(Handle ph, EN_TimeParameter code, long *OUTPUT); 167 | int anlys_settimeparam(Handle ph, EN_TimeParameter code, long value); 168 | int anlys_getqualinfo(Handle ph, int *OUTPUT, char *id_out, char *id_out, int *OUTPUT); 169 | int anlys_getqualtype(Handle ph, int *OUTPUT, int *OUTPUT); 170 | int anlys_setqualtype(Handle ph, EN_QualityType qualcode, char *chemname, char *chemunits, char *tracenode); 171 | 172 | 173 | int node_add(Handle ph, char *id, EN_NodeType nodeType); 174 | int node_delete(Handle ph, int index, int actionCode); 175 | int node_getindex(Handle ph, char *id, int *OUTPUT); 176 | int node_getid(Handle ph, int index, char *id_out); 177 | int node_setid(Handle ph, int index, char *newid); 178 | int node_gettype(Handle ph, int index, int *OUTPUT); 179 | int node_getvalue(Handle ph, int index, EN_NodeProperty code, double *OUTPUT); 180 | int node_setvalue(Handle ph, int index, EN_NodeProperty code, double value); 181 | int node_getcoord(Handle ph, int index, double *OUTPUT, double *OUTPUT); 182 | int node_setcoord(Handle ph, int index, double x, double y); 183 | 184 | 185 | int dmnd_getmodel(Handle ph, int *OUTPUT, double *OUTPUT, double *OUTPUT, double *OUTPUT); 186 | int dmnd_setmodel(Handle ph, int type, double pmin, double preq, double pexp); 187 | int dmnd_getcount(Handle ph, int nodeIndex, int *OUTPUT); 188 | int dmnd_getbase(Handle ph, int nodeIndex, int demandIndex, double *OUTPUT); 189 | int dmnd_setbase(Handle ph, int nodeIndex, int demandIndex, double baseDemand); 190 | int dmnd_getpattern(Handle ph, int nodeIndex, int demandIndex, int *OUTPUT); 191 | int dmnd_setpattern(Handle ph, int nodeIndex, int demandIndex, int patIndex); 192 | int dmnd_getname(Handle ph, int nodeIndex, int demandIdx, char *msg_out); 193 | int dmnd_setname(Handle ph, int nodeIndex, int demandIdx, char *demandName); 194 | 195 | 196 | int link_add(Handle ph, char *id, EN_LinkType linkType, char *fromNode, char *toNode); 197 | int link_delete(Handle ph, int index, int actionCode); 198 | int link_getindex(Handle ph, char *id, int *OUTPUT); 199 | int link_getid(Handle ph, int index, char *id_out); 200 | int link_setid(Handle ph, int index, char *newid); 201 | int link_gettype(Handle ph, int index, int *OUTPUT); 202 | int link_settype(Handle ph, int *index, EN_LinkType type, int actionCode); 203 | int link_getnodes(Handle ph, int index, int *OUTPUT, int *OUTPUT); 204 | int link_setnodes(Handle ph, int index, int node1, int node2); 205 | int link_getvalue(Handle ph, int index, EN_LinkProperty code, double *OUTPUT); 206 | int link_setvalue(Handle ph, int index, int code, double value); 207 | 208 | 209 | int pump_gettype(Handle ph, int linkIndex, int *OUTPUT); 210 | int pump_getheadcurveindex(Handle ph, int pumpIndex, int *OUTPUT); 211 | int pump_setheadcurveindex(Handle ph, int pumpIndex, int curveIndex); 212 | 213 | 214 | int ptrn_add(Handle ph, char *id); 215 | int ptrn_getindex(Handle ph, char *id, int *OUTPUT); 216 | int ptrn_getid(Handle ph, int index, char *id); 217 | int ptrn_getlength(Handle ph, int index, int *OUTPUT); 218 | int ptrn_getvalue(Handle ph, int index, int period, double *OUTPUT); 219 | int ptrn_setvalue(Handle ph, int index, int period, double value); 220 | int ptrn_getavgvalue(Handle ph, int index, double *OUTPUT); 221 | int ptrn_set(Handle ph, int index, double *values, int len); 222 | 223 | 224 | int curv_add(Handle ph, char *id); 225 | int curv_getindex(Handle ph, char *id, int *OUTPUT); 226 | int curv_getid(Handle ph, int index, char *id); 227 | int curv_getlength(Handle ph, int index, int *OUTPUT); 228 | int curv_gettype(Handle ph, int curveIndex, int *OUTPUT); 229 | int curv_getvalue(Handle ph, int curveIndex, int pointIndex, double *OUTPUT, double *OUTPUT); 230 | int curv_setvalue(Handle ph, int curveIndex, int pointIndex, double x, double y); 231 | int curv_get(Handle ph, int curveIndex, char* id, int *nValues, double **xValues, double **yValues); 232 | int curv_set(Handle ph, int index, double *x, double *y, int len); 233 | 234 | 235 | int scntl_add(Handle ph, int type, int linkIndex, double setting, int nodeIndex, double level, int *index); 236 | int scntl_delete(Handle ph, int index); 237 | int scntl_get(Handle ph, int controlIndex, int *OUTPUT, int *OUTPUT, double *OUTPUT, int *OUTPUT, double *OUTPUT); 238 | int scntl_set(Handle ph, int cindex, int ctype, int lindex, double setting, int nindex, double level); 239 | 240 | 241 | int rcntl_add(Handle ph, char *rule); 242 | int rcntl_delete(Handle ph, int index); 243 | int rcntl_get(Handle ph, int index, int *nPremises, int *nThenActions, int *nElseActions, double *priority); 244 | int rcntl_getid(Handle ph, int index, char* id); 245 | int rcntl_getpremise(Handle ph, int ruleIndex, int premiseIndex, int *logop, int *object, int *objIndex, int *variable, int *relop, int *status, double *value); 246 | int rcntl_setpremise(Handle ph, int ruleIndex, int premiseIndex, int logop, int object, int objIndex, int variable, int relop, int status, double value); 247 | int rcntl_setpremiseindex(Handle ph, int ruleIndex, int premiseIndex, int objIndex); 248 | int rcntl_setpremisestatus(Handle ph, int ruleIndex, int premiseIndex, int status); 249 | int rcntl_setpremisevalue(Handle ph, int ruleIndex, int premiseIndex, double value); 250 | int rcntl_getthenaction(Handle ph, int ruleIndex, int actionIndex, int *linkIndex, int *status, double *setting); 251 | int rcntl_setthenaction(Handle ph, int ruleIndex, int actionIndex, int linkIndex, int status, double setting); 252 | int rcntl_getelseaction(Handle ph, int ruleIndex, int actionIndex, int *linkIndex, int *status, double *setting); 253 | int rcntl_setelseaction(Handle ph, int ruleIndex, int actionIndex, int linkIndex, int status, double setting); 254 | int rcntl_setrulepriority(Handle ph, int index, double priority); 255 | 256 | 257 | int toolkit_getversion(int *int_out); 258 | 259 | 260 | %exception; 261 | 262 | /* NO EXCEPTION HANDLING FOR THESE FUNCTIONS */ 263 | 264 | int proj_create(Handle *ph_out); 265 | int proj_delete(Handle *ph_inout); 266 | 267 | void err_clear(Handle ph); 268 | int err_check(Handle ph, char** msg_buffer); 269 | void toolkit_free(void **memory); 270 | 271 | 272 | /* CODE ADDED DIRECTLY TO SWIGGED INTERFACE MODULE */ 273 | %pythoncode%{ 274 | 275 | import enum 276 | 277 | class NodeProperty(enum.Enum): 278 | ELEVATION = EN_ELEVATION 279 | BASEDEMAND = EN_BASEDEMAND 280 | PATTERN = EN_PATTERN 281 | EMITTER = EN_EMITTER 282 | INITQUAL = EN_INITQUAL 283 | SOURCEQUAL = EN_SOURCEQUAL 284 | SOURCEPAT = EN_SOURCEPAT 285 | SOURCETYPE = EN_SOURCETYPE 286 | TANKLEVEL = EN_TANKLEVEL 287 | DEMAND = EN_DEMAND 288 | HEAD = EN_HEAD 289 | PRESSURE = EN_PRESSURE 290 | QUALITY = EN_QUALITY 291 | SOURCEMASS = EN_SOURCEMASS 292 | INITVOLUME = EN_INITVOLUME 293 | MIXMODEL = EN_MIXMODEL 294 | MIXZONEVOL = EN_MIXZONEVOL 295 | TANKDIAM = EN_TANKDIAM 296 | MINVOLUME = EN_MINVOLUME 297 | VOLCURVE = EN_VOLCURVE 298 | MINLEVEL = EN_MINLEVEL 299 | MAXLEVEL = EN_MAXLEVEL 300 | MIXFRACTION = EN_MIXFRACTION 301 | TANK_KBULK = EN_TANK_KBULK 302 | TANKVOLUME = EN_TANKVOLUME 303 | MAXVOLUME = EN_MAXVOLUME 304 | 305 | 306 | class LinkProperty(enum.Enum): 307 | DIAMETER = EN_DIAMETER 308 | LENGTH = EN_LENGTH 309 | ROUGHNESS = EN_ROUGHNESS 310 | MINORLOSS = EN_MINORLOSS 311 | INITSTATUS = EN_INITSTATUS 312 | INITSETTING = EN_INITSETTING 313 | KBULK = EN_KBULK 314 | KWALL = EN_KWALL 315 | FLOW = EN_FLOW 316 | VELOCITY = EN_VELOCITY 317 | HEADLOSS = EN_HEADLOSS 318 | STATUS = EN_STATUS 319 | SETTING = EN_SETTING 320 | ENERGY = EN_ENERGY 321 | LINKQUAL = EN_LINKQUAL 322 | LINKPATTERN = EN_LINKPATTERN 323 | PUMP_STATE = EN_PUMP_STATE 324 | PUMP_EFFIC = EN_PUMP_EFFIC 325 | PUMP_POWER = EN_PUMP_POWER 326 | PUMP_HCURVE = EN_PUMP_HCURVE 327 | PUMP_ECURVE = EN_PUMP_ECURVE 328 | PUMP_ECOST = EN_PUMP_ECOST 329 | PUMP_EPAT = EN_PUMP_EPAT 330 | 331 | 332 | class TimeParameter(enum.Enum): 333 | DURATION = EN_DURATION 334 | HYDSTEP = EN_HYDSTEP 335 | QUALSTEP = EN_QUALSTEP 336 | PATTERNSTEP = EN_PATTERNSTEP 337 | PATTERNSTART = EN_PATTERNSTART 338 | REPORTSTEP = EN_REPORTSTEP 339 | REPORTSTART = EN_REPORTSTART 340 | RULESTEP = EN_RULESTEP 341 | STATISTIC = EN_STATISTIC 342 | PERIODS = EN_PERIODS 343 | STARTTIME = EN_STARTTIME 344 | HTIME = EN_HTIME 345 | QTIME = EN_QTIME 346 | HALTFLAG = EN_HALTFLAG 347 | NEXTEVENT = EN_NEXTEVENT 348 | NEXTEVENTTANK = EN_NEXTEVENTTANK 349 | 350 | 351 | class AnalysisStatistic(enum.Enum): 352 | ITERATIONS = EN_ITERATIONS 353 | RELATIVEERROR = EN_RELATIVEERROR 354 | MAXHEADERROR = EN_MAXHEADERROR 355 | MAXFLOWCHANGE = EN_MAXFLOWCHANGE 356 | MASSBALANCE = EN_MASSBALANCE 357 | 358 | 359 | class CountType(enum.Enum): 360 | NODES = EN_NODECOUNT 361 | TANKS = EN_TANKCOUNT 362 | LINKS = EN_LINKCOUNT 363 | PTRNS = EN_PATCOUNT 364 | CURVS = EN_CURVECOUNT 365 | CNTLS = EN_CONTROLCOUNT 366 | RULES = EN_RULECOUNT 367 | 368 | 369 | class NodeType(enum.Enum): 370 | JUNCTION = EN_JUNCTION 371 | RESERVOIR = EN_RESERVOIR 372 | TANK = EN_TANK 373 | 374 | 375 | class LinkType(enum.Enum): 376 | CVPIPE = EN_CVPIPE 377 | PIPE = EN_PIPE 378 | PUMP = EN_PUMP 379 | PRV = EN_PRV 380 | PSV = EN_PSV 381 | PBV = EN_PBV 382 | FCV = EN_FCV 383 | TCV = EN_TCV 384 | GPV = EN_GPV 385 | 386 | 387 | class QualityType(enum.Enum): 388 | NONE = EN_NONE 389 | CHEM = EN_CHEM 390 | AGE = EN_AGE 391 | TRACE = EN_TRACE 392 | 393 | 394 | class SourceType(enum.Enum): 395 | CONCEN = EN_CONCEN 396 | MASS = EN_MASS 397 | SETPOINT = EN_SETPOINT 398 | FLOWPACED = EN_FLOWPACED 399 | 400 | 401 | class HeadLossType(enum.Enum): 402 | HW = EN_HW 403 | DW = EN_DW 404 | CM = EN_CM 405 | 406 | 407 | class FlowUnits(enum.Enum): 408 | CFS = EN_CFS 409 | GPM = EN_GPM 410 | MGD = EN_MGD 411 | IMGD = EN_IMGD 412 | AFD = EN_AFD 413 | LPS = EN_LPS 414 | LPM = EN_LPM 415 | MLD = EN_MLD 416 | CMH = EN_CMH 417 | CMD = EN_CMD 418 | 419 | 420 | class DemandModel(enum.Enum): 421 | DDA = EN_DDA 422 | PDA = EN_PDA 423 | 424 | 425 | class Option(enum.Enum): 426 | TRIALS = EN_TRIALS 427 | ACCURACY = EN_ACCURACY 428 | TOLERANCE = EN_TOLERANCE 429 | EMITEXPON = EN_EMITEXPON 430 | DEMANDMULT = EN_DEMANDMULT 431 | HEADERROR = EN_HEADERROR 432 | FLOWCHANGE = EN_FLOWCHANGE 433 | DEFDEMANDPAT = EN_DEFDEMANDPAT 434 | HEADLOSSFORM = EN_HEADLOSSFORM 435 | GLOBALEFFIC = EN_GLOBALEFFIC 436 | GLOBALPRICE = EN_GLOBALPRICE 437 | GLOBALPATTERN = EN_GLOBALPATTERN 438 | DEMANDCHARGE = EN_DEMANDCHARGE 439 | 440 | 441 | class ControlType(enum.Enum): 442 | LOWLEVEL = EN_LOWLEVEL 443 | HILEVEL = EN_HILEVEL 444 | TIMER = EN_TIMER 445 | TIMEOFDAY = EN_TIMEOFDAY 446 | 447 | 448 | class StatisticType(enum.Enum): 449 | SERIES = EN_SERIES 450 | AVERAGE = EN_AVERAGE 451 | MINIMUM = EN_MINIMUM 452 | MAXIMUM = EN_MAXIMUM 453 | RANGE = EN_RANGE 454 | 455 | 456 | class MixingModel(enum.Enum): 457 | MIX1 = EN_MIX1 458 | MIX2 = EN_MIX2 459 | FIFO = EN_FIFO 460 | LIFO = EN_LIFO 461 | 462 | 463 | class SaveOption(enum.Enum): 464 | NOSAVE = EN_NOSAVE 465 | SAVE = EN_SAVE 466 | INITFLOW = EN_INITFLOW 467 | SAVE_AND_INIT = EN_SAVE_AND_INIT 468 | 469 | 470 | class PumpType(enum.Enum): 471 | CONST_HP = EN_CONST_HP 472 | POWER_FUNC = EN_POWER_FUNC 473 | CUSTOM = EN_CUSTOM 474 | NOCURVE = EN_NOCURVE 475 | 476 | 477 | class CurveType(enum.Enum): 478 | VOLUME_CURVE = EN_VOLUME_CURVE 479 | PUMP_CURVE = EN_PUMP_CURVE 480 | EFFIC_CURVE = EN_EFFIC_CURVE 481 | HLOSS_CURVE = EN_HLOSS_CURVE 482 | GENERIC_CURVE = EN_GENERIC_CURVE 483 | 484 | 485 | class ActionCode(enum.Enum): 486 | UNCONDITIONAL = EN_UNCONDITIONAL 487 | CONDITIONAL = EN_CONDITIONAL 488 | 489 | 490 | class RuleObject(enum.Enum): 491 | R_NODE = EN_R_NODE 492 | R_LINK = EN_R_LINK 493 | R_SYSTEM = EN_R_SYSTEM 494 | 495 | 496 | class RuleVariable(enum.Enum): 497 | R_DEMAND = EN_R_DEMAND 498 | R_HEAD = EN_R_HEAD 499 | R_GRADE = EN_R_GRADE 500 | R_LEVEL = EN_R_LEVEL 501 | R_PRESSURE = EN_R_PRESSURE 502 | R_FLOW = EN_R_FLOW 503 | R_STATUS = EN_R_STATUS 504 | R_SETTING = EN_R_SETTING 505 | R_POWER = EN_R_POWER 506 | R_TIME = EN_R_TIME 507 | R_CLOCKTIME = EN_R_CLOCKTIME 508 | R_FILLTIME = EN_R_FILLTIME 509 | R_DRAINTIME = EN_R_DRAINTIME 510 | 511 | 512 | class RuleOperator(enum.Enum): 513 | R_EQ = EN_R_EQ 514 | R_NE = EN_R_NE 515 | R_LE = EN_R_LE 516 | R_GE = EN_R_GE 517 | R_LT = EN_R_LT 518 | R_GT = EN_R_GT 519 | R_IS = EN_R_IS 520 | R_NOT = EN_R_NOT 521 | R_BELOW = EN_R_BELOW 522 | R_ABOVE = EN_R_ABOVE 523 | 524 | 525 | class RuleStatus(enum.Enum): 526 | R_IS_OPEN = EN_R_IS_OPEN 527 | R_IS_CLOSED = EN_R_IS_CLOSED 528 | R_IS_ACTIVE = EN_R_IS_ACTIVE 529 | 530 | 531 | class StatusReport(enum.Enum): 532 | NO_REPORT = EN_NO_REPORT 533 | NORMAL_REPORT = EN_NORMAL_REPORT 534 | FULL_REPORT = EN_FULL_REPORT 535 | 536 | %} 537 | -------------------------------------------------------------------------------- /epanet_python/epanet_python/toolkit/setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # setup.py - Setup up script for en_toolkit python extension 4 | # 5 | # Created: 11/27/2017 6 | # Author: Michael E. Tryby 7 | # US EPA - ORD/NRMRL 8 | # 9 | # Requires: 10 | # Platform C language compiler 11 | # 12 | 13 | 14 | from setuptools import setup, Extension 15 | from setuptools.command.build_ext import build_ext 16 | 17 | 18 | setup( 19 | name = 'epanet.toolkit', 20 | version = "0.2.1.dev0", 21 | ext_modules = [ 22 | Extension("epanet.toolkit._toolkit", 23 | sources = ['epanet/toolkit/toolkit_wrap.c'], 24 | include_dirs = ['epanet/toolkit'], 25 | library_dirs = ['epanet/toolkit'], 26 | libraries = ['epanet_py'], 27 | language = 'C' 28 | ) 29 | ], 30 | 31 | # tox can't find epanet module at test time unless namespace is declared 32 | namespace_packages = ['epanet'], 33 | 34 | packages = {'epanet.toolkit'}, 35 | py_modules = ['toolkit'], 36 | package_data = {'epanet.toolkit':['*epanet_py.dll', '*epanet_py.so']}, 37 | 38 | zip_safe=False 39 | 40 | ) 41 | -------------------------------------------------------------------------------- /epanet_python/epanet_python/toolkit/tests/data/Net1.inp: -------------------------------------------------------------------------------- 1 | [TITLE] 2 | EPANET Example Network 1 3 | A simple example of modeling chlorine decay. Both bulk and 4 | wall reactions are included. 5 | 6 | [JUNCTIONS] 7 | ;ID Elev Demand Pattern 8 | 10 710 0 ; 9 | 11 710 150 ; 10 | 12 700 150 ; 11 | 13 695 100 ; 12 | 21 700 150 ; 13 | 22 695 200 ; 14 | 23 690 150 ; 15 | 31 700 100 ; 16 | 32 710 100 ; 17 | 18 | [RESERVOIRS] 19 | ;ID Head Pattern 20 | 9 800 ; 21 | 22 | [TANKS] 23 | ;ID Elevation InitLevel MinLevel MaxLevel Diameter MinVol VolCurve 24 | 2 850 120 100 150 50.5 0 ; 25 | 26 | [PIPES] 27 | ;ID Node1 Node2 Length Diameter Roughness MinorLoss Status 28 | 10 10 11 10530 18 100 0 Open ; 29 | 11 11 12 5280 14 100 0 Open ; 30 | 12 12 13 5280 10 100 0 Open ; 31 | 21 21 22 5280 10 100 0 Open ; 32 | 22 22 23 5280 12 100 0 Open ; 33 | 31 31 32 5280 6 100 0 Open ; 34 | 110 2 12 200 18 100 0 Open ; 35 | 111 11 21 5280 10 100 0 Open ; 36 | 112 12 22 5280 12 100 0 Open ; 37 | 113 13 23 5280 8 100 0 Open ; 38 | 121 21 31 5280 8 100 0 Open ; 39 | 122 22 32 5280 6 100 0 Open ; 40 | 41 | [PUMPS] 42 | ;ID Node1 Node2 Parameters 43 | 9 9 10 HEAD 1 ; 44 | 45 | [VALVES] 46 | ;ID Node1 Node2 Diameter Type Setting MinorLoss 47 | 48 | [TAGS] 49 | 50 | [DEMANDS] 51 | ;Junction Demand Pattern Category 52 | 53 | [STATUS] 54 | ;ID Status/Setting 55 | 56 | [PATTERNS] 57 | ;ID Multipliers 58 | ;Demand Pattern 59 | 1 1.0 1.2 1.4 1.6 1.4 1.2 60 | 1 1.0 0.8 0.6 0.4 0.6 0.8 61 | 62 | [CURVES] 63 | ;ID X-Value Y-Value 64 | ;PUMP: Pump Curve for Pump 9 65 | 1 1500 250 66 | 67 | [CONTROLS] 68 | LINK 9 OPEN IF NODE 2 BELOW 110 69 | LINK 9 CLOSED IF NODE 2 ABOVE 140 70 | 71 | 72 | [RULES] 73 | 74 | [ENERGY] 75 | Global Efficiency 75 76 | Global Price 0.0 77 | Demand Charge 0.0 78 | 79 | [EMITTERS] 80 | ;Junction Coefficient 81 | 82 | [QUALITY] 83 | ;Node InitQual 84 | 10 0.5 85 | 11 0.5 86 | 12 0.5 87 | 13 0.5 88 | 21 0.5 89 | 22 0.5 90 | 23 0.5 91 | 31 0.5 92 | 32 0.5 93 | 9 1.0 94 | 2 1.0 95 | 96 | [SOURCES] 97 | ;Node Type Quality Pattern 98 | 99 | [REACTIONS] 100 | ;Type Pipe/Tank Coefficient 101 | 102 | 103 | [REACTIONS] 104 | Order Bulk 1 105 | Order Tank 1 106 | Order Wall 1 107 | Global Bulk -.5 108 | Global Wall -1 109 | Limiting Potential 0.0 110 | Roughness Correlation 0.0 111 | 112 | [MIXING] 113 | ;Tank Model 114 | 115 | [TIMES] 116 | Duration 24:00 117 | Hydraulic Timestep 1:00 118 | Quality Timestep 0:05 119 | Pattern Timestep 2:00 120 | Pattern Start 0:00 121 | Report Timestep 1:00 122 | Report Start 0:00 123 | Start ClockTime 12 am 124 | Statistic None 125 | 126 | [REPORT] 127 | Status Yes 128 | Summary No 129 | Page 0 130 | 131 | [OPTIONS] 132 | Units GPM 133 | Headloss H-W 134 | Specific Gravity 1.0 135 | Viscosity 1.0 136 | Trials 40 137 | Accuracy 0.001 138 | CHECKFREQ 2 139 | MAXCHECK 10 140 | DAMPLIMIT 0 141 | Unbalanced Continue 10 142 | Pattern 1 143 | Demand Multiplier 1.0 144 | Emitter Exponent 0.5 145 | Quality Chlorine mg/L 146 | Diffusivity 1.0 147 | Tolerance 0.01 148 | 149 | [COORDINATES] 150 | ;Node X-Coord Y-Coord 151 | 10 20.00 70.00 152 | 11 30.00 70.00 153 | 12 50.00 70.00 154 | 13 70.00 70.00 155 | 21 30.00 40.00 156 | 22 50.00 40.00 157 | 23 70.00 40.00 158 | 31 30.00 10.00 159 | 32 50.00 10.00 160 | 9 10.00 70.00 161 | 2 50.00 90.00 162 | 163 | [VERTICES] 164 | ;Link X-Coord Y-Coord 165 | 166 | [LABELS] 167 | ;X-Coord Y-Coord Label & Anchor Node 168 | 6.99 73.63 "Source" 169 | 13.48 68.13 "Pump" 170 | 43.85 91.21 "Tank" 171 | 172 | [BACKDROP] 173 | DIMENSIONS 7.00 6.00 73.00 94.00 174 | UNITS None 175 | FILE 176 | OFFSET 0.00 0.00 177 | 178 | [END] 179 | -------------------------------------------------------------------------------- /epanet_python/epanet_python/toolkit/tests/data/test_warnings.inp: -------------------------------------------------------------------------------- 1 | [TITLE] 2 | EPANET Example Network 1 3 | A simple example of modeling chlorine decay. Both bulk and 4 | wall reactions are included. 5 | 6 | [JUNCTIONS] 7 | ;ID Elev Demand Pattern 8 | 10 710 0 ; 9 | 11 710 750 ; 10 | 12 700 750 ; 11 | 13 695 100 ; 12 | 21 700 150 ; 13 | 22 695 200 ; 14 | 23 690 150 ; 15 | 31 700 100 ; 16 | 32 710 100 ; 17 | 18 | [RESERVOIRS] 19 | ;ID Head Pattern 20 | 9 800 ; 21 | 22 | [TANKS] 23 | ;ID Elevation InitLevel MinLevel MaxLevel Diameter MinVol VolCurve 24 | 2 850 120 100 150 50.5 0 ; 25 | 26 | [PIPES] 27 | ;ID Node1 Node2 Length Diameter Roughness MinorLoss Status 28 | 10 10 11 10530 18 100 0 Open ; 29 | 11 11 12 5280 14 100 0 Open ; 30 | 12 12 13 5280 10 100 0 Open ; 31 | 21 21 22 5280 10 100 0 Open ; 32 | 22 22 23 5280 12 100 0 Open ; 33 | 31 31 32 5280 6 100 0 Open ; 34 | 110 2 12 200 18 100 0 Open ; 35 | 111 11 21 5280 10 100 0 Open ; 36 | 112 12 22 5280 12 100 0 Open ; 37 | 113 13 23 5280 8 100 0 Open ; 38 | 121 21 31 5280 8 100 0 Open ; 39 | 122 22 32 5280 6 100 0 Open ; 40 | 41 | [PUMPS] 42 | ;ID Node1 Node2 Parameters 43 | 9 9 10 HEAD 1 ; 44 | 45 | [VALVES] 46 | ;ID Node1 Node2 Diameter Type Setting MinorLoss 47 | 48 | [TAGS] 49 | 50 | [DEMANDS] 51 | ;Junction Demand Pattern Category 52 | 53 | [STATUS] 54 | ;ID Status/Setting 55 | 56 | [PATTERNS] 57 | ;ID Multipliers 58 | ;Demand Pattern 59 | 1 1.0 1.2 1.4 1.6 1.4 1.2 60 | 1 1.0 0.8 0.6 0.4 0.6 0.8 61 | 62 | [CURVES] 63 | ;ID X-Value Y-Value 64 | ;PUMP: Pump Curve for Pump 9 65 | 1 1500 250 66 | 67 | [CONTROLS] 68 | LINK 9 OPEN IF NODE 2 BELOW 110 69 | LINK 9 CLOSED IF NODE 2 ABOVE 140 70 | 71 | 72 | [RULES] 73 | 74 | [ENERGY] 75 | Global Efficiency 75 76 | Global Price 0.0 77 | Demand Charge 0.0 78 | 79 | [EMITTERS] 80 | ;Junction Coefficient 81 | 82 | [QUALITY] 83 | ;Node InitQual 84 | 10 0.5 85 | 11 0.5 86 | 12 0.5 87 | 13 0.5 88 | 21 0.5 89 | 22 0.5 90 | 23 0.5 91 | 31 0.5 92 | 32 0.5 93 | 9 1.0 94 | 2 1.0 95 | 96 | [SOURCES] 97 | ;Node Type Quality Pattern 98 | 99 | [REACTIONS] 100 | ;Type Pipe/Tank Coefficient 101 | 102 | 103 | [REACTIONS] 104 | Order Bulk 1 105 | Order Tank 1 106 | Order Wall 1 107 | Global Bulk -.5 108 | Global Wall -1 109 | Limiting Potential 0.0 110 | Roughness Correlation 0.0 111 | 112 | [MIXING] 113 | ;Tank Model 114 | 115 | [TIMES] 116 | Duration 24:00 117 | Hydraulic Timestep 1:00 118 | Quality Timestep 0:05 119 | Pattern Timestep 2:00 120 | Pattern Start 0:00 121 | Report Timestep 1:00 122 | Report Start 0:00 123 | Start ClockTime 12 am 124 | Statistic None 125 | 126 | [REPORT] 127 | Status Yes 128 | Summary No 129 | Page 0 130 | 131 | [OPTIONS] 132 | Units GPM 133 | Headloss H-W 134 | Specific Gravity 1.0 135 | Viscosity 1.0 136 | Trials 40 137 | Accuracy 0.001 138 | CHECKFREQ 2 139 | MAXCHECK 10 140 | DAMPLIMIT 0 141 | Unbalanced Continue 10 142 | Pattern 1 143 | Demand Multiplier 1.0 144 | Emitter Exponent 0.5 145 | Quality Chlorine mg/L 146 | Diffusivity 1.0 147 | Tolerance 0.01 148 | 149 | [COORDINATES] 150 | ;Node X-Coord Y-Coord 151 | 10 20.00 70.00 152 | 11 30.00 70.00 153 | 12 50.00 70.00 154 | 13 70.00 70.00 155 | 21 30.00 40.00 156 | 22 50.00 40.00 157 | 23 70.00 40.00 158 | 31 30.00 10.00 159 | 32 50.00 10.00 160 | 9 10.00 70.00 161 | 2 50.00 90.00 162 | 163 | [VERTICES] 164 | ;Link X-Coord Y-Coord 165 | 166 | [LABELS] 167 | ;X-Coord Y-Coord Label & Anchor Node 168 | 6.99 73.63 "Source" 169 | 13.48 68.13 "Pump" 170 | 43.85 91.21 "Tank" 171 | 172 | [BACKDROP] 173 | DIMENSIONS 7.00 6.00 73.00 94.00 174 | UNITS None 175 | FILE 176 | OFFSET 0.00 0.00 177 | 178 | [END] 179 | -------------------------------------------------------------------------------- /epanet_python/epanet_python/toolkit/tests/test_toolkit.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | # 4 | # test_toolkit.py 5 | # 6 | # Created: October 19, 2018 7 | # Author: Michael E. Tryby 8 | # US EPA - ORD/NRMRL 9 | # 10 | 11 | import os 12 | 13 | import pytest 14 | import os.path as osp 15 | 16 | import epanet.toolkit.toolkit as en 17 | 18 | 19 | DATA_PATH = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'data') 20 | INPUT_FILE_NET_1 = os.path.join(DATA_PATH, 'Net1.inp') 21 | REPORT_FILE_TEST = os.path.join(DATA_PATH, 'test.rpt') 22 | OUTPUT_FILE_TEST = os.path.join(DATA_PATH, 'test.out') 23 | 24 | 25 | def test_createdelete(): 26 | 27 | _handle = en.proj_create() 28 | assert(_handle != None) 29 | 30 | _handle = en.proj_delete(_handle) 31 | assert(_handle == None) 32 | 33 | 34 | def test_run(): 35 | _handle = en.proj_create() 36 | 37 | en.proj_run(_handle, INPUT_FILE_NET_1, REPORT_FILE_TEST, OUTPUT_FILE_TEST) 38 | assert osp.isfile(REPORT_FILE_TEST) == True 39 | assert osp.isfile(OUTPUT_FILE_TEST) == True 40 | 41 | en.proj_delete(_handle) 42 | 43 | 44 | def test_openclose(): 45 | _handle = en.proj_create() 46 | en.proj_open(_handle, INPUT_FILE_NET_1, REPORT_FILE_TEST, OUTPUT_FILE_TEST) 47 | 48 | en.proj_close(_handle) 49 | en.proj_delete(_handle) 50 | 51 | 52 | def test_savereopen(): 53 | input_file_reopen = os.path.join(DATA_PATH, 'test_reopen.inp') 54 | 55 | _handle = en.proj_create() 56 | 57 | en.proj_open(_handle, INPUT_FILE_NET_1, REPORT_FILE_TEST, OUTPUT_FILE_TEST) 58 | en.proj_savefile(_handle, input_file_reopen) 59 | en.proj_close(_handle) 60 | 61 | _handle = en.proj_delete(_handle) 62 | 63 | _handle = en.proj_create() 64 | 65 | en.proj_open(_handle, input_file_reopen, REPORT_FILE_TEST, OUTPUT_FILE_TEST) 66 | en.proj_close(_handle) 67 | 68 | _handle = en.proj_delete(_handle) 69 | 70 | 71 | @pytest.fixture() 72 | def handle(request): 73 | _handle = en.proj_create() 74 | en.proj_open(_handle, INPUT_FILE_NET_1, REPORT_FILE_TEST, OUTPUT_FILE_TEST) 75 | 76 | def close(): 77 | en.proj_close(_handle) 78 | en.proj_delete(_handle) 79 | 80 | request.addfinalizer(close) 81 | return _handle 82 | 83 | 84 | def test_hyd_step(handle): 85 | en.hydr_open(handle) 86 | 87 | en.hydr_init(handle, en.SaveOption.NOSAVE) 88 | 89 | while True: 90 | time = en.hydr_run(handle) 91 | 92 | step = en.hydr_next(handle) 93 | 94 | if not step > 0.: 95 | break 96 | 97 | en.hydr_close(handle) 98 | 99 | 100 | def test_qual_step(handle): 101 | en.hydr_solve(handle) 102 | 103 | en.qual_open(handle) 104 | 105 | en.qual_init(handle, en.SaveOption.NOSAVE) 106 | 107 | while True: 108 | time = en.qual_run(handle) 109 | 110 | step = en.qual_next(handle) 111 | 112 | if not step > 0.: 113 | break 114 | 115 | en.qual_close(handle) 116 | 117 | 118 | def test_report(handle): 119 | 120 | nlinks = en.proj_getcount(handle, en.CountType.LINKS) 121 | assert nlinks == 13 122 | 123 | en.hydr_solve(handle) 124 | en.qual_solve(handle) 125 | 126 | en.rprt_set(handle, 'NODES ALL') 127 | en.rprt_writeresults(handle) 128 | assert osp.isfile(REPORT_FILE_TEST) == True 129 | 130 | 131 | def test_analysis(handle): 132 | test_value = []; 133 | 134 | for code in en.Option: 135 | test_value.append(en.anlys_getoption(handle, code)) 136 | 137 | funits = en.anlys_getflowunits(handle) 138 | assert en.FlowUnits(funits) == en.FlowUnits.GPM 139 | 140 | test_value.clear() 141 | ref_value = [86400, 3600, 300, 7200, 0, 3600, 0, 360, 0, 0, 0, 0, 0, 0, 3600, 0] 142 | for code in en.TimeParameter: 143 | test_value.append(en.anlys_gettimeparam(handle, code)) 144 | assert test_value == ref_value 145 | 146 | qualinfo = en.anlys_getqualinfo(handle) 147 | assert qualinfo == [1, 'Chlorine' ,'mg/L', 0] 148 | 149 | 150 | 151 | def test_node(handle): 152 | index = en.node_getindex(handle, '10') 153 | assert index == 1 154 | 155 | id = en.node_getid(handle, index) 156 | assert id == '10' 157 | 158 | type = en.node_gettype(handle, index) 159 | assert en.NodeType(type) == en.NodeType.JUNCTION 160 | 161 | coord = en.node_getcoord(handle, index) 162 | assert coord == [20.0, 70.0] 163 | 164 | 165 | def test_demand(handle): 166 | index = en.node_getindex(handle, '22') 167 | count = en.dmnd_getcount(handle, index) 168 | assert count == 1 169 | 170 | model = en.dmnd_getmodel(handle) 171 | assert model == [0, 0.0, 0.0, 0.5] 172 | 173 | base = en.dmnd_getbase(handle, index, count) 174 | assert base == 200.0 175 | 176 | ptrn = en.dmnd_getpattern(handle, index, count) 177 | assert ptrn == 1 178 | 179 | en.dmnd_setname(handle, index, count, 'default') 180 | name = en.dmnd_getname(handle, index, count) 181 | assert name == 'default' 182 | 183 | 184 | def test_link(handle): 185 | index = en.link_getindex(handle, '10') 186 | assert index == 1 187 | 188 | id = en.link_getid(handle, index) 189 | assert id == '10' 190 | 191 | type = en.link_gettype(handle, index) 192 | assert en.LinkType(type) == en.LinkType.PIPE 193 | 194 | nodes = en.link_getnodes(handle, index) 195 | assert nodes == [1, 2] 196 | 197 | test_value = [] 198 | ref_value = [18.0, 10530.0, 100.0, 0.0, 1.0, 100.0, -0.5, -1.0, 0.0, 0.0, 0.0, 0.0] 199 | for code in range(en.LinkProperty.SETTING.value): 200 | test_value.append(en.link_getvalue(handle, index, en.LinkProperty(code))) 201 | assert test_value == pytest.approx(ref_value) 202 | 203 | 204 | def test_pump(handle): 205 | index = en.link_getindex(handle, "9") 206 | assert index == 13 207 | 208 | type = en.link_gettype(handle, index) 209 | assert en.LinkType(type) == en.LinkType.PUMP 210 | 211 | type = en.pump_gettype(handle, index) 212 | assert en.PumpType(type) == en.PumpType.POWER_FUNC 213 | 214 | 215 | def test_pattern(handle): 216 | index = en.ptrn_getindex(handle, "1") 217 | assert index == 1 218 | 219 | length = en.ptrn_getlength(handle, index) 220 | assert length == 12 221 | 222 | test_value = [] 223 | ref_value = [1.0, 1.2, 1.4, 1.6, 1.4, 1.2, 1.0, 0.8, 0.6, 0.4, 0.6, 0.8] 224 | for i in range(1, length+1): 225 | test_value.append(en.ptrn_getvalue(handle, index, i)) 226 | assert test_value == pytest.approx(ref_value) 227 | 228 | value = en.ptrn_getavgvalue(handle, index) 229 | assert value == pytest.approx(1.0) 230 | 231 | 232 | def test_curve(handle): 233 | index = en.curv_getindex(handle, "1") 234 | assert index == 1 235 | 236 | length = en.curv_getlength(handle, index) 237 | assert length == 1 238 | 239 | type = en.curv_gettype(handle, index) 240 | assert en.CurveType(type) == en.CurveType.PUMP_CURVE 241 | 242 | value = en.curv_getvalue(handle, index, length) 243 | assert value == [1500.0, 250.0] 244 | 245 | 246 | def test_simplecontrol(handle): 247 | 248 | value = en.scntl_get(handle, 1) 249 | assert value == [0, 13, 1.0, 11, 110.0] 250 | 251 | value.clear() 252 | value = en.scntl_get(handle, 2) 253 | assert value == [1, 13, 0.0, 11, 140.0] 254 | 255 | 256 | WARNING_TEST_INP = os.path.join(DATA_PATH, 'test_warnings.inp') 257 | WARNING_TEST_RPT = os.path.join(DATA_PATH, 'test_warnings.rpt') 258 | WARNING_TEST_OUT = os.path.join(DATA_PATH, 'test_warnings.out') 259 | 260 | @pytest.fixture() 261 | def handle_warn(request): 262 | _handle = en.proj_create() 263 | en.proj_open(_handle, WARNING_TEST_INP, WARNING_TEST_RPT, WARNING_TEST_OUT) 264 | 265 | def close(): 266 | en.proj_close(_handle) 267 | en.proj_delete(_handle) 268 | 269 | request.addfinalizer(close) 270 | return _handle 271 | 272 | 273 | import warnings 274 | warnings.simplefilter("default") 275 | 276 | def test_hyd_warning(handle_warn): 277 | with pytest.warns(Warning): 278 | en.hydr_open(handle_warn) 279 | en.hydr_init(handle_warn, en.SaveOption.NOSAVE) 280 | 281 | while True: 282 | time = en.hydr_run(handle_warn) 283 | 284 | step = en.hydr_next(handle_warn) 285 | 286 | if not step > 0.: 287 | break 288 | 289 | en.hydr_close(handle_warn) 290 | 291 | 292 | def test_exception(handle_warn): 293 | with pytest.raises(Exception): 294 | #en.hydr_open(handle_warn) 295 | en.hydr_init(handle_warn, en.SaveOption.NOSAVE) 296 | 297 | while True: 298 | time = en.hydr_run(handle_warn) 299 | 300 | step = en.hydr_next(handle_warn) 301 | 302 | if not step > 0.: 303 | break 304 | 305 | en.hydr_close(handle_warn) 306 | -------------------------------------------------------------------------------- /epanet_python/requirements.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | --index-url https://pypi.python.org/simple 4 | -e . 5 | -------------------------------------------------------------------------------- /epanet_python/setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | test=pytest 3 | 4 | [tool:pytest] 5 | addopts = --ignore=epanet-module/ 6 | -------------------------------------------------------------------------------- /epanet_python/setup.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import os 4 | import pathlib 5 | 6 | from six import iteritems 7 | 8 | from setuptools import setup 9 | from setuptools.command.develop import develop 10 | from setuptools.command.install import install 11 | 12 | 13 | import sys 14 | import subprocess 15 | 16 | 17 | PACKAGE_NAME = 'epanet' 18 | SOURCES = { 19 | 'epanet.toolkit': 'epanet_python/toolkit', 20 | 'epanet.output': 'epanet_python/output' 21 | } 22 | 23 | 24 | def install_microlibs(sources, develop=False): 25 | """ Use pip to install all microlibraries. """ 26 | print("installing all microlibs in {} mode".format( 27 | "development" if develop else "normal")) 28 | wd = pathlib.Path.cwd() 29 | for k, v in iteritems(sources): 30 | try: 31 | microlib_dir = os.fspath(wd.joinpath(v)) 32 | if develop: 33 | subprocess.check_call([sys.executable, '-m', 'pip', 'install', '-e', '.'], cwd=microlib_dir) 34 | else: 35 | subprocess.check_call([sys.executable, '-m', 'pip', 'install', '.'], cwd=microlib_dir) 36 | except Exception as e: 37 | print("Oops, something went wrong installing", k, microlib_dir) 38 | print(e) 39 | finally: 40 | os.chdir(wd) 41 | 42 | 43 | class DevelopCmd(develop): 44 | """ Add custom steps for the develop command """ 45 | def run(self): 46 | install_microlibs(SOURCES, develop=True) 47 | develop.run(self) 48 | 49 | 50 | class InstallCmd(install): 51 | """ Add custom steps for the install command """ 52 | def run(self): 53 | install_microlibs(SOURCES, develop=False) 54 | install.run(self) 55 | 56 | 57 | setup( 58 | name=PACKAGE_NAME, 59 | version="0.3.0.dev1", 60 | 61 | cmdclass={ 62 | 'install': InstallCmd, 63 | 'develop': DevelopCmd 64 | }, 65 | 66 | author="Michael Tryby", 67 | author_email="Michael Tryby@epa.gov", 68 | description="epanet_python - SWIG generated python wrappers for epanet libraries", 69 | license="CC0", 70 | classifiers=[ 71 | 'Private :: Do Not Upload to pypi server', 72 | ], 73 | 74 | setup_requires=["pytest-runner"], 75 | tests_require=["pytest", 'numpy'] 76 | ) 77 | -------------------------------------------------------------------------------- /epanet_python/tox.ini: -------------------------------------------------------------------------------- 1 | 2 | 3 | [tox] 4 | envlist = py36 5 | 6 | [testenv] 7 | passenv = APPVEYOR APPVEYOR_* 8 | whitelist_externals = swig 9 | skipsdist = True 10 | usedevelop = True 11 | deps = 12 | pytest 13 | numpy 14 | PyYAML 15 | coveralls 16 | commands = 17 | coverage run --source epanet_python --omit */tests/test_*.py setup.py test 18 | coveralls 19 | -------------------------------------------------------------------------------- /owa-epanet/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | project(owa-epanet) 4 | set(Python3_EXECUTABLE ${PYTHON_EXECUTABLE}) 5 | 6 | if(SKBUILD) 7 | message(STATUS "The project is built using scikit-build") 8 | endif() 9 | 10 | # Setup python 11 | if(NOT MSVC) 12 | find_package (Python3 ${PYTHON_VERSION_STRING} COMPONENTS Interpreter Development.Module EXACT REQUIRED) 13 | else() 14 | find_package (Python3 ${PYTHON_VERSION_STRING} COMPONENTS Interpreter Development EXACT REQUIRED) 15 | endif() 16 | 17 | # Setup swig 18 | find_package(SWIG REQUIRED) 19 | cmake_policy(SET CMP0078 NEW) 20 | cmake_policy(SET CMP0086 NEW) 21 | include(${SWIG_USE_FILE}) 22 | set(CMAKE_SWIG_FLAGS -py3) 23 | 24 | message("PYTHONLIBS_VERSION_STRING: ${PYTHONLIBS_VERSION_STRING}") 25 | message("CMAKE_SWIG_FLAGS: ${CMAKE_SWIG_FLAGS}") 26 | 27 | # Build the EPANET library 28 | ADD_SUBDIRECTORY(EPANET) 29 | 30 | # Set up rpath on MacOS and Linux 31 | if(APPLE) 32 | set(PACKAGE_RPATH "@loader_path") 33 | else() 34 | set(PACKAGE_RPATH "$ORIGIN") 35 | endif() 36 | 37 | 38 | # Include files for swig 39 | INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) 40 | INCLUDE_DIRECTORIES(EPANET/include) 41 | INCLUDE_DIRECTORIES(EPANET/src/outfile/include) 42 | 43 | 44 | # Allow target_include_directories to be used later 45 | set_property(SOURCE wrapper/toolkit.i 46 | PROPERTY 47 | USE_TARGET_INCLUDE_DIRECTORIES TRUE 48 | ) 49 | set_property(TARGET epanet2 50 | PROPERTY 51 | USE_TARGET_INCLUDE_DIRECTORIES TRUE 52 | ) 53 | 54 | # Create cmake target 55 | swig_add_library(toolkit 56 | TYPE 57 | MODULE 58 | LANGUAGE 59 | python 60 | SOURCES 61 | wrapper/toolkit.i 62 | ) 63 | 64 | target_include_directories(toolkit 65 | PUBLIC 66 | ${Python3_INCLUDE_DIRS} 67 | ) 68 | 69 | target_link_options(toolkit 70 | PUBLIC 71 | $<$:-undefined dynamic_lookup> 72 | ) 73 | 74 | 75 | 76 | swig_link_libraries(toolkit 77 | PUBLIC 78 | $<$>:Python3::Module> 79 | epanet2 80 | ) 81 | 82 | set_target_properties(toolkit 83 | PROPERTIES 84 | SWIG_COMPILE_DEFINITIONS EXPORT_OUT_API 85 | MACOSX_RPATH TRUE 86 | SKIP_BUILD_RPATH FALSE 87 | BUILD_WITH_INSTALL_RPATH FALSE 88 | INSTALL_RPATH "${PACKAGE_RPATH}" 89 | INSTALL_RPATH_USE_LINK_PATH TRUE 90 | ) 91 | 92 | install(TARGETS toolkit LIBRARY DESTINATION packages/epanet) 93 | 94 | add_custom_command( 95 | TARGET toolkit 96 | POST_BUILD 97 | COMMAND ${CMAKE_COMMAND} -E copy 98 | "${CMAKE_CURRENT_BINARY_DIR}/toolkit.py" 99 | $<$:${CMAKE_CURRENT_BINARY_DIR}/_toolkit.pyd> 100 | $<$:${CMAKE_CURRENT_BINARY_DIR}/bin/epanet2.dll> 101 | $<$>:${CMAKE_CURRENT_BINARY_DIR}/lib/libepanet2.*> 102 | ${CMAKE_SOURCE_DIR}/packages/epanet 103 | ) 104 | 105 | -------------------------------------------------------------------------------- /owa-epanet/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 CitiLogics 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /owa-epanet/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include packages/epanet/*.py 2 | include setup.py 3 | include README.md 4 | include CMakeLists.txt 5 | include LICENSE 6 | include wrapper/*.i 7 | graft EPANET 8 | prune _skbuild 9 | prune _cmake_test_compile 10 | prune *.so 11 | prune *.dylib 12 | prune *.dll 13 | -------------------------------------------------------------------------------- /owa-epanet/README.md: -------------------------------------------------------------------------------- 1 | # epanet python package 2 | 3 | A slender, auto-generated python wrapper around the owa:epanet hydraulic network analysis toolkit. This package uses SWIG and scikit-build to generate python bindings into the C library. The objective of this package is to establish basic python support for the toolkit, rather than present a "pythonic" interface. More abstractions can be built atop this package to further abstract the API, but the set of functions here is meant to (as closely as practical) mirror the well-known and established C API. 4 | 5 | Where possible, SWIG has been configured to throw warnings/exceptions instead of using the customary EPANET return integer value for success-checking. Also any output (pointer) parameters from the C API have been re-routed to return values. In these cases, the return tuple from the Python API will contain the values desired. 6 | 7 | ## Installation 8 | To install with pip: 9 | ```shell 10 | pip install owa-epanet 11 | ``` 12 | 13 | Wheels are now provided for most installations, some extra work will be needed if installing from source. 14 | 15 | 16 | ## Building the libraries 17 | 18 | __Ensure the EPANET subproject is initialized__ by running `git submodule update --init` (if necessary) and running the following commands (on Windows skip the line `./script/clean.sh`). The following method uses `scikit-build` to invoke `cmake` for compiling and linking the shared libaries, and builds a python wheel. 19 | 20 | ``` 21 | ./scripts/clean.sh 22 | python3 setup.py sdist bdist_wheel 23 | ``` 24 | Test your builds using the following commands. 25 | ``` 26 | cd test && \ 27 | pipenv install ../dist/*.whl && \ 28 | pipenv install pytest && \ 29 | pipenv run pytest 30 | ``` 31 | 32 | This python library was packaged in the following way: 33 | 34 | ``` 35 | python3 setup.py sdist bdist_wheel 36 | python3 -m twine upload dist/* 37 | ``` 38 | 39 | ## Versioning 40 | This package loosely follows EPANET's release version numbers. Its major and minor numbers will correspond to the given EPANET version, but the patch number is independent. This system might change in the future. 41 | -------------------------------------------------------------------------------- /owa-epanet/build_owa-epanet.bat: -------------------------------------------------------------------------------- 1 | cls 2 | 3 | rem Bulding the toolkit package for Windows. The preferred way to build the package is by running 'python setup.py sdist bdist_wheel'. 4 | 5 | git submodule update --init 6 | 7 | SET CMAKE_PATH=cmake.exe 8 | SET Build_PATH=%CD% 9 | SET COMPILE_PATH=%Build_PATH%\_cmake_build\windows\cmake-build 10 | 11 | MKDIR "%COMPILE_PATH%" 12 | CD "%COMPILE_PATH%" 13 | rem Building 64-bit toolkit for 64-bit python. 14 | rem If 32-bit toolkit is required ensure 32-bit python is located 15 | rem by cmake (check CmakeList.txt) and replace '%CMAKE_PATH% ../ -A x64' 16 | rem with '%CMAKE_PATH% ../ -A Win32'." 17 | %CMAKE_PATH% ../../../ -A x64 18 | %CMAKE_PATH% --build . --config Release 19 | 20 | rem 'After successful compilation "toolkit.py", "_toolkit.pyd", "epanet2.dll" will be created. You can start using the toolkit by importing the module "toolkit.py". 21 | 22 | cd .. 23 | pause 24 | -------------------------------------------------------------------------------- /owa-epanet/packages/epanet/__init__.py: -------------------------------------------------------------------------------- 1 | name = "epanet" 2 | __all__ = ["epanet"] 3 | -------------------------------------------------------------------------------- /owa-epanet/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel", "scikit-build", "cmake"] 3 | -------------------------------------------------------------------------------- /owa-epanet/scripts/clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -rf \ 4 | packages/epanet/toolkit.py \ 5 | packages/epanet/output.py \ 6 | packages/epanet/*.so \ 7 | packages/epanet/*.dylib \ 8 | _skbuild \ 9 | _cmake_test_compile \ 10 | dist \ 11 | packages/epanet.egg-info \ 12 | test/Pipf* 13 | 14 | touch test/Pipfile 15 | -------------------------------------------------------------------------------- /owa-epanet/setup.py: -------------------------------------------------------------------------------- 1 | from skbuild import setup 2 | from setuptools import find_packages 3 | 4 | with open("README.md", "r") as fh: 5 | long_description = fh.read() 6 | 7 | setup( 8 | name = "owa-epanet", 9 | version = "2.2.4", 10 | author = "Sam Hatchett", 11 | author_email = "samhatchett@gmail.com", 12 | description = "a thin wrapper for epanet hydraulic toolkit", 13 | long_description = long_description, 14 | long_description_content_type="text/markdown", 15 | url = "https://github.com/OpenWaterAnalytics/epanet-python", 16 | cmake_args=["-DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.9"], 17 | #cmake_with_sdist = True, 18 | package_dir = {"":"packages"}, 19 | packages = ["epanet"], 20 | package_data = {"epanet":["*.dylib", "*.dll", "*.so"]}, 21 | zip_safe=False 22 | ) 23 | -------------------------------------------------------------------------------- /owa-epanet/test/Pipfile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenWaterAnalytics/epanet-python/f71869a908928a5fe274a3fdf2a0e0ad8c703e2f/owa-epanet/test/Pipfile -------------------------------------------------------------------------------- /owa-epanet/test/README.md: -------------------------------------------------------------------------------- 1 | Run unittests with: 2 | 3 | ```python 4 | pytest test_owa_epanet.py 5 | ``` -------------------------------------------------------------------------------- /owa-epanet/test/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # ----------------------------------------------------------------------------- 3 | """test owa-epanet toolkit.""" 4 | -------------------------------------------------------------------------------- /owa-epanet/test/data/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # ----------------------------------------------------------------------------- 3 | # Copyright (c) 2019 CitiLogics, a Xylem Brand 4 | # 5 | # ----------------------------------------------------------------------------- 6 | """test data for owa-epanet toolkit.""" 7 | 8 | # Standard library imports 9 | import os 10 | 11 | data_path = os.path.abspath(os.path.dirname(__file__)) 12 | 13 | # Test object paths 14 | example_1_path = os.path.join(data_path, 'example_1.inp') -------------------------------------------------------------------------------- /owa-epanet/test/data/example_1.inp: -------------------------------------------------------------------------------- 1 | [TITLE] 2 | EPANET Example Network 1 3 | A simple example of modeling chlorine decay. Both bulk and 4 | wall reactions are included. 5 | 6 | [JUNCTIONS] 7 | ;ID Elev Demand Pattern 8 | 10 710 0 ; 9 | 11 710 150 ; 10 | 12 700 150 ; 11 | 13 695 100 ; 12 | 21 700 150 ; 13 | 22 695 200 ; 14 | 23 690 150 ; 15 | 31 700 100 ; 16 | 32 710 100 ; 17 | 18 | [RESERVOIRS] 19 | ;ID Head Pattern 20 | 9 800 ; 21 | 22 | [TANKS] 23 | ;ID Elevation InitLevel MinLevel MaxLevel Diameter MinVol VolCurve 24 | 2 850 120 100 150 50.5 0 ; 25 | 26 | [PIPES] 27 | ;ID Node1 Node2 Length Diameter Roughness MinorLoss Status 28 | 10 10 11 10530 18 100 0 Open ; 29 | 11 11 12 5280 14 100 0 Open ; 30 | 12 12 13 5280 10 100 0 Open ; 31 | 21 21 22 5280 10 100 0 Open ; 32 | 22 22 23 5280 12 100 0 Open ; 33 | 31 31 32 5280 6 100 0 Open ; 34 | 110 2 12 200 18 100 0 Open ; 35 | 111 11 21 5280 10 100 0 Open ; 36 | 112 12 22 5280 12 100 0 Open ; 37 | 113 13 23 5280 8 100 0 Open ; 38 | 121 21 31 5280 8 100 0 Open ; 39 | 122 22 32 5280 6 100 0 Open ; 40 | 41 | [PUMPS] 42 | ;ID Node1 Node2 Parameters 43 | 9 9 10 HEAD 1 ; 44 | 45 | [VALVES] 46 | ;ID Node1 Node2 Diameter Type Setting MinorLoss 47 | 48 | [TAGS] 49 | 50 | [DEMANDS] 51 | ;Junction Demand Pattern Category 52 | 53 | [STATUS] 54 | ;ID Status/Setting 55 | 56 | [PATTERNS] 57 | ;ID Multipliers 58 | ;Demand Pattern 59 | 1 1.0 1.2 1.4 1.6 1.4 1.2 60 | 1 1.0 0.8 0.6 0.4 0.6 0.8 61 | 62 | [CURVES] 63 | ;ID X-Value Y-Value 64 | ;PUMP: Pump Curve for Pump 9 65 | 1 1500 250 66 | 67 | [CONTROLS] 68 | LINK 9 OPEN IF NODE 2 BELOW 110 69 | LINK 9 CLOSED IF NODE 2 ABOVE 140 70 | 71 | 72 | [RULES] 73 | 74 | [ENERGY] 75 | Global Efficiency 75 76 | Global Price 0.0 77 | Demand Charge 0.0 78 | 79 | [EMITTERS] 80 | ;Junction Coefficient 81 | 82 | [QUALITY] 83 | ;Node InitQual 84 | 10 0.5 85 | 11 0.5 86 | 12 0.5 87 | 13 0.5 88 | 21 0.5 89 | 22 0.5 90 | 23 0.5 91 | 31 0.5 92 | 32 0.5 93 | 9 1.0 94 | 2 1.0 95 | 96 | [SOURCES] 97 | ;Node Type Quality Pattern 98 | 99 | [REACTIONS] 100 | ;Type Pipe/Tank Coefficient 101 | 102 | 103 | [REACTIONS] 104 | Order Bulk 1 105 | Order Tank 1 106 | Order Wall 1 107 | Global Bulk -.5 108 | Global Wall -1 109 | Limiting Potential 0.0 110 | Roughness Correlation 0.0 111 | 112 | [MIXING] 113 | ;Tank Model 114 | 115 | [TIMES] 116 | Duration 24:00 117 | Hydraulic Timestep 1:00 118 | Quality Timestep 0:05 119 | Pattern Timestep 2:00 120 | Pattern Start 0:00 121 | Report Timestep 1:00 122 | Report Start 0:00 123 | Start ClockTime 12 am 124 | Statistic None 125 | 126 | [REPORT] 127 | Status Yes 128 | Summary No 129 | Page 0 130 | 131 | [OPTIONS] 132 | Units GPM 133 | Headloss H-W 134 | Specific Gravity 1.0 135 | Viscosity 1.0 136 | Trials 40 137 | Accuracy 0.001 138 | CHECKFREQ 2 139 | MAXCHECK 10 140 | DAMPLIMIT 0 141 | Unbalanced Continue 10 142 | Pattern 1 143 | Demand Multiplier 1.0 144 | Emitter Exponent 0.5 145 | Quality Chlorine mg/L 146 | Diffusivity 1.0 147 | Tolerance 0.01 148 | 149 | [COORDINATES] 150 | ;Node X-Coord Y-Coord 151 | 10 20.00 70.00 152 | 11 30.00 70.00 153 | 12 50.00 70.00 154 | 13 70.00 70.00 155 | 21 30.00 40.00 156 | 22 50.00 40.00 157 | 23 70.00 40.00 158 | 31 30.00 10.00 159 | 32 50.00 10.00 160 | 9 10.00 70.00 161 | 2 50.00 90.00 162 | 163 | [VERTICES] 164 | ;Link X-Coord Y-Coord 165 | 166 | [LABELS] 167 | ;X-Coord Y-Coord Label & Anchor Node 168 | 6.99 73.63 "Source" 169 | 13.48 68.13 "Pump" 170 | 43.85 91.21 "Tank" 171 | 172 | [BACKDROP] 173 | DIMENSIONS 7.00 6.00 73.00 94.00 174 | UNITS None 175 | FILE 176 | OFFSET 0.00 0.00 177 | 178 | [END] -------------------------------------------------------------------------------- /owa-epanet/test/test-lib.py: -------------------------------------------------------------------------------- 1 | from epanet import toolkit 2 | 3 | for i in range(1,4): 4 | p = toolkit.createproject() 5 | toolkit.deleteproject(p) 6 | 7 | p = toolkit.createproject() 8 | del p 9 | -------------------------------------------------------------------------------- /owa-epanet/test/test_owa_epanet.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import pytest 5 | import epanet.toolkit as en 6 | from test.data import example_1_path 7 | 8 | 9 | timesteps = [3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 1954, 1646, 3600, 10 | 3600, 3600, 3600, 3600, 3600, 3600, 3600, 3600, 2490, 1110, 3600, 0] 11 | wq_times = [86100, 85800, 85500, 85200, 84900, 84600, 84300, 84000, 83700, 83400, 83100, 82800, 82500, 82200, 81900, 12 | 81600, 81300, 81000, 80700, 80400, 80100, 79800, 79500, 79200, 78900, 78600, 78300, 78000, 77700, 77400, 13 | 77100, 76800, 76500, 76200, 75900, 75600, 75300, 75000, 74700, 74400, 74100, 73800, 73500, 73200, 72900, 14 | 72600, 72300, 72000, 71700, 71400, 71100, 70800, 70500, 70200, 69900, 69600, 69300, 69000, 68700, 68400] 15 | 16 | 17 | def clean_dir(): 18 | def safe_delete(filename): 19 | try: 20 | os.remove(filename) 21 | except PermissionError: 22 | pass 23 | if os.path.exists('report.rpt'): 24 | safe_delete('report.rpt') 25 | if os.path.exists('output.out'): 26 | safe_delete('output.out') 27 | if os.path.exists('saved_inp_file.inp'): 28 | safe_delete('saved_inp_file.inp') 29 | 30 | 31 | def test_create_project(): 32 | epanet_proj = en.createproject() 33 | assert str(type(epanet_proj)) == '' 34 | 35 | 36 | def test_open(): 37 | epanet_proj = en.createproject() 38 | en.open(ph=epanet_proj, inpFile=example_1_path, rptFile='report.rpt', outFile='output.out') 39 | clean_dir() 40 | 41 | 42 | def test_close(): 43 | epanet_proj = en.createproject() 44 | en.open(ph=epanet_proj, inpFile=example_1_path, rptFile='report.rpt', outFile='output.out') 45 | en.close(ph=epanet_proj) 46 | clean_dir() 47 | 48 | 49 | def test_run_project(): 50 | epanet_proj = en.createproject() 51 | en.runproject(ph=epanet_proj, inpFile=example_1_path, rptFile='report.rpt', outFile='output.out', pviewprog=None) 52 | clean_dir() 53 | 54 | 55 | def test_get_count(): 56 | epanet_proj = en.createproject() 57 | en.open(ph=epanet_proj, inpFile=example_1_path, rptFile='report.rpt', outFile='output.out') 58 | num_nodes = en.getcount(ph=epanet_proj, object=en.NODECOUNT) 59 | assert(num_nodes == 11) 60 | num_links = en.getcount(ph=epanet_proj, object=en.LINKCOUNT) 61 | assert(num_links == 13) 62 | num_tanks = en.getcount(ph=epanet_proj, object=en.TANKCOUNT) 63 | assert(num_tanks == 2) 64 | num_pat = en.getcount(ph=epanet_proj, object=en.PATCOUNT) 65 | assert(num_pat == 1) 66 | num_curv = en.getcount(ph=epanet_proj, object=en.CURVECOUNT) 67 | assert (num_curv == 1) 68 | num_contr = en.getcount(ph=epanet_proj, object=en.CONTROLCOUNT) 69 | assert (num_contr == 2) 70 | en.close(ph=epanet_proj) 71 | clean_dir() 72 | 73 | 74 | def test_save_inp_file(): 75 | epanet_proj = en.createproject() 76 | en.open(ph=epanet_proj, inpFile=example_1_path, rptFile='report.rpt', outFile='output.out') 77 | en.saveinpfile(ph=epanet_proj, filename='saved_inp_file.inp') 78 | assert os.path.isfile('saved_inp_file.inp') 79 | clean_dir() 80 | 81 | 82 | def test_openh(): 83 | epanet_proj = en.createproject() 84 | en.open(ph=epanet_proj, inpFile=example_1_path, rptFile='report.rpt', outFile='output.out') 85 | res = en.openH(ph=epanet_proj) 86 | en.close(ph=epanet_proj) 87 | assert not res 88 | clean_dir() 89 | 90 | 91 | def test_inith(): 92 | epanet_proj = en.createproject() 93 | en.open(ph=epanet_proj, inpFile=example_1_path, rptFile='report.rpt', outFile='output.out') 94 | en.openH(ph=epanet_proj) 95 | res = en.initH(ph=epanet_proj, initFlag=0) 96 | en.close(ph=epanet_proj) 97 | assert not res 98 | clean_dir() 99 | 100 | 101 | def test_runh(): 102 | epanet_proj = en.createproject() 103 | en.open(ph=epanet_proj, inpFile=example_1_path, rptFile='report.rpt', outFile='output.out') 104 | en.openH(ph=epanet_proj) 105 | en.initH(ph=epanet_proj, initFlag=0) 106 | res = en.runH(ph=epanet_proj) 107 | en.close(ph=epanet_proj) 108 | assert res == 0 109 | clean_dir() 110 | 111 | 112 | def test_nexth(): 113 | epanet_proj = en.createproject() 114 | en.open(ph=epanet_proj, inpFile=example_1_path, rptFile='report.rpt', outFile='output.out') 115 | en.openH(ph=epanet_proj) 116 | en.initH(ph=epanet_proj, initFlag=0) 117 | en.runH(ph=epanet_proj) 118 | res = en.nextH(ph=epanet_proj) 119 | en.close(ph=epanet_proj) 120 | assert res == 3600 121 | clean_dir() 122 | 123 | 124 | def test_closeh(): 125 | epanet_proj = en.createproject() 126 | en.open(ph=epanet_proj, inpFile=example_1_path, rptFile='report.rpt', outFile='output.out') 127 | en.openH(ph=epanet_proj) 128 | en.initH(ph=epanet_proj, initFlag=0) 129 | res = en.closeH(ph=epanet_proj) 130 | en.close(ph=epanet_proj) 131 | assert not res 132 | clean_dir() 133 | 134 | 135 | def test_inith_runh_nexth(): 136 | epanet_proj = en.createproject() 137 | en.open(ph=epanet_proj, inpFile=example_1_path, rptFile='report.rpt', outFile='output.out') 138 | en.openH(ph=epanet_proj) 139 | en.initH(ph=epanet_proj, initFlag=0) 140 | tlist = [] 141 | while True: 142 | en.runH(ph=epanet_proj) 143 | t = en.nextH(ph=epanet_proj) 144 | tlist.append(t) 145 | if t <= 0: 146 | break 147 | en.closeH(ph=epanet_proj) 148 | en.close(ph=epanet_proj) 149 | assert tlist == timesteps 150 | clean_dir() 151 | 152 | 153 | def test_solveh_solveq(): 154 | epanet_proj = en.createproject() 155 | en.open(ph=epanet_proj, inpFile=example_1_path, rptFile='report.rpt', outFile='output.out') 156 | en.solveH(ph=epanet_proj) 157 | en.solveQ(ph=epanet_proj) 158 | en.report(ph=epanet_proj) 159 | en.close(ph=epanet_proj) 160 | assert os.path.isfile('output.out') 161 | clean_dir() 162 | 163 | 164 | def test_initq_runq_nextq(): 165 | epanet_proj = en.createproject() 166 | en.open(ph=epanet_proj, inpFile=example_1_path, rptFile='report.rpt', outFile='output.out') 167 | en.solveH(ph=epanet_proj) 168 | en.openQ(ph=epanet_proj) 169 | en.initQ(ph=epanet_proj, saveFlag=1) 170 | tlist = [] 171 | while True: 172 | en.runQ(ph=epanet_proj) 173 | t = en.nextQ(ph=epanet_proj) 174 | tlist.append(t) 175 | if t <= 0: 176 | break 177 | en.closeQ(ph=epanet_proj) 178 | en.report(ph=epanet_proj) 179 | en.close(ph=epanet_proj) 180 | assert tlist == timesteps 181 | clean_dir() 182 | 183 | 184 | def test_stepq(): 185 | epanet_proj = en.createproject() 186 | en.open(ph=epanet_proj, inpFile=example_1_path, rptFile='report.rpt', outFile='output.out') 187 | en.solveH(ph=epanet_proj) 188 | en.openQ(ph=epanet_proj) 189 | en.initQ(ph=epanet_proj, saveFlag=1) 190 | tlist = [] 191 | while True: 192 | en.runQ(ph=epanet_proj) 193 | tleft = en.stepQ(ph=epanet_proj) 194 | tlist.append(tleft) 195 | if tleft <= 0: 196 | break 197 | en.closeQ(ph=epanet_proj) 198 | en.close(ph=epanet_proj) 199 | assert tlist[:60] == wq_times 200 | clean_dir() 201 | 202 | 203 | def test_getqualtype(): 204 | epanet_proj = en.createproject() 205 | en.open(ph=epanet_proj, inpFile=example_1_path, rptFile='report.rpt', outFile='output.out') 206 | qual_type = en.getqualtype(ph=epanet_proj) 207 | en.close(ph=epanet_proj) 208 | assert qual_type == [1, 0] 209 | clean_dir() 210 | 211 | 212 | def test_set_qualtype(): 213 | epanet_proj = en.createproject() 214 | en.open(ph=epanet_proj, inpFile=example_1_path, rptFile='report.rpt', outFile='output.out') 215 | en.setqualtype(ph=epanet_proj, qualType=2, chemName='Age', chemUnits='seconds', traceNode=None) 216 | qual_type = en.getqualtype(ph=epanet_proj) 217 | en.close(ph=epanet_proj) 218 | assert qual_type == [2, 0] 219 | clean_dir() 220 | 221 | 222 | def test_water_age_sim(): 223 | epanet_proj = en.createproject() 224 | en.open(ph=epanet_proj, inpFile=example_1_path, rptFile='report.rpt', outFile='output.out') 225 | en.setqualtype(ph=epanet_proj, qualType=2, chemName='Age', chemUnits='hours', traceNode=None) 226 | en.solveH(ph=epanet_proj) 227 | en.openQ(ph=epanet_proj) 228 | en.initQ(ph=epanet_proj, saveFlag=1) 229 | num_nodes = en.getcount(ph=epanet_proj, object=en.NODECOUNT) 230 | age_list = [] 231 | while True: 232 | node_age_list = [] 233 | en.runQ(ph=epanet_proj) 234 | t = en.nextQ(ph=epanet_proj) 235 | for i in range(1, num_nodes*1): 236 | node_qual = en.getnodevalue(ph=epanet_proj, index=i, property=en.QUALITY) 237 | node_age_list.append(node_qual) 238 | age_list.append(node_age_list) 239 | if t <= 0: 240 | break 241 | en.closeQ(ph=epanet_proj) 242 | en.closeH(ph=epanet_proj) 243 | en.close(ph=epanet_proj) 244 | assert age_list[26] == pytest.approx( 245 | [1.0, 2.2141675704376946, 12.939125434025273, 24.44152992466322, 13.174235412569542, 246 | 24.441519659540887, 15.679376648181817, 21.97064181429266, 19.048343501261524, 1.0]) 247 | clean_dir() 248 | 249 | 250 | # this test is failing due to a seg fault 251 | def _test_add_node(): 252 | epanet_proj = en.createproject() 253 | en.open(ph=epanet_proj, inpFile=example_1_path, rptFile='report.rpt', outFile='output.out') 254 | en.addnode(ph=epanet_proj, id='33', nodeType=en.JUNCTION) 255 | num_nodes = en.getcount(ph=epanet_proj, object=en.NODECOUNT) 256 | assert num_nodes == 12 257 | clean_dir() 258 | 259 | 260 | def test_getnodeindex(): 261 | epanet_proj = en.createproject() 262 | en.open(ph=epanet_proj, inpFile=example_1_path, rptFile='report.rpt', outFile='output.out') 263 | node_ids = ['10', '11', '12', '13', '21', '22', '23', '31', '32', '9', '2'] 264 | node_idx = [] 265 | for node_id in node_ids: 266 | node_idx.append(en.getnodeindex(ph=epanet_proj, id=node_id)) 267 | assert node_idx == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] 268 | clean_dir() 269 | 270 | 271 | def test_getnodeid(): 272 | epanet_proj = en.createproject() 273 | en.open(ph=epanet_proj, inpFile=example_1_path, rptFile='report.rpt', outFile='output.out') 274 | node_idx = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] 275 | node_id = [] 276 | for nidx in node_idx: 277 | node_id.append(en.getnodeid(ph=epanet_proj, index=nidx)) 278 | assert node_id == ['10', '11', '12', '13', '21', '22', '23', '31', '32', '9', '2'] 279 | clean_dir() 280 | 281 | 282 | def test_add_link(): 283 | epanet_proj = en.createproject() 284 | en.open(ph=epanet_proj, inpFile=example_1_path, rptFile='report.rpt', outFile='output.out') 285 | en.addlink(ph=epanet_proj, id='L123', linkType=en.PIPE, fromNode='10', toNode='12') 286 | num_links = en.getcount(ph=epanet_proj, object=en.LINKCOUNT) 287 | assert num_links == 14 288 | clean_dir() 289 | 290 | 291 | def test_setnodevalue(): 292 | epanet_proj = en.createproject() 293 | en.open(ph=epanet_proj, inpFile=example_1_path, rptFile='report.rpt', outFile='output.out') 294 | num_nodes = en.getcount(ph=epanet_proj, object=en.NODECOUNT) 295 | elev_list = [] 296 | demand_list = [] 297 | pattern_list=[] 298 | emitter_list = [] 299 | initqual_list = [] 300 | tank_level_list = [] 301 | for node_ind in range(1, num_nodes+1): 302 | # elevation 303 | elev = en.getnodevalue(ph=epanet_proj, index=node_ind, property=en.ELEVATION) 304 | en.setnodevalue(ph=epanet_proj, index=node_ind, property=en.ELEVATION, value=elev + 1) 305 | elev_list.append(en.getnodevalue(ph=epanet_proj, index=node_ind, property=en.ELEVATION)) 306 | # base demand 307 | demand = en.getnodevalue(ph=epanet_proj, index=node_ind, property=en.BASEDEMAND) 308 | en.setnodevalue(ph=epanet_proj, index=node_ind, property=en.BASEDEMAND, value=demand + 1) 309 | demand_list.append(en.getnodevalue(ph=epanet_proj, index=node_ind, property=en.BASEDEMAND)) 310 | # pattern 311 | en.setnodevalue(ph=epanet_proj, index=node_ind, property=en.PATTERN, value=0) 312 | pattern_list.append(en.getnodevalue(ph=epanet_proj, index=node_ind, property=en.PATTERN)) 313 | # emitter 314 | en.setnodevalue(ph=epanet_proj, index=node_ind, property=en.EMITTER, value=0.01) 315 | emitter_list.append(en.getnodevalue(ph=epanet_proj, index=node_ind, property=en.EMITTER)) 316 | # initqual 317 | initqual = en.getnodevalue(ph=epanet_proj, index=node_ind, property=en.INITQUAL) 318 | en.setnodevalue(ph=epanet_proj, index=node_ind, property=en.INITQUAL, value=initqual + 0.1) 319 | initqual_list.append(en.getnodevalue(ph=epanet_proj, index=node_ind, property=en.INITQUAL)) 320 | # tanklevel 321 | if en.getnodetype(ph=epanet_proj, index=node_ind) == en.TANK: 322 | tank_level = en.getnodevalue(ph=epanet_proj, index=node_ind, property=en.TANKLEVEL) 323 | en.setnodevalue(ph=epanet_proj, index=node_ind, property=en.TANKLEVEL, value=tank_level + 1) 324 | tank_level_list.append(en.getnodevalue(ph=epanet_proj, index=node_ind, property=en.TANKLEVEL)) 325 | assert elev_list == [711.0, 711.0, 701.0, 696.0, 701.0, 696.0, 691.0, 701.0, 711.0, 801.0, 851.0] 326 | assert demand_list == [1.0, 151.0, 151.0, 101.0, 151.0, 201.0, 151.0, 101.0, 101.0, 0.0, 0.0] 327 | assert pattern_list == [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 328 | assert emitter_list == [0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.0, 0.0] 329 | assert initqual_list == [0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 1.1, 1.1] 330 | assert tank_level_list ==[121.0] 331 | clean_dir() 332 | 333 | 334 | def test_setcurve(): 335 | def make_array(values): 336 | dbl_arr = en.doubleArray(len(values)) 337 | for i in range(len(values)): 338 | dbl_arr[i] = values[i] 339 | return dbl_arr 340 | 341 | epanet_proj = en.createproject() 342 | en.open(ph=epanet_proj, inpFile=example_1_path, rptFile='report.rpt', outFile='output.out') 343 | ok = en.addcurve(ph=epanet_proj, id="my_curve") 344 | curve_index = en.getcurveindex(ph=epanet_proj, id="my_curve") 345 | xvalues = make_array([1,2,3,4,5]) 346 | yvalues = make_array([1,2,3,4,5]) 347 | en.setcurve(ph=epanet_proj, index=curve_index, xValues=xvalues, yValues=yvalues, nPoints=5) 348 | count = en.getcurvelen(ph=epanet_proj, index=curve_index) 349 | assert count == 5 350 | 351 | 352 | def test_coords(): 353 | epanet_proj = en.createproject() 354 | en.open(ph=epanet_proj, inpFile=example_1_path, rptFile='report.rpt', outFile='output.out') 355 | coords = en.getcoord(epanet_proj,1) 356 | en.setcoord(epanet_proj,1,3,4) 357 | coords = en.getcoord(epanet_proj,1) 358 | assert coords[0] == pytest.approx(3) 359 | assert coords[1] == pytest.approx(4) 360 | -------------------------------------------------------------------------------- /owa-epanet/wrapper/output.i: -------------------------------------------------------------------------------- 1 | %include "typemaps.i" 2 | %include "cstring.i" 3 | 4 | /* epanet simple python wrapper */ 5 | %module (package="epanet") output 6 | %{ 7 | #define SHARED_EXPORTS_BUILT_AS_STATIC 8 | #include 9 | %} 10 | %include 11 | 12 | /* strip the pseudo-scope from function declarations */ 13 | %rename("%(strip:[ENR_])s") ""; 14 | 15 | %typemap(in,numinputs=0) ENR_Handle* p_handle_out (ENR_Handle temp) { 16 | $1 = &temp; 17 | } 18 | 19 | %typemap(argout) ENR_Handle* p_handle_out { 20 | %append_output(SWIG_NewPointerObj(SWIG_as_voidptr(retval$argnum), $1_descriptor, 0)); 21 | } 22 | 23 | /* TYPEMAP FOR IGNORING INT ERROR CODE RETURN VALUE */ 24 | %typemap(out) int { 25 | $result = Py_None; 26 | Py_INCREF($result); 27 | } 28 | 29 | 30 | /* TYPEMAPS FOR MEMORY MANAGEMENT OF FLOAT ARRAYS */ 31 | %typemap(in, numinputs=0)float** float_out (float* temp), int* int_dim (int temp){ 32 | $1 = &temp; 33 | } 34 | %typemap(argout) (float** float_out, int* int_dim) { 35 | if (*$1) { 36 | PyObject *o = PyList_New(*$2); 37 | int i; 38 | float* temp = *$1; 39 | for(i=0; i<*$2; i++) { 40 | PyList_SetItem(o, i, PyFloat_FromDouble((double)temp[i])); 41 | } 42 | $result = SWIG_Python_AppendOutput($result, o); 43 | free(*$1); 44 | } 45 | } 46 | 47 | /* TYPEMAPS FOR MEMORY MANAGEMENT OF INT ARRAYS */ 48 | %typemap(in, numinputs=0)int** int_out (long* temp), int* int_dim (int temp){ 49 | $1 = &temp; 50 | } 51 | %typemap(argout) (int** int_out, int* int_dim) { 52 | if (*$1) { 53 | PyObject *o = PyList_New(*$2); 54 | int i; 55 | long* temp = *$1; 56 | for(i=0; i<*$2; i++) { 57 | PyList_SetItem(o, i, PyInt_FromLong(temp[i])); 58 | } 59 | $result = SWIG_Python_AppendOutput($result, o); 60 | free(*$1); 61 | } 62 | } 63 | 64 | /* TYPEMAP FOR MEMORY MANAGEMENT AND ENCODING OF STRINGS */ 65 | %typemap(in, numinputs=0)char** string_out (char* temp), int* slen (int temp){ 66 | $1 = &temp; 67 | } 68 | %typemap(argout)(char** string_out, int* slen) { 69 | if (*$1) { 70 | PyObject* o; 71 | o = PyUnicode_FromStringAndSize(*$1, *$2); 72 | 73 | $result = SWIG_Python_AppendOutput($result, o); 74 | free(*$1); 75 | } 76 | } 77 | 78 | %apply int *OUTPUT { 79 | int *int_out 80 | }; 81 | 82 | /* INSERTS CUSTOM EXCEPTION HANDLING IN WRAPPER */ 83 | %exception 84 | { 85 | $action 86 | if ( result > 0) { 87 | PyErr_SetString(PyExc_Exception, "ERROR"); 88 | SWIG_fail; 89 | } 90 | } 91 | 92 | %feature("autodoc", "2"); 93 | #define SHARED_EXPORTS_BUILT_AS_STATIC 94 | %include 95 | 96 | %exception; 97 | -------------------------------------------------------------------------------- /owa-epanet/wrapper/toolkit.i: -------------------------------------------------------------------------------- 1 | %include "typemaps.i" 2 | %include "cstring.i" 3 | %include "carrays.i" 4 | 5 | /* arrays for toolkit functions wanting lists */ 6 | %array_class(int, intArray); 7 | %array_class(double, doubleArray); 8 | 9 | /* epanet simple python wrapper */ 10 | %module (package="epanet") toolkit 11 | %{ 12 | #include 13 | %} 14 | 15 | /* strip the pseudo-scope from function declarations and enums*/ 16 | %rename("%(strip:[EN_])s") ""; 17 | 18 | %typemap(in,numinputs=0) EN_Project* (EN_Project temp) { 19 | $1 = &temp; 20 | } 21 | 22 | %typemap(argout) EN_Project* { 23 | %append_output(SWIG_NewPointerObj(*$1, SWIGTYPE_p_Project, SWIG_POINTER_NEW)); 24 | } 25 | 26 | /* TYPEMAP FOR IGNORING INT ERROR CODE RETURN VALUE */ 27 | %typemap(out) int { 28 | $result = Py_None; 29 | Py_INCREF($result); 30 | } 31 | 32 | %apply int *OUTPUT { 33 | int *out_count, 34 | int *out_version, 35 | int *out_units, 36 | int *out_qualType, 37 | int *out_traceNode, 38 | int *out_index, 39 | int *out_nodeType, 40 | int *out_type, 41 | int *out_demandIndex, 42 | int *out_numDemands, 43 | int *out_patIndex, 44 | int *out_linkType, 45 | int *out_node1, 46 | int *out_node2, 47 | int *out_pumpType, 48 | int *out_curveIndex, 49 | int *out_len, 50 | int *out_nPoints, 51 | int *out_nodeIndex, 52 | int *out_linkIndex, 53 | int *out_nPremises, 54 | int *out_nThenActions, 55 | int *out_nElseActions, 56 | int *out_logop, 57 | int *out_object, 58 | int *out_objIndex, 59 | int *out_variable, 60 | int *out_relop, 61 | int *out_status, 62 | int *out_value 63 | }; 64 | 65 | %apply double *OUTPUT { 66 | double *out_value, 67 | double *out_x, 68 | double *out_y, 69 | double *out_baseDemand, 70 | double *out_pmin, 71 | double *out_preq, 72 | double *out_pexp, 73 | double *out_setting, 74 | double *out_level, 75 | double *out_priority 76 | }; 77 | 78 | %apply long *OUTPUT { 79 | long *out_value, 80 | long *out_currentTime, 81 | long *out_tStep, 82 | long *out_timeLeft 83 | }; 84 | 85 | %cstring_bounded_output(char *OUTCHAR, EN_MAXMSG); 86 | 87 | %apply char *OUTCHAR { 88 | char *out_line1, 89 | char *out_line2, 90 | char *out_line3, 91 | char *out_comment, 92 | char *out_errmsg, 93 | char *out_chemName, 94 | char *out_chemUnits, 95 | char *out_id, 96 | char *out_demandName 97 | }; 98 | 99 | %apply int *INOUT { 100 | int *inout_index 101 | } 102 | 103 | %nodefault Project; 104 | struct Project {}; 105 | %extend Project { 106 | ~Project() { 107 | EN_deleteproject($self); 108 | } 109 | }; 110 | ignore Project; 111 | 112 | /* INSERTS CUSTOM EXCEPTION HANDLING IN WRAPPER */ 113 | %exception 114 | { 115 | $action 116 | if ( result > 10) { 117 | char errmsg[EN_MAXMSG]; 118 | EN_geterror(result, errmsg, EN_MAXMSG); 119 | PyErr_SetString(PyExc_Exception, errmsg); 120 | SWIG_fail; 121 | } 122 | else if (result > 0) { 123 | PyErr_WarnEx(PyExc_Warning, "WARNING", 2); 124 | } 125 | } 126 | 127 | %feature("autodoc", "2"); 128 | %newobject EN_createproject; 129 | %delobject EN_deleteproject; 130 | %include 131 | %include 132 | %exception; 133 | --------------------------------------------------------------------------------