Skip to content

Commit

Permalink
WIP finalizing core api, unit testing, installation config; #204, #162
Browse files Browse the repository at this point in the history
…, #163, #184, #180
  • Loading branch information
cbuahin committed Dec 16, 2024
1 parent 6c08f7e commit 11f2a60
Show file tree
Hide file tree
Showing 15 changed files with 384 additions and 74 deletions.
55 changes: 46 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ using CMake and the Microsoft Visual Studio C compiler on Windows:
Readme file resides (which should have 'src' as a sub-directory
underneath it).

2. Issue the following commands:
2. Issue the following command to create the directory for storing the built binaries:

```bash
mkdir build
cd build
```

3. Then enter the following CMake commands:
3. Then enter the following CMake commands to build the binaries:

``` bash
cmake -G <compiler> .. -A <platform>
Expand All @@ -77,22 +77,59 @@ cmake --build .
The resulting shared object library (libswmm5.so or libswmm5.dylib) and
command line executable (runswmm) will appear in the build directory.

The exprimental python bindings can be built and installed locally using the following command.
### Python Bindings (Experimental)

Experimental python bindings for the SWMM API are being developed to support regression and benchmark testing as well as other applications. _**These bindings are still under development and testing and has yet to be cleared through US EPA ORD's official quality assurance review process**_. The exprimental python bindings can be built and installed locally using the following command.

```bash
cd python
python -m pip install -r requirements.txt
python -m pip install .
```
Users may also build python wheels for installation or distribution. Once the python bindings
have been validated and cleared through EPA'S clearance process, they will be available for installation
via ropsitories such as pypi.
have been validated and cleared through EPA's quality assuracnce clearance process, they will be available for installation via package indexing repositories such as pypi.

Example usage of python bindings can be found below. More extensive documentation will be provided once cleared.

```python

from epaswmm import solver
from epaswmm.solver import Solver
from epaswmm.output import Output

with Solver(inp_file="input_file.inp") as swmm_solver:

# Open swmm file and initialize objects
swmm_solver.initialize()

# Set initialization parameters e.g., time step stride, start date, end date etc.
swmm_solver.time_stride = 600

for elapsed_time in swmm_solver:

# Get and set attributes per timestep
print(swmm_solver.current_datetime)

swmm_solver.set_value(
object_type=solver.SWMMObjects.RAIN_GAGE.value,
property_type=solver.SWMMRainGageProperties.GAGE_RAINFALL.value,
index=0,
value=3.6
)

swmm_output = Output(output_file='output_file.out')

# Dict[datetime, float]
link_timeseries = swmm_output.get_link_timeseries(
element_index=5,
attribute=output.LinkAttribute.FLOW_RATE.value,
)

```

## Unit and Regression Testing

Unit tests and regression tests have been developed for both the natively compiled SWMM computational engine and output toolkit as
well as their respective python bindings. Unit tests for the natively compiled toolkits use the Boost 1.67.0 library and can be
compiled by adding DBUILD_TESTS=ON flag during the cmake build phase as shown below:
Unit tests and regression tests have been developed for both the natively compiled SWMM computational engine and output toolkit as well as their respective python bindings. Unit tests for the natively compiled toolkits use the Boost 1.67.0 library and can be compiled by adding DBUILD_TESTS=ON flag during the cmake build phase as shown below:

```bash
ctest --test-dir . -DBUILD_TESTS=ON --config Debug --output-on-failure
Expand All @@ -116,7 +153,7 @@ pytest --data-dir <path-to-regression-testing-files> --atol <absolute-tolerance>
The source code distributed here is identical to the code found at the official [SWMM website](https://www.epa.gov/water-research/storm-water-management-model-swmm).
The SWMM website also hosts the official manuals and installation binaries for the SWMM software.

A live web version of the SWMM documentation of the API and user manuals can be found on the [SWMM GitHub Pages website](https://usepa.github.io/Stormwater-Management-Model). Note that this is an alpha version that is still under development and has yet to go through EPA'S official QAQC review process.
A live web version of the SWMM documentation of the API and user manuals can be found on the [SWMM GitHub Pages website](https://usepa.github.io/Stormwater-Management-Model). Note that this is an experimental version that is still under development and has yet to go through EPA'S official quality assurance review process.

## Disclaimer
The United States Environmental Protection Agency (EPA) GitHub project code is provided on an "as is" basis and the user assumes responsibility for its use. EPA has relinquished control of the information and no longer has responsibility to protect the integrity, confidentiality, or availability of the information. Any reference to specific commercial products, processes, or services by service mark, trademark, manufacturer, or otherwise, does not constitute or imply their endorsement, recommendation or favoring by EPA. The EPA seal and logo shall not be used in any manner to imply endorsement of any commercial product or activity by EPA or the United States Government.
Expand Down
Empty file added python/MANIFEST.in
Empty file.
3 changes: 3 additions & 0 deletions python/epaswmm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
# Created by: Caleb Buahin (EPA/ORD/CESER/WID)
# Created on: 2024-11-19

# Find Python extensions
find_package(PythonExtensions REQUIRED)

# Add Cython targets for epaswmm
add_cython_target(epaswmm epaswmm.pyx CXX PY3)

Expand Down
23 changes: 23 additions & 0 deletions python/epaswmm/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,24 @@
"""
EPASWMM Python API
This module provides a Python interface to the EPASWMM library.
"""
import os
import platform
import sys

# Adds directory containing swmm libraries to path
if platform.system() == "Windows":
lib_dir = os.path.join(sys.prefix, 'bin')
if hasattr(os, 'add_dll_directory'):
conda_exists = os.path.exists(os.path.join(sys.prefix, 'conda-meta'))
if conda_exists:
os.environ['CONDA_DLL_SEARCH_MODIFICATION_ENABLE'] = "1"
os.add_dll_directory(lib_dir)
else:
os.environ["PATH"] = lib_dir + ";" + os.environ["PATH"]

from .epaswmm import *
from . import solver
from . import output
9 changes: 2 additions & 7 deletions python/epaswmm/epaswmm.pyi
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
# Description: Cython module for encoding and decoding SWMM datetimes
# Created by: Caleb Buahin (EPA/ORD/CESER/WID)
# Created on: 2024-11-19

"""
This type stub file was generated by cyright.
"""

from typing import Any
from datetime import datetime

def encode_swmm_datetime(pdatetime: datetime) -> float:
def encode_swmm_datetime(pdatetime: Any) -> float:
"""
Encode a datetime object into a SWMM datetime.
Expand All @@ -25,7 +20,7 @@ def encode_swmm_datetime(pdatetime: datetime) -> float:
"""
...

def decode_swmm_datetime(swmm_datetime: float) -> datetime:
def decode_swmm_datetime(swmm_datetime: float) -> Any:
"""
Decode a SWMM datetime into a datetime object.
Expand Down
2 changes: 0 additions & 2 deletions python/epaswmm/output/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,4 @@
SystemAttribute,
SWMMOutputException,
Output,
decode_swmm_datetime,
encode_swmm_datetime,
)
68 changes: 40 additions & 28 deletions python/epaswmm/output/output.pyi
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
# Description: Cython module for epaswmm output file processing and data extraction functions for the epaswmm python package.
# Created by: Caleb Buahin (EPA/ORD/CESER/WID)
# Created on: 2024-11-19
"""
This type stub file was generated by cyright.
"""

from enum import IntEnum
from enum import Enum
from typing import Any, Dict, List, Optional, Tuple
from datetime import datetime
from cpython.datetime import datetime

class UnitSystem(IntEnum):
class UnitSystem(Enum):
"""
Enumeration of the unit system used in the output file.
Expand All @@ -22,7 +19,7 @@ class UnitSystem(IntEnum):
SI = ...


class FlowUnits(IntEnum):
class FlowUnits(Enum):
"""
Enumeration of the flow units used in the simulation.
Expand All @@ -47,7 +44,7 @@ class FlowUnits(IntEnum):
MLD = ...


class ConcentrationUnits(IntEnum):
class ConcentrationUnits(Enum):
"""
Enumeration of the concentration units used in the simulation.
Expand All @@ -66,7 +63,7 @@ class ConcentrationUnits(IntEnum):
NONE = ...


class ElementType(IntEnum):
class ElementType(Enum):
"""
Enumeration of the SWMM element types.
Expand All @@ -88,7 +85,7 @@ class ElementType(IntEnum):
POLLUTANT = ...


class TimeAttribute(IntEnum):
class TimeAttribute(Enum):
"""
Enumeration of the report time related attributes.
Expand All @@ -101,7 +98,7 @@ class TimeAttribute(IntEnum):
NUM_PERIODS = ...


class SubcatchAttribute(IntEnum):
class SubcatchAttribute(Enum):
"""
Enumeration of the subcatchment attributes.
Expand Down Expand Up @@ -135,7 +132,7 @@ class SubcatchAttribute(IntEnum):
POLLUTANT_CONCENTRATION = ...


class NodeAttribute(IntEnum):
class NodeAttribute(Enum):
"""
Enumeration of the node attributes.
Expand Down Expand Up @@ -163,7 +160,7 @@ class NodeAttribute(IntEnum):
POLLUTANT_CONCENTRATION = ...


class LinkAttribute(IntEnum):
class LinkAttribute(Enum):
"""
Enumeration of the link attributes.
Expand All @@ -188,7 +185,7 @@ class LinkAttribute(IntEnum):
POLLUTANT_CONCENTRATION = ...


class SystemAttribute(IntEnum):
class SystemAttribute(Enum):
"""
Enumeration of the system attributes.
Expand Down Expand Up @@ -237,6 +234,21 @@ class SystemAttribute(IntEnum):
EVAPORATION_RATE = ...


class SWMMOutputException(Exception):
"""
Exception class for SWMM output file processing errors.
"""
def __init__(self, message: str) -> None:
"""
Constructor to initialize the exception message.
:param message: Error message.
:type message: str
"""
...



class Output:
"""
Class to read and process the output file generated by the SWMM simulation.
Expand Down Expand Up @@ -315,7 +327,7 @@ class Output:
...

@property
def start_date(self) -> datetime:
def start_date(self) -> Any:
"""
Method to get the start date of the simulation in the SWMM output file.
Expand Down Expand Up @@ -356,7 +368,7 @@ class Output:
"""
...

def get_element_names(self, element_type: int) -> List[str]:
def get_element_names(self, element_type: int) -> list:
"""
Method to get the names of all elements of a given type in the SWMM output file.
Expand All @@ -367,7 +379,7 @@ class Output:
"""
...

def get_subcatchment_timeseries(self, element_index: int, attribute: int, start_date_index: int = ..., end_date_index: int = ...) -> Dict[datetime, float]:
def get_subcatchment_timeseries(self, element_index: int, attribute: int, start_date_index: int = ..., end_date_index: int = ...) -> dict:
"""
Method to get the time series data for a subcatchment attribute in the SWMM output file.
Expand All @@ -385,7 +397,7 @@ class Output:
"""
...

def get_node_timeseries(self, element_index: int, attribute: int, start_date_index: int = ..., end_date_index: int = ...) -> Dict[datetime, float]:
def get_node_timeseries(self, element_index: int, attribute: int, start_date_index: int = ..., end_date_index: int = ...) -> dict:
"""
Method to get the time series data for a node attribute in the SWMM output file.
Expand All @@ -402,7 +414,7 @@ class Output:
"""
...

def get_link_timeseries(self, element_index: int, attribute: int, start_date_index: int = ..., end_date_index: int = ...) -> Dict[datetime, float]:
def get_link_timeseries(self, element_index: int, attribute: int, start_date_index: int = ..., end_date_index: int = ...) -> dict:
"""
Method to get the time series data for a link attribute in the SWMM output file.
Expand All @@ -419,7 +431,7 @@ class Output:
"""
...

def get_system_timeseries(self, attribute: int, start_date_index: int = ..., end_date_index: int = ...) -> Dict[datetime, float]:
def get_system_timeseries(self, attribute: int, start_date_index: int = ..., end_date_index: int = ...) -> dict:
"""
Method to get the time series data for a system attribute in the SWMM output file.
Expand All @@ -434,7 +446,7 @@ class Output:
"""
...

def get_subcatchment_values_by_time_and_attribute(self, time_index: int, attribute: int) -> Dict[str, float]:
def get_subcatchment_values_by_time_and_attribute(self, time_index: int, attribute: int) -> dict:
"""
Method to get the subcatchment values for all subcatchments for a given time index and attribute.
Expand All @@ -447,7 +459,7 @@ class Output:
"""
...

def get_node_values_by_time_and_attribute(self, time_index: int, attribute: int) -> Dict[str, float]:
def get_node_values_by_time_and_attribute(self, time_index: int, attribute: int) -> dict:
"""
Method to get the node values for all nodes for a given time index and attribute.
Expand All @@ -460,7 +472,7 @@ class Output:
"""
...

def get_link_values_by_time_and_attribute(self, time_index: int, attribute: int) -> Dict[str, float]:
def get_link_values_by_time_and_attribute(self, time_index: int, attribute: int) -> dict:
"""
Method to get the link values for all links for a given time index and attribute.
Expand All @@ -473,7 +485,7 @@ class Output:
"""
...

def get_system_values_by_time_and_attribute(self, time_index: int, attribute: int) -> Dict[str, float]:
def get_system_values_by_time_and_attribute(self, time_index: int, attribute: int) -> dict:
"""
Method to get the system values for a given time index and attribute.
Expand All @@ -486,7 +498,7 @@ class Output:
"""
...

def get_subcatchment_values_by_time_and_element_index(self, time_index: int, element_index: int) -> Dict[str, float]:
def get_subcatchment_values_by_time_and_element_index(self, time_index: int, element_index: int) -> dict:
"""
Method to get all attributes of a given subcatchment for specified time.
Expand All @@ -499,7 +511,7 @@ class Output:
"""
...

def get_node_values_by_time_and_element_index(self, time_index: int, element_index: int) -> Dict[str, float]:
def get_node_values_by_time_and_element_index(self, time_index: int, element_index: int) -> dict:
"""
Method to get all attributes of a given node for specified time.
Expand All @@ -512,7 +524,7 @@ class Output:
"""
...

def get_link_values_by_time_and_element_index(self, time_index: int, element_index: int) -> Dict[str, float]:
def get_link_values_by_time_and_element_index(self, time_index: int, element_index: int) -> dict:
"""
Method to get all attributes of a given link for specified time.
Expand All @@ -526,7 +538,7 @@ class Output:
"""
...

def get_system_values_by_time(self, time_index: int) -> Dict[str, float]:
def get_system_values_by_time(self, time_index: int) -> dict:
"""
Method to get all attributes of the system for specified time.
Expand Down
Loading

0 comments on commit 11f2a60

Please sign in to comment.