From 4b7be75f447b9c321f9a97711ef4c8f04bca5769 Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 5 Mar 2024 11:38:45 -0800 Subject: [PATCH 01/11] added patterns and controls to Model.inp --- swmmio/core.py | 75 +++++++++++++++++++++++++++++++++ swmmio/defs/inp_sections.yml | 4 +- swmmio/version_control/utils.py | 5 +++ 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/swmmio/core.py b/swmmio/core.py index f0f8aa0..8c0286e 100644 --- a/swmmio/core.py +++ b/swmmio/core.py @@ -600,6 +600,8 @@ def __init__(self, file_path): self._streets_df = None self._inlets_df = None self._inlet_usage_df = None + self._patterns_df = None + self._controls_df = None SWMMIOFile.__init__(self, file_path) # run the superclass init @@ -644,6 +646,8 @@ def __init__(self, file_path): '[STREETS]', '[INLETS]', '[INLET_USAGE]', + '[PATTERNS]', + '[CONTROLS]', ] def save(self, target_path=None): @@ -1508,6 +1512,77 @@ def timeseries(self, df): """Set inp.timeseries DataFrame.""" self._timeseries_df = df + @property + def patterns(self): + """ + get/set patterns section of model + :return: multi-index dataframe of model patterns + """ + if self._patterns_df is not None: + return self._patterns_df + self._patterns_df = dataframe_from_inp(self.path, '[PATTERNS]') + + if self._patterns_df.shape[0] > 0: + # reformat, 1 row per pattern + pattern_entry_list = [] + for name, pattern in self._patterns_df.groupby('Name'): + pattern_entry = {} + pattern_entry['Name'] = name + pattern_entry['Type'] = pattern['Type'].iloc[0] + if pattern.shape[0] > 1: + # shift pattern values to the right + pattern.iloc[1::, 1::] = pattern.iloc[1::, 0:-1].values + pattern['Factors'] = pattern['Factors'].astype(float) + values = pattern.iloc[:, 1:].values.flatten() + for i in range(len(values)): + pattern_entry['Factor'+str(i+1)] = values[i] + pattern_entry_list.append(pattern_entry) + + self._patterns_df = pd.DataFrame(pattern_entry_list) + self._patterns_df.set_index('Name', inplace=True) + + return self._patterns_df + + @patterns.setter + def patterns(self, df): + """Set inp.patterns DataFrame.""" + self._patterns_df = df + + @property + def controls(self): + """ + Get/set controls section of the INP file. + """ + if self._controls_df is None: + self._controls_df = dataframe_from_inp(self.path, "[CONTROLS]") + + if self._controls_df.shape[0] > 0: + # reformat, 1 row per control + control_entry_list = [] + control_entry = {} + controls = self._controls_df['[CONTROLS]'] + for row in controls: + if 'RULE ' in row: # new control + if len(control_entry) > 0: + control_entry_list.append(control_entry) + control_entry = {} + control_entry['Name'] = row.rstrip() # remove white space + control_entry['Control'] = '' + else: + control_entry['Control'] = control_entry['Control'] + row + ' ' + if len(control_entry) > 0: + control_entry_list.append(control_entry) + + self._controls_df = pd.DataFrame(control_entry_list) + self._controls_df.set_index('Name', inplace=True) + + return self._controls_df + + @controls.setter + def controls(self, df): + """Set inp.controls DataFrame.""" + self._controls_df = df + @property def tags(self): """ diff --git a/swmmio/defs/inp_sections.yml b/swmmio/defs/inp_sections.yml index ab898ed..715c0a7 100644 --- a/swmmio/defs/inp_sections.yml +++ b/swmmio/defs/inp_sections.yml @@ -78,8 +78,8 @@ inp_file_objects: STREETS: [Name, Tcrown, Hcurb, Sx, nRoad, a, W, Sides, Tback, Sback, nBack] INLETS: [Name, Type, Param1, Param2, Param3, Param4, Param5] INLET_USAGE: [Link, Inlet, Node, Number, "%Clogged", Qmax, aLocal, wLocal, Placement] - - + PATTERNS: [Name, Type, Factors] + CONTROLS: [blob] inp_section_tags: ['[TITLE', '[OPTION', '[FILE', '[RAINGAGES', '[TEMPERATURE', '[EVAP', diff --git a/swmmio/version_control/utils.py b/swmmio/version_control/utils.py index 6cd2c1a..119950d 100644 --- a/swmmio/version_control/utils.py +++ b/swmmio/version_control/utils.py @@ -81,6 +81,11 @@ def write_inp_section(file_object, allheaders, sectionheader, section_data, pad_ formatters=objectformatter # {'Comment':formatter} ) + # Deliminate control string using keywords + if sectionheader == '[CONTROLS]': + for sep in [' IF ', ' THEN ', ' PRIORITY ', ' AND ', ' OR ', ' ELSE ']: + add_str = add_str.replace(sep, '\n'+sep) + # write the dataframe as a string f.write(add_str + '\n\n') From 9c91e659c22f7fc7f411259be7b354b7210230c8 Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 5 Mar 2024 11:39:38 -0800 Subject: [PATCH 02/11] Replace quotes for inflows --- swmmio/core.py | 2 +- swmmio/tests/test_dataframes.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/swmmio/core.py b/swmmio/core.py index 8c0286e..4b04510 100644 --- a/swmmio/core.py +++ b/swmmio/core.py @@ -1456,7 +1456,7 @@ def inflows(self): if self._inflows_df is not None: return self._inflows_df inf = dataframe_from_inp(self.path, 'INFLOWS', quote_replace='_!!!!_') - self._inflows_df = inf.replace('_!!!!_', np.nan) + self._inflows_df = inf.replace('_!!!!_', '""') # revert quote replace return self._inflows_df @inflows.setter diff --git a/swmmio/tests/test_dataframes.py b/swmmio/tests/test_dataframes.py index 05b010c..04ff687 100644 --- a/swmmio/tests/test_dataframes.py +++ b/swmmio/tests/test_dataframes.py @@ -173,7 +173,7 @@ def test_inflow_dwf_dataframe(): inf = m.inp.inflows assert (inf.loc['dummy_node2', 'Time Series'] == 'my_time_series') - assert (pd.isna(inf.loc['dummy_node6', 'Time Series'])) + assert inf.loc['dummy_node6', 'Time Series'] == '""' def test_coordinates(): From 64207c52c8443a3566eafcdfeee1be7499f92b31 Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 5 Mar 2024 11:41:45 -0800 Subject: [PATCH 03/11] Find max number of tokens, resolves issue with xsection --- swmmio/utils/dataframes.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/swmmio/utils/dataframes.py b/swmmio/utils/dataframes.py index d7e1b29..53a4c31 100644 --- a/swmmio/utils/dataframes.py +++ b/swmmio/utils/dataframes.py @@ -142,16 +142,22 @@ def dataframe_from_inp(inp_path, section, additional_cols=None, quote_replace=' # replace occurrences of double quotes "" s = s.replace('""', quote_replace) - # count tokens in first non-empty line, after the header, ignoring comments + # find max tokens after the header, ignoring comments # if zero tokens counted (i.e. empty line), fall back to headers dict - n_tokens = len(re.sub(r"(\n)\1+", r"\1", s).split('\n')[1].split(';')[0].split()) + n_tokens = 0 + lines = re.sub(r"(\n)\1+", r"\1", s).split('\n') + for line in lines[1::]: + n_tokens = max([n_tokens, len(line.split(';')[0].split())]) n_tokens = len(headers[sect]['columns']) if n_tokens == 0 else n_tokens # and get the list of columns to use for parsing this section # add any additional columns needed for special cases (build instructions) additional_cols = [] if additional_cols is None else additional_cols - cols = headers[sect]['columns'][:n_tokens] + additional_cols - + cols = headers[sect]['columns'][:n_tokens] + if n_tokens > len(cols): + cols = cols + ["col"+str(len(cols)+i) for i in range(n_tokens-len(cols))] + cols = cols + additional_cols + if headers[sect]['columns'][0] == 'blob': # return the whole row, without specific col headers return pd.read_csv(StringIO(s), delim_whitespace=False) From 0049488532dd3cef9a0b78104a9fcea049191669 Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 5 Mar 2024 12:06:00 -0800 Subject: [PATCH 04/11] changed df index for curves --- swmmio/utils/dataframes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swmmio/utils/dataframes.py b/swmmio/utils/dataframes.py index 53a4c31..5abd91f 100644 --- a/swmmio/utils/dataframes.py +++ b/swmmio/utils/dataframes.py @@ -56,7 +56,7 @@ def create_dataframe_multi_index(inp_path, section='CURVES'): df = pd.DataFrame(data=data, columns=cols) if sect == 'CURVES': - df = df.set_index(['Name', 'Type']) + df = df.set_index(['Name']) #, 'Type']) # changed index to Name to correct INP write elif sect == 'TIMESERIES': df = df.set_index(['Name']) From 020ca0629d988e55a684fcc48970556fc5f58be1 Mon Sep 17 00:00:00 2001 From: kaklise Date: Tue, 12 Mar 2024 14:14:50 -0700 Subject: [PATCH 05/11] check to make sure rule name starts with RULE --- swmmio/core.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/swmmio/core.py b/swmmio/core.py index 4b04510..bc6bc5d 100644 --- a/swmmio/core.py +++ b/swmmio/core.py @@ -1561,16 +1561,18 @@ def controls(self): control_entry_list = [] control_entry = {} controls = self._controls_df['[CONTROLS]'] + # make sure the first entry starts with RULE + assert controls[0][0:5] == "RULE " for row in controls: - if 'RULE ' in row: # new control - if len(control_entry) > 0: + if row[0:5] == 'RULE ': # new control + if len(control_entry) > 0: # add control to the list control_entry_list.append(control_entry) control_entry = {} control_entry['Name'] = row.rstrip() # remove white space control_entry['Control'] = '' else: control_entry['Control'] = control_entry['Control'] + row + ' ' - if len(control_entry) > 0: + if len(control_entry) > 0: # add last control to the list control_entry_list.append(control_entry) self._controls_df = pd.DataFrame(control_entry_list) From 52415556bcf2617e7188fb090cba0f3b0ca63882 Mon Sep 17 00:00:00 2001 From: kaklise Date: Wed, 10 Apr 2024 12:29:17 -0700 Subject: [PATCH 06/11] Added Pumping Summary to rpt sections and 'Analysis begun on' to read --- swmmio/defs/section_headers.yml | 10 ++++++++++ swmmio/utils/dataframes.py | 1 + 2 files changed, 11 insertions(+) diff --git a/swmmio/defs/section_headers.yml b/swmmio/defs/section_headers.yml index f52adb0..f93658a 100644 --- a/swmmio/defs/section_headers.yml +++ b/swmmio/defs/section_headers.yml @@ -132,6 +132,16 @@ rpt_sections: - SlopePerc - RainGage - Outlet + Pumping Summary: + - PercentUtilized + - NumberOfStartUps + - MinFlowCFS + - AvgFlowCFS + - MaxFlowCFS + - TotalVolume(MG) + - PowerUsage(kW-hr) + - PercentTimeOffPumpCurveLow + - PercentTimeOffPumpCurveHigh swmm5_version: 1.13: diff --git a/swmmio/utils/dataframes.py b/swmmio/utils/dataframes.py index 5abd91f..fa32b09 100644 --- a/swmmio/utils/dataframes.py +++ b/swmmio/utils/dataframes.py @@ -92,6 +92,7 @@ def dataframe_from_rpt(rpt_path, section, element_id=None): # and get the list of columns to use for parsing this section end_strings = list(headers.keys()) end_strings.append('***********') + end_strings.append('Analysis begun on') start_strings = [section, '-'*20, '-'*20] cols = headers[section]['columns'] From 080a9cb44902af72776323a5f7f0e268b0c15dcb Mon Sep 17 00:00:00 2001 From: kaklise Date: Wed, 10 Apr 2024 17:10:10 -0700 Subject: [PATCH 07/11] Added minimal test for INP file read/write/run --- swmmio/tests/test_dataframes.py | 59 +++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/swmmio/tests/test_dataframes.py b/swmmio/tests/test_dataframes.py index 04ff687..231d1f3 100644 --- a/swmmio/tests/test_dataframes.py +++ b/swmmio/tests/test_dataframes.py @@ -1,4 +1,6 @@ from io import StringIO +from pandas.testing import assert_series_equal +import subprocess from swmmio.elements import Links from swmmio.tests.data import (MODEL_FULL_FEATURES_PATH, MODEL_FULL_FEATURES__NET_PATH, @@ -7,6 +9,7 @@ MODEL_CURVE_NUMBER, MODEL_MOD_HORTON, MODEL_GREEN_AMPT, MODEL_MOD_GREEN_AMPT, MODEL_INFILTRAION_PARSE_FAILURE, OWA_RPT_EXAMPLE) from swmmio.utils.dataframes import (dataframe_from_rpt, dataframe_from_inp, dataframe_from_bi) +from swmmio.utils.text import get_inp_sections_details import swmmio import pandas as pd @@ -258,3 +261,59 @@ def test_polygons(test_model_02): assert poly1.equals(test_model_02.inp.polygons) # print() + +def test_inp_sections(): + + inpfiles = [ + MODEL_FULL_FEATURES_PATH, + MODEL_CURVE_NUMBER, + MODEL_MOD_HORTON, + ] + + for inpfile in inpfiles: + print(inpfile) + + # Run SWMM with the original INP file + headers = get_inp_sections_details(inpfile, include_brackets=False) + p = subprocess.run("python -m swmmio --run " + inpfile) + model = swmmio.Model(inpfile, include_rpt=True) + links = model.links() + + # Check that the simulation results loaded into the swmmio model + assert 'MaxQ' in links.columns + + # Create a swmmio model, reassign each section to force the use + # of "replace_inp_section", save the INP file, and then run SWMM + temp_inpfile = 'temp.inp' + model = swmmio.Model(inpfile, include_rpt=False) + empty_sections = [] + unsupported_sections = [] + for sec in headers.keys(): + try: + df = getattr(model.inp, sec.lower()) + if df.empty: + empty_sections.append(sec) + setattr(model.inp, sec, df.copy()) + except: + unsupported_sections.append(sec) + model.inp.save(temp_inpfile) + headers2 = get_inp_sections_details(temp_inpfile, + include_brackets=False) + p = subprocess.run("python -m swmmio --run " + temp_inpfile) + model2 = swmmio.Model(temp_inpfile, include_rpt=True) + links2 = model2.links() + + # Check that the INP file headers are the same + header_set = set(headers.keys() - empty_sections) + header2_set = set(headers2.keys() - empty_sections) + assert header_set == header2_set + # Check that the simulation results loaded into the swmmio model + assert 'MaxQ' in links2.columns + # Check that results are the same + assert_series_equal(links['MaxQ'], links2['MaxQ']) + # Print empty and unsupported INP file sections + print('Empty sections', empty_sections) + print('Unsupported sections', unsupported_sections) + +if __name__ == "__main__": + test_inp_sections() \ No newline at end of file From 5df37ba4b17da1416d4c4d150c131d5c8fd5a512 Mon Sep 17 00:00:00 2001 From: kaklise Date: Thu, 11 Apr 2024 07:48:24 -0700 Subject: [PATCH 08/11] changed subprocess call to run_simple --- swmmio/tests/test_dataframes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/swmmio/tests/test_dataframes.py b/swmmio/tests/test_dataframes.py index 231d1f3..b8cb83c 100644 --- a/swmmio/tests/test_dataframes.py +++ b/swmmio/tests/test_dataframes.py @@ -1,6 +1,5 @@ from io import StringIO from pandas.testing import assert_series_equal -import subprocess from swmmio.elements import Links from swmmio.tests.data import (MODEL_FULL_FEATURES_PATH, MODEL_FULL_FEATURES__NET_PATH, @@ -10,6 +9,7 @@ MODEL_INFILTRAION_PARSE_FAILURE, OWA_RPT_EXAMPLE) from swmmio.utils.dataframes import (dataframe_from_rpt, dataframe_from_inp, dataframe_from_bi) from swmmio.utils.text import get_inp_sections_details +from swmmio.run_models.run import run_simple import swmmio import pandas as pd @@ -275,7 +275,7 @@ def test_inp_sections(): # Run SWMM with the original INP file headers = get_inp_sections_details(inpfile, include_brackets=False) - p = subprocess.run("python -m swmmio --run " + inpfile) + run_simple(inpfile) model = swmmio.Model(inpfile, include_rpt=True) links = model.links() @@ -299,7 +299,7 @@ def test_inp_sections(): model.inp.save(temp_inpfile) headers2 = get_inp_sections_details(temp_inpfile, include_brackets=False) - p = subprocess.run("python -m swmmio --run " + temp_inpfile) + run_simple(temp_inpfile) model2 = swmmio.Model(temp_inpfile, include_rpt=True) links2 = model2.links() From 3398eca975947da026d58241a7bc97c0a23b0625 Mon Sep 17 00:00:00 2001 From: kaklise Date: Mon, 15 Apr 2024 11:19:33 -0700 Subject: [PATCH 09/11] Added pump control model and api docs for controls and patterns --- swmmio/core.py | 24 +- swmmio/examples.py | 4 +- swmmio/tests/data/Pump_Control_Model.inp | 306 +++++++++++++++++++++++ swmmio/tests/data/__init__.py | 3 + 4 files changed, 333 insertions(+), 4 deletions(-) create mode 100644 swmmio/tests/data/Pump_Control_Model.inp diff --git a/swmmio/core.py b/swmmio/core.py index bc6bc5d..695b2a4 100644 --- a/swmmio/core.py +++ b/swmmio/core.py @@ -1515,9 +1515,17 @@ def timeseries(self, df): @property def patterns(self): """ - get/set patterns section of model - :return: multi-index dataframe of model patterns + Get/set patterns section of the model + + :return: dataframe of patterns + + >>> from swmmio.examples import pump_control + >>> pump_control.inp.patterns #doctest: +NORMALIZE_WHITESPACE + Type Factor1 ... Factor23 Factor24 + Name ... + DWF HOURLY 0.0151 ... 0.02499 0.02718 """ + if self._patterns_df is not None: return self._patterns_df self._patterns_df = dataframe_from_inp(self.path, '[PATTERNS]') @@ -1551,8 +1559,18 @@ def patterns(self, df): @property def controls(self): """ - Get/set controls section of the INP file. + Get/set controls section of the model + + :return: dataframe of controls + + >>> from swmmio.examples import pump_control + >>> pump_control.inp.controls #doctest: +NORMALIZE_WHITESPACE + Control + Name + RULE PUMP1A IF NODE SU1 DEPTH >= 4 THEN PUMP PUMP1 status = ON PRIORITY 1 + RULE PUMP1B IF NODE SU1 DEPTH < 1 THEN PUMP PUMP1 status = OFF PRIORITY 1 """ + if self._controls_df is None: self._controls_df = dataframe_from_inp(self.path, "[CONTROLS]") diff --git a/swmmio/examples.py b/swmmio/examples.py index 9a4b5da..53c1445 100644 --- a/swmmio/examples.py +++ b/swmmio/examples.py @@ -1,7 +1,8 @@ from swmmio import Model from swmmio.tests.data import (MODEL_A_PATH, MODEL_EX_1, MODEL_FULL_FEATURES_XY, MODEL_FULL_FEATURES__NET_PATH, MODEL_FULL_FEATURES_XY_B, - MODEL_GREEN_AMPT, MODEL_TEST_INLET_DRAINS, MODEL_GROUNDWATER) + MODEL_GREEN_AMPT, MODEL_TEST_INLET_DRAINS, MODEL_GROUNDWATER, + MODEL_PUMP_CONTROL) # example models philly = Model(MODEL_A_PATH, crs="+init=EPSG:2817") @@ -12,3 +13,4 @@ green = Model(MODEL_GREEN_AMPT) streets = Model(MODEL_TEST_INLET_DRAINS) groundwater = Model(MODEL_GROUNDWATER) +pump_control = Model(MODEL_PUMP_CONTROL) diff --git a/swmmio/tests/data/Pump_Control_Model.inp b/swmmio/tests/data/Pump_Control_Model.inp new file mode 100644 index 0000000..5ae69dd --- /dev/null +++ b/swmmio/tests/data/Pump_Control_Model.inp @@ -0,0 +1,306 @@ +[TITLE] +;;Project Title/Notes +A pump control model. +From SWMM 5.2 examples + +[OPTIONS] +;;Option Value +FLOW_UNITS CFS +INFILTRATION HORTON +FLOW_ROUTING DYNWAVE +LINK_OFFSETS DEPTH +MIN_SLOPE 0 +ALLOW_PONDING NO +SKIP_STEADY_STATE NO + +START_DATE 01/01/2001 +START_TIME 00:00:00 +REPORT_START_DATE 01/01/2001 +REPORT_START_TIME 00:00:00 +END_DATE 01/02/2001 +END_TIME 00:00:00 +SWEEP_START 01/01 +SWEEP_END 12/31 +DRY_DAYS 0 +REPORT_STEP 00:00:20 +WET_STEP 00:15:00 +DRY_STEP 01:00:00 +ROUTING_STEP 0:00:20 +RULE_STEP 00:00:00 + +INERTIAL_DAMPING PARTIAL +NORMAL_FLOW_LIMITED BOTH +FORCE_MAIN_EQUATION H-W +VARIABLE_STEP 0.00 +LENGTHENING_STEP 0 +MIN_SURFAREA 12.566 +MAX_TRIALS 8 +HEAD_TOLERANCE 0.005 +SYS_FLOW_TOL 5 +LAT_FLOW_TOL 5 +MINIMUM_STEP 0.5 +THREADS 1 + +[EVAPORATION] +;;Data Source Parameters +;;-------------- ---------------- +CONSTANT 0.0 +DRY_ONLY NO + +[JUNCTIONS] +;;Name Elevation MaxDepth InitDepth SurDepth Aponded +;;-------------- ---------- ---------- ---------- ---------- ---------- +KRO3001 556.19 10 0 0 0 +KRO6015 585.98 8 0 0 0 +KRO6016 584.14 9 0 0 0 +KRO6017 582.01 14 0 0 0 +KRO1002 594.89 3 0 0 0 +KRO1003 594.73 5 0 0 0 +KRO1004 588.09 8 0 0 0 +KRO1005 579.40 16 0 0 0 +KRO1006 602.48 7 0 0 0 +KRO1007 596.76 15 0 0 0 +KRO1008 593.58 12 0 0 0 +KRO1009 590.56 15 0 0 0 +KRO1010 584.82 11 0 0 0 +KRO1012 584.25 10 0 0 0 +KRO1013 591.58 14 0 0 0 +KRO1014 592.41 6 0 0 0 +KRO1015 586.69 10 0 0 0 +KRO2001 576.29 8 0 0 0 +KRO4004 587.39 11 0 0 0 +KRO4008 583.48 10 0 0 0 +KRO4009 581.68 8 0 0 0 +KRO4010 579.88 12 0 0 0 +KRO4011 578.43 10 0 0 0 +KRO4012 564.71 10 0 0 0 +KRO4013 567.88 12 0 0 0 +KRO4014 573.18 14 0 0 0 +KRO4015 563.71 10 0 0 0 +KRO4016 563.24 10 0 0 0 +KRO4017 558.36 10 0 0 0 +KRO4018 556.02 9 0 0 0 +KRO4019 552.42 10 0 0 0 + +[OUTFALLS] +;;Name Elevation Type Stage Data Gated Route To +;;-------------- ---------- ---------- ---------------- -------- ---------------- +KRO2005 574.32 FREE NO +PSO 548.36 FREE NO + +[STORAGE] +;;Name Elev. MaxDepth InitDepth Shape Curve Type/Params SurDepth Fevap Psi Ksat IMD +;;-------------- -------- ---------- ----------- ---------- ---------------------------- --------- -------- -------- -------- +;4' diameter wet well +SU1 544.74 17 0 CYLINDRICAL 6 6 0 0 0 + +[CONDUITS] +;;Name From Node To Node Length Roughness InOffset OutOffset InitFlow MaxFlow +;;-------------- ---------------- ---------------- ---------- ---------- ---------- ---------- ---------- ---------- +KRO3001-KRO3002 KRO3001 SU1 176.7171053 0.013 0 5 0 0 +;Pump Station +SU1-PSO SU1 PSO 65.78947368 0.013 6 0 0 0 +KRO1002-KRO1003 KRO1002 KRO1003 27.23684211 0.013 0 0 0 0 +KRO1003-KRO1008 KRO1003 KRO1008 197.7236842 0.013 0 0 0 0 +KRO1004-KRO6016 KRO1004 KRO6016 197.4144737 0.013 0 0 0 0 +KRO1005-KRO2001 KRO1005 KRO2001 134.3092105 0.013 0 0 0 0 +KRO1006-KRO1007 KRO1006 KRO1007 162.5921053 0.013 0 0 0 0 +KRO1007-KRO1008 KRO1007 KRO1008 165.8881579 0.013 0 0 0 0 +KRO1008-KRO1009 KRO1008 KRO1009 157.3223684 0.013 0 0 0 0 +KRO1009-KRO1010 KRO1009 KRO1010 210.9539474 0.013 0 0 0 0 +KRO1010-KRO2001 KRO1010 KRO2001 190.1842105 0.013 0 0 0 0 +KRO1012-KRO4009 KRO1012 KRO4009 128.2631579 0.013 0 0 0 0 +KRO1013-KRO1009 KRO1013 KRO1009 170.1052632 0.013 0 0 0 0 +KRO1014-KRO1013 KRO1014 KRO1013 500 0.013 0 0 0 0 +KRO1015-KRO4014 KRO1015 KRO4014 151.4342105 0.013 0 0 0 0 +KRO2001-KRO2005 KRO2001 KRO2005 243.7631579 0.013 0 0 0 0 +KRO4004-KRO4008 KRO4004 KRO4008 140 0.013 0 0 0 0 +KRO4008-KRO4011 KRO4008 KRO4011 78.72368421 0.013 0 0 0 0 +KRO4009-KRO4010 KRO4009 KRO4010 180.0526316 0.013 0 0 0 0 +KRO4010-KRO4011 KRO4010 KRO4011 144.5328947 0.013 0 0 0 0 +KRO4011-KRO4012 KRO4011 KRO4012 198.0723684 0.013 0 0 0 0 +KRO4012-KRO3001 KRO4012 KRO3001 129.0526316 0.013 0 0.5 0 0 +KRO4013-KRO4017 KRO4013 KRO4017 176.5394737 0.013 0 0 0 0 +KRO4014-KRO4015 KRO4014 KRO4015 138.7039474 0.013 0 0 0 0 +KRO4015-KRO4016 KRO4015 KRO4016 16.95394737 0.013 0 0 0 0 +KRO4016-KRO4017 KRO4016 KRO4017 108.7697368 0.013 0 0 0 0 +KRO4017-KRO4018 KRO4017 KRO4018 101.5592105 0.013 0 0 0 0 +KRO4018-KRO4019 KRO4018 KRO4019 159.8486842 0.013 0 0 0 0 +KRO4019-KRO3002 KRO4019 SU1 87.57894737 0.013 0 5 0 0 +KRO6015-KRO6016 KRO6015 KRO6016 91.85526316 0.013 0 0 0 0 +KRO6016-KRO6017 KRO6016 KRO6017 211.9736842 0.013 0 0 0 0 +KRO6017-KRO1005 KRO6017 KRO1005 178.8092105 0.013 0 0 0 0 + +[PUMPS] +;;Name From Node To Node Pump Curve Status Sartup Shutoff +;;-------------- ---------------- ---------------- ---------------- ------ -------- -------- +PUMP1 SU1 KRO1014 PUMP_CURVE1 ON 0 0 + +[XSECTIONS] +;;Link Shape Geom1 Geom2 Geom3 Geom4 Barrels Culvert +;;-------------- ------------ ---------------- ---------- ---------- ---------- ---------- ---------- +KRO3001-KRO3002 CIRCULAR 1 0 0 0 1 +SU1-PSO CIRCULAR 1 0 0 0 1 +KRO1002-KRO1003 CIRCULAR 1 0 0 0 1 +KRO1003-KRO1008 CIRCULAR 1 0 0 0 1 +KRO1004-KRO6016 CIRCULAR 1 0 0 0 1 +KRO1005-KRO2001 CIRCULAR 1 0 0 0 1 +KRO1006-KRO1007 CIRCULAR 1 0 0 0 1 +KRO1007-KRO1008 CIRCULAR 1 0 0 0 1 +KRO1008-KRO1009 CIRCULAR 1 0 0 0 1 +KRO1009-KRO1010 CIRCULAR 1 0 0 0 1 +KRO1010-KRO2001 CIRCULAR 1 0 0 0 1 +KRO1012-KRO4009 CIRCULAR 1 0 0 0 1 +KRO1013-KRO1009 CIRCULAR 1 0 0 0 1 +KRO1014-KRO1013 CIRCULAR 1 0 0 0 1 +KRO1015-KRO4014 CIRCULAR 1 0 0 0 1 +KRO2001-KRO2005 CIRCULAR 1 0 0 0 1 +KRO4004-KRO4008 CIRCULAR 1 0 0 0 1 +KRO4008-KRO4011 CIRCULAR 1 0 0 0 1 +KRO4009-KRO4010 CIRCULAR 1 0 0 0 1 +KRO4010-KRO4011 CIRCULAR 1 0 0 0 1 +KRO4011-KRO4012 CIRCULAR 1 0 0 0 1 +KRO4012-KRO3001 CIRCULAR 1 0 0 0 1 +KRO4013-KRO4017 CIRCULAR 1 0 0 0 1 +KRO4014-KRO4015 CIRCULAR 1 0 0 0 1 +KRO4015-KRO4016 CIRCULAR 1 0 0 0 1 +KRO4016-KRO4017 CIRCULAR 1 0 0 0 1 +KRO4017-KRO4018 CIRCULAR 1 0 0 0 1 +KRO4018-KRO4019 CIRCULAR 1 0 0 0 1 +KRO4019-KRO3002 CIRCULAR 1 0 0 0 1 +KRO6015-KRO6016 CIRCULAR 1 0 0 0 1 +KRO6016-KRO6017 CIRCULAR 1 0 0 0 1 +KRO6017-KRO1005 CIRCULAR 1 0 0 0 1 + +[CONTROLS] +RULE PUMP1A +IF NODE SU1 DEPTH >= 4 +THEN PUMP PUMP1 status = ON +PRIORITY 1 + +RULE PUMP1B +IF NODE SU1 DEPTH < 1 +THEN PUMP PUMP1 status = OFF +PRIORITY 1 + + + + + + +[DWF] +;;Node Constituent Baseline Patterns +;;-------------- ---------------- ---------- ---------- +KRO3001 FLOW 1 "" "" "DWF" +KRO6015 FLOW 1 "" "" "DWF" +KRO6016 FLOW 1 "" "" "DWF" +KRO6017 FLOW 1 "" "" "DWF" +KRO1002 FLOW 1 "" "" "DWF" +KRO1003 FLOW 1 "" "" "DWF" +KRO1004 FLOW 1 "" "" "DWF" +KRO1005 FLOW 1 "" "" "DWF" +KRO1006 FLOW 1 "" "" "DWF" +KRO1007 FLOW 1 "" "" "DWF" +KRO1008 FLOW 1 "" "" "DWF" +KRO1009 FLOW 1 "" "" "DWF" +KRO1010 FLOW 1 "" "" "DWF" +KRO1012 FLOW 1 "" "" "DWF" +KRO1013 FLOW 1 "" "" "DWF" +KRO1015 FLOW 1 "" "" "DWF" +KRO2001 FLOW 1 "" "" "DWF" +KRO4004 FLOW 1 "" "" "DWF" +KRO4008 FLOW 1 "" "" "DWF" +KRO4009 FLOW 1 "" "" "DWF" +KRO4010 FLOW 1 "" "" "DWF" +KRO4011 FLOW 1 "" "" "DWF" +KRO4012 FLOW 1 "" "" "DWF" +KRO4013 FLOW 1 "" "" "DWF" +KRO4014 FLOW 1 "" "" "DWF" +KRO4015 FLOW 1 "" "" "DWF" +KRO4017 FLOW 1 "" "" "DWF" +KRO4018 FLOW 1 "" "" "DWF" +KRO4019 FLOW 1 "" "" "DWF" +SU1 FLOW 1 "" "" "DWF" + +[CURVES] +;;Name Type X-Value Y-Value +;;-------------- ---------- ---------- ---------- +PUMP_CURVE1 Pump4 0 0 +PUMP_CURVE1 1 0.2 +PUMP_CURVE1 2 0.4 +PUMP_CURVE1 3 0.6 +PUMP_CURVE1 4 0.9 +PUMP_CURVE1 17 0.9 + +[PATTERNS] +;;Name Type Multipliers +;;-------------- ---------- ----------- +DWF HOURLY .0151 .01373 .01812 .01098 .01098 .01922 +DWF .02773 .03789 .03515 .03982 .02059 .02471 +DWF .03021 .03789 .03350 .03158 .03954 .02114 +DWF .02801 .03680 .02911 .02334 .02499 .02718 + +[REPORT] +;;Reporting Options +CONTROLS YES +SUBCATCHMENTS ALL +NODES ALL +LINKS ALL + +[TAGS] + +[MAP] +DIMENSIONS 1361856.362 428552.651 1363638.769 431248.506 +Units None + +[COORDINATES] +;;Node X-Coord Y-Coord +;;-------------- ------------------ ------------------ +KRO3001 1362408.250 431113.810 +KRO6015 1362748.630 428675.190 +KRO6016 1362767.000 428813.590 +KRO6017 1363087.250 428778.190 +KRO1002 1362361.780 429189.370 +KRO1003 1362367.000 429230.440 +KRO1004 1362860.250 429098.810 +KRO1005 1363142.000 429044.410 +KRO1006 1361937.380 429699.810 +KRO1007 1362171.000 429619.190 +KRO1008 1362406.250 429528.410 +KRO1009 1362629.000 429441.410 +KRO1010 1362927.630 429324.590 +KRO1012 1362287.630 429934.410 +KRO1013 1362657.380 429698.410 +KRO1014 1362656.752 430800.636 +KRO1015 1362725.750 429953.810 +KRO2001 1363203.750 429239.000 +KRO4004 1362024.630 430653.190 +KRO4008 1362236.380 430632.000 +KRO4009 1362307.000 430128.410 +KRO4010 1362333.380 430400.810 +KRO4011 1362355.380 430619.410 +KRO4012 1362385.250 430919.000 +KRO4013 1362511.750 430605.810 +KRO4014 1362751.190 430182.590 +KRO4015 1362774.000 430392.190 +KRO4016 1362761.250 430414.590 +KRO4017 1362778.750 430579.000 +KRO4018 1362796.000 430732.410 +KRO4019 1362704.750 430957.590 +KRO2005 1363557.750 429129.590 +PSO 1362683.696 431125.967 +SU1 1362652.040 431078.910 + +[VERTICES] +;;Link X-Coord Y-Coord +;;-------------- ------------------ ------------------ + +[Polygons] +;;Storage Node X-Coord Y-Coord +;;-------------- ------------------ ------------------ +SU1 1362652.040 431078.910 + +[LABELS] +;;X-Coord Y-Coord Label +1362744.750 431134.090 "Regulator Point" PSO "Arial" 10 0 0 + diff --git a/swmmio/tests/data/__init__.py b/swmmio/tests/data/__init__.py index 5682893..d1f81ed 100644 --- a/swmmio/tests/data/__init__.py +++ b/swmmio/tests/data/__init__.py @@ -48,3 +48,6 @@ df_test_coordinates_csv = os.path.join(DATA_PATH, 'df_test_coordinates.csv') OUTFALLS_MODIFIED = os.path.join(DATA_PATH, 'outfalls_modified_10.csv') + +# SWMM example model +MODEL_PUMP_CONTROL = os.path.join(DATA_PATH, 'Pump_Control_Model.inp') From 2dfd9e4b2447bc067ad870d6a47893cd0f4069a4 Mon Sep 17 00:00:00 2001 From: kaklise Date: Mon, 15 Apr 2024 11:23:54 -0700 Subject: [PATCH 10/11] updated inp sections test to include additional models --- swmmio/tests/test_dataframes.py | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/swmmio/tests/test_dataframes.py b/swmmio/tests/test_dataframes.py index b8cb83c..42c21b8 100644 --- a/swmmio/tests/test_dataframes.py +++ b/swmmio/tests/test_dataframes.py @@ -6,7 +6,8 @@ BUILD_INSTR_01, MODEL_XSECTION_ALT_01, df_test_coordinates_csv, MODEL_FULL_FEATURES_XY, DATA_PATH, MODEL_XSECTION_ALT_03, MODEL_CURVE_NUMBER, MODEL_MOD_HORTON, MODEL_GREEN_AMPT, MODEL_MOD_GREEN_AMPT, - MODEL_INFILTRAION_PARSE_FAILURE, OWA_RPT_EXAMPLE) + MODEL_INFILTRAION_PARSE_FAILURE, OWA_RPT_EXAMPLE, + MODEL_TEST_INLET_DRAINS, MODEL_PUMP_CONTROL) from swmmio.utils.dataframes import (dataframe_from_rpt, dataframe_from_inp, dataframe_from_bi) from swmmio.utils.text import get_inp_sections_details from swmmio.run_models.run import run_simple @@ -263,13 +264,23 @@ def test_polygons(test_model_02): # print() def test_inp_sections(): - + + # Additional models could be added to this test, or additional features + # could be added to the full feature model inpfiles = [ MODEL_FULL_FEATURES_PATH, MODEL_CURVE_NUMBER, MODEL_MOD_HORTON, + MODEL_GREEN_AMPT, + MODEL_TEST_INLET_DRAINS, + MODEL_PUMP_CONTROL, ] + from swmmio.defs import INP_OBJECTS + all_sections = set(sec.upper() for sec in INP_OBJECTS.keys()) + unsupported_sections = set() + tested_sections = set() + for inpfile in inpfiles: print(inpfile) @@ -287,15 +298,16 @@ def test_inp_sections(): temp_inpfile = 'temp.inp' model = swmmio.Model(inpfile, include_rpt=False) empty_sections = [] - unsupported_sections = [] for sec in headers.keys(): try: df = getattr(model.inp, sec.lower()) - if df.empty: - empty_sections.append(sec) + if not df.empty: + tested_sections.add(sec.upper()) + else: + empty_sections.append(sec.upper()) setattr(model.inp, sec, df.copy()) except: - unsupported_sections.append(sec) + unsupported_sections.add(sec.upper()) model.inp.save(temp_inpfile) headers2 = get_inp_sections_details(temp_inpfile, include_brackets=False) @@ -311,9 +323,10 @@ def test_inp_sections(): assert 'MaxQ' in links2.columns # Check that results are the same assert_series_equal(links['MaxQ'], links2['MaxQ']) - # Print empty and unsupported INP file sections - print('Empty sections', empty_sections) - print('Unsupported sections', unsupported_sections) - + + # Print empty and unsupported INP file sections + print('Unsupported sections', unsupported_sections) + print('Untested sections', all_sections - unsupported_sections - tested_sections) + if __name__ == "__main__": test_inp_sections() \ No newline at end of file From 26a04b65b4cc644bb6e55164c3daea59526a1846 Mon Sep 17 00:00:00 2001 From: kaklise Date: Mon, 15 Apr 2024 13:29:49 -0700 Subject: [PATCH 11/11] simplified output --- swmmio/core.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/swmmio/core.py b/swmmio/core.py index 695b2a4..2da7c5b 100644 --- a/swmmio/core.py +++ b/swmmio/core.py @@ -1520,10 +1520,11 @@ def patterns(self): :return: dataframe of patterns >>> from swmmio.examples import pump_control - >>> pump_control.inp.patterns #doctest: +NORMALIZE_WHITESPACE - Type Factor1 ... Factor23 Factor24 - Name ... - DWF HOURLY 0.0151 ... 0.02499 0.02718 + >>> # NOTE, only the first 5 columns are shown in the following example + >>> pump_control.inp.patterns.iloc[:,0:5] #doctest: +NORMALIZE_WHITESPACE + Type Factor1 Factor2 Factor3 Factor4 + Name + DWF HOURLY 0.0151 0.01373 0.01812 0.01098 """ if self._patterns_df is not None: