easysewer 0.0.1__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.
easysewer/Options.py ADDED
@@ -0,0 +1,373 @@
1
+ """
2
+ Calculation Options Module
3
+
4
+ This module manages simulation options and parameters for the drainage model,
5
+ including time steps, routing methods, and other calculation settings.
6
+ """
7
+ from .utils import *
8
+
9
+
10
+ class CalculationInformation:
11
+ """
12
+ Controls simulation parameters and calculation options.
13
+
14
+ Manages various settings that control how the drainage simulation is performed,
15
+ including time steps, routing methods, and reporting options.
16
+
17
+ Attributes:
18
+ start_date (str): Simulation start date
19
+ start_time (str): Simulation start time
20
+ report_start_date (str): Report period start date
21
+ report_start_time (str): Report period start time
22
+ end_date (str): Simulation end date
23
+ end_time (str): Simulation end time
24
+ sweep_start (str): Street sweeping start date
25
+ sweep_end (str): Street sweeping end date
26
+ dry_days (float): Days with no rain prior to simulation
27
+ report_step (str): Reporting time step
28
+ wet_step (str): Wet weather time step
29
+ dry_step (str): Dry weather time step
30
+ routing_step (str): Flow routing time step
31
+ allow_ponding (bool): Whether ponding is allowed
32
+ inertial_damping (str): Type of inertial damping
33
+ normal_flow_limited (str): Normal flow limitation method
34
+ force_main_equation (str): Force main equation selection
35
+ variable_step (float): Variable step for dynamic wave routing
36
+ lengthening_step (float): Lengthening step for dynamic wave routing
37
+ min_surface_area (float): Minimum surface area for nodes
38
+ max_trials (int): Maximum trials per time step
39
+ head_tolerance (float): Head difference tolerance
40
+ sys_flow_tolerance (float): System flow tolerance
41
+ lat_flow_tolerance (float): Lateral flow tolerance
42
+ """
43
+ def __init__(self):
44
+ # general option
45
+ self.flow_unit = 'CFS'
46
+ self.infiltration_method = 'HORTON'
47
+ self.flow_routing_method = 'KINWAVE'
48
+ self.link_offsets_type = 'DEPTH'
49
+ self.force_main_equation = 'H-W'
50
+
51
+ self.ignore_rainfall = False
52
+ self.ignore_snow_melt = False
53
+ self.ignore_ground_water = False
54
+ self.ignore_RDII = False
55
+ self.ignore_routing = False
56
+ self.ignore_water_quality = False
57
+
58
+ self.allow_ponding = True
59
+ self.skip_steady_state = False
60
+ self.system_flow_tol = 5
61
+ self.lateral_flow_tol = 5
62
+
63
+ self.simulation_start = {"year": 2023, "month": 4, "day": 28, "hour": 8, "minute": 0}
64
+ self.simulation_end = {"year": 2023, "month": 4, "day": 28, "hour": 17, "minute": 0}
65
+ self.report_start = {"year": 2023, "month": 4, "day": 28, "hour": 8, "minute": 0}
66
+ self.sweep_start = {"month": 1, "day": 1}
67
+ self.sweep_end = {"month": 12, "day": 31}
68
+ self.dry_days = 0
69
+
70
+ self.report_step = {"hour": 0, "minute": 15, "second": 0}
71
+ self.wet_step = {"hour": 0, "minute": 5, "second": 0} # runoff
72
+ self.dry_step = {"hour": 1, "minute": 0, "second": 0} # runoff
73
+ self.routing_step = 600 # in seconds
74
+ self.lengthening_step = 0 # in seconds
75
+ self.variable_step = 0
76
+ self.minimum_step = 0.5 # in seconds
77
+
78
+ self.inertial_damping = 'PARTIAL'
79
+ self.normal_flow_limited = 'BOTH'
80
+
81
+ self.minimum_surface_area = 0
82
+ self.minimum_slope = 0
83
+ self.max_trials = 8
84
+ self.head_tolerance = 0.0015 # in meters
85
+
86
+ self.threads = 1
87
+ self.temp_directory = None
88
+
89
+ # report section information
90
+ self.report_input = False
91
+ self.report_check_continuity = True
92
+ self.report_flow_statistics = True
93
+ self.report_controls = False
94
+ self.report_subcatchments = 'ALL'
95
+ self.report_nodes = 'ALL'
96
+ self.report_links = 'ALL'
97
+
98
+ # map section information
99
+ self.map_dimensions = [0, 0, 1000, 1000]
100
+ self.map_units = 'None'
101
+
102
+ # evaporation section information
103
+ self.evaporation_constant = 0
104
+ self.evaporation_dry_only = False
105
+
106
+ def __repr__(self):
107
+ return f'unit: {self.flow_unit}'
108
+
109
+ def write_to_swmm_inp(self, filename):
110
+ """
111
+
112
+ :param filename:
113
+ """
114
+ with open(filename, 'a', encoding='utf-8') as f:
115
+ f.write('[OPTIONS]\n')
116
+ f.write(f'FLOW_UNITS {self.flow_unit}\n')
117
+ f.write(f'INFILTRATION {self.infiltration_method}\n')
118
+ f.write(f'FLOW_ROUTING {self.flow_routing_method}\n')
119
+ f.write(f'LINK_OFFSETS {self.link_offsets_type}\n')
120
+ f.write(f'FORCE_MAIN_EQUATION {self.force_main_equation}\n')
121
+ f.write('\n')
122
+ f.write('IGNORE_RAINFALL ' + ('YES' if self.ignore_rainfall else 'NO') + '\n')
123
+ f.write('IGNORE_SNOWMELT ' + ('YES' if self.ignore_snow_melt else 'NO') + '\n')
124
+ f.write('IGNORE_GROUNDWATER ' + ('YES' if self.ignore_ground_water else 'NO') + '\n')
125
+ f.write('IGNORE_RDII ' + ('YES' if self.ignore_RDII else 'NO') + '\n')
126
+ f.write('IGNORE_ROUTING ' + ('YES' if self.ignore_routing else 'NO') + '\n')
127
+ f.write('IGNORE_QUALITY ' + ('YES' if self.ignore_water_quality else 'NO') + '\n')
128
+ f.write('\n')
129
+ f.write('ALLOW_PONDING ' + ('YES' if self.allow_ponding else 'NO') + '\n')
130
+ f.write('SKIP_STEADY_STATE ' + ('YES' if self.skip_steady_state else 'NO') + '\n')
131
+ f.write(f'SYS_FLOW_TOL {self.system_flow_tol}\n')
132
+ f.write(f'LAT_FLOW_TOL {self.lateral_flow_tol}\n')
133
+
134
+ f.write('\n')
135
+ f.write('START_DATE ')
136
+ f.write(str(self.simulation_start['month']).zfill(2) + '/')
137
+ f.write(str(self.simulation_start['day']).zfill(2) + '/')
138
+ f.write(str(self.simulation_start['year']) + '\n')
139
+ f.write('START_TIME ')
140
+ f.write(str(self.simulation_start['hour']).zfill(2) + ':')
141
+ f.write(str(self.simulation_start['minute']).zfill(2) + '\n')
142
+ f.write('END_DATE ')
143
+ f.write(str(self.simulation_end['month']).zfill(2) + '/')
144
+ f.write(str(self.simulation_end['day']).zfill(2) + '/')
145
+ f.write(str(self.simulation_end['year']) + '\n')
146
+ f.write('END_TIME ')
147
+ f.write(str(self.simulation_end['hour']).zfill(2) + ':')
148
+ f.write(str(self.simulation_end['minute']).zfill(2) + '\n')
149
+ f.write('REPORT_START_DATE ')
150
+ f.write(str(self.report_start['month']).zfill(2) + '/')
151
+ f.write(str(self.report_start['day']).zfill(2) + '/')
152
+ f.write(str(self.report_start['year']) + '\n')
153
+ f.write('REPORT_START_TIME ')
154
+ f.write(str(self.report_start['hour']).zfill(2) + ':')
155
+ f.write(str(self.report_start['minute']).zfill(2) + '\n')
156
+ f.write('SWEEP_START ')
157
+ f.write(str(self.sweep_start['month']).zfill(2) + '/')
158
+ f.write(str(self.sweep_start['day']).zfill(2) + '\n')
159
+ f.write('SWEEP_END ')
160
+ f.write(str(self.sweep_end['month']).zfill(2) + '/')
161
+ f.write(str(self.sweep_end['day']).zfill(2) + '\n')
162
+
163
+ f.write('\n')
164
+ f.write(f'DRY_DAYS {self.dry_days}\n')
165
+ f.write('REPORT_STEP ')
166
+ f.write(str(self.report_step['hour']).zfill(2) + ':')
167
+ f.write(str(self.report_step['minute']).zfill(2) + ':')
168
+ f.write(str(self.report_step['second']).zfill(2) + '\n')
169
+ f.write('WET_STEP ')
170
+ f.write(str(self.wet_step['hour']).zfill(2) + ':')
171
+ f.write(str(self.wet_step['minute']).zfill(2) + ':')
172
+ f.write(str(self.wet_step['second']).zfill(2) + '\n')
173
+ f.write('DRY_STEP ')
174
+ f.write(str(self.dry_step['hour']).zfill(2) + ':')
175
+ f.write(str(self.dry_step['minute']).zfill(2) + ':')
176
+ f.write(str(self.dry_step['second']).zfill(2) + '\n')
177
+
178
+ f.write('\n')
179
+ f.write(f'ROUTING_STEP {self.routing_step}\n')
180
+ f.write(f'LENGTHENING_STEP {self.lengthening_step}\n')
181
+ f.write(f'VARIABLE_STEP {self.variable_step}\n')
182
+ f.write(f'MINIMUM_STEP {self.minimum_step}\n')
183
+
184
+ f.write('\n')
185
+ f.write(f'INERTIAL_DAMPING {self.inertial_damping}\n')
186
+ f.write(f'NORMAL_FLOW_LIMITED {self.normal_flow_limited}\n')
187
+ f.write(f'MIN_SURFAREA {self.minimum_surface_area}\n')
188
+ f.write(f'MIN_SLOPE {self.minimum_slope}\n')
189
+ f.write(f'MAX_TRIALS {self.max_trials}\n')
190
+ f.write(f'HEAD_TOLERANCE {self.head_tolerance}\n')
191
+ f.write(f'THREADS {self.threads}\n')
192
+ if self.temp_directory is not None:
193
+ f.write(f'TEMPDIR {self.temp_directory}\n')
194
+
195
+ f.write('\n\n[REPORT]\n')
196
+ f.write('INPUT ' + ('YES' if self.report_input else 'NO') + '\n')
197
+ f.write('CONTINUITY ' + ('YES' if self.report_check_continuity else 'NO') + '\n')
198
+ f.write('FLOWSTATS ' + ('YES' if self.report_flow_statistics else 'NO') + '\n')
199
+ f.write('CONTROLS ' + ('YES' if self.report_controls else 'NO') + '\n')
200
+ f.write(f'SUBCATCHMENTS {self.report_subcatchments}\n')
201
+ f.write(f'NODES {self.report_nodes}\n')
202
+ f.write(f'LINKS {self.report_links}\n')
203
+
204
+ f.write('\n\n[MAP]\n')
205
+ f.write(
206
+ f'DIMENSIONS {self.map_dimensions[0]} {self.map_dimensions[1]} {self.map_dimensions[2]} {self.map_dimensions[3]}\n')
207
+ f.write(f'Units {self.map_units}\n')
208
+
209
+ f.write('\n\n[EVAPORATION]\n')
210
+ f.write(f'CONSTANT {self.evaporation_constant}\n')
211
+ f.write('DRY_ONLY ' + ('YES' if self.evaporation_dry_only else 'NO') + '\n')
212
+
213
+ def read_from_swmm_inp(self, filename):
214
+ """
215
+
216
+ :param filename:
217
+ :return:
218
+ """
219
+ contents = get_swmm_inp_content(filename, '[OPTIONS]')
220
+ for line in contents:
221
+ pair = line.split()
222
+ match pair[0]:
223
+ case 'FLOW_UNITS':
224
+ self.flow_unit = pair[1]
225
+ case 'INFILTRATION':
226
+ self.infiltration_method = pair[1]
227
+ case 'FLOW_ROUTING':
228
+ self.flow_routing_method = pair[1]
229
+ case 'LINK_OFFSETS':
230
+ self.link_offsets_type = pair[1]
231
+ case 'FORCE_MAIN_EQUATION':
232
+ self.force_main_equation = pair[1]
233
+ case 'IGNORE_RAINFALL':
234
+ self.ignore_rainfall = True if pair[1] == 'YES' else False
235
+ case 'IGNORE_SNOWMELT':
236
+ self.ignore_snow_melt = True if pair[1] == 'YES' else False
237
+ case 'IGNORE_GROUNDWATER':
238
+ self.ignore_ground_water = True if pair[1] == 'YES' else False
239
+ case 'IGNORE_RDII':
240
+ self.ignore_RDII = True if pair[1] == 'YES' else False
241
+ case 'IGNORE_ROUTING':
242
+ self.ignore_routing = True if pair[1] == 'YES' else False
243
+ case 'IGNORE_QUALITY':
244
+ self.ignore_water_quality = True if pair[1] == 'YES' else False
245
+ case 'ALLOW_PONDING':
246
+ self.allow_ponding = True if pair[1] == 'YES' else False
247
+ case 'SKIP_STEADY_STATE':
248
+ self.skip_steady_state = True if pair[1] == 'YES' else False
249
+ case 'SYS_FLOW_TOL':
250
+ self.system_flow_tol = int(pair[1])
251
+ case 'LAT_FLOW_TOL':
252
+ self.lateral_flow_tol = int(pair[1])
253
+ case 'START_DATE':
254
+ keys = [int(i) for i in pair[1].split('/')]
255
+ self.simulation_start['year'] = keys[2]
256
+ self.simulation_start['month'] = keys[0]
257
+ self.simulation_start['day'] = keys[1]
258
+ case 'START_TIME':
259
+ keys = [int(i) for i in pair[1].split(':')]
260
+ self.simulation_start['hour'] = keys[0]
261
+ self.simulation_start['minute'] = keys[1]
262
+ case 'END_DATE':
263
+ keys = [int(i) for i in pair[1].split('/')]
264
+ self.simulation_end['year'] = keys[2]
265
+ self.simulation_end['month'] = keys[0]
266
+ self.simulation_end['day'] = keys[1]
267
+ case 'END_TIME':
268
+ keys = [int(i) for i in pair[1].split(':')]
269
+ self.simulation_end['hour'] = keys[0]
270
+ self.simulation_end['minute'] = keys[1]
271
+ case 'REPORT_START_DATE':
272
+ keys = [int(i) for i in pair[1].split('/')]
273
+ self.report_start['year'] = keys[2]
274
+ self.report_start['month'] = keys[0]
275
+ self.report_start['day'] = keys[1]
276
+ case 'REPORT_START_TIME':
277
+ keys = [int(i) for i in pair[1].split(':')]
278
+ self.report_start['hour'] = keys[0]
279
+ self.report_start['minute'] = keys[1]
280
+ case 'SWEEP_START':
281
+ keys = [int(i) for i in pair[1].split('/')]
282
+ self.sweep_start['month'] = keys[0]
283
+ self.sweep_start['day'] = keys[1]
284
+ case 'SWEEP_END':
285
+ keys = [int(i) for i in pair[1].split('/')]
286
+ self.sweep_end['month'] = keys[0]
287
+ self.sweep_end['day'] = keys[1]
288
+ case 'DRY_DAYS':
289
+ self.dry_days = int(pair[1])
290
+ case 'REPORT_STEP':
291
+ keys = [int(i) for i in pair[1].split(':')]
292
+ self.report_step['hour'] = keys[0]
293
+ self.report_step['minute'] = keys[1]
294
+ self.report_step['second'] = keys[2]
295
+ case 'WET_STEP':
296
+ keys = [int(i) for i in pair[1].split(':')]
297
+ self.wet_step['hour'] = keys[0]
298
+ self.wet_step['minute'] = keys[1]
299
+ self.wet_step['second'] = keys[2]
300
+ case 'DRY_STEP':
301
+ keys = [int(i) for i in pair[1].split(':')]
302
+ self.dry_step['hour'] = keys[0]
303
+ self.dry_step['minute'] = keys[1]
304
+ self.dry_step['second'] = keys[2]
305
+ case 'ROUTING_STEP':
306
+ keys = [int(i) for i in pair[1].split(':')]
307
+ if len(keys) == 1:
308
+ self.routing_step = keys[0]
309
+ elif len(keys) == 2:
310
+ self.routing_step = keys[-1] + keys[-2] * 60
311
+ elif len(keys) == 3:
312
+ self.routing_step = keys[-1] + keys[-2] * 60 + keys[-3] * 3600
313
+ else:
314
+ pass
315
+ case 'LENGTHENING_STEP':
316
+ keys = [int(i) for i in pair[1].split(':')]
317
+ if len(keys) == 1:
318
+ self.lengthening_step = keys[0]
319
+ elif len(keys) == 2:
320
+ self.lengthening_step = keys[-1] + keys[-2] * 60
321
+ elif len(keys) == 3:
322
+ self.lengthening_step = keys[-1] + keys[-2] * 60 + keys[-3] * 3600
323
+ else:
324
+ pass
325
+ case 'VARIABLE_STEP':
326
+ self.variable_step = float(pair[1])
327
+ case 'MINIMUM_STEP':
328
+ self.minimum_step = float(pair[1])
329
+ case 'INERTIAL_DAMPING':
330
+ self.inertial_damping = pair[1]
331
+ case 'NORMAL_FLOW_LIMITED':
332
+ self.normal_flow_limited = pair[1]
333
+ case 'MIN_SURFAREA':
334
+ self.minimum_surface_area = float(pair[1])
335
+ case 'MIN_SLOPE':
336
+ self.minimum_slope = float(pair[1])
337
+ case 'MAX_TRIALS':
338
+ self.max_trials = int(pair[1])
339
+ case 'HEAD_TOLERANCE':
340
+ self.head_tolerance = float(pair[1])
341
+ case 'THREADS':
342
+ self.threads = int(pair[1])
343
+ case 'TEMPDIR':
344
+ self.temp_directory = pair[1]
345
+ case _:
346
+ pass
347
+ contents = get_swmm_inp_content(filename, '[REPORT]')
348
+ for line in contents:
349
+ pair = line.split()
350
+ match pair[0]:
351
+ case 'INPUT':
352
+ self.report_input = True if pair[1] == 'YES' else False
353
+ case 'CONTINUITY':
354
+ self.report_check_continuity = True if pair[1] == 'YES' else False
355
+ case 'FLOWSTATS':
356
+ self.report_flow_statistics = True if pair[1] == 'YES' else False
357
+ case 'CONTROLS':
358
+ self.report_controls = True if pair[1] == 'YES' else False
359
+ case 'SUBCATCHMENTS':
360
+ self.report_subcatchments = pair[1]
361
+ case 'NODES':
362
+ self.report_nodes = pair[1]
363
+ case 'LINKS':
364
+ self.report_links = pair[1]
365
+ contents = get_swmm_inp_content(filename, '[MAP]')
366
+ for line in contents:
367
+ pair = line.split()
368
+ match pair[0]:
369
+ case 'DIMENSIONS':
370
+ self.map_dimensions = [float(pair[1]), float(pair[2]), float(pair[3]), float(pair[4])]
371
+ case 'Units':
372
+ self.map_units = pair[1]
373
+ return 0
easysewer/OutputAPI.py ADDED
@@ -0,0 +1,179 @@
1
+ """
2
+ pass
3
+ """
4
+ import ctypes
5
+ import platform
6
+ import os
7
+
8
+
9
+ class SWMMOutputAPI:
10
+ def __init__(self):
11
+ #
12
+ system = platform.system()
13
+ if system == 'Windows':
14
+ lib_path = os.path.join(os.path.dirname(__file__), 'libs', 'win', 'swmm-output.dll')
15
+ elif system == 'Linux':
16
+ lib_path = os.path.join(os.path.dirname(__file__), 'libs', 'linux', 'swmm-output.so')
17
+ else:
18
+ raise OSError('Unsupported operating system')
19
+ #
20
+ # Load the shared library
21
+ self.lib = ctypes.CDLL(lib_path)
22
+ #
23
+ self._set_prototypes()
24
+
25
+ def _set_prototypes(self):
26
+ # Define the function prototypes
27
+ self.lib.SMO_init.argtypes = [ctypes.POINTER(ctypes.c_void_p)]
28
+ self.lib.SMO_init.restype = ctypes.c_int
29
+
30
+ self.lib.SMO_close.argtypes = [ctypes.POINTER(ctypes.c_void_p)]
31
+ self.lib.SMO_close.restype = ctypes.c_int
32
+
33
+ self.lib.SMO_open.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
34
+ self.lib.SMO_open.restype = ctypes.c_int
35
+
36
+ self.lib.SMO_getVersion.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_int)]
37
+ self.lib.SMO_getVersion.restype = ctypes.c_int
38
+
39
+ self.lib.SMO_getFlowUnits.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_int)]
40
+ self.lib.SMO_getFlowUnits.restype = ctypes.c_int
41
+
42
+ self.lib.SMO_getStartDate.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_double)]
43
+ self.lib.SMO_getStartDate.restype = ctypes.c_int
44
+
45
+ self.lib.SMO_getTimes.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.POINTER(ctypes.c_int)]
46
+ self.lib.SMO_getTimes.restype = ctypes.c_int
47
+
48
+ self.lib.SMO_getSubcatchSeries.argtypes = [
49
+ ctypes.c_void_p, # Handle
50
+ ctypes.c_int, # Subcatch index
51
+ ctypes.c_int, # Subcatch attribute
52
+ ctypes.c_int, # Start period
53
+ ctypes.c_int, # End period
54
+ ctypes.POINTER(ctypes.POINTER(ctypes.c_float)), # Output value array
55
+ ctypes.POINTER(ctypes.c_int) # Length
56
+ ]
57
+ self.lib.SMO_getSubcatchSeries.restype = ctypes.c_int
58
+
59
+ self.lib.SMO_getNodeSeries.argtypes = [
60
+ ctypes.c_void_p, # Handle
61
+ ctypes.c_int, # Node index
62
+ ctypes.c_int, # Node attribute
63
+ ctypes.c_int, # Start period
64
+ ctypes.c_int, # End period
65
+ ctypes.POINTER(ctypes.POINTER(ctypes.c_float)), # Output value array
66
+ ctypes.POINTER(ctypes.c_int) # Length
67
+ ]
68
+ self.lib.SMO_getNodeSeries.restype = ctypes.c_int
69
+
70
+ self.lib.SMO_getLinkSeries.argtypes = [
71
+ ctypes.c_void_p, # Handle
72
+ ctypes.c_int, # Link index
73
+ ctypes.c_int, # Link attribute
74
+ ctypes.c_int, # Start period
75
+ ctypes.c_int, # End period
76
+ ctypes.POINTER(ctypes.POINTER(ctypes.c_float)), # Output value array
77
+ ctypes.POINTER(ctypes.c_int) # Length
78
+ ]
79
+ self.lib.SMO_getLinkSeries.restype = ctypes.c_int
80
+
81
+ self.lib.SMO_getSystemSeries.argtypes = [
82
+ ctypes.c_void_p, # Handle
83
+ ctypes.c_int, # System attribute
84
+ ctypes.c_int, # Start period
85
+ ctypes.c_int, # End period
86
+ ctypes.POINTER(ctypes.POINTER(ctypes.c_float)), # Output value array
87
+ ctypes.POINTER(ctypes.c_int) # Length
88
+ ]
89
+ self.lib.SMO_getSystemSeries.restype = ctypes.c_int
90
+
91
+ self.lib.SMO_free.argtypes = [ctypes.POINTER(ctypes.POINTER(ctypes.c_float))]
92
+ self.lib.SMO_free.restype = None
93
+
94
+ # Initialize the API
95
+ self.handle = ctypes.c_void_p()
96
+ if self.lib.SMO_init(ctypes.byref(self.handle)) != 0:
97
+ raise Exception("Failed to initialize SWMM Output API")
98
+
99
+ def __enter__(self):
100
+ if self.lib.SMO_init(ctypes.byref(self.handle)) != 0:
101
+ raise Exception("Failed to initialize SWMM Output API")
102
+ self.initialized = True
103
+ return self
104
+
105
+ def __exit__(self, exc_type, exc_value, traceback):
106
+ if self.initialized and self.handle:
107
+ if self.lib.SMO_close(ctypes.byref(self.handle)) != 0:
108
+ raise Exception("Failed to close SWMM Output API")
109
+ self.initialized = False
110
+
111
+ def open(self, file_path):
112
+ if self.lib.SMO_open(self.handle, file_path.encode('utf-8')) != 0:
113
+ raise Exception("Failed to open SWMM output file")
114
+
115
+ def get_flow_units(self):
116
+ unit_flag = ctypes.c_int()
117
+ if self.lib.SMO_getFlowUnits(self.handle, ctypes.byref(unit_flag)) != 0:
118
+ raise Exception("Failed to get flow units")
119
+ return unit_flag.value
120
+
121
+ def get_start_date(self):
122
+ start_date = ctypes.c_double()
123
+ if self.lib.SMO_getStartDate(self.handle, ctypes.byref(start_date)) != 0:
124
+ raise Exception("Failed to get start date")
125
+ return start_date.value
126
+
127
+ def get_times(self, code):
128
+ time = ctypes.c_int()
129
+ if self.lib.SMO_getTimes(self.handle, code, ctypes.byref(time)) != 0:
130
+ raise Exception(f"Failed to get times for code {code}")
131
+ return time.value
132
+
133
+ def get_subcatch_series(self, subcatch_index, attr, start_period, end_period):
134
+ value_array = ctypes.POINTER(ctypes.c_float)()
135
+ length = ctypes.c_int()
136
+ if self.lib.SMO_getSubcatchSeries(
137
+ self.handle, subcatch_index, attr, start_period, end_period,
138
+ ctypes.byref(value_array), ctypes.byref(length)
139
+ ) != 0:
140
+ raise Exception("Failed to get subcatch series")
141
+ values = [value_array[i] for i in range(length.value)]
142
+ self.lib.SMO_free(ctypes.byref(value_array))
143
+ return values
144
+
145
+ def get_node_series(self, node_index, attr, start_period, end_period):
146
+ value_array = ctypes.POINTER(ctypes.c_float)()
147
+ length = ctypes.c_int()
148
+ if self.lib.SMO_getNodeSeries(
149
+ self.handle, node_index, attr, start_period, end_period,
150
+ ctypes.byref(value_array), ctypes.byref(length)
151
+ ) != 0:
152
+ raise Exception("Failed to get node series")
153
+ values = [value_array[i] for i in range(length.value)]
154
+ self.lib.SMO_free(ctypes.byref(value_array))
155
+ return values
156
+
157
+ def get_link_series(self, link_index, attr, start_period, end_period):
158
+ value_array = ctypes.POINTER(ctypes.c_float)()
159
+ length = ctypes.c_int()
160
+ if self.lib.SMO_getLinkSeries(
161
+ self.handle, link_index, attr, start_period, end_period,
162
+ ctypes.byref(value_array), ctypes.byref(length)
163
+ ) != 0:
164
+ raise Exception("Failed to get link series")
165
+ values = [value_array[i] for i in range(length.value)]
166
+ self.lib.SMO_free(ctypes.byref(value_array))
167
+ return values
168
+
169
+ def get_system_series(self, attr, start_period, end_period):
170
+ value_array = ctypes.POINTER(ctypes.c_float)()
171
+ length = ctypes.c_int()
172
+ if self.lib.SMO_getSystemSeries(
173
+ self.handle, attr, start_period, end_period,
174
+ ctypes.byref(value_array), ctypes.byref(length)
175
+ ) != 0:
176
+ raise Exception("Failed to get system series")
177
+ values = [value_array[i] for i in range(length.value)]
178
+ self.lib.SMO_free(ctypes.byref(value_array))
179
+ return values