diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 2160f1e..212a8d9 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -44,7 +44,7 @@ jobs: path: './docs/build' deploy: - if: startsWith(github.event.ref, 'refs/tags/v') + # if: startsWith(github.event.ref, 'refs/tags/v') environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} diff --git a/.gitignore b/.gitignore index 2e38097..08a3a0a 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,8 @@ _build/ *.thm # env -venv/ \ No newline at end of file +venv/ + +# docs artifacts +docs/_build/ +docs/site/ \ No newline at end of file diff --git a/docs/img/default_draw.png b/docs/_static/img/default_draw.png similarity index 100% rename from docs/img/default_draw.png rename to docs/_static/img/default_draw.png diff --git a/docs/img/flooded_anno_example.png b/docs/_static/img/flooded_anno_example.png similarity index 100% rename from docs/img/flooded_anno_example.png rename to docs/_static/img/flooded_anno_example.png diff --git a/docs/img/impact_of_option.png b/docs/_static/img/impact_of_option.png similarity index 100% rename from docs/img/impact_of_option.png rename to docs/_static/img/impact_of_option.png diff --git a/docs/conf.py b/docs/conf.py index e98e6bd..778b388 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,8 +12,9 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # -from recommonmark.transform import AutoStructify -from m2r import MdInclude +# from recommonmark.transform import AutoStructify +# from m2r import MdInclude +from datetime import datetime import os import sys import swmmio @@ -23,11 +24,11 @@ # -- Project information ----------------------------------------------------- project = 'swmmio' -copyright = '2022, Adam Erispaha' +copyright = f'{datetime.now().year}, Adam Erispaha' author = 'Adam Erispaha' # The short X.Y version -version = '' +version = swmmio.__version__ # The full version, including alpha/beta/rc tags release = swmmio.__version__ @@ -46,7 +47,8 @@ 'sphinx.ext.coverage', 'sphinx.ext.mathjax', 'sphinx.ext.viewcode', - 'recommonmark', + 'sphinx.ext.autosummary', + 'myst_parser', ] # Add any paths that contain templates here, relative to this directory. @@ -55,8 +57,10 @@ # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # -source_suffix = ['.rst', '.md'] -# source_suffix = '.rst' +source_suffix = { + '.rst': 'restructuredtext', + '.md': 'markdown', +} # The master toctree document. master_doc = 'index' @@ -66,12 +70,12 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' @@ -83,7 +87,7 @@ # a list of builtin themes. # # html_theme = 'alabaster' -html_theme = "sphinx_rtd_theme" +html_theme = "pydata_sphinx_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -94,7 +98,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['.'] +html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. @@ -181,27 +185,3 @@ # A list of files that should not be packed into the epub file. epub_exclude_files = ['search.html'] - -# -- Extension configuration ------------------------------------------------- -def setup(app): - config = { - # 'url_resolver': lambda url: github_doc_root + url, - 'auto_toc_tree_section': 'Contents', - 'enable_eval_rst': True, - } - app.add_config_value('recommonmark_config', config, True) - app.add_transform(AutoStructify) - - # from m2r to make `mdinclude` work - app.add_config_value('no_underscore_emphasis', False, 'env') - app.add_config_value('m2r_parse_relative_links', False, 'env') - app.add_config_value('m2r_anonymous_references', False, 'env') - app.add_config_value('m2r_disable_inline_math', False, 'env') - app.add_directive('mdinclude', MdInclude) - - -# custom logic -from shutil import copytree -import os -os.makedirs('_build/html/docs', exist_ok=True) -copytree('img', '_build/html/docs/img') diff --git a/docs/index.rst b/docs/index.rst index 5518444..c5f07f8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,4 +1,20 @@ -.. mdinclude:: ../README.md +swmmio +======================== + +.. image:: _static/img/impact_of_option.png + :alt: Impact of Option + + +`swmmio` provides a Pythonic interface to the EPA Stormwater Management Model (SWMM) +allowing engineers to programmatically create, modify, and +analyze SWMM models. + +With `swmmio`, model parameters encoded in the `.inp` file can be +retrieved conveniently as Pandas dataframes making it possible to make +automate your hydraulics and hydologic modeling workflow. After simulations are +completed, results in the `.rpt` file can also be retrieved as Pandas dataframes, +making post-processing automation trivial. + .. swmmio documentation master file, created by @@ -6,15 +22,17 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. + +Contents +================== + .. toctree:: :maxdepth: 2 - :caption: Contents: - swmmio - elements - version_control - utils - graphics + reference/index + installing + usage + Indices and tables ================== diff --git a/docs/installing.md b/docs/installing.md new file mode 100644 index 0000000..e5202dc --- /dev/null +++ b/docs/installing.md @@ -0,0 +1,8 @@ +### Installation +Before installation, it's recommended to first activate a [virtualenv](https://github.com/pypa/virtualenv) to +not crowd your system's package library. If you don't use any of the dependencies listed above, +this step is less important. `swmmio` can be installed via pip in your command line: + +```console +pip install swmmio +``` diff --git a/docs/swmmio.rst b/docs/reference/core.rst similarity index 83% rename from docs/swmmio.rst rename to docs/reference/core.rst index a257436..37ef94e 100644 --- a/docs/swmmio.rst +++ b/docs/reference/core.rst @@ -1,4 +1,4 @@ -`swmmio` Core Objects +`swmmio` core objects ======================== .. automodule:: swmmio.core diff --git a/docs/elements.rst b/docs/reference/elements.rst similarity index 100% rename from docs/elements.rst rename to docs/reference/elements.rst diff --git a/docs/graphics.rst b/docs/reference/graphics.rst similarity index 100% rename from docs/graphics.rst rename to docs/reference/graphics.rst diff --git a/docs/reference/index.rst b/docs/reference/index.rst new file mode 100644 index 0000000..eeb7c94 --- /dev/null +++ b/docs/reference/index.rst @@ -0,0 +1,20 @@ +.. -*- coding: utf-8 -*- + +Reference +********* +.. only:: html + + :Release: |release| + :Date: |today| + + +.. toctree:: + :maxdepth: 2 + + core + elements + utils + graphics + version_control + +.. only:: html \ No newline at end of file diff --git a/docs/utils.rst b/docs/reference/utils.rst similarity index 100% rename from docs/utils.rst rename to docs/reference/utils.rst diff --git a/docs/version_control.rst b/docs/reference/version_control.rst similarity index 100% rename from docs/version_control.rst rename to docs/reference/version_control.rst diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 0000000..78eecb7 --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,153 @@ +# Usage +The `swmmio.Model()` class provides the basic endpoint for interfacing with SWMM models. To get started, save a SWMM5 +model (.inp) in a directory with its report file (.rpt). A few examples: + +```python +import swmmio + +# instantiate a swmmio model object +mymodel = swmmio.Model('/path/to/directory with swmm files') + +# dataframe with useful data related to model nodes, conduits, and subcatchments +nodes = mymodel.nodes.dataframe +links = mymodel.links.dataframe +subs = mymodel.subcatchments.dataframe + +# enjoy all the Pandas functions +nodes.head() + +# write to a csv +nodes.to_csv('/path/mynodes.csv') + +# calculate average and weighted average impervious +avg_imperviousness = subs.PercImperv.mean() +weighted_avg_imp = (subs.Area * subs.PercImperv).sum() / len(subs) +``` + +# Nodes and Links Objects +Specific sections of data from the inp and rpt can be extracted with `Nodes` and `Links` objects. +Although these are the same object-type of the `swmmio.Model.nodes` and `swmmio.Model.links`, +accessing them directly allows for custom control over what sections of data are retrieved. + +```python +from swmmio import Model, Nodes +m = Model("coolest-model.inp") + +# pass custom init arguments into the Nodes object instead of using default settings referenced by m.nodes() +nodes = Nodes( + model=m, + inp_sections=['junctions', 'storage', 'outfalls'], + rpt_sections=['Node Depth Summary', 'Node Inflow Summary'], + columns=[ 'InvertElev', 'MaxDepth', 'InitDepth', 'SurchargeDepth', 'MaxTotalInflow', 'coords'] +) + +# access data +nodes.dataframe +``` + +# Generating Graphics +Create an image (.png) visualization of the model. By default, pipe stress and node flood duration is +visualized if your model includes output data (a .rpt file should accompany the .inp). + +```python +swmmio.draw_model(mymodel) +``` + +![Default Draw Output](_static/img/default_draw.png "Sewer Stress, Node Flooding") + +Use pandas to calculate some interesting stats, and generate a image to highlight +what's interesting or important for your project: + +```python +#isolate nodes that have flooded for more than 30 minutes +flooded_series = nodes.loc[nodes.HoursFlooded>0.5, 'TotalFloodVol'] +flood_vol = sum(flooded_series) #total flood volume (million gallons) +flooded_count = len(flooded_series) #count of flooded nodes + +#highlight these nodes in a graphic +nodes['draw_color'] = '#787882' #grey, default node color +nodes.loc[nodes.HoursFlooded>0.5, 'draw_color'] = '#751167' #purple, flooded nodes + +#set the radius of flooded nodes as a function of HoursFlooded +nodes.loc[nodes.HoursFlooded>1, 'draw_size'] = nodes.loc[nodes.HoursFlooded>1, 'HoursFlooded'] * 12 + +#make the conduits grey, sized as function of their geometry +conds['draw_color'] = '#787882' +conds['draw_size'] = conds.Geom1 + +#add an informative annotation, and draw: +annotation = 'Flooded Volume: {}MG\nFlooded Nodes:{}'.format(round(flood_vol), flooded_count) +swmmio.draw_model(mymodel, annotation=annotation, file_path='flooded_anno_example.png') +``` +![Flooded highlight](_static/img/flooded_anno_example.png "Node Flooding with annotation") + +# Building Variations of Models +Starting with a base SWMM model, other models can be created by inserting altered data into a new inp file. Useful for sensitivity analysis or varying boundary conditions, models can be created using a fairly simple loop, leveraging the `modify_model` package. + +For example, climate change impacts can be investigated by creating a set of models with varying outfall Fixed Stage elevations: + +```python +import os +import swmmio + +# initialize a baseline model object +baseline = swmmio.Model(r'path\to\baseline.inp') +rise = 0.0 #set the starting sea level rise condition + +# create models up to 5ft of sea level rise. +while rise <= 5: + + # create a dataframe of the model's outfalls + outfalls = baseline.inp.outfalls + + # create the Pandas logic to access the StageOrTimeseries column of FIXED outfalls + slice_condition = outfalls.OutfallType == 'FIXED', 'StageOrTimeseries' + + # add the current rise to the outfalls' stage elevation + outfalls.loc[slice_condition] = pd.to_numeric(outfalls.loc[slice_condition]) + rise + baseline.inp.outfalls = outfalls + + # copy the base model into a new directory + newdir = os.path.join(baseline.inp.dir, str(rise)) + os.mkdir(newdir) + newfilepath = os.path.join(newdir, baseline.inp.name + "_" + str(rise) + '_SLR.inp') + + # Overwrite the OUTFALLS section of the new model with the adjusted data + baseline.inp.save(newfilepath) + + # increase sea level rise for the next loop + rise += 0.25 + +``` + +# Access Model Network +The `swmmio.Model` class returns a Networkx MultiDiGraph representation of the model via that `network` parameter: +```python + +# access the model as a Networkx MutliDiGraph +G = model.network + +# iterate through links +for u, v, key, data in model.network.edges(data=True, keys=True): + + print (key, data['Geom1']) + # do stuff with the network +``` + +# Running Models +Using the command line tool, individual SWMM5 models can be run by invoking the swmmio module in your shell as such: +```shell +python -m swmmio --run path/to/mymodel.inp +``` +If you have many models to run and would like to take advantage of your machine's cores, you can start a pool of simulations with the `--start_pool` (or `-sp`) command. After pointing `-sp` to one or more directories, swmmio will search for SWMM .inp files and add all them to a multiprocessing pool. By default, `-sp` leaves 4 of your machine's cores unused. This can be changed via the `-cores_left` argument. +```shell +# run all models in models in directories Model_Dir1 Model_Dir2 +python -m swmmio -sp Model_Dir1 Model_Dir2 + +# leave 1 core unused +python -m swmmio -sp Model_Dir1 Model_Dir2 -cores_left=1 +``` +
+

Warning

+

Using all cores for simultaneous model runs can put your machine's CPU usage at 100% for extended periods of time. This probably puts stress on your hardware. Use at your own risk.

+