openswmm 5.3.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openswmm/CMakeLists.txt +31 -0
- openswmm/__init__.py +42 -0
- openswmm/_openswmm.pyx +12 -0
- openswmm/data/__init__.py +0 -0
- openswmm/gym/__init__.py +0 -0
- openswmm/openswmm.pxd +10 -0
- openswmm/output/CMakeLists.txt +45 -0
- openswmm/output/__init__.pxd +1 -0
- openswmm/output/__init__.py +13 -0
- openswmm/output/_output.pyi +732 -0
- openswmm/output/_output.pyx +1557 -0
- openswmm/output/output.pxd +368 -0
- openswmm/solver/CMakeLists.txt +50 -0
- openswmm/solver/__init__.pxd +1 -0
- openswmm/solver/__init__.py +22 -0
- openswmm/solver/_solver.pyi +1012 -0
- openswmm/solver/_solver.pyx +1646 -0
- openswmm/solver/solver.pxd +356 -0
- openswmm-5.3.0.dev0.dist-info/METADATA +228 -0
- openswmm-5.3.0.dev0.dist-info/RECORD +36 -0
- openswmm-5.3.0.dev0.dist-info/WHEEL +5 -0
- openswmm-5.3.0.dev0.dist-info/licenses/LICENSE +12 -0
- openswmm-5.3.0.dev0.dist-info/top_level.txt +2 -0
- tests/.DS_Store +0 -0
- tests/__init__.py +3 -0
- tests/data/.DS_Store +0 -0
- tests/data/__init__.py +3 -0
- tests/data/output/__init__.py +22 -0
- tests/data/output/example_output_1.out +0 -0
- tests/data/output/json_time_series.pickle +0 -0
- tests/data/solver/__init__.py +17 -0
- tests/data/solver/non_existent_input_file.rpt +2387 -0
- tests/data/solver/site_drainage_example.inp +499 -0
- tests/data/solver/site_drainage_example.rpt +354 -0
- tests/test_swmm_solver.py +716 -0
- tests/test_swwm_output.py +1048 -0
|
@@ -0,0 +1,1646 @@
|
|
|
1
|
+
# cython: language_level=3str
|
|
2
|
+
# Description: Cython module for openswmmcore solver
|
|
3
|
+
# Created by: Caleb Buahin (EPA/ORD/CESER/WID)
|
|
4
|
+
# Created on: 2024-11-19
|
|
5
|
+
|
|
6
|
+
# python and cython imports
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from warnings import warn
|
|
9
|
+
from typing import List, Tuple, Union, Dict, Set, Callable
|
|
10
|
+
from cpython.datetime cimport datetime as cython_datetime
|
|
11
|
+
from datetime import datetime, timedelta
|
|
12
|
+
from libc.stdlib cimport free, malloc
|
|
13
|
+
from functools import partialmethod
|
|
14
|
+
|
|
15
|
+
# external imports
|
|
16
|
+
from .solver cimport (
|
|
17
|
+
PyObject_CallObject,
|
|
18
|
+
clock_t,
|
|
19
|
+
clock,
|
|
20
|
+
swmm_Object,
|
|
21
|
+
swmm_NodeType,
|
|
22
|
+
swmm_LinkType,
|
|
23
|
+
swmm_GageProperty,
|
|
24
|
+
swmm_SubcatchProperty,
|
|
25
|
+
swmm_NodeProperty,
|
|
26
|
+
swmm_LinkProperty,
|
|
27
|
+
swmm_SystemProperty,
|
|
28
|
+
swmm_FlowUnitsProperty,
|
|
29
|
+
swmm_API_Errors,
|
|
30
|
+
progress_callback,
|
|
31
|
+
swmm_run,
|
|
32
|
+
swmm_run_with_callback,
|
|
33
|
+
swmm_open,
|
|
34
|
+
swmm_start,
|
|
35
|
+
swmm_step,
|
|
36
|
+
swmm_stride,
|
|
37
|
+
swmm_useHotStart,
|
|
38
|
+
swmm_saveHotStart,
|
|
39
|
+
swmm_end,
|
|
40
|
+
swmm_report,
|
|
41
|
+
swmm_close,
|
|
42
|
+
swmm_getMassBalErr,
|
|
43
|
+
swmm_getVersion,
|
|
44
|
+
swmm_getError,
|
|
45
|
+
swmm_getErrorFromCode,
|
|
46
|
+
swmm_getWarnings,
|
|
47
|
+
swmm_getCount,
|
|
48
|
+
swmm_getName,
|
|
49
|
+
swmm_getIndex,
|
|
50
|
+
swmm_getValue,
|
|
51
|
+
swmm_getValueExpanded,
|
|
52
|
+
swmm_setValue,
|
|
53
|
+
swmm_setValueExpanded,
|
|
54
|
+
swmm_getSavedValue,
|
|
55
|
+
swmm_writeLine,
|
|
56
|
+
swmm_decodeDate,
|
|
57
|
+
swmm_encodeDate
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
class SWMMObjects(Enum):
|
|
61
|
+
"""
|
|
62
|
+
Enumeration of SWMM objects.
|
|
63
|
+
|
|
64
|
+
:ivar RAIN_GAGE: Raingage object
|
|
65
|
+
:type RAIN_GAGE: int
|
|
66
|
+
:ivar SUBCATCHMENT: Subcatchment object
|
|
67
|
+
:type SUBCATCHMENT: int
|
|
68
|
+
:ivar NODE: Node object
|
|
69
|
+
:type NODE: int
|
|
70
|
+
:ivar LINK: Link object
|
|
71
|
+
:type LINK: int
|
|
72
|
+
:ivar AQUIFER: Aquifer object
|
|
73
|
+
:type AQUIFER: int
|
|
74
|
+
:ivar SNOWPACK: Snowpack object
|
|
75
|
+
:type SNOWPACK: int
|
|
76
|
+
:ivar UNIT_HYDROGRAPH: Unit hydrograph object
|
|
77
|
+
:type UNIT_HYDROGRAPH: int
|
|
78
|
+
:ivar LID: LID object
|
|
79
|
+
:type LID: int
|
|
80
|
+
:ivar STREET: Street object
|
|
81
|
+
:type STREET: int
|
|
82
|
+
:ivar INLET: Inlet object
|
|
83
|
+
:type INLET: int
|
|
84
|
+
:ivar TRANSECT: Transect object
|
|
85
|
+
:type TRANSECT: int
|
|
86
|
+
:ivar XSECTION_SHAPE: Cross-section shape object
|
|
87
|
+
:type XSECTION_SHAPE: int
|
|
88
|
+
:ivar CONTROL_RULE: Control rule object
|
|
89
|
+
:type CONTROL_RULE: int
|
|
90
|
+
:ivar POLLUTANT: Pollutant object
|
|
91
|
+
:type POLLUTANT: int
|
|
92
|
+
:ivar LANDUSE: Land use object
|
|
93
|
+
:type LANDUSE: int
|
|
94
|
+
:ivar CURVE: Curve object
|
|
95
|
+
:type CURVE: int
|
|
96
|
+
:ivar TIMESERIES: Time series object
|
|
97
|
+
:type TIMESERIES: int
|
|
98
|
+
:ivar TIME_PATTERN: Time pattern object
|
|
99
|
+
:type TIME_PATTERN: int
|
|
100
|
+
:ivar SYSTEM: System object
|
|
101
|
+
:type SYSTEM: int
|
|
102
|
+
"""
|
|
103
|
+
RAIN_GAGE = swmm_Object.swmm_GAGE
|
|
104
|
+
SUBCATCHMENT = swmm_Object.swmm_SUBCATCH
|
|
105
|
+
NODE = swmm_Object.swmm_NODE
|
|
106
|
+
LINK = swmm_Object.swmm_LINK
|
|
107
|
+
AQUIFER = swmm_Object.swmm_AQUIFER
|
|
108
|
+
SNOWPACK = swmm_Object.swmm_SNOWPACK
|
|
109
|
+
UNIT_HYDROGRAPH = swmm_Object.swmm_UNIT_HYDROGRAPH
|
|
110
|
+
LID = swmm_Object.swmm_LID
|
|
111
|
+
STREET = swmm_Object.swmm_STREET
|
|
112
|
+
INLET = swmm_Object.swmm_INLET
|
|
113
|
+
TRANSECT = swmm_Object.swmm_TRANSECT
|
|
114
|
+
XSECTION_SHAPE = swmm_Object.smmm_XSECTION_SHAPE
|
|
115
|
+
CONTROL_RULE = swmm_Object.swmm_CONTROL_RULE
|
|
116
|
+
POLLUTANT = swmm_Object.swmm_POLLUTANT
|
|
117
|
+
LANDUSE = swmm_Object.swmm_LANDUSE
|
|
118
|
+
CURVE = swmm_Object.swmm_CURVE
|
|
119
|
+
TIMESERIES = swmm_Object.swmm_TIMESERIES
|
|
120
|
+
TIME_PATTERN = swmm_Object.swmm_TIME_PATTERN
|
|
121
|
+
SYSTEM = swmm_Object.swmm_SYSTEM
|
|
122
|
+
|
|
123
|
+
class SWMMNodeTypes(Enum):
|
|
124
|
+
"""
|
|
125
|
+
Enumeration of SWMM node types.
|
|
126
|
+
|
|
127
|
+
:ivar JUNCTION: Junction node
|
|
128
|
+
:type JUNCTION: int
|
|
129
|
+
:ivar OUTFALL: Outfall node
|
|
130
|
+
:type OUTFALL: int
|
|
131
|
+
:ivar STORAGE: Storage node
|
|
132
|
+
:type STORAGE: int
|
|
133
|
+
:ivar DIVIDER: Divider node
|
|
134
|
+
:type DIVIDER: int
|
|
135
|
+
"""
|
|
136
|
+
JUNCTION = swmm_NodeType.swmm_JUNCTION
|
|
137
|
+
OUTFALL = swmm_NodeType.swmm_OUTFALL
|
|
138
|
+
STORAGE = swmm_NodeType.swmm_STORAGE
|
|
139
|
+
DIVIDER = swmm_NodeType.swmm_DIVIDER
|
|
140
|
+
|
|
141
|
+
class SWMMRainGageProperties(Enum):
|
|
142
|
+
"""
|
|
143
|
+
Enumeration of SWMM raingage properties.
|
|
144
|
+
|
|
145
|
+
:ivar GAGE_TOTAL_PRECIPITATION: Total precipitation
|
|
146
|
+
:type GAGE_TOTAL_PRECIPITATION: int
|
|
147
|
+
:ivar GAGE_RAINFALL: Rainfall
|
|
148
|
+
:type GAGE_RAINFALL: int
|
|
149
|
+
:ivar GAGE_SNOWFALL: Snowfall
|
|
150
|
+
:type GAGE_SNOWFALL: int
|
|
151
|
+
"""
|
|
152
|
+
GAGE_TOTAL_PRECIPITATION = swmm_GageProperty.swmm_GAGE_TOTAL_PRECIPITATION # Total precipitation
|
|
153
|
+
GAGE_RAINFALL = swmm_GageProperty.swmm_GAGE_RAINFALL # Rainfall
|
|
154
|
+
GAGE_SNOWFALL = swmm_GageProperty.swmm_GAGE_SNOWFALL # Snowfall
|
|
155
|
+
|
|
156
|
+
class SWMMSubcatchmentProperties(Enum):
|
|
157
|
+
"""
|
|
158
|
+
Enumeration of SWMM subcatchment properties.
|
|
159
|
+
|
|
160
|
+
:ivar AREA: Area
|
|
161
|
+
:type AREA: int
|
|
162
|
+
:ivar RAINGAGE: Raingage
|
|
163
|
+
:type RAINGAGE: int
|
|
164
|
+
:ivar RAINFALL: Rainfall
|
|
165
|
+
:type RAINFALL: int
|
|
166
|
+
:ivar EVAPORATION: Evaporation
|
|
167
|
+
:type EVAPORATION: int
|
|
168
|
+
:ivar INFILTRATION: Infiltration
|
|
169
|
+
:type INFILTRATION: int
|
|
170
|
+
:ivar RUNOFF: Runoff
|
|
171
|
+
:type RUNOFF: int
|
|
172
|
+
:ivar REPORT_FLAG: Report flag
|
|
173
|
+
:type REPORT_FLAG: int
|
|
174
|
+
:ivar WIDTH: Width
|
|
175
|
+
:type WIDTH: int
|
|
176
|
+
:ivar SLOPE: Slope
|
|
177
|
+
:type SLOPE: int
|
|
178
|
+
:ivar CURB_LENGTH: Curb length
|
|
179
|
+
:type CURB_LENGTH: int
|
|
180
|
+
:ivar API_RAINFALL: API Rainfall
|
|
181
|
+
:type API_RAINFALL: int
|
|
182
|
+
:ivar API_SNOWFALL: API Snowfall
|
|
183
|
+
:type API_SNOWFALL: int
|
|
184
|
+
:ivar POLLUTANT_BUILDUP: Pollutant buildup
|
|
185
|
+
:type POLLUTANT_BUILDUP: int
|
|
186
|
+
:ivar EXTERNAL_POLLUTANT_BUILDUP: External pollutant buildup
|
|
187
|
+
:type EXTERNAL_POLLUTANT_BUILDUP: int
|
|
188
|
+
:ivar POLLUTANT_RUNOFF_CONCENTRATION: Pollutant runoff concentration
|
|
189
|
+
:type POLLUTANT_RUNOFF_CONCENTRATION: int
|
|
190
|
+
:ivar POLLUTANT_PONDED_CONCENTRATION: Pollutant ponded concentration
|
|
191
|
+
:type POLLUTANT_PONDED_CONCENTRATION: int
|
|
192
|
+
:ivar POLLUTANT_TOTAL_LOAD: Pollutant total load
|
|
193
|
+
:type POLLUTANT_TOTAL_LOAD: int
|
|
194
|
+
"""
|
|
195
|
+
AREA = swmm_SubcatchProperty.swmm_SUBCATCH_AREA
|
|
196
|
+
RAINGAGE = swmm_SubcatchProperty.swmm_SUBCATCH_RAINGAGE
|
|
197
|
+
RAINFALL = swmm_SubcatchProperty.swmm_SUBCATCH_RAINFALL
|
|
198
|
+
EVAPORATION = swmm_SubcatchProperty.swmm_SUBCATCH_EVAP
|
|
199
|
+
INFILTRATION = swmm_SubcatchProperty.swmm_SUBCATCH_INFIL
|
|
200
|
+
RUNOFF = swmm_SubcatchProperty.swmm_SUBCATCH_RUNOFF
|
|
201
|
+
REPORT_FLAG = swmm_SubcatchProperty.swmm_SUBCATCH_RPTFLAG
|
|
202
|
+
WIDTH = swmm_SubcatchProperty.swmm_SUBCATCH_WIDTH
|
|
203
|
+
SLOPE = swmm_SubcatchProperty.swmm_SUBCATCH_SLOPE
|
|
204
|
+
CURB_LENGTH = swmm_SubcatchProperty.swmm_SUBCATCH_CURB_LENGTH
|
|
205
|
+
API_RAINFALL = swmm_SubcatchProperty.swmm_SUBCATCH_API_RAINFALL
|
|
206
|
+
API_SNOWFALL = swmm_SubcatchProperty.swmm_SUBCATCH_API_SNOWFALL
|
|
207
|
+
POLLUTANT_BUILDUP = swmm_SubcatchProperty.swmm_SUBCATCH_POLLUTANT_BUILDUP
|
|
208
|
+
EXTERNAL_POLLUTANT_BUILDUP = swmm_SubcatchProperty.swmm_SUBCATCH_EXTERNAL_POLLUTANT_BUILDUP
|
|
209
|
+
POLLUTANT_RUNOFF_CONCENTRATION = swmm_SubcatchProperty.swmm_SUBCATCH_POLLUTANT_RUNOFF_CONCENTRATION
|
|
210
|
+
POLLUTANT_PONDED_CONCENTRATION = swmm_SubcatchProperty.swmm_SUBCATCH_POLLUTANT_PONDED_CONCENTRATION
|
|
211
|
+
POLLUTANT_TOTAL_LOAD = swmm_SubcatchProperty.swmm_SUBCATCH_POLLUTANT_TOTAL_LOAD
|
|
212
|
+
|
|
213
|
+
class SWMMNodeProperties(Enum):
|
|
214
|
+
"""
|
|
215
|
+
Enumeration of SWMM node properties.
|
|
216
|
+
|
|
217
|
+
:ivar TYPE: Node type
|
|
218
|
+
:type TYPE: int
|
|
219
|
+
:ivar INVERT_ELEVATION: Invert elevation
|
|
220
|
+
:type INVERT_ELEVATION: int
|
|
221
|
+
:ivar MAX_DEPTH: Maximum depth
|
|
222
|
+
:type MAX_DEPTH: int
|
|
223
|
+
:ivar DEPTH: Depth
|
|
224
|
+
:type DEPTH: int
|
|
225
|
+
:ivar HYDRAULIC_HEAD: Hydraulic head
|
|
226
|
+
:type HYDRAULIC_HEAD: int
|
|
227
|
+
:ivar VOLUME: Volume
|
|
228
|
+
:type VOLUME: int
|
|
229
|
+
:ivar LATERAL_INFLOW: Lateral inflow
|
|
230
|
+
:type LATERAL_INFLOW: int
|
|
231
|
+
:ivar TOTAL_INFLOW: Total inflow
|
|
232
|
+
:type TOTAL_INFLOW: int
|
|
233
|
+
:ivar FLOODING: Flooding
|
|
234
|
+
:type FLOODING: int
|
|
235
|
+
:ivar REPORT_FLAG: Report flag
|
|
236
|
+
:type REPORT_FLAG: int
|
|
237
|
+
:ivar SURCHARGE_DEPTH: Surcharge depth
|
|
238
|
+
:type SURCHARGE_DEPTH: int
|
|
239
|
+
:ivar PONDING_AREA: Ponding area
|
|
240
|
+
:type PONDING_AREA: int
|
|
241
|
+
:ivar INITIAL_DEPTH: Initial depth
|
|
242
|
+
:type INITIAL_DEPTH: int
|
|
243
|
+
:ivar POLLUTANT_CONCENTRATION: Pollutant concentration
|
|
244
|
+
:type POLLUTANT_CONCENTRATION: int
|
|
245
|
+
:ivar POLLUTANT_LATERAL_MASS_FLUX: Pollutant lateral mass flux
|
|
246
|
+
:type POLLUTANT_LATERAL_MASS_FLUX: int
|
|
247
|
+
"""
|
|
248
|
+
TYPE = swmm_NodeProperty.swmm_NODE_TYPE
|
|
249
|
+
INVERT_ELEVATION = swmm_NodeProperty.swmm_NODE_ELEV
|
|
250
|
+
MAX_DEPTH = swmm_NodeProperty.swmm_NODE_MAXDEPTH
|
|
251
|
+
DEPTH = swmm_NodeProperty.swmm_NODE_DEPTH
|
|
252
|
+
HYDRAULIC_HEAD = swmm_NodeProperty.swmm_NODE_HEAD
|
|
253
|
+
VOLUME = swmm_NodeProperty.swmm_NODE_VOLUME
|
|
254
|
+
LATERAL_INFLOW = swmm_NodeProperty.swmm_NODE_LATFLOW
|
|
255
|
+
TOTAL_INFLOW = swmm_NodeProperty.swmm_NODE_INFLOW
|
|
256
|
+
FLOODING = swmm_NodeProperty.swmm_NODE_OVERFLOW
|
|
257
|
+
REPORT_FLAG = swmm_NodeProperty.swmm_NODE_RPTFLAG
|
|
258
|
+
SURCHARGE_DEPTH = swmm_NodeProperty.swmm_NODE_SURCHARGE_DEPTH
|
|
259
|
+
PONDING_AREA = swmm_NodeProperty.swmm_NODE_PONDED_AREA
|
|
260
|
+
INITIAL_DEPTH = swmm_NodeProperty.swmm_NODE_INITIAL_DEPTH
|
|
261
|
+
POLLUTANT_CONCENTRATION = swmm_NodeProperty.swmm_NODE_POLLUTANT_CONCENTRATION # Pollutant concentration
|
|
262
|
+
POLLUTANT_LATERAL_MASS_FLUX = swmm_NodeProperty.swmm_NODE_POLLUTANT_LATMASS_FLUX # Pollutant inflow concentration
|
|
263
|
+
|
|
264
|
+
class SWMMLinkProperties(Enum):
|
|
265
|
+
"""
|
|
266
|
+
Enumeration of SWMM link properties.
|
|
267
|
+
|
|
268
|
+
:ivar TYPE: Link type
|
|
269
|
+
:type TYPE: int
|
|
270
|
+
:ivar START_NODE: Start node
|
|
271
|
+
:type START_NODE: int
|
|
272
|
+
:ivar END_NODE: End node
|
|
273
|
+
:type END_NODE: int
|
|
274
|
+
:ivar LENGTH: Length
|
|
275
|
+
:type LENGTH: int
|
|
276
|
+
:ivar SLOPE: Slope
|
|
277
|
+
:type SLOPE: int
|
|
278
|
+
:ivar FULL_DEPTH: Full depth
|
|
279
|
+
:type FULL_DEPTH: int
|
|
280
|
+
:ivar FULL_FLOW: Full flow
|
|
281
|
+
:type FULL_FLOW: int
|
|
282
|
+
:ivar SETTING: Setting
|
|
283
|
+
:type SETTING: int
|
|
284
|
+
:ivar TIME_OPEN: Time open
|
|
285
|
+
:type TIME_OPEN: int
|
|
286
|
+
:ivar TIME_CLOSED: Time closed
|
|
287
|
+
:type TIME_CLOSED: int
|
|
288
|
+
:ivar FLOW: Flow
|
|
289
|
+
:type FLOW: int
|
|
290
|
+
:ivar DEPTH: Depth
|
|
291
|
+
:type DEPTH: int
|
|
292
|
+
:ivar VELOCITY: Velocity
|
|
293
|
+
:type VELOCITY: int
|
|
294
|
+
:ivar TOP_WIDTH: Top width
|
|
295
|
+
:type TOP_WIDTH: int
|
|
296
|
+
:ivar REPORT_FLAG: Report flag
|
|
297
|
+
:type REPORT_FLAG: int
|
|
298
|
+
:ivar START_NODE_OFFSET: Start node offset
|
|
299
|
+
:type START_NODE_OFFSET: int
|
|
300
|
+
:ivar END_NODE_OFFSET: End node offset
|
|
301
|
+
:type END_NODE_OFFSET: int
|
|
302
|
+
:ivar INITIAL_FLOW: Initial flow
|
|
303
|
+
:type INITIAL_FLOW: int
|
|
304
|
+
:ivar FLOW_LIMIT: Flow limit
|
|
305
|
+
:type FLOW_LIMIT: int
|
|
306
|
+
:ivar INLET_LOSS: Inlet loss
|
|
307
|
+
:type INLET_LOSS: int
|
|
308
|
+
:ivar OUTLET_LOSS: Outlet loss
|
|
309
|
+
:type OUTLET_LOSS: int
|
|
310
|
+
:ivar AVERAGE_LOSS: Average loss
|
|
311
|
+
:type AVERAGE_LOSS: int
|
|
312
|
+
:ivar SEEPAGE_RATE: Seepage rate
|
|
313
|
+
:type SEEPAGE_RATE: int
|
|
314
|
+
:ivar HAS_FLAPGATE: Has flapgate
|
|
315
|
+
:type HAS_FLAPGATE: int
|
|
316
|
+
:ivar POLLUTANT_CONCENTRATION: Pollutant concentration
|
|
317
|
+
:type POLLUTANT_CONCENTRATION: int
|
|
318
|
+
:ivar POLLUTANT_LOAD: Pollutant load
|
|
319
|
+
:type POLLUTANT_LOAD: int
|
|
320
|
+
:ivar POLLUTANT_LATERAL_MASS_FLUX: Pollutant lateral mass flux
|
|
321
|
+
:type POLLUTANT_LATERAL_MASS_FLUX: int
|
|
322
|
+
"""
|
|
323
|
+
TYPE = swmm_LinkProperty.swmm_LINK_TYPE
|
|
324
|
+
START_NODE = swmm_LinkProperty.swmm_LINK_NODE1
|
|
325
|
+
END_NODE = swmm_LinkProperty.swmm_LINK_NODE2
|
|
326
|
+
LENGTH = swmm_LinkProperty.swmm_LINK_LENGTH
|
|
327
|
+
SLOPE = swmm_LinkProperty.swmm_LINK_SLOPE
|
|
328
|
+
FULL_DEPTH = swmm_LinkProperty.swmm_LINK_FULLDEPTH
|
|
329
|
+
FULL_FLOW = swmm_LinkProperty.swmm_LINK_FULLFLOW
|
|
330
|
+
SETTING = swmm_LinkProperty.swmm_LINK_SETTING
|
|
331
|
+
TIME_OPEN = swmm_LinkProperty.swmm_LINK_TIMEOPEN
|
|
332
|
+
TIME_CLOSED = swmm_LinkProperty.swmm_LINK_TIMECLOSED
|
|
333
|
+
FLOW = swmm_LinkProperty.swmm_LINK_FLOW
|
|
334
|
+
DEPTH = swmm_LinkProperty.swmm_LINK_DEPTH
|
|
335
|
+
VELOCITY = swmm_LinkProperty.swmm_LINK_VELOCITY
|
|
336
|
+
TOP_WIDTH = swmm_LinkProperty.swmm_LINK_TOPWIDTH
|
|
337
|
+
VOLUME = swmm_LinkProperty.swmm_LINK_VOLUME
|
|
338
|
+
CAPACITY = swmm_LinkProperty.swmm_LINK_CAPACITY
|
|
339
|
+
REPORT_FLAG = swmm_LinkProperty.swmm_LINK_RPTFLAG
|
|
340
|
+
START_NODE_OFFSET = swmm_LinkProperty.swmm_LINK_OFFSET1
|
|
341
|
+
END_NODE_OFFSET = swmm_LinkProperty.swmm_LINK_OFFSET2
|
|
342
|
+
INITIAL_FLOW = swmm_LinkProperty.swmm_LINK_INITIAL_FLOW
|
|
343
|
+
FLOW_LIMIT = swmm_LinkProperty.swmm_LINK_FLOW_LIMIT
|
|
344
|
+
INLET_LOSS = swmm_LinkProperty.swmm_LINK_INLET_LOSS
|
|
345
|
+
OUTLET_LOSS = swmm_LinkProperty.swmm_LINK_OUTLET_LOSS
|
|
346
|
+
AVERAGE_LOSS = swmm_LinkProperty.swmm_LINK_AVERAGE_LOSS
|
|
347
|
+
SEEPAGE_RATE = swmm_LinkProperty.swmm_LINK_SEEPAGE_RATE
|
|
348
|
+
HAS_FLAPGATE = swmm_LinkProperty.swmm_LINK_HAS_FLAPGATE
|
|
349
|
+
POLLUTANT_CONCENTRATION = swmm_LinkProperty.swmm_LINK_POLLUTANT_CONCENTRATION # Pollutant concentration
|
|
350
|
+
POLLUTANT_LOAD = swmm_LinkProperty.swmm_LINK_POLLUTANT_LOAD # Pollutant load
|
|
351
|
+
POLLUTANT_LATERAL_MASS_FLUX = swmm_LinkProperty.swmm_LINK_POLLUTANT_LATMASS_FLUX # Pollutant lateral mass flux
|
|
352
|
+
|
|
353
|
+
class SWMMLinkTypes(Enum):
|
|
354
|
+
"""
|
|
355
|
+
Enumeration of SWMM link types.
|
|
356
|
+
|
|
357
|
+
:ivar CONDUIT: Conduit link
|
|
358
|
+
:type CONDUIT: int
|
|
359
|
+
:ivar PUMP: Pump link
|
|
360
|
+
:type PUMP: int
|
|
361
|
+
:ivar ORIFICE: Orifice link
|
|
362
|
+
:type ORIFICE: int
|
|
363
|
+
:ivar WEIR: Weir link
|
|
364
|
+
:type WEIR: int
|
|
365
|
+
:ivar OUTLET: Outlet link
|
|
366
|
+
:type OUTLET: int
|
|
367
|
+
"""
|
|
368
|
+
CONDUIT = swmm_LinkType.swmm_CONDUIT
|
|
369
|
+
PUMP = swmm_LinkType.swmm_PUMP
|
|
370
|
+
ORIFICE = swmm_LinkType.swmm_ORIFICE
|
|
371
|
+
WEIR = swmm_LinkType.swmm_WEIR
|
|
372
|
+
OUTLET = swmm_LinkType.swmm_OUTLET
|
|
373
|
+
|
|
374
|
+
class SWMMSystemProperties(Enum):
|
|
375
|
+
"""
|
|
376
|
+
Enumeration of SWMM system properties.
|
|
377
|
+
|
|
378
|
+
:ivar START_DATE: Start date for the simulation
|
|
379
|
+
:type START_DATE: int
|
|
380
|
+
:ivar CURRENT_DATE: Current date for the simulation
|
|
381
|
+
:type CURRENT_DATE: int
|
|
382
|
+
:ivar ELAPSED_TIME: Elapsed time for the simulation
|
|
383
|
+
:type ELAPSED_TIME: int
|
|
384
|
+
:ivar ROUTING_STEP: Routing time step
|
|
385
|
+
:type ROUTING_STEP: int
|
|
386
|
+
:ivar MAX_ROUTING_STEP: Maximum routing time step
|
|
387
|
+
:type MAX_ROUTING_STEP: int
|
|
388
|
+
:ivar REPORT_STEP: Report time step
|
|
389
|
+
:type REPORT_STEP: int
|
|
390
|
+
:ivar TOTAL_STEPS: Total number of steps
|
|
391
|
+
:type TOTAL_STEPS: int
|
|
392
|
+
:ivar NO_REPORT_FLAG: No report flag
|
|
393
|
+
:type NO_REPORT_FLAG: int
|
|
394
|
+
:ivar FLOW_UNITS: Flow units
|
|
395
|
+
:type FLOW_UNITS: int
|
|
396
|
+
:ivar END_DATE: End date for the simulation
|
|
397
|
+
:type END_DATE: int
|
|
398
|
+
:ivar REPORT_START_DATE: Report start date
|
|
399
|
+
:type REPORT_START_DATE: int
|
|
400
|
+
:ivar UNIT_SYSTEM: Unit system
|
|
401
|
+
:type UNIT_SYSTEM: int
|
|
402
|
+
:ivar SURCHARGE_METHOD: Surcharge method
|
|
403
|
+
:type SURCHARGE_METHOD: int
|
|
404
|
+
:ivar ALLOW_PONDING: Allow ponding
|
|
405
|
+
:type ALLOW_PONDING: int
|
|
406
|
+
:ivar INTERTIAL_DAMPING: Inertial damping
|
|
407
|
+
:type INTERTIAL_DAMPING: int
|
|
408
|
+
:ivar NORMAL_FLOW_LIMITED: Normal flow limited
|
|
409
|
+
:type NORMAL_FLOW_LIMITED: int
|
|
410
|
+
:ivar SKIP_STEADY_STATE: Skip steady state
|
|
411
|
+
:type SKIP_STEADY_STATE: int
|
|
412
|
+
:ivar IGNORE_RAINFALL: Ignore rainfall
|
|
413
|
+
:type IGNORE_RAINFALL: int
|
|
414
|
+
:ivar IGNORE_RDII: Ignore RDII
|
|
415
|
+
:type IGNORE_RDII: int
|
|
416
|
+
:ivar IGNORE_SNOWMELT: Ignore snowmelt
|
|
417
|
+
:type IGNORE_SNOWMELT: int
|
|
418
|
+
:ivar IGNORE_GROUNDWATER: Ignore groundwater
|
|
419
|
+
:type IGNORE_GROUNDWATER: int
|
|
420
|
+
:ivar IGNORE_ROUTING: Ignore routing
|
|
421
|
+
:type IGNORE_ROUTING: int
|
|
422
|
+
:ivar IGNORE_QUALITY: Ignore quality
|
|
423
|
+
:type IGNORE_QUALITY: int
|
|
424
|
+
:ivar RULE_STEP: Rule step
|
|
425
|
+
:type RULE_STEP: int
|
|
426
|
+
:ivar SWEEP_START: Sweep start
|
|
427
|
+
:type SWEEP_START: int
|
|
428
|
+
:ivar SWEEP_END: Sweep end
|
|
429
|
+
:type SWEEP_END: int
|
|
430
|
+
:ivar MAX_TRIALS: Maximum trials
|
|
431
|
+
:type MAX_TRIALS: int
|
|
432
|
+
:ivar NUM_THREADS: Number of threads
|
|
433
|
+
:type NUM_THREADS: int
|
|
434
|
+
:ivar MIN_ROUTE_STEP: Minimum routing step
|
|
435
|
+
:type MIN_ROUTE_STEP: int
|
|
436
|
+
:ivar LENGTHENING_STEP: Lengthening step
|
|
437
|
+
:type LENGTHENING_STEP: int
|
|
438
|
+
:ivar START_DRY_DAYS: Start dry days
|
|
439
|
+
:type START_DRY_DAYS: int
|
|
440
|
+
:ivar COURANT_FACTOR: Courant factor
|
|
441
|
+
:type COURANT_FACTOR: int
|
|
442
|
+
:ivar MIN_SURF_AREA: Minimum surface area
|
|
443
|
+
:type MIN_SURF_AREA: int
|
|
444
|
+
:ivar MIN_SLOPE: Minimum slope
|
|
445
|
+
:type MIN_SLOPE: int
|
|
446
|
+
:ivar RUNOFF_ERROR: Runoff error
|
|
447
|
+
:type RUNOFF_ERROR: int
|
|
448
|
+
:ivar FLOW_ERROR: Flow error
|
|
449
|
+
:type FLOW_ERROR: int
|
|
450
|
+
:ivar QUAL_ERROR: Quality error
|
|
451
|
+
:type QUAL_ERROR: int
|
|
452
|
+
:ivar HEAD_TOL: Head tolerance
|
|
453
|
+
:type HEAD_TOL: int
|
|
454
|
+
:ivar SYS_FLOW_TOL: System flow tolerance
|
|
455
|
+
:type SYS_FLOW_TOL: int
|
|
456
|
+
:ivar LAT_FLOW_TOL: Lateral flow tolerance
|
|
457
|
+
:type LAT_FLOW_TOL: int
|
|
458
|
+
|
|
459
|
+
"""
|
|
460
|
+
START_DATE = swmm_SystemProperty.swmm_STARTDATE
|
|
461
|
+
CURRENT_DATE = swmm_SystemProperty.swmm_CURRENTDATE
|
|
462
|
+
ELAPSED_TIME = swmm_SystemProperty.swmm_ELAPSEDTIME
|
|
463
|
+
ROUTING_STEP = swmm_SystemProperty.swmm_ROUTESTEP
|
|
464
|
+
MAX_ROUTING_STEP = swmm_SystemProperty.swmm_MAXROUTESTEP
|
|
465
|
+
REPORT_STEP = swmm_SystemProperty.swmm_REPORTSTEP
|
|
466
|
+
TOTAL_STEPS = swmm_SystemProperty.swmm_TOTALSTEPS
|
|
467
|
+
NO_REPORT_FLAG = swmm_SystemProperty.swmm_NOREPORT
|
|
468
|
+
FLOW_UNITS = swmm_SystemProperty.swmm_FLOWUNITS
|
|
469
|
+
END_DATE = swmm_SystemProperty.swmm_ENDDATE
|
|
470
|
+
REPORT_START_DATE = swmm_SystemProperty.swmm_REPORTSTART
|
|
471
|
+
UNIT_SYSTEM = swmm_SystemProperty.swmm_UNITSYSTEM
|
|
472
|
+
SURCHARGE_METHOD = swmm_SystemProperty.swmm_SURCHARGEMETHOD
|
|
473
|
+
ALLOW_PONDING = swmm_SystemProperty.swmm_ALLOWPONDING
|
|
474
|
+
INTERTIAL_DAMPING = swmm_SystemProperty.swmm_INERTIADAMPING
|
|
475
|
+
NORMAL_FLOW_LIMITED = swmm_SystemProperty.swmm_NORMALFLOWLTD
|
|
476
|
+
SKIP_STEADY_STATE = swmm_SystemProperty.swmm_SKIPSTEADYSTATE
|
|
477
|
+
IGNORE_RAINFALL = swmm_SystemProperty.swmm_IGNORERAINFALL
|
|
478
|
+
IGNORE_RDII = swmm_SystemProperty.swmm_IGNORERDII
|
|
479
|
+
IGNORE_SNOWMELT = swmm_SystemProperty.swmm_IGNORESNOWMELT
|
|
480
|
+
IGNORE_GROUNDWATER = swmm_SystemProperty.swmm_IGNOREGROUNDWATER
|
|
481
|
+
IGNORE_ROUTING = swmm_SystemProperty.swmm_IGNOREROUTING
|
|
482
|
+
IGNORE_QUALITY = swmm_SystemProperty.swmm_IGNOREQUALITY
|
|
483
|
+
ERROR_CODE = swmm_SystemProperty.swmm_ERROR_CODE
|
|
484
|
+
RULE_STEP = swmm_SystemProperty.swmm_RULESTEP
|
|
485
|
+
SWEEP_START = swmm_SystemProperty.swmm_SWEEPSTART
|
|
486
|
+
SWEEP_END = swmm_SystemProperty.swmm_SWEEPEND
|
|
487
|
+
MAX_TRIALS = swmm_SystemProperty.swmm_MAXTRIALS
|
|
488
|
+
NUM_THREADS = swmm_SystemProperty.swmm_NUMTHREADS
|
|
489
|
+
MIN_ROUTE_STEP = swmm_SystemProperty.swmm_MINROUTESTEP
|
|
490
|
+
LENGTHENING_STEP = swmm_SystemProperty.swmm_LENGTHENINGSTEP
|
|
491
|
+
START_DRY_DAYS = swmm_SystemProperty.swmm_STARTDRYDAYS
|
|
492
|
+
COURANT_FACTOR = swmm_SystemProperty.swmm_COURANTFACTOR
|
|
493
|
+
MIN_SURF_AREA = swmm_SystemProperty.swmm_MINSURFAREA
|
|
494
|
+
MIN_SLOPE = swmm_SystemProperty.swmm_MINSLOPE
|
|
495
|
+
RUNOFF_ERROR = swmm_SystemProperty.swmm_RUNOFFERROR
|
|
496
|
+
FLOW_ERROR = swmm_SystemProperty.swmm_FLOWERROR
|
|
497
|
+
QUAL_ERROR = swmm_SystemProperty.swmm_QUALERROR
|
|
498
|
+
HEAD_TOL = swmm_SystemProperty.swmm_HEADTOL
|
|
499
|
+
SYS_FLOW_TOL = swmm_SystemProperty.swmm_SYSFLOWTOL
|
|
500
|
+
LAT_FLOW_TOL = swmm_SystemProperty.swmm_LATFLOWTOL
|
|
501
|
+
|
|
502
|
+
class SWMMFlowUnits(Enum):
|
|
503
|
+
"""
|
|
504
|
+
Enumeration of SWMM flow units.
|
|
505
|
+
|
|
506
|
+
:ivar CFS: Cubic feet per second
|
|
507
|
+
:type CFS: int
|
|
508
|
+
:ivar GPM: Gallons per minute
|
|
509
|
+
:type GPM: int
|
|
510
|
+
:ivar MGD: Million gallons per day
|
|
511
|
+
:type MGD: int
|
|
512
|
+
:ivar CMS: Cubic meters per second
|
|
513
|
+
:type CMS: int
|
|
514
|
+
:ivar LPS: Liters per second
|
|
515
|
+
:type LPS: int
|
|
516
|
+
:ivar MLD: Million liters per day
|
|
517
|
+
:type MLD: int
|
|
518
|
+
"""
|
|
519
|
+
CFS = swmm_FlowUnitsProperty.swmm_CFS
|
|
520
|
+
GPM = swmm_FlowUnitsProperty.swmm_GPM
|
|
521
|
+
MGD = swmm_FlowUnitsProperty.swmm_MGD
|
|
522
|
+
CMS = swmm_FlowUnitsProperty.swmm_CMS
|
|
523
|
+
LPS = swmm_FlowUnitsProperty.swmm_LPS
|
|
524
|
+
MLD = swmm_FlowUnitsProperty.swmm_MLD
|
|
525
|
+
|
|
526
|
+
class SWMMAPIErrors(Enum):
|
|
527
|
+
"""
|
|
528
|
+
Enumeration of SWMM API errors.
|
|
529
|
+
|
|
530
|
+
:ivar PROJECT_NOT_OPENED: Project not opened
|
|
531
|
+
:type PROJECT_NOT_OPENED: int
|
|
532
|
+
:ivar SIMULATION_NOT_STARTED: Simulation not started
|
|
533
|
+
:type SIMULATION_NOT_STARTED: int
|
|
534
|
+
:ivar SIMULATION_NOT_ENDED: Simulation not ended
|
|
535
|
+
:type SIMULATION_NOT_ENDED: int
|
|
536
|
+
"""
|
|
537
|
+
PROJECT_NOT_OPENED = swmm_API_Errors.ERR_API_NOT_OPEN # API not open
|
|
538
|
+
SIMULATION_NOT_STARTED = swmm_API_Errors.ERR_API_NOT_STARTED # API not started
|
|
539
|
+
SIMULATION_NOT_ENDED = swmm_API_Errors.ERR_API_NOT_ENDED # API not ended
|
|
540
|
+
OBJECT_TYPE = swmm_API_Errors.ERR_API_OBJECT_TYPE # Invalid object type
|
|
541
|
+
OBJECT_INDEX = swmm_API_Errors.ERR_API_OBJECT_INDEX # Invalid object index
|
|
542
|
+
OBJECT_NAME = swmm_API_Errors.ERR_API_OBJECT_NAME # Invalid object name
|
|
543
|
+
PROPERTY_TYPE = swmm_API_Errors.ERR_API_PROPERTY_TYPE # Invalid property type
|
|
544
|
+
PROPERTY_VALUE = swmm_API_Errors.ERR_API_PROPERTY_VALUE # Invalid property value
|
|
545
|
+
TIME_PERIOD = swmm_API_Errors.ERR_API_TIME_PERIOD # Invalid time period
|
|
546
|
+
HOTSTART_FILE_OPEN = swmm_API_Errors.ERR_API_HOTSTART_FILE_OPEN # Error opening hotstart file
|
|
547
|
+
HOTSTART_FILE_FORMAT = swmm_API_Errors.ERR_API_HOTSTART_FILE_FORMAT # Invalid hotstart file format
|
|
548
|
+
SIMULATION_IS_RUNNING = swmm_API_Errors.ERR_API_IS_RUNNING # Simulation is running
|
|
549
|
+
|
|
550
|
+
cdef void c_wrapper_function(double x):
|
|
551
|
+
"""
|
|
552
|
+
Wrapper function to call a Python function.
|
|
553
|
+
|
|
554
|
+
:param x: Input value
|
|
555
|
+
:type x: double
|
|
556
|
+
"""
|
|
557
|
+
global py_progress_callback
|
|
558
|
+
cdef tuple args = (x,)
|
|
559
|
+
PyObject_CallObject(py_progress_callback, args)
|
|
560
|
+
|
|
561
|
+
cdef progress_callback wrap_python_function_as_callback(object py_func):
|
|
562
|
+
"""
|
|
563
|
+
Wrap a Python function as a callback.
|
|
564
|
+
|
|
565
|
+
:param py_func: Python function
|
|
566
|
+
:type py_func: callable
|
|
567
|
+
:return: Callback function
|
|
568
|
+
:rtype: progress_callback
|
|
569
|
+
"""
|
|
570
|
+
global py_progress_callback
|
|
571
|
+
py_progress_callback = py_func
|
|
572
|
+
return <progress_callback>c_wrapper_function
|
|
573
|
+
|
|
574
|
+
cdef object global_solver = None
|
|
575
|
+
|
|
576
|
+
cdef void progress_callback_wrapper(double progress):
|
|
577
|
+
"""
|
|
578
|
+
Wrapper function to call the instance method.
|
|
579
|
+
|
|
580
|
+
:param progress: Progress percentage
|
|
581
|
+
:type progress: double
|
|
582
|
+
|
|
583
|
+
"""
|
|
584
|
+
global solver_instance
|
|
585
|
+
|
|
586
|
+
if solver_instance is not None:
|
|
587
|
+
solver_instance.__progress_callback(progress)
|
|
588
|
+
|
|
589
|
+
def run_solver(
|
|
590
|
+
inp_file: str,
|
|
591
|
+
rpt_file: str = None,
|
|
592
|
+
out_file: str = None,
|
|
593
|
+
swmm_progress_callback: Callable[[float], None] = None
|
|
594
|
+
) -> int:
|
|
595
|
+
"""
|
|
596
|
+
Run a SWMM simulation with a progress callback.
|
|
597
|
+
|
|
598
|
+
:param inp_file: Input file name
|
|
599
|
+
:rtype inp_file: str
|
|
600
|
+
:param rpt_file: Report file name
|
|
601
|
+
:rtype rpt_file: str
|
|
602
|
+
:param out_file: Output file name
|
|
603
|
+
:rtype out_file: str
|
|
604
|
+
:param swmm_progress_callback: Progress callback function
|
|
605
|
+
:type swmm_progress_callback: callable
|
|
606
|
+
:return: Error code (0 if successful)
|
|
607
|
+
"""
|
|
608
|
+
cdef int error_code = 0
|
|
609
|
+
cdef bytes c_inp_file_bytes = inp_file.encode('utf-8')
|
|
610
|
+
cdef progress_callback c_swm_progress_callback
|
|
611
|
+
|
|
612
|
+
if rpt_file is not None:
|
|
613
|
+
rpt_file = inp_file.replace('.inp', '.rpt')
|
|
614
|
+
|
|
615
|
+
if out_file is not None:
|
|
616
|
+
out_file = inp_file.replace('.inp', '.out')
|
|
617
|
+
|
|
618
|
+
cdef bytes c_rpt_file_bytes = rpt_file.encode('utf-8')
|
|
619
|
+
cdef bytes c_out_file_bytes = out_file.encode('utf-8')
|
|
620
|
+
|
|
621
|
+
cdef const char* c_inp_file = c_inp_file_bytes
|
|
622
|
+
cdef const char* c_rpt_file = c_rpt_file_bytes
|
|
623
|
+
cdef const char* c_out_file = c_out_file_bytes
|
|
624
|
+
|
|
625
|
+
if swmm_progress_callback is not None:
|
|
626
|
+
c_swm_progress_callback = <progress_callback>wrap_python_function_as_callback(swmm_progress_callback)
|
|
627
|
+
error_code = swmm_run_with_callback(c_inp_file, c_rpt_file, c_out_file, c_swm_progress_callback)
|
|
628
|
+
else:
|
|
629
|
+
error_code = swmm_run(c_inp_file, c_rpt_file, c_out_file)
|
|
630
|
+
|
|
631
|
+
if error_code != 0:
|
|
632
|
+
raise SWMMSolverException(f'Run failed with message: {get_error_message(error_code)}')
|
|
633
|
+
|
|
634
|
+
return error_code
|
|
635
|
+
|
|
636
|
+
cpdef cython_datetime decode_swmm_datetime(double swmm_datetime):
|
|
637
|
+
"""
|
|
638
|
+
Decode a SWMM datetime into a datetime object.
|
|
639
|
+
|
|
640
|
+
:param swmm_datetime: SWMM datetime float value
|
|
641
|
+
:type swmm_datetime: float
|
|
642
|
+
|
|
643
|
+
:return: datetime object
|
|
644
|
+
:rtype: datetime
|
|
645
|
+
"""
|
|
646
|
+
cdef int year, month, day, hour, minute, second, day_of_week
|
|
647
|
+
swmm_decodeDate(swmm_datetime, &year, &month, &day, &hour, &minute, &second, &day_of_week)
|
|
648
|
+
|
|
649
|
+
return datetime(year, month, day, hour, minute, second)
|
|
650
|
+
|
|
651
|
+
cpdef double encode_swmm_datetime(cython_datetime dt):
|
|
652
|
+
"""
|
|
653
|
+
Encode a datetime object into a SWMM datetime float value.
|
|
654
|
+
|
|
655
|
+
:param dt: datetime object
|
|
656
|
+
:type dt: datetime
|
|
657
|
+
:return: SWMM datetime float value
|
|
658
|
+
:rtype: float
|
|
659
|
+
"""
|
|
660
|
+
cdef int year = dt.year
|
|
661
|
+
cdef int month = dt.month
|
|
662
|
+
cdef int day = dt.day
|
|
663
|
+
cdef int hour = dt.hour
|
|
664
|
+
cdef int minute = dt.minute
|
|
665
|
+
cdef int second = dt.second
|
|
666
|
+
|
|
667
|
+
return swmm_encodeDate(year, month, day, hour, minute, second)
|
|
668
|
+
|
|
669
|
+
cpdef int version():
|
|
670
|
+
"""
|
|
671
|
+
Get the SWMM version.
|
|
672
|
+
|
|
673
|
+
:return: SWMM version
|
|
674
|
+
:rtype: str
|
|
675
|
+
"""
|
|
676
|
+
cdef int swmm_version = swmm_getVersion()
|
|
677
|
+
|
|
678
|
+
return swmm_version
|
|
679
|
+
|
|
680
|
+
cpdef str get_error_message(int error_code):
|
|
681
|
+
"""
|
|
682
|
+
Get the error message for a SWMM error code.
|
|
683
|
+
|
|
684
|
+
:param error_code: Error code
|
|
685
|
+
:type error_code: int
|
|
686
|
+
:return: Error message
|
|
687
|
+
:rtype: str
|
|
688
|
+
"""
|
|
689
|
+
cdef char* c_error_message = <char*>malloc(1024*sizeof(char))
|
|
690
|
+
|
|
691
|
+
swmm_getErrorFromCode(error_code, &c_error_message)
|
|
692
|
+
|
|
693
|
+
error_message = c_error_message.decode('utf-8')
|
|
694
|
+
|
|
695
|
+
free(c_error_message)
|
|
696
|
+
|
|
697
|
+
return error_message
|
|
698
|
+
|
|
699
|
+
class SolverState(Enum):
|
|
700
|
+
"""
|
|
701
|
+
An enumeration to represent the state of the solver.
|
|
702
|
+
"""
|
|
703
|
+
CREATED = 0
|
|
704
|
+
OPEN = 1
|
|
705
|
+
STARTED = 2
|
|
706
|
+
FINISHED = 3
|
|
707
|
+
ENDED = 4
|
|
708
|
+
REPORTED = 5
|
|
709
|
+
CLOSED = 6
|
|
710
|
+
|
|
711
|
+
class CallbackType(Enum):
|
|
712
|
+
"""
|
|
713
|
+
An enumeration to represent the type of callback.
|
|
714
|
+
"""
|
|
715
|
+
BEFORE_INITIALIZE = 0
|
|
716
|
+
BEFORE_OPEN = 1
|
|
717
|
+
AFTER_OPEN = 2
|
|
718
|
+
BEFORE_START = 3
|
|
719
|
+
AFTER_START = 4
|
|
720
|
+
BEFORE_STEP = 5
|
|
721
|
+
AFTER_STEP = 6
|
|
722
|
+
BEFORE_END = 7
|
|
723
|
+
AFTER_END = 8
|
|
724
|
+
BEFORE_REPORT = 9
|
|
725
|
+
AFTER_REPORT = 10
|
|
726
|
+
BEFORE_CLOSE = 11
|
|
727
|
+
AFTER_CLOSE = 12
|
|
728
|
+
|
|
729
|
+
class SWMMSolverException(Exception):
|
|
730
|
+
"""
|
|
731
|
+
Exception class for SWMM output file processing errors.
|
|
732
|
+
"""
|
|
733
|
+
def __init__(self, message: str) -> None:
|
|
734
|
+
"""
|
|
735
|
+
Constructor to initialize the exception message.
|
|
736
|
+
|
|
737
|
+
:param message: Error message.
|
|
738
|
+
:type message: str
|
|
739
|
+
"""
|
|
740
|
+
super().__init__(message)
|
|
741
|
+
|
|
742
|
+
cdef class Solver:
|
|
743
|
+
"""
|
|
744
|
+
A class to represent a SWMM solver.
|
|
745
|
+
"""
|
|
746
|
+
cdef str _inp_file
|
|
747
|
+
cdef str _rpt_file
|
|
748
|
+
cdef str _out_file
|
|
749
|
+
cdef bint _save_results
|
|
750
|
+
cdef int _stride_step
|
|
751
|
+
cdef dict _callbacks
|
|
752
|
+
cdef int _progress_callbacks_per_second
|
|
753
|
+
cdef list _progress_callbacks
|
|
754
|
+
cdef clock_t _clock
|
|
755
|
+
cdef double _total_duration
|
|
756
|
+
cdef object _solver_state
|
|
757
|
+
cdef object _partial_step_function
|
|
758
|
+
|
|
759
|
+
def __cinit__(
|
|
760
|
+
self,
|
|
761
|
+
str inp_file,
|
|
762
|
+
str rpt_file = None,
|
|
763
|
+
str out_file = None,
|
|
764
|
+
int stride_step = 300,
|
|
765
|
+
bint save_results=True
|
|
766
|
+
):
|
|
767
|
+
"""
|
|
768
|
+
Constructor to create a new SWMM solver.
|
|
769
|
+
|
|
770
|
+
:param inp_file: Input file name
|
|
771
|
+
:param rpt_file: Report file name
|
|
772
|
+
:param out_file: Output file name
|
|
773
|
+
"""
|
|
774
|
+
global global_solver
|
|
775
|
+
self._save_results = save_results
|
|
776
|
+
self._inp_file = inp_file
|
|
777
|
+
self._progress_callbacks_per_second = 2
|
|
778
|
+
self._stride_step = stride_step
|
|
779
|
+
self._clock = clock()
|
|
780
|
+
global_solver = self
|
|
781
|
+
|
|
782
|
+
if rpt_file is not None:
|
|
783
|
+
self._rpt_file = rpt_file
|
|
784
|
+
else:
|
|
785
|
+
self._rpt_file = inp_file.replace('.inp', '.rpt')
|
|
786
|
+
|
|
787
|
+
if out_file is not None:
|
|
788
|
+
self._out_file = out_file
|
|
789
|
+
else:
|
|
790
|
+
self._out_file = inp_file.replace('.inp', '.out')
|
|
791
|
+
|
|
792
|
+
self._callbacks = {
|
|
793
|
+
CallbackType.BEFORE_INITIALIZE: [],
|
|
794
|
+
CallbackType.BEFORE_OPEN: [],
|
|
795
|
+
CallbackType.AFTER_OPEN: [],
|
|
796
|
+
CallbackType.BEFORE_START: [],
|
|
797
|
+
CallbackType.AFTER_START: [],
|
|
798
|
+
CallbackType.BEFORE_STEP: [],
|
|
799
|
+
CallbackType.AFTER_STEP: [],
|
|
800
|
+
CallbackType.BEFORE_END: [],
|
|
801
|
+
CallbackType.AFTER_END: [],
|
|
802
|
+
CallbackType.BEFORE_REPORT: [],
|
|
803
|
+
CallbackType.AFTER_REPORT: [],
|
|
804
|
+
CallbackType.BEFORE_CLOSE: [],
|
|
805
|
+
CallbackType.AFTER_CLOSE: []
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
self._progress_callbacks = []
|
|
809
|
+
|
|
810
|
+
self._solver_state = SolverState.CREATED
|
|
811
|
+
|
|
812
|
+
def __enter__(self):
|
|
813
|
+
"""
|
|
814
|
+
Enter method for context manager.
|
|
815
|
+
"""
|
|
816
|
+
self.__execute_callbacks(CallbackType.BEFORE_INITIALIZE)
|
|
817
|
+
self.open()
|
|
818
|
+
return self
|
|
819
|
+
|
|
820
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
821
|
+
"""
|
|
822
|
+
Exit method for context manager.
|
|
823
|
+
"""
|
|
824
|
+
self.finalize()
|
|
825
|
+
|
|
826
|
+
def __dealloc__(self):
|
|
827
|
+
"""
|
|
828
|
+
Destructor to free the solver.
|
|
829
|
+
"""
|
|
830
|
+
self.finalize()
|
|
831
|
+
|
|
832
|
+
def __iter__(self):
|
|
833
|
+
"""
|
|
834
|
+
Iterator method for the solver.
|
|
835
|
+
"""
|
|
836
|
+
return self
|
|
837
|
+
|
|
838
|
+
def __next__(self):
|
|
839
|
+
"""
|
|
840
|
+
Next method for the solver.
|
|
841
|
+
"""
|
|
842
|
+
if self._solver_state == SolverState.FINISHED:
|
|
843
|
+
raise StopIteration
|
|
844
|
+
else:
|
|
845
|
+
return self.step()
|
|
846
|
+
|
|
847
|
+
@property
|
|
848
|
+
def start_datetime(self) -> datetime:
|
|
849
|
+
"""
|
|
850
|
+
Get the start date of the simulation.
|
|
851
|
+
|
|
852
|
+
:return: Start date
|
|
853
|
+
:rtype: datetime
|
|
854
|
+
"""
|
|
855
|
+
cdef double start_date = swmm_getValueExpanded(
|
|
856
|
+
objType=SWMMObjects.SYSTEM.value,
|
|
857
|
+
property=SWMMSystemProperties.START_DATE.value,
|
|
858
|
+
index=0,
|
|
859
|
+
subIndex=0,
|
|
860
|
+
pollutantIndex=0
|
|
861
|
+
)
|
|
862
|
+
|
|
863
|
+
return decode_swmm_datetime(start_date)
|
|
864
|
+
|
|
865
|
+
@start_datetime.setter
|
|
866
|
+
def start_datetime(self, sim_start_datetime: datetime) -> None:
|
|
867
|
+
"""
|
|
868
|
+
Initialize the solver.
|
|
869
|
+
|
|
870
|
+
:param sim_start_datetime: Start date of the simulation
|
|
871
|
+
:return: Error code (0 if successful)
|
|
872
|
+
"""
|
|
873
|
+
cdef double start_date = encode_swmm_datetime(dt=sim_start_datetime)
|
|
874
|
+
cdef int error_code = swmm_setValueExpanded(
|
|
875
|
+
objType=SWMMObjects.SYSTEM.value,
|
|
876
|
+
property=SWMMSystemProperties.START_DATE.value,
|
|
877
|
+
index=0,
|
|
878
|
+
subindex=0,
|
|
879
|
+
pollutantIndex=0,
|
|
880
|
+
value=start_date
|
|
881
|
+
)
|
|
882
|
+
|
|
883
|
+
self.__validate_error(error_code)
|
|
884
|
+
|
|
885
|
+
@property
|
|
886
|
+
def end_datetime(self) -> datetime:
|
|
887
|
+
"""
|
|
888
|
+
Get the end date of the simulation.
|
|
889
|
+
|
|
890
|
+
:return: End date
|
|
891
|
+
:rtype: datetime
|
|
892
|
+
"""
|
|
893
|
+
cdef double end_date = swmm_getValueExpanded(
|
|
894
|
+
objType=SWMMObjects.SYSTEM.value,
|
|
895
|
+
property=SWMMSystemProperties.END_DATE.value,
|
|
896
|
+
index=0,
|
|
897
|
+
subIndex=0,
|
|
898
|
+
pollutantIndex=0
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
return decode_swmm_datetime(end_date)
|
|
902
|
+
|
|
903
|
+
@end_datetime.setter
|
|
904
|
+
def end_datetime(self, sim_end_datetime: datetime) -> None:
|
|
905
|
+
"""
|
|
906
|
+
Set the end date of the simulation.
|
|
907
|
+
|
|
908
|
+
:param sim_end_datetime: End date of the simulation
|
|
909
|
+
:return: Error code (0 if successful)
|
|
910
|
+
"""
|
|
911
|
+
cdef double end_date = encode_swmm_datetime(dt=sim_end_datetime)
|
|
912
|
+
cdef int error_code = swmm_setValueExpanded(
|
|
913
|
+
objType=SWMMObjects.SYSTEM.value,
|
|
914
|
+
property=SWMMSystemProperties.END_DATE.value,
|
|
915
|
+
index=0,
|
|
916
|
+
subindex=0,
|
|
917
|
+
value=end_date,
|
|
918
|
+
pollutantIndex=0,
|
|
919
|
+
)
|
|
920
|
+
|
|
921
|
+
self.__validate_error(error_code)
|
|
922
|
+
|
|
923
|
+
@property
|
|
924
|
+
def routing_step(self) -> float:
|
|
925
|
+
"""
|
|
926
|
+
Get the routing time step of the simulation in seconds.
|
|
927
|
+
|
|
928
|
+
:return: Routing time step
|
|
929
|
+
:rtype: double
|
|
930
|
+
"""
|
|
931
|
+
cdef double routing_step = swmm_getValueExpanded(
|
|
932
|
+
objType=SWMMObjects.SYSTEM.value,
|
|
933
|
+
property=SWMMSystemProperties.ROUTING_STEP.value,
|
|
934
|
+
index=0,
|
|
935
|
+
subIndex=0,
|
|
936
|
+
pollutantIndex=0
|
|
937
|
+
)
|
|
938
|
+
|
|
939
|
+
return routing_step
|
|
940
|
+
|
|
941
|
+
@routing_step.setter
|
|
942
|
+
def routing_step(self, value: float) -> None:
|
|
943
|
+
"""
|
|
944
|
+
Set the routing time step of the simulation in seconds.
|
|
945
|
+
|
|
946
|
+
:param value: Routing time step in seconds
|
|
947
|
+
:type value: float
|
|
948
|
+
"""
|
|
949
|
+
cdef int error_code = swmm_setValueExpanded(
|
|
950
|
+
objType=SWMMObjects.SYSTEM.value,
|
|
951
|
+
property=SWMMSystemProperties.ROUTING_STEP.value,
|
|
952
|
+
index=0,
|
|
953
|
+
subindex=0,
|
|
954
|
+
pollutantIndex=0,
|
|
955
|
+
value=value
|
|
956
|
+
)
|
|
957
|
+
|
|
958
|
+
self.__validate_error(error_code)
|
|
959
|
+
|
|
960
|
+
@property
|
|
961
|
+
def reporting_step(self) -> float:
|
|
962
|
+
"""
|
|
963
|
+
Get the reporting time step of the simulation in seconds.
|
|
964
|
+
|
|
965
|
+
:return: Reporting time step
|
|
966
|
+
:rtype: double
|
|
967
|
+
"""
|
|
968
|
+
cdef double reporting_step = swmm_getValueExpanded(
|
|
969
|
+
objType=SWMMObjects.SYSTEM.value,
|
|
970
|
+
property=SWMMSystemProperties.REPORT_STEP.value,
|
|
971
|
+
index=0,
|
|
972
|
+
subIndex=0,
|
|
973
|
+
pollutantIndex=0
|
|
974
|
+
)
|
|
975
|
+
|
|
976
|
+
return reporting_step
|
|
977
|
+
|
|
978
|
+
@reporting_step.setter
|
|
979
|
+
def reporting_step(self, value: float) -> None:
|
|
980
|
+
"""
|
|
981
|
+
Set the reporting time step of the simulation in seconds.
|
|
982
|
+
|
|
983
|
+
:param value: Reporting time step in seconds
|
|
984
|
+
:type value: float
|
|
985
|
+
"""
|
|
986
|
+
cdef int error_code = swmm_setValueExpanded(
|
|
987
|
+
objType=SWMMObjects.SYSTEM.value,
|
|
988
|
+
property=SWMMSystemProperties.REPORT_STEP.value,
|
|
989
|
+
index=0,
|
|
990
|
+
subindex=0,
|
|
991
|
+
pollutantIndex=0,
|
|
992
|
+
value=value
|
|
993
|
+
)
|
|
994
|
+
|
|
995
|
+
self.__validate_error(error_code)
|
|
996
|
+
|
|
997
|
+
@property
|
|
998
|
+
def report_start_datetime(self) -> datetime:
|
|
999
|
+
"""
|
|
1000
|
+
Get the report start date of the simulation.
|
|
1001
|
+
|
|
1002
|
+
:return: Report start date
|
|
1003
|
+
:rtype: datetime
|
|
1004
|
+
"""
|
|
1005
|
+
cdef double report_start_date = swmm_getValueExpanded(
|
|
1006
|
+
objType=SWMMObjects.SYSTEM.value,
|
|
1007
|
+
property=SWMMSystemProperties.REPORT_START_DATE.value,
|
|
1008
|
+
index=0,
|
|
1009
|
+
subIndex=0,
|
|
1010
|
+
pollutantIndex=0
|
|
1011
|
+
)
|
|
1012
|
+
|
|
1013
|
+
return decode_swmm_datetime(report_start_date)
|
|
1014
|
+
|
|
1015
|
+
@report_start_datetime.setter
|
|
1016
|
+
def report_start_datetime(self, report_start_datetime: datetime) -> None:
|
|
1017
|
+
"""
|
|
1018
|
+
Set the report start date of the simulation.
|
|
1019
|
+
|
|
1020
|
+
:param report_start_datetime: Report start date
|
|
1021
|
+
:type report_start_datetime: datetime
|
|
1022
|
+
"""
|
|
1023
|
+
cdef double report_start_date = encode_swmm_datetime(report_start_datetime)
|
|
1024
|
+
cdef int error_code = swmm_setValueExpanded(
|
|
1025
|
+
objType=SWMMObjects.SYSTEM.value,
|
|
1026
|
+
property=SWMMSystemProperties.REPORT_START_DATE.value,
|
|
1027
|
+
index=0,
|
|
1028
|
+
subindex=0,
|
|
1029
|
+
pollutantIndex=0,
|
|
1030
|
+
value=report_start_date
|
|
1031
|
+
)
|
|
1032
|
+
|
|
1033
|
+
self.__validate_error(error_code)
|
|
1034
|
+
|
|
1035
|
+
@property
|
|
1036
|
+
def current_datetime(self) -> datetime:
|
|
1037
|
+
"""
|
|
1038
|
+
Get the current date of the simulation.
|
|
1039
|
+
|
|
1040
|
+
:return: Current date
|
|
1041
|
+
:rtype: datetime
|
|
1042
|
+
"""
|
|
1043
|
+
cdef double current_date = swmm_getValueExpanded(
|
|
1044
|
+
objType=SWMMObjects.SYSTEM.value,
|
|
1045
|
+
property=SWMMSystemProperties.CURRENT_DATE.value,
|
|
1046
|
+
index=0,
|
|
1047
|
+
subIndex=0,
|
|
1048
|
+
pollutantIndex=0
|
|
1049
|
+
)
|
|
1050
|
+
|
|
1051
|
+
return decode_swmm_datetime(current_date)
|
|
1052
|
+
|
|
1053
|
+
@property
|
|
1054
|
+
def progress_callbacks_per_second(self) -> int:
|
|
1055
|
+
"""
|
|
1056
|
+
Get the number of progress callbacks per second.
|
|
1057
|
+
|
|
1058
|
+
:return: Progress callbacks per second
|
|
1059
|
+
:rtype: int
|
|
1060
|
+
"""
|
|
1061
|
+
return self._progress_callbacks_per_second
|
|
1062
|
+
|
|
1063
|
+
@progress_callbacks_per_second.setter
|
|
1064
|
+
def progress_callbacks_per_second(self, value: int) -> None:
|
|
1065
|
+
"""
|
|
1066
|
+
Set the number of progress callbacks per second.
|
|
1067
|
+
|
|
1068
|
+
:param value: Progress callbacks per second
|
|
1069
|
+
:type value: int
|
|
1070
|
+
"""
|
|
1071
|
+
self._progress_callbacks_per_second = max(1, value)
|
|
1072
|
+
|
|
1073
|
+
def get_object_count(self, object_type: SWMMObjects) -> int:
|
|
1074
|
+
"""
|
|
1075
|
+
Get the count of a SWMM object type.
|
|
1076
|
+
|
|
1077
|
+
:param object_type: SWMM object type
|
|
1078
|
+
:type object_type: SWMMObjects
|
|
1079
|
+
:return: Object count
|
|
1080
|
+
:rtype: int
|
|
1081
|
+
"""
|
|
1082
|
+
cdef int count = swmm_getCount(objType=object_type.value)
|
|
1083
|
+
|
|
1084
|
+
self.__validate_error(count)
|
|
1085
|
+
|
|
1086
|
+
return count
|
|
1087
|
+
|
|
1088
|
+
def get_object_name(self, object_type: SWMMObjects, index: int) -> str:
|
|
1089
|
+
"""
|
|
1090
|
+
Get the name of a SWMM object.
|
|
1091
|
+
|
|
1092
|
+
:param object_type: SWMM object type
|
|
1093
|
+
:type object_type: SWMMObjects
|
|
1094
|
+
:param index: Object index
|
|
1095
|
+
:type index: int
|
|
1096
|
+
:return: Object name
|
|
1097
|
+
:rtype: str
|
|
1098
|
+
"""
|
|
1099
|
+
cdef char* c_object_name = <char*>malloc(1024*sizeof(char))
|
|
1100
|
+
|
|
1101
|
+
cdef int error_code = swmm_getName(
|
|
1102
|
+
objType=object_type.value,
|
|
1103
|
+
index=index,
|
|
1104
|
+
name=c_object_name,
|
|
1105
|
+
size=1024
|
|
1106
|
+
)
|
|
1107
|
+
|
|
1108
|
+
self.__validate_error(error_code)
|
|
1109
|
+
|
|
1110
|
+
object_name = c_object_name.decode('utf-8')
|
|
1111
|
+
|
|
1112
|
+
free(c_object_name)
|
|
1113
|
+
|
|
1114
|
+
return object_name
|
|
1115
|
+
|
|
1116
|
+
def get_object_names(self, object_type: SWMMObjects) -> List[str]:
|
|
1117
|
+
"""
|
|
1118
|
+
Get the names of all SWMM objects of a given type.
|
|
1119
|
+
|
|
1120
|
+
:param object_type: SWMM object type
|
|
1121
|
+
:type object_type: SWMMObjects
|
|
1122
|
+
:return: Object names
|
|
1123
|
+
:rtype: List[str]
|
|
1124
|
+
"""
|
|
1125
|
+
cdef char* c_object_name = <char*>malloc(1024*sizeof(char))
|
|
1126
|
+
cdef list object_names = []
|
|
1127
|
+
cdef int count = self.get_object_count(object_type=object_type)
|
|
1128
|
+
|
|
1129
|
+
for i in range(count):
|
|
1130
|
+
|
|
1131
|
+
error_code = swmm_getName(
|
|
1132
|
+
objType=object_type.value,
|
|
1133
|
+
index=i,
|
|
1134
|
+
name=c_object_name,
|
|
1135
|
+
size=1024
|
|
1136
|
+
)
|
|
1137
|
+
|
|
1138
|
+
self.__validate_error(error_code)
|
|
1139
|
+
|
|
1140
|
+
object_name = c_object_name.decode('utf-8')
|
|
1141
|
+
object_names.append(object_name)
|
|
1142
|
+
|
|
1143
|
+
|
|
1144
|
+
free(c_object_name)
|
|
1145
|
+
|
|
1146
|
+
return object_names
|
|
1147
|
+
|
|
1148
|
+
def get_object_index(self, object_type: SWMMObjects, object_name: str) -> int:
|
|
1149
|
+
"""
|
|
1150
|
+
Get the index of a SWMM object.
|
|
1151
|
+
|
|
1152
|
+
:param object_type: SWMM object type
|
|
1153
|
+
:type object_type: SWMMObjects
|
|
1154
|
+
:param object_name: Object name
|
|
1155
|
+
:type object_name: str
|
|
1156
|
+
:return: Object index
|
|
1157
|
+
:rtype: int
|
|
1158
|
+
"""
|
|
1159
|
+
cdef int index = swmm_getIndex(
|
|
1160
|
+
objType=object_type.value,
|
|
1161
|
+
name=object_name.encode('utf-8')
|
|
1162
|
+
)
|
|
1163
|
+
|
|
1164
|
+
return index
|
|
1165
|
+
|
|
1166
|
+
def set_value(
|
|
1167
|
+
self,
|
|
1168
|
+
object_type: SWMMObjects,
|
|
1169
|
+
property_type: Union[
|
|
1170
|
+
SWMMRainGageProperties,
|
|
1171
|
+
SWMMSubcatchmentProperties,
|
|
1172
|
+
SWMMNodeProperties,
|
|
1173
|
+
SWMMLinkProperties,
|
|
1174
|
+
SWMMSystemProperties
|
|
1175
|
+
],
|
|
1176
|
+
index: Union[int, str],
|
|
1177
|
+
value: float,
|
|
1178
|
+
sub_index: int = -1,
|
|
1179
|
+
pollutant_index: int = -1
|
|
1180
|
+
) -> None:
|
|
1181
|
+
"""
|
|
1182
|
+
Set a SWMM system property value.
|
|
1183
|
+
|
|
1184
|
+
:param object_type: SWMM object type (e.g., SWMMObjects.NODE)
|
|
1185
|
+
:type object_type: SWMMObjects
|
|
1186
|
+
:param property_type: SWMM system property type (e.g., SWMMSystemProperties.START_DATE)
|
|
1187
|
+
:type property_type: Union[SWMMRainGageProperties, SWMMSubcatchmentProperties, SWMMNodeProperties, SWMMLinkProperties, SWMMSystemProperties]
|
|
1188
|
+
:param index: Object index (e.g., 0, 'J1')
|
|
1189
|
+
:type index: int or str
|
|
1190
|
+
:param value: Property value (e.g., 10.0)
|
|
1191
|
+
:type value: double
|
|
1192
|
+
:param sub_index: Sub-index (e.g., 0) for properties with sub-indexes. For example pollutant index for POLLUTANT properties.
|
|
1193
|
+
:type sub_index: int
|
|
1194
|
+
:param pollutant_index: Pollutant index (e.g., 0) for POLLUTANT properties.
|
|
1195
|
+
:type pollutant_index: int
|
|
1196
|
+
"""
|
|
1197
|
+
cdef int element_index = -1
|
|
1198
|
+
|
|
1199
|
+
if isinstance(index, str):
|
|
1200
|
+
element_index = self.get_object_index(object_type, index)
|
|
1201
|
+
self.__validate_error(element_index)
|
|
1202
|
+
else:
|
|
1203
|
+
element_index = index
|
|
1204
|
+
|
|
1205
|
+
cdef int error_code = swmm_setValueExpanded(
|
|
1206
|
+
objType=<int>object_type.value,
|
|
1207
|
+
property=<int>property_type.value,
|
|
1208
|
+
index=element_index,
|
|
1209
|
+
subindex=sub_index,
|
|
1210
|
+
pollutantIndex=<int>pollutant_index,
|
|
1211
|
+
value=value
|
|
1212
|
+
)
|
|
1213
|
+
|
|
1214
|
+
self.__validate_error(error_code)
|
|
1215
|
+
|
|
1216
|
+
def get_value(
|
|
1217
|
+
self,
|
|
1218
|
+
object_type: SWMMObjects,
|
|
1219
|
+
property_type: Union[
|
|
1220
|
+
SWMMRainGageProperties,
|
|
1221
|
+
SWMMSubcatchmentProperties,
|
|
1222
|
+
SWMMNodeProperties,
|
|
1223
|
+
SWMMLinkProperties,
|
|
1224
|
+
SWMMSystemProperties
|
|
1225
|
+
],
|
|
1226
|
+
index: Union[int, str],
|
|
1227
|
+
sub_index: int = -1,
|
|
1228
|
+
pollutant_index: int = -1
|
|
1229
|
+
) -> float:
|
|
1230
|
+
"""
|
|
1231
|
+
Get a SWMM system property value.
|
|
1232
|
+
|
|
1233
|
+
:param object_type: SWMM object type (e.g., SWMMObjects.NODE)
|
|
1234
|
+
:type object_type: SWMMObjects
|
|
1235
|
+
:param property_type: SWMM system property type (e.g., SWMMSystemProperties.START_DATE)
|
|
1236
|
+
:type property_type: Union[SWMMRainGageProperties, SWMMSubcatchmentProperties, SWMMNodeProperties, SWMMLinkProperties, SWMMSystemProperties]
|
|
1237
|
+
:param index: Object index (e.g., 0, 'J1')
|
|
1238
|
+
:type index: int or str
|
|
1239
|
+
:param sub_index: Sub-index (e.g., 0) for properties with sub-indexes. For example pollutant index for POLLUTANT properties.
|
|
1240
|
+
:type sub_index: int
|
|
1241
|
+
:param pollutant_index: Pollutant index (e.g., 0) for POLLUTANT properties.
|
|
1242
|
+
:type pollutant_index: int
|
|
1243
|
+
:return: Property value
|
|
1244
|
+
:rtype: double
|
|
1245
|
+
"""
|
|
1246
|
+
|
|
1247
|
+
cdef int element_index = -1
|
|
1248
|
+
|
|
1249
|
+
if isinstance(index, str):
|
|
1250
|
+
element_index = self.get_object_index(object_type, index)
|
|
1251
|
+
self.__validate_error(element_index)
|
|
1252
|
+
else:
|
|
1253
|
+
element_index = index
|
|
1254
|
+
|
|
1255
|
+
cdef double value = swmm_getValueExpanded(
|
|
1256
|
+
objType=<int>object_type.value,
|
|
1257
|
+
property=<int>property_type.value,
|
|
1258
|
+
index=element_index,
|
|
1259
|
+
subIndex=sub_index,
|
|
1260
|
+
pollutantIndex=pollutant_index
|
|
1261
|
+
)
|
|
1262
|
+
|
|
1263
|
+
self.__validate_error(<int>value)
|
|
1264
|
+
|
|
1265
|
+
return value
|
|
1266
|
+
|
|
1267
|
+
@property
|
|
1268
|
+
def stride_step(self) -> int:
|
|
1269
|
+
"""
|
|
1270
|
+
Get the stride step of the simulation.
|
|
1271
|
+
|
|
1272
|
+
:return: Stride step
|
|
1273
|
+
:rtype: int
|
|
1274
|
+
"""
|
|
1275
|
+
return self._stride_step
|
|
1276
|
+
|
|
1277
|
+
@stride_step.setter
|
|
1278
|
+
def stride_step(self, value: int):
|
|
1279
|
+
"""
|
|
1280
|
+
Set the stride time step of the simulation.
|
|
1281
|
+
|
|
1282
|
+
:param value: Stride step in seconds
|
|
1283
|
+
:type value: int
|
|
1284
|
+
"""
|
|
1285
|
+
self._stride_step = value
|
|
1286
|
+
|
|
1287
|
+
@property
|
|
1288
|
+
def solver_state(self) -> SolverState:
|
|
1289
|
+
"""
|
|
1290
|
+
Get the state of the solver.
|
|
1291
|
+
|
|
1292
|
+
:return: Solver state
|
|
1293
|
+
:rtype: SolverState
|
|
1294
|
+
"""
|
|
1295
|
+
return self._solver_state
|
|
1296
|
+
|
|
1297
|
+
def add_callback(self, callback_type: CallbackType, callback: Callable[[Solver], None]) -> None:
|
|
1298
|
+
"""
|
|
1299
|
+
Add a callback to the solver.
|
|
1300
|
+
|
|
1301
|
+
:param callback_type: Type of callback
|
|
1302
|
+
:type callback_type: CallbackType
|
|
1303
|
+
:param callback: Callback function
|
|
1304
|
+
:type callback: callable
|
|
1305
|
+
"""
|
|
1306
|
+
self._callbacks[callback_type].append(callback)
|
|
1307
|
+
|
|
1308
|
+
def add_progress_callback(self, callback: Callable[[float], None]) -> None:
|
|
1309
|
+
"""
|
|
1310
|
+
Add a progress callback to the solver.
|
|
1311
|
+
|
|
1312
|
+
:param callback: Progress callback function
|
|
1313
|
+
:type callback: callable
|
|
1314
|
+
"""
|
|
1315
|
+
self._progress_callbacks.append(callback)
|
|
1316
|
+
|
|
1317
|
+
cpdef void open(self):
|
|
1318
|
+
"""
|
|
1319
|
+
Opens the SWMM solver by calling the open method in the SWMM API.
|
|
1320
|
+
"""
|
|
1321
|
+
cdef int error_code = 0
|
|
1322
|
+
cdef bytes c_inp_file_bytes = self._inp_file.encode('utf-8')
|
|
1323
|
+
cdef bytes c_rpt_file_bytes = self._rpt_file.encode('utf-8')
|
|
1324
|
+
cdef bytes c_out_file_bytes = self._out_file.encode('utf-8')
|
|
1325
|
+
|
|
1326
|
+
cdef const char* c_inp_file = c_inp_file_bytes
|
|
1327
|
+
cdef const char* c_rpt_file = c_rpt_file_bytes
|
|
1328
|
+
cdef const char* c_out_file = c_out_file_bytes
|
|
1329
|
+
|
|
1330
|
+
if self._solver_state == SolverState.OPEN:
|
|
1331
|
+
pass
|
|
1332
|
+
elif self._solver_state == SolverState.CREATED or self._solver_state == SolverState.CLOSED:
|
|
1333
|
+
self.__execute_callbacks(CallbackType.BEFORE_OPEN)
|
|
1334
|
+
|
|
1335
|
+
error_code = swmm_open(
|
|
1336
|
+
inp_file=c_inp_file,
|
|
1337
|
+
rpt_file=c_rpt_file,
|
|
1338
|
+
out_file=c_out_file
|
|
1339
|
+
)
|
|
1340
|
+
|
|
1341
|
+
self.__validate_error(error_code)
|
|
1342
|
+
self._solver_state = SolverState.OPEN
|
|
1343
|
+
self.__execute_callbacks(CallbackType.AFTER_OPEN)
|
|
1344
|
+
self._clock = clock()
|
|
1345
|
+
else:
|
|
1346
|
+
raise SWMMSolverException(f'Open failed: Solver is not in a valid state: {self._solver_state}')
|
|
1347
|
+
|
|
1348
|
+
self._total_duration = swmm_getValue(
|
|
1349
|
+
property=SWMMSystemProperties.END_DATE.value,
|
|
1350
|
+
index=0
|
|
1351
|
+
) - swmm_getValue(
|
|
1352
|
+
property=SWMMSystemProperties.START_DATE.value,
|
|
1353
|
+
index=0
|
|
1354
|
+
)
|
|
1355
|
+
|
|
1356
|
+
cpdef void start(self):
|
|
1357
|
+
"""
|
|
1358
|
+
Starts the SWMM solver.
|
|
1359
|
+
"""
|
|
1360
|
+
cdef int error_code = 0
|
|
1361
|
+
|
|
1362
|
+
if self._solver_state == SolverState.STARTED:
|
|
1363
|
+
pass
|
|
1364
|
+
elif self._solver_state == SolverState.OPEN:
|
|
1365
|
+
self.__execute_callbacks(CallbackType.BEFORE_START)
|
|
1366
|
+
error_code = swmm_start(save_flag=self._save_results)
|
|
1367
|
+
self.__validate_error(error_code)
|
|
1368
|
+
self._solver_state = SolverState.STARTED
|
|
1369
|
+
self.__execute_callbacks(CallbackType.AFTER_START)
|
|
1370
|
+
else:
|
|
1371
|
+
raise SWMMSolverException(f'Start failed: Solver is not in a valid state: {self._solver_state}')
|
|
1372
|
+
|
|
1373
|
+
cpdef void initialize(self):
|
|
1374
|
+
"""
|
|
1375
|
+
Initializes the solver and starts the simulation. Calls the open and start methods in
|
|
1376
|
+
the SWMM API.
|
|
1377
|
+
"""
|
|
1378
|
+
self.__execute_callbacks(CallbackType.BEFORE_INITIALIZE)
|
|
1379
|
+
self.open()
|
|
1380
|
+
self.start()
|
|
1381
|
+
|
|
1382
|
+
cpdef tuple step(self, int steps = 0):
|
|
1383
|
+
"""
|
|
1384
|
+
Step a SWMM simulation.
|
|
1385
|
+
|
|
1386
|
+
:param steps: Number of steps to run. Overrides internal stride step if greater than 0.
|
|
1387
|
+
:type steps: int
|
|
1388
|
+
:return: elapsed_time, current_date
|
|
1389
|
+
:rtype: Tuple[float, datetime]
|
|
1390
|
+
"""
|
|
1391
|
+
cdef double elapsed_time = 0.0
|
|
1392
|
+
cdef double progress = 0.0
|
|
1393
|
+
|
|
1394
|
+
error_code = swmm_stride(strideStep=steps if steps > 0 else self._stride_step, elapsedTime=&elapsed_time)
|
|
1395
|
+
|
|
1396
|
+
if error_code < 0:
|
|
1397
|
+
self.__validate_error(error_code)
|
|
1398
|
+
|
|
1399
|
+
progress = (
|
|
1400
|
+
swmm_getValue(
|
|
1401
|
+
property=SWMMSystemProperties.CURRENT_DATE.value,
|
|
1402
|
+
index=0
|
|
1403
|
+
) - swmm_getValue(
|
|
1404
|
+
property=SWMMSystemProperties.START_DATE.value,
|
|
1405
|
+
index=0
|
|
1406
|
+
)
|
|
1407
|
+
) / self._total_duration
|
|
1408
|
+
|
|
1409
|
+
self.__execute_progress_callbacks(progress)
|
|
1410
|
+
|
|
1411
|
+
if elapsed_time <= 0.0:
|
|
1412
|
+
self._solver_state = SolverState.FINISHED
|
|
1413
|
+
|
|
1414
|
+
return elapsed_time, decode_swmm_datetime(
|
|
1415
|
+
swmm_datetime=swmm_getValue(
|
|
1416
|
+
property=SWMMSystemProperties.CURRENT_DATE.value,
|
|
1417
|
+
index=0
|
|
1418
|
+
)
|
|
1419
|
+
)
|
|
1420
|
+
# else:
|
|
1421
|
+
# raise SWMMSolverException(f'Step failed: Solver is not in a valid state: {self._solver_state}')
|
|
1422
|
+
|
|
1423
|
+
cpdef void end(self):
|
|
1424
|
+
"""
|
|
1425
|
+
Ends the SWMM simulation.
|
|
1426
|
+
"""
|
|
1427
|
+
cdef int error_code = 0
|
|
1428
|
+
|
|
1429
|
+
if self._solver_state == SolverState.ENDED or \
|
|
1430
|
+
self._solver_state == SolverState.CREATED:
|
|
1431
|
+
pass
|
|
1432
|
+
elif self._solver_state == SolverState.OPEN or \
|
|
1433
|
+
self._solver_state == SolverState.STARTED or \
|
|
1434
|
+
self._solver_state == SolverState.FINISHED:
|
|
1435
|
+
|
|
1436
|
+
self.__execute_callbacks(CallbackType.BEFORE_END)
|
|
1437
|
+
error_code = swmm_end()
|
|
1438
|
+
self.__validate_error(error_code)
|
|
1439
|
+
self._solver_state = SolverState.ENDED
|
|
1440
|
+
self.__execute_callbacks(CallbackType.AFTER_END)
|
|
1441
|
+
else:
|
|
1442
|
+
raise SWMMSolverException(f'End failed: Solver is not in a valid state: {self._solver_state}')
|
|
1443
|
+
|
|
1444
|
+
cpdef void report(self):
|
|
1445
|
+
"""
|
|
1446
|
+
Reports the results of the SWMM simulation.
|
|
1447
|
+
"""
|
|
1448
|
+
cdef int error_code = 0
|
|
1449
|
+
|
|
1450
|
+
if self._solver_state == SolverState.REPORTED or self._solver_state == SolverState.CREATED:
|
|
1451
|
+
pass
|
|
1452
|
+
elif self._solver_state == SolverState.ENDED:
|
|
1453
|
+
self.__execute_callbacks(CallbackType.BEFORE_REPORT)
|
|
1454
|
+
error_code = swmm_report()
|
|
1455
|
+
self.__validate_error(error_code)
|
|
1456
|
+
self._solver_state = SolverState.REPORTED
|
|
1457
|
+
self.__execute_callbacks(CallbackType.AFTER_REPORT)
|
|
1458
|
+
else:
|
|
1459
|
+
raise SWMMSolverException(f'Report failed: Solver is not in a valid state: {self._solver_state}')
|
|
1460
|
+
|
|
1461
|
+
cpdef void close(self):
|
|
1462
|
+
"""
|
|
1463
|
+
Close the solver.
|
|
1464
|
+
"""
|
|
1465
|
+
cdef int error_code = 0
|
|
1466
|
+
|
|
1467
|
+
if self._solver_state == SolverState.CREATED:
|
|
1468
|
+
pass
|
|
1469
|
+
elif self._solver_state == SolverState.REPORTED:
|
|
1470
|
+
self.__execute_callbacks(CallbackType.BEFORE_CLOSE)
|
|
1471
|
+
error_code = swmm_close()
|
|
1472
|
+
self.__validate_error(error_code)
|
|
1473
|
+
self._solver_state = SolverState.CLOSED
|
|
1474
|
+
self.__execute_callbacks(CallbackType.AFTER_CLOSE)
|
|
1475
|
+
else:
|
|
1476
|
+
raise SWMMSolverException(f'Close failed: Solver is not in a valid state: {self._solver_state}')
|
|
1477
|
+
|
|
1478
|
+
cpdef void finalize(self):
|
|
1479
|
+
"""
|
|
1480
|
+
Finalize the solver. Ends simulation, reports the results, and dispose objects.
|
|
1481
|
+
"""
|
|
1482
|
+
cdef int error_code = 0
|
|
1483
|
+
|
|
1484
|
+
if self._solver_state == SolverState.OPEN or \
|
|
1485
|
+
self._solver_state == SolverState.STARTED or \
|
|
1486
|
+
self._solver_state == SolverState.FINISHED:
|
|
1487
|
+
|
|
1488
|
+
self.end()
|
|
1489
|
+
self.report()
|
|
1490
|
+
self.close()
|
|
1491
|
+
elif self._solver_state == SolverState.ENDED:
|
|
1492
|
+
self.report()
|
|
1493
|
+
self.close()
|
|
1494
|
+
elif self._solver_state == SolverState.REPORTED:
|
|
1495
|
+
self.close()
|
|
1496
|
+
elif self._solver_state == SolverState.CREATED or self._solver_state == SolverState.CLOSED:
|
|
1497
|
+
pass
|
|
1498
|
+
else:
|
|
1499
|
+
raise SWMMSolverException(f'Finalize failed: Solver is not in a valid state: {self._solver_state}')
|
|
1500
|
+
|
|
1501
|
+
cpdef void execute(self):
|
|
1502
|
+
"""
|
|
1503
|
+
Run the solver to completion.
|
|
1504
|
+
|
|
1505
|
+
:return: Error code (0 if successful)
|
|
1506
|
+
"""
|
|
1507
|
+
cdef int error_code = 0
|
|
1508
|
+
cdef progress_callback swmm_progress_callback = <progress_callback>progress_callback_wrapper
|
|
1509
|
+
cdef bytes c_inp_file_bytes = self._inp_file.encode('utf-8')
|
|
1510
|
+
cdef bytes c_rpt_file_bytes = self._rpt_file.encode('utf-8')
|
|
1511
|
+
cdef bytes c_out_file_bytes = self._out_file.encode('utf-8')
|
|
1512
|
+
|
|
1513
|
+
cdef const char* c_inp_file = c_inp_file_bytes
|
|
1514
|
+
cdef const char* c_rpt_file = c_rpt_file_bytes
|
|
1515
|
+
cdef const char* c_out_file = c_out_file_bytes
|
|
1516
|
+
|
|
1517
|
+
if (self._solver_state != SolverState.CREATED or self._solver_state != SolverState.CLOSED):
|
|
1518
|
+
if len(self._progress_callbacks) > 0:
|
|
1519
|
+
error_code = swmm_run_with_callback(
|
|
1520
|
+
inp_file=c_inp_file,
|
|
1521
|
+
rpt_file=c_rpt_file,
|
|
1522
|
+
out_file=c_out_file,
|
|
1523
|
+
progress=swmm_progress_callback
|
|
1524
|
+
)
|
|
1525
|
+
else:
|
|
1526
|
+
error_code = swmm_run(
|
|
1527
|
+
inp_file=c_inp_file,
|
|
1528
|
+
rpt_file=c_rpt_file,
|
|
1529
|
+
out_file=c_out_file
|
|
1530
|
+
)
|
|
1531
|
+
else:
|
|
1532
|
+
raise SWMMSolverException(f'Solver is not in a valid state: {self._solver_state}')
|
|
1533
|
+
|
|
1534
|
+
cpdef void use_hotstart(self, str hotstart_file):
|
|
1535
|
+
"""
|
|
1536
|
+
Use a hotstart file.
|
|
1537
|
+
|
|
1538
|
+
:param hotstart_file: Hotstart file name
|
|
1539
|
+
"""
|
|
1540
|
+
cdef bytes c_hotstart_file = hotstart_file.encode('utf-8')
|
|
1541
|
+
cdef const char* cc_hotstart_file = c_hotstart_file
|
|
1542
|
+
cdef int error_code = swmm_useHotStart(hotStartFile=cc_hotstart_file)
|
|
1543
|
+
|
|
1544
|
+
self.__validate_error(error_code)
|
|
1545
|
+
|
|
1546
|
+
cpdef void save_hotstart(self, str hotstart_file):
|
|
1547
|
+
"""
|
|
1548
|
+
Save a hotstart file.
|
|
1549
|
+
|
|
1550
|
+
:param hotstart_file: Hotstart file name
|
|
1551
|
+
"""
|
|
1552
|
+
cdef bytes c_hotstart_file = hotstart_file.encode('utf-8')
|
|
1553
|
+
cdef const char* cc_hotstart_file = c_hotstart_file
|
|
1554
|
+
cdef int error_code = swmm_saveHotStart(hotStartFile=cc_hotstart_file)
|
|
1555
|
+
|
|
1556
|
+
self.__validate_error(error_code)
|
|
1557
|
+
|
|
1558
|
+
def get_mass_balance_error(self) -> Tuple[float, float, float]:
|
|
1559
|
+
"""
|
|
1560
|
+
Get the mass balance error.
|
|
1561
|
+
|
|
1562
|
+
:return: Mass balance error
|
|
1563
|
+
:rtype: Tuple[float, float, float]
|
|
1564
|
+
"""
|
|
1565
|
+
cdef int error_code = 0
|
|
1566
|
+
cdef float runoffErr, flowErr, qualErr
|
|
1567
|
+
|
|
1568
|
+
swmm_getMassBalErr(
|
|
1569
|
+
runoffErr=&runoffErr,
|
|
1570
|
+
flowErr=&flowErr,
|
|
1571
|
+
qualErr=&qualErr
|
|
1572
|
+
)
|
|
1573
|
+
|
|
1574
|
+
self.__validate_error(error_code)
|
|
1575
|
+
|
|
1576
|
+
def __execute_callbacks(self, callback_type: CallbackType) -> None:
|
|
1577
|
+
"""
|
|
1578
|
+
Execute the callbacks for the given type.
|
|
1579
|
+
|
|
1580
|
+
:param callback_type: Type of callback
|
|
1581
|
+
:type callback_type: CallbackType
|
|
1582
|
+
"""
|
|
1583
|
+
for callback in self._callbacks[callback_type]:
|
|
1584
|
+
callback(self)
|
|
1585
|
+
|
|
1586
|
+
cpdef void __execute_progress_callbacks(self, double percent_complete):
|
|
1587
|
+
"""
|
|
1588
|
+
Execute the progress callbacks.
|
|
1589
|
+
|
|
1590
|
+
:param percent_complete: Percent complete
|
|
1591
|
+
:type percent_complete: float
|
|
1592
|
+
"""
|
|
1593
|
+
for callback in self._progress_callbacks:
|
|
1594
|
+
callback(percent_complete)
|
|
1595
|
+
|
|
1596
|
+
cdef void __progress_callback(self, double percent_complete):
|
|
1597
|
+
"""
|
|
1598
|
+
Progress callback for the solver.
|
|
1599
|
+
|
|
1600
|
+
:param percent_complete: Percent complete
|
|
1601
|
+
:type percent_complete: float
|
|
1602
|
+
"""
|
|
1603
|
+
cdef clock_t elapsed_time = clock() - self._clock
|
|
1604
|
+
|
|
1605
|
+
if elapsed_time > 1.0 / self._progress_callbacks_per_second:
|
|
1606
|
+
self.__execute_progress_callbacks(
|
|
1607
|
+
percent_complete=percent_complete
|
|
1608
|
+
)
|
|
1609
|
+
|
|
1610
|
+
self._clock = clock()
|
|
1611
|
+
|
|
1612
|
+
cdef void __validate_error(self, error_code: int) :
|
|
1613
|
+
"""
|
|
1614
|
+
Validate the error code and raise an exception if it is not 0.
|
|
1615
|
+
|
|
1616
|
+
:param error_code: Error code to validate
|
|
1617
|
+
:type error_code: int
|
|
1618
|
+
"""
|
|
1619
|
+
cdef int internal_error_code = <int>swmm_getValue(
|
|
1620
|
+
property=SWMMObjects.SYSTEM.value,
|
|
1621
|
+
index=SWMMSystemProperties.ERROR_CODE.value
|
|
1622
|
+
)
|
|
1623
|
+
|
|
1624
|
+
if error_code < 0:
|
|
1625
|
+
if internal_error_code > 0:
|
|
1626
|
+
raise SWMMSolverException(f'SWMM failed with message: {internal_error_code}, {self.__get_error()}')
|
|
1627
|
+
else:
|
|
1628
|
+
raise SWMMSolverException(f'SWMM failed with message: {error_code}, {get_error_message(error_code)}')
|
|
1629
|
+
|
|
1630
|
+
cdef str __get_error(self):
|
|
1631
|
+
"""
|
|
1632
|
+
Get the error code from the solver.
|
|
1633
|
+
|
|
1634
|
+
:return: Error code
|
|
1635
|
+
:rtype: int
|
|
1636
|
+
"""
|
|
1637
|
+
cdef char* c_error_message = <char*>malloc(1024*sizeof(char))
|
|
1638
|
+
swmm_getError(c_error_message, 1024)
|
|
1639
|
+
|
|
1640
|
+
error_message = c_error_message.decode('utf-8')
|
|
1641
|
+
|
|
1642
|
+
free(c_error_message)
|
|
1643
|
+
|
|
1644
|
+
return error_message
|
|
1645
|
+
|
|
1646
|
+
|