From 5e03873a5359a18cbe2a54fffa93fbca7e56d207 Mon Sep 17 00:00:00 2001 From: Dobson Date: Wed, 28 Feb 2024 13:01:18 +0000 Subject: [PATCH 1/4] Function to run a swmm model --- swmmanywhere/defs/basic_drainage_all_bits.inp | 2 +- swmmanywhere/swmmanywhere.py | 84 +++++++++++++++++++ tests/test_swmmanywhere.py | 27 +++++- 3 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 swmmanywhere/swmmanywhere.py diff --git a/swmmanywhere/defs/basic_drainage_all_bits.inp b/swmmanywhere/defs/basic_drainage_all_bits.inp index 0d8a4f5b..80059277 100644 --- a/swmmanywhere/defs/basic_drainage_all_bits.inp +++ b/swmmanywhere/defs/basic_drainage_all_bits.inp @@ -48,7 +48,7 @@ DRY_ONLY NO [RAINGAGES] ;;Name Format Interval SCF Source ;;-------------- --------- ------ ------ ---------- -1 INTENSITY 00:05 1.0 FILE "january.dat" 1 MM +1 INTENSITY 00:05 1.0 FILE "storm.dat" 1 MM [SUBCATCHMENTS] ;;Name Rain Gage Outlet Area %Imperv Width %Slope CurbLen SnowPack diff --git a/swmmanywhere/swmmanywhere.py b/swmmanywhere/swmmanywhere.py new file mode 100644 index 00000000..825d9f9c --- /dev/null +++ b/swmmanywhere/swmmanywhere.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +"""Created on 2024-01-26. + +@author: Barney +""" +from pathlib import Path + +import pandas as pd +import pyswmm + + +def run(model: Path, + reporting_iters: int = 50, + duration: int = 86400, + storevars: list[str] = ['flooding','flow']): + """Run a SWMM model and store the results. + + Args: + model (Path): The path to the SWMM model .inp file. + reporting_iters (int, optional): The number of iterations between + storing results. Defaults to 50. + duration (int, optional): The duration of the simulation in seconds. + Starts at the 'START_DATE' and 'START_TIME' defined in the 'model' + .inp file Defaults to 86400. + storevars (list[str], optional): The variables to store. Defaults to + ['flooding','flow']. + + Returns: + pd.DataFrame: A DataFrame containing the results. + """ + with pyswmm.Simulation(str(model)) as sim: + sim.start() + + # Define the variables to store + variables = { + 'flooding': {'class': pyswmm.Nodes, 'id': '_nodeid'}, + 'depth': {'class': pyswmm.Nodes, 'id': '_nodeid'}, + 'flow': {'class': pyswmm.Links, 'id': '_linkid'}, + 'runoff': {'class': pyswmm.Subcatchments, 'id': '_subcatchmentid'} + } + + results_list = [] + for var, info in variables.items(): + if var in storevars: + # Rather than calling eg Nodes or Links, only call them if they + # are needed for storevars because they carry a significant + # overhead + pobjs = info['class'](sim) + results_list += [{'object': x, + 'variable': var, + 'id': info['id']} for x in pobjs] + + # Iterate the model + results = [] + t_ = sim.current_time + ind = 0 + while ((sim.current_time - t_).total_seconds() <= duration) & \ + (sim.current_time < sim.end_time) & (not sim._terminate_request): + + ind+=1 + + # Iterate the main model timestep + time = sim._model.swmm_step() + + # Break condition + if time < 0: + sim._terminate_request = True + break + + # Check whether to save results + if ind % reporting_iters != 1: + continue + + # Store results in a list of dictionaries + for storevar in results_list: + results.append({'date' : sim.current_time, + 'value' : getattr(storevar['object'], + storevar['variable']), + 'variable' : storevar['variable'], + 'id' : getattr(storevar['object'], + storevar['id'])}) + + + return pd.DataFrame(results) \ No newline at end of file diff --git a/tests/test_swmmanywhere.py b/tests/test_swmmanywhere.py index a3d619be..77713d51 100644 --- a/tests/test_swmmanywhere.py +++ b/tests/test_swmmanywhere.py @@ -1,7 +1,32 @@ """Tests for the main module.""" -from swmmanywhere import __version__ +from pathlib import Path + +from swmmanywhere import __version__, swmmanywhere def test_version(): """Check that the version is acceptable.""" assert __version__ == "0.1.0" + + +def test_run(): + """Test the run function.""" + model = Path(__file__).parent.parent / 'swmmanywhere' / 'defs' /\ + 'basic_drainage_all_bits.inp' + storevars = ['flooding','flow','runoff','depth'] + results = swmmanywhere.run(model, + reporting_iters = 50, + storevars = storevars) + assert set(results.variable.unique()) == set(storevars) + + # Ensure more reporting iterations results in more results + results_ = swmmanywhere.run(model, + reporting_iters = 25, + storevars = storevars) + assert results_.shape[0] > results.shape[0] + + # Ensure a shorter duration results in fewer results + results_ = swmmanywhere.run(model, + duration = 10000, + storevars = storevars) + assert results_.shape[0] < results.shape[0] \ No newline at end of file From 4d89eaa1985bcccfd7f838d51892f46c5a1f33b7 Mon Sep 17 00:00:00 2001 From: Dobson Date: Wed, 28 Feb 2024 13:03:20 +0000 Subject: [PATCH 2/4] Update test_swmmanywhere.py remove the sim files after running test --- tests/test_swmmanywhere.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/test_swmmanywhere.py b/tests/test_swmmanywhere.py index 77713d51..b854c230 100644 --- a/tests/test_swmmanywhere.py +++ b/tests/test_swmmanywhere.py @@ -11,8 +11,8 @@ def test_version(): def test_run(): """Test the run function.""" - model = Path(__file__).parent.parent / 'swmmanywhere' / 'defs' /\ - 'basic_drainage_all_bits.inp' + demo_dir = Path(__file__).parent.parent / 'swmmanywhere' / 'defs' + model = demo_dir / 'basic_drainage_all_bits.inp' storevars = ['flooding','flow','runoff','depth'] results = swmmanywhere.run(model, reporting_iters = 50, @@ -29,4 +29,7 @@ def test_run(): results_ = swmmanywhere.run(model, duration = 10000, storevars = storevars) - assert results_.shape[0] < results.shape[0] \ No newline at end of file + assert results_.shape[0] < results.shape[0] + + model.with_suffix('.out').unlink() + model.with_suffix('.rpt').unlink() \ No newline at end of file From 574f4c70f6edf42be674e9fbedc001cb8dbad237 Mon Sep 17 00:00:00 2001 From: barneydobson Date: Mon, 4 Mar 2024 13:06:37 +0000 Subject: [PATCH 3/4] Update swmmanywhere/swmmanywhere.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Diego Alonso Álvarez <6095790+dalonsoa@users.noreply.github.com> --- swmmanywhere/swmmanywhere.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/swmmanywhere/swmmanywhere.py b/swmmanywhere/swmmanywhere.py index 825d9f9c..db1f4bba 100644 --- a/swmmanywhere/swmmanywhere.py +++ b/swmmanywhere/swmmanywhere.py @@ -41,7 +41,8 @@ def run(model: Path, results_list = [] for var, info in variables.items(): - if var in storevars: + if var not in storevars: + continue # Rather than calling eg Nodes or Links, only call them if they # are needed for storevars because they carry a significant # overhead From 8344318b6484bd9fa7d56adc672039b87dcc4442 Mon Sep 17 00:00:00 2001 From: Dobson Date: Mon, 4 Mar 2024 13:11:16 +0000 Subject: [PATCH 4/4] Update swmmanywhere.py --- swmmanywhere/swmmanywhere.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/swmmanywhere/swmmanywhere.py b/swmmanywhere/swmmanywhere.py index db1f4bba..a8afb1db 100644 --- a/swmmanywhere/swmmanywhere.py +++ b/swmmanywhere/swmmanywhere.py @@ -43,13 +43,13 @@ def run(model: Path, for var, info in variables.items(): if var not in storevars: continue - # Rather than calling eg Nodes or Links, only call them if they - # are needed for storevars because they carry a significant - # overhead - pobjs = info['class'](sim) - results_list += [{'object': x, - 'variable': var, - 'id': info['id']} for x in pobjs] + # Rather than calling eg Nodes or Links, only call them if they + # are needed for storevars because they carry a significant + # overhead + pobjs = info['class'](sim) + results_list += [{'object': x, + 'variable': var, + 'id': info['id']} for x in pobjs] # Iterate the model results = []