From 6c08f7ee94551569584272cb3a73a89b9e588583 Mon Sep 17 00:00:00 2001 From: Caleb Date: Wed, 11 Dec 2024 05:28:54 -0500 Subject: [PATCH] WIP API and bindings unit testing #204, #162, #163, #184, #180 --- .github/workflows/build-and-deploy.yml | 14 ++ .../workflows/build-and-regression-test.yml | 14 ++ .github/workflows/build-and-unit-test.yml | 2 +- python/epaswmm/solver/__init__.py | 1 + python/epaswmm/solver/solver.pxd | 1 + python/epaswmm/solver/solver.pyx | 75 ++++-- python/tests/test_swmm_solver.py | 227 +++++++++++++++++- src/solver/error.txt | 4 +- src/solver/include/swmm5.h | 71 +++--- src/solver/swmm5.c | 10 +- 10 files changed, 357 insertions(+), 62 deletions(-) diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index e69de29bb..7a0f90162 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -0,0 +1,14 @@ +name: Build for Release + +on: + push: + branches: [ master] + # pull_request: + # branches: [ master, develop, release ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 diff --git a/.github/workflows/build-and-regression-test.yml b/.github/workflows/build-and-regression-test.yml index e69de29bb..6c238896e 100644 --- a/.github/workflows/build-and-regression-test.yml +++ b/.github/workflows/build-and-regression-test.yml @@ -0,0 +1,14 @@ +name: Build and Regression Test + +on: + push: + branches: [ master] + # pull_request: + # branches: [ master, develop, release ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 diff --git a/.github/workflows/build-and-unit-test.yml b/.github/workflows/build-and-unit-test.yml index 649b9c4f2..858e10590 100644 --- a/.github/workflows/build-and-unit-test.yml +++ b/.github/workflows/build-and-unit-test.yml @@ -2,7 +2,7 @@ name: Build and Unit Test on: push: - branches: [ master, develop, release ] + branches: [ master] # pull_request: # branches: [ master, develop, release ] diff --git a/python/epaswmm/solver/__init__.py b/python/epaswmm/solver/__init__.py index 07ede387a..f7ae7d9ec 100644 --- a/python/epaswmm/solver/__init__.py +++ b/python/epaswmm/solver/__init__.py @@ -17,5 +17,6 @@ encode_swmm_datetime, version, SolverState, + SWMMSolverException, Solver ) diff --git a/python/epaswmm/solver/solver.pxd b/python/epaswmm/solver/solver.pxd index 4eeb7dabb..cd5829adc 100644 --- a/python/epaswmm/solver/solver.pxd +++ b/python/epaswmm/solver/solver.pxd @@ -147,6 +147,7 @@ cdef extern from "swmm5.h": swmm_IGNOREGROUNDWATER # Flag indicating whether groundwater is ignored. swmm_IGNOREROUTING # Flag indicating whether routing is ignored. swmm_IGNOREQUALITY # Flag indicating whether water quality is ignored. + swmm_ERROR_CODE # The error code. swmm_RULESTEP # The rule step size. swmm_SWEEPSTART # The start date of the sweep start. swmm_SWEEPEND # The end date of the sweep end. diff --git a/python/epaswmm/solver/solver.pyx b/python/epaswmm/solver/solver.pyx index 20840990c..4434d3c5d 100644 --- a/python/epaswmm/solver/solver.pyx +++ b/python/epaswmm/solver/solver.pyx @@ -10,6 +10,7 @@ from warnings import warn from typing import List, Tuple, Union, Optional, Dict, Set, Callable from cpython.datetime cimport datetime, timedelta from libc.stdlib cimport free, malloc +from functools import partialmethod # external imports @@ -105,7 +106,7 @@ class SWMMObjects(Enum): :type SYSTEM: int """ RAIN_GAGE = swmm_Object.swmm_GAGE - SUBCATCH = swmm_Object.swmm_SUBCATCH + SUBCATCHMENT = swmm_Object.swmm_SUBCATCH NODE = swmm_Object.swmm_NODE LINK = swmm_Object.swmm_LINK AQUIFER = swmm_Object.swmm_AQUIFER @@ -450,6 +451,7 @@ class SWMMSystemProperties(Enum): IGNORE_GROUNDWATER = swmm_SystemProperty.swmm_IGNOREGROUNDWATER IGNORE_ROUTING = swmm_SystemProperty.swmm_IGNOREROUTING IGNORE_QUALITY = swmm_SystemProperty.swmm_IGNOREQUALITY + ERROR_CODE = swmm_SystemProperty.swmm_ERROR_CODE RULE_STEP = swmm_SystemProperty.swmm_RULESTEP SWEEP_START = swmm_SystemProperty.swmm_SWEEPSTART SWEEP_END = swmm_SystemProperty.swmm_SWEEPEND @@ -712,8 +714,9 @@ cdef class Solver: cdef clock_t _clock cdef double _total_duration cdef object _solver_state + cdef object _partial_step_function - def __cinit__(self, str inp_file, str rpt_file, str out_file, bint save_results=True): + def __cinit__(self, str inp_file, str rpt_file, str out_file, int stride_step = 300, bint save_results=True): """ Constructor to create a new SWMM solver. @@ -725,6 +728,7 @@ cdef class Solver: self._save_results = save_results self._inp_file = inp_file self._progress_callbacks_per_second = 2 + self._stride_step = stride_step self._clock = clock() global_solver = self @@ -738,8 +742,6 @@ cdef class Solver: else: self._out_file = inp_file.replace('.inp', '.out') - self._stride_step = 0 - self._callbacks = { CallbackType.BEFORE_INITIALIZE: [], CallbackType.BEFORE_OPEN: [], @@ -932,6 +934,8 @@ cdef class Solver: """ cdef int count = swmm_getCount(object_type.value) + self.__validate_error(count) + return count def get_object_name(self, object_type: SWMMObjects, index: int) -> str: @@ -946,8 +950,8 @@ cdef class Solver: :rtype: str """ cdef char* c_object_name = malloc(1024*sizeof(char)) - cdef int error_code = swmm_getName(object_type.value, index, c_object_name, 1024) + cdef int error_code = swmm_getName(object_type.value, index, c_object_name, 1024) self.__validate_error(error_code) object_name = c_object_name.decode('utf-8') @@ -956,6 +960,32 @@ cdef class Solver: return object_name + def get_object_names(self, object_type: SWMMObjects) -> List[str]: + """ + Get the names of all SWMM objects of a given type. + + :param object_type: SWMM object type + :type object_type: SWMMObjects + :return: Object names + :rtype: List[str] + """ + cdef char* c_object_name = malloc(1024*sizeof(char)) + cdef list object_names = [] + cdef int count = self.get_object_count(object_type) + + for i in range(count): + + error_code = swmm_getName(object_type.value, i, c_object_name, 1024) + self.__validate_error(error_code) + + object_name = c_object_name.decode('utf-8') + object_names.append(object_name) + + + free(c_object_name) + + return object_names + def get_object_index(self, object_type: SWMMObjects, object_name: str) -> int: """ Get the index of a SWMM object. @@ -971,7 +1001,7 @@ cdef class Solver: return index - cpdef void set_value(self, int object_type, int property_type, int index, int sub_index, double value): + cpdef void set_value(self, int object_type, int property_type, int index, double value, int sub_index = -1): """ Set a SWMM system property value. @@ -989,7 +1019,7 @@ cdef class Solver: cdef int error_code = swmm_setValueExpanded(object_type, property_type, index, sub_index, value) self.__validate_error(error_code) - cpdef double get_value(self, int object_type, int property_type, int index, int sub_index): + cpdef double get_value(self, int object_type, int property_type, int index, int sub_index = -1): """ Get a SWMM system property value. @@ -999,6 +1029,8 @@ cdef class Solver: :rtype: double """ cdef double value = swmm_getValueExpanded(object_type, property_type, index, sub_index) + self.__validate_error(value) + return value @property @@ -1009,7 +1041,7 @@ cdef class Solver: :return: Stride step :rtype: int """ - pass + return self._stride_step @stride_step.setter def stride_step(self, value: int): @@ -1019,7 +1051,7 @@ cdef class Solver: :param value: Stride step in seconds :type value: int """ - pass + self._stride_step = value @property def solver_state(self) -> SolverState: @@ -1078,18 +1110,11 @@ cdef class Solver: self.__validate_error(error_code) self._solver_state = SolverState.OPEN self.__execute_callbacks(CallbackType.AFTER_OPEN) - - self.__execute_callbacks(CallbackType.BEFORE_START) - error_code = swmm_start(self._save_results) - self.__validate_error(error_code) - self._solver_state = SolverState.STARTED - self.__execute_callbacks(CallbackType.AFTER_START) else: raise SWMMSolverException(f'Initialize failed: Solver is not in a valid state: {self._solver_state}') - self._total_duration = swmm_getValue(SWMMSystemProperties.END_DATE.value, 0) - swmm_getValue(SWMMSystemProperties.START_DATE.value, 0) - + cpdef double step(self): """ Step a SWMM simulation. @@ -1100,6 +1125,13 @@ cdef class Solver: cdef double elapsed_time = 0.0 cdef double progress = 0.0 + if self._solver_state == SolverState.OPEN: + self.__execute_callbacks(CallbackType.BEFORE_START) + error_code = swmm_start(self._save_results) + self.__validate_error(error_code) + self._solver_state = SolverState.STARTED + self.__execute_callbacks(CallbackType.AFTER_START) + if self._stride_step > 0: error_code = swmm_stride(self._stride_step, &elapsed_time) else: @@ -1241,8 +1273,13 @@ cdef class Solver: :param error_code: Error code to validate :type error_code: int """ - if error_code != 0: - raise SWMMSolverException(f'SWMM failed with message: {self.__get_error()}') + cdef int internal_error_code = swmm_getValue(SWMMObjects.SYSTEM.value, SWMMSystemProperties.ERROR_CODE.value) + + if error_code < 0: + if internal_error_code > 0: + raise SWMMSolverException(f'SWMM failed with message: {internal_error_code}, {self.__get_error()}') + else: + raise SWMMSolverException(f'SWMM failed with message: {error_code}, {get_error_message(error_code)}') cdef str __get_error(self): """ diff --git a/python/tests/test_swmm_solver.py b/python/tests/test_swmm_solver.py index a50bea1c2..37ac20391 100644 --- a/python/tests/test_swmm_solver.py +++ b/python/tests/test_swmm_solver.py @@ -140,8 +140,6 @@ def test_run_solver_with_context_manager(self): rpt_file=example_solver_data.NON_EXISTENT_INPUT_FILE.replace(".inp", ".rpt"), out_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE.replace(".inp", ".out"), ) as swmm_solver: - swmm_solver.initialize() - for t in swmm_solver: pass @@ -155,9 +153,232 @@ def test_solver_get_time_attributes(self): rpt_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE.replace(".inp", ".rpt"), out_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE.replace(".inp", ".out") ) as swmm_solver: - swmm_solver.initialize() start_date = swmm_solver.start_datetime self.assertEqual(start_date, datetime(year=1998, month=1, day=1)) + + def test_get_object_count(self): + """ + Test the get_object_count function of the SWMM solver + :return: + """ + with solver.Solver( + inp_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE, + rpt_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE.replace(".inp", ".rpt"), + out_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE.replace(".inp", ".out") + ) as swmm_solver: + swmm_solver.initialize() + + num_raingages = swmm_solver.get_object_count(solver.SWMMObjects.RAIN_GAGE) + num_subcatchments = swmm_solver.get_object_count(solver.SWMMObjects.SUBCATCHMENT) + num_nodes = swmm_solver.get_object_count(solver.SWMMObjects.NODE) + num_links = swmm_solver.get_object_count(solver.SWMMObjects.LINK) + num_aquifers = swmm_solver.get_object_count(solver.SWMMObjects.AQUIFER) + num_snowpacks = swmm_solver.get_object_count(solver.SWMMObjects.SNOWPACK) + num_hydrographs = swmm_solver.get_object_count(solver.SWMMObjects.UNIT_HYDROGRAPH) + num_lids = swmm_solver.get_object_count(solver.SWMMObjects.LID) + num_streets = swmm_solver.get_object_count(solver.SWMMObjects.STREET) + num_inlets = swmm_solver.get_object_count(solver.SWMMObjects.INLET) + num_transects = swmm_solver.get_object_count(solver.SWMMObjects.TRANSECT) + num_xsections = swmm_solver.get_object_count(solver.SWMMObjects.XSECTION_SHAPE) + num_controls = swmm_solver.get_object_count(solver.SWMMObjects.CONTROL_RULE) + num_pollutants = swmm_solver.get_object_count(solver.SWMMObjects.POLLUTANT) + num_landuses = swmm_solver.get_object_count(solver.SWMMObjects.LANDUSE) + num_curves = swmm_solver.get_object_count(solver.SWMMObjects.CURVE) + num_timeseries = swmm_solver.get_object_count(solver.SWMMObjects.TIMESERIES) + num_time_patterns = swmm_solver.get_object_count(solver.SWMMObjects.TIME_PATTERN) + + self.assertEqual(num_raingages, 1) + self.assertEqual(num_subcatchments, 7) + self.assertEqual(num_nodes, 12) + self.assertEqual(num_links, 11) + self.assertEqual(num_aquifers, 0) + self.assertEqual(num_snowpacks, 0) + self.assertEqual(num_hydrographs, 0) + self.assertEqual(num_lids, 0) + self.assertEqual(num_streets, 0) + self.assertEqual(num_inlets, 0) + self.assertEqual(num_transects, 0) + self.assertEqual(num_xsections, 0) + self.assertEqual(num_controls, 0) + self.assertEqual(num_pollutants, 1) + self.assertEqual(num_landuses, 4) + self.assertEqual(num_curves, 0) + self.assertEqual(num_timeseries, 3) + self.assertEqual(num_time_patterns, 0) + + with self.assertRaises(solver.SWMMSolverException) as context: + system_vars = swmm_solver.get_object_count(solver.SWMMObjects.SYSTEM) + + self.assertIn('API Error -999904: invalid object type.', str(context.exception)) + + def test_get_object_names(self): + """ + Test the get_object_names function of the SWMM solver + :return: + """ + + with solver.Solver( + inp_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE, + rpt_file=example_solver_data.NON_EXISTENT_INPUT_FILE.replace(".inp", ".rpt"), + out_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE.replace(".inp", ".out"), + ) as swmm_solver: + swmm_solver.initialize() + + raingage_names = swmm_solver.get_object_names(solver.SWMMObjects.RAIN_GAGE) + subcatchment_names = swmm_solver.get_object_names(solver.SWMMObjects.SUBCATCHMENT) + node_names = swmm_solver.get_object_names(solver.SWMMObjects.NODE) + link_names = swmm_solver.get_object_names(solver.SWMMObjects.LINK) + pollutant_names = swmm_solver.get_object_names(solver.SWMMObjects.POLLUTANT) + landuse_names = swmm_solver.get_object_names(solver.SWMMObjects.LANDUSE) + timeseries_names = swmm_solver.get_object_names(solver.SWMMObjects.TIMESERIES) + + self.assertListEqual(raingage_names, ['RainGage']) + self.assertListEqual(subcatchment_names, ['S1', 'S2', 'S3', 'S4', 'S5', 'S6', 'S7']) + self.assertListEqual(node_names, ['J1', 'J2', 'J3', 'J4', 'J5', 'J6', 'J7', 'J8', 'J9', 'J10', 'J11', 'O1']) + self.assertListEqual(link_names, ['C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9', 'C10', 'C11']) + self.assertListEqual(pollutant_names, ['TSS']) + self.assertListEqual(landuse_names, ['Residential_1', 'Residential_2', 'Commercial', 'Undeveloped']) + self.assertListEqual(timeseries_names, ['2-yr', '10-yr', '100-yr']) + + def test_get_object_index(self): + """ + Test the get_object_index function of the SWMM solver + :return: + """ + + with solver.Solver( + inp_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE, + rpt_file=example_solver_data.NON_EXISTENT_INPUT_FILE.replace(".inp", ".rpt"), + out_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE.replace(".inp", ".out"), + ) as swmm_solver: + swmm_solver.initialize() + + rg_index = swmm_solver.get_object_index(solver.SWMMObjects.RAIN_GAGE, 'RainGage') + sc_index = swmm_solver.get_object_index(solver.SWMMObjects.SUBCATCHMENT, 'S2') + node_index = swmm_solver.get_object_index(solver.SWMMObjects.NODE, 'J6') + link_index = swmm_solver.get_object_index(solver.SWMMObjects.LINK, 'C10') + pollutant_index = swmm_solver.get_object_index(solver.SWMMObjects.POLLUTANT, 'TSS') + landuse_index = swmm_solver.get_object_index(solver.SWMMObjects.LANDUSE, 'Commercial') + timeseries_index = swmm_solver.get_object_index(solver.SWMMObjects.TIMESERIES, '10-yr') + + self.assertEqual(rg_index, 0) + self.assertEqual(sc_index, 1) + self.assertEqual(node_index, 5) + self.assertEqual(link_index, 9) + self.assertEqual(pollutant_index, 0) + self.assertEqual(landuse_index, 2) + self.assertEqual(timeseries_index, 1) + + def test_get_gage_value(self): + """ + Test the get_gage_value function of the SWMM solver + :return: + """ + + with solver.Solver( + inp_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE, + rpt_file=example_solver_data.NON_EXISTENT_INPUT_FILE.replace(".inp", ".rpt"), + out_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE.replace(".inp", ".out"), + ) as swmm_solver: + swmm_solver.initialize() + + for t in range(12): + swmm_solver.step() + + rg_value = swmm_solver.get_value( + object_type=solver.SWMMObjects.RAIN_GAGE.value, + property_type=solver.SWMMRainGageProperties.GAGE_TOTAL_PRECIPITATION.value, + index=0, + ) + + self.assertAlmostEqual(rg_value / 12.0, 0.3) + + def test_set_gage_value(self): + """ + Test the set_gage_value function of the SWMM solver + :return: + """ + + with solver.Solver( + inp_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE, + rpt_file=example_solver_data.NON_EXISTENT_INPUT_FILE.replace(".inp", ".rpt"), + out_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE.replace(".inp", ".out"), + ) as swmm_solver: + swmm_solver.initialize() + + swmm_solver.set_value( + object_type=solver.SWMMObjects.RAIN_GAGE.value, + property_type=solver.SWMMRainGageProperties.GAGE_RAINFALL.value, + index=0, + value=3.6 + ) + + for _ in range(12): + swmm_solver.step() + + rg_value = swmm_solver.get_value( + object_type=solver.SWMMObjects.RAIN_GAGE.value, + property_type=solver.SWMMRainGageProperties.GAGE_TOTAL_PRECIPITATION.value, + index=0, + ) + + self.assertAlmostEqual(rg_value , 3.6) + + def test_get_subcatchment_value(self): + """ + Test the get_subcatchment_value function of the SWMM solver + :return: + """ + + with solver.Solver( + inp_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE, + rpt_file=example_solver_data.NON_EXISTENT_INPUT_FILE.replace(".inp", ".rpt"), + out_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE.replace(".inp", ".out"), + ) as swmm_solver: + swmm_solver.initialize() + + for t in range(12): + swmm_solver.step() + + sc_value = swmm_solver.get_value( + object_type=solver.SWMMObjects.SUBCATCHMENT.value, + property_type=solver.SWMMSubcatchmentProperties.RUNOFF.value, + index=1, + ) + + self.assertAlmostEqual(sc_value, 17.527141504933294) + + def test_set_subcatchment_value(self): + """ + Test the set_subcatchment_value function of the SWMM solver + :return: + """ + + with solver.Solver( + inp_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE, + rpt_file=example_solver_data.NON_EXISTENT_INPUT_FILE.replace(".inp", ".rpt"), + out_file=example_solver_data.SITE_DRAINAGE_EXAMPLE_INPUT_FILE.replace(".inp", ".out"), + ) as swmm_solver: + + swmm_solver.initialize() + + error_code = swmm_solver.set_value( + object_type=solver.SWMMObjects.SUBCATCHMENT.value, + property_type=solver.SWMMSubcatchmentProperties.WIDTH.value, + index=1, + value=100.0 + ) + + for _ in range(12): + swmm_solver.step() + + sc_value = swmm_solver.get_value( + object_type=solver.SWMMObjects.SUBCATCHMENT.value, + property_type=solver.SWMMSubcatchmentProperties.WIDTH.value, + index=1, + ) + + self.assertAlmostEqual(sc_value, 100.0) diff --git a/src/solver/error.txt b/src/solver/error.txt index fdd743e07..8d7a4ad26 100644 --- a/src/solver/error.txt +++ b/src/solver/error.txt @@ -1,4 +1,4 @@ -// SWMM 5.2 Error Messages +// SWMM 5.3 Error Messages ERR(101,"\n ERROR 101: memory allocation error.") ERR(103,"\n ERROR 103: cannot solve KW equations for Link %s.") @@ -135,3 +135,5 @@ ERR(-999907,"\n API Error -999907: invalid property type.") ERR(-999908,"\n API Error -999908: invalid property value.") ERR(-999909,"\n API Error -999909: invalid time period.") ERR(-999910,"\n API Error -999910: cannot open hot start interface file %s.") +ERR(-999911,"\n API Error -999911: invalid hot start interface file format %s.") +ERR(-999912,"\n API Error -999912: simulation still running %s.") diff --git a/src/solver/include/swmm5.h b/src/solver/include/swmm5.h index 46440bb3c..b2e5af6bc 100644 --- a/src/solver/include/swmm5.h +++ b/src/solver/include/swmm5.h @@ -44,24 +44,24 @@ extern "C" { #endif typedef enum { - swmm_GAGE = 0, - swmm_SUBCATCH = 1, - swmm_NODE = 2, - swmm_LINK = 3, - swmm_AQUIFER = 4, - swmm_SNOWPACK = 5, - swmm_UNIT_HYDROGRAPH = 6, - swmm_LID = 7, - swmm_STREET = 8, - swmm_INLET = 9, - swmm_TRANSECT = 10, - smmm_XSECTION_SHAPE = 11, - swmm_CONTROL_RULE = 12, - swmm_POLLUTANT = 13, - swmm_LANDUSE = 14, - swmm_CURVE = 15, - swmm_TIMESERIES = 16, - swmm_TIME_PATTERN = 17, + swmm_GAGE, + swmm_SUBCATCH, + swmm_NODE, + swmm_LINK, + swmm_POLLUTANT, + swmm_LANDUSE, + swmm_TIME_PATTERN, + swmm_CURVE, + swmm_TIMESERIES, + swmm_CONTROL_RULE, + swmm_TRANSECT, + swmm_AQUIFER, + swmm_UNIT_HYDROGRAPH, + swmm_SNOWPACK, + smmm_XSECTION_SHAPE, + swmm_LID, + swmm_STREET, + swmm_INLET, swmm_SYSTEM = 100 } swmm_Object; @@ -178,23 +178,24 @@ typedef enum { swmm_IGNOREGROUNDWATER = 20, swmm_IGNOREROUTING = 21, swmm_IGNOREQUALITY = 22, - swmm_RULESTEP= 23, - swmm_SWEEPSTART = 24, - swmm_SWEEPEND = 25, - swmm_MAXTRIALS = 26, - swmm_NUMTHREADS = 27, - swmm_MINROUTESTEP = 28, - swmm_LENGTHENINGSTEP = 29, - swmm_STARTDRYDAYS = 30, - swmm_COURANTFACTOR = 31, - swmm_MINSURFAREA = 32, - swmm_MINSLOPE = 33, - swmm_RUNOFFERROR = 34, - swmm_FLOWERROR = 35, - swmm_QUALERROR = 36, - swmm_HEADTOL = 37, - swmm_SYSFLOWTOL = 38, - swmm_LATFLOWTOL = 39, + swmm_ERROR_CODE = 23, + swmm_RULESTEP= 24, + swmm_SWEEPSTART = 25, + swmm_SWEEPEND = 26, + swmm_MAXTRIALS = 27, + swmm_NUMTHREADS = 28, + swmm_MINROUTESTEP = 29, + swmm_LENGTHENINGSTEP = 30, + swmm_STARTDRYDAYS = 31, + swmm_COURANTFACTOR = 32, + swmm_MINSURFAREA = 33, + swmm_MINSLOPE = 34, + swmm_RUNOFFERROR = 35, + swmm_FLOWERROR = 36, + swmm_QUALERROR = 37, + swmm_HEADTOL = 38, + swmm_SYSFLOWTOL = 39, + swmm_LATFLOWTOL = 40, } swmm_SystemProperty; diff --git a/src/solver/swmm5.c b/src/solver/swmm5.c index 86bb41d03..d5fc99d74 100644 --- a/src/solver/swmm5.c +++ b/src/solver/swmm5.c @@ -975,7 +975,7 @@ int DLLEXPORT swmm_getCount(int objType) { if (!IsOpenFlag) return ERR_API_NOT_OPEN; - if (objType < swmm_GAGE || objType > swmm_SYSTEM) + if (objType < swmm_GAGE || objType >= swmm_SYSTEM) return ERR_API_OBJECT_TYPE; return Nobjects[objType]; } @@ -1060,7 +1060,7 @@ int DLLEXPORT swmm_getIndex(int objType, const char *name) { if (!IsOpenFlag) return ERR_API_NOT_OPEN; - if (objType < swmm_GAGE || objType > swmm_LINK) + if (objType < swmm_GAGE || objType >= swmm_SYSTEM) return ERR_API_OBJECT_TYPE; return project_findObject(objType, name); } @@ -1653,7 +1653,7 @@ double getGageValue(int property, int index) total = gage_getPrecip(index, &rain, &snow); - switch (index) + switch (property) { case swmm_GAGE_TOTAL_PRECIPITATION: return total * UCF(RAINFALL); @@ -1701,6 +1701,8 @@ double getSubcatchValue(int property, int index, int subIndex) return subcatch->newRunoff * UCF(FLOW); case swmm_SUBCATCH_RPTFLAG: return (subcatch->rptFlag > 0); + case swmm_SUBCATCH_WIDTH: + return subcatch->width * UCF(LENGTH); case swmm_SUBCATCH_SLOPE: return subcatch->slope; case swmm_SUBCATCH_CURB_LENGTH: @@ -1957,6 +1959,8 @@ double getSystemValue(int property) return IgnoreRouting; case swmm_IGNOREQUALITY: return IgnoreQuality; + case swmm_ERROR_CODE: + return ErrorCode; case swmm_RULESTEP: return RuleStep; case swmm_SWEEPSTART: