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.
@@ -0,0 +1,1557 @@
1
+ # cython: language_level=3str
2
+ # Description: Cython module for openswmmcore output file processing and data extraction functions for the openswmmcore python package.
3
+ # Created by: Caleb Buahin (EPA/ORD/CESER/WID)
4
+ # Created on: 2024-11-19
5
+
6
+ # python and cython imports
7
+ import os
8
+ from enum import Enum
9
+ from typing import List, Tuple, Union, Optional, Dict, Set
10
+ from cpython.datetime cimport datetime, timedelta
11
+ from libc.stdlib cimport free, malloc
12
+
13
+ # external imports
14
+
15
+ # local python and cython imports
16
+ from ..solver import (
17
+ decode_swmm_datetime,
18
+ )
19
+
20
+ from .output cimport (
21
+ SMO_unitSystem,
22
+ SMO_flowUnits,
23
+ SMO_concUnits,
24
+ SMO_elementType,
25
+ SMO_time,
26
+ SMO_subcatchAttribute,
27
+ SMO_nodeAttribute,
28
+ SMO_linkAttribute,
29
+ SMO_systemAttribute,
30
+ SMO_Handle,
31
+ MAXFILENAME,
32
+ MAXELENAME,
33
+ SMO_init,
34
+ SMO_open,
35
+ SMO_close,
36
+ SMO_getVersion,
37
+ SMO_getProjectSize,
38
+ SMO_getUnits,
39
+ SMO_getFlowUnits,
40
+ SMO_getPollutantUnits,
41
+ SMO_getStartDate,
42
+ SMO_getTimes,
43
+ SMO_getElementName,
44
+ SMO_getNumVars,
45
+ SMO_getVarCode,
46
+ SMO_getVarCodes,
47
+ SMO_getNumProperties,
48
+ SMO_getPropertyCode,
49
+ SMO_getPropertyCodes,
50
+ SMO_getPropertyValue,
51
+ SMO_getPropertyValues,
52
+ SMO_getSubcatchSeries,
53
+ SMO_getNodeSeries,
54
+ SMO_getLinkSeries,
55
+ SMO_getSystemSeries,
56
+ SMO_getSubcatchAttribute,
57
+ SMO_getNodeAttribute,
58
+ SMO_getLinkAttribute,
59
+ SMO_getLinkAttribute,
60
+ SMO_getSystemAttribute,
61
+ SMO_getSubcatchResult,
62
+ SMO_getNodeResult,
63
+ SMO_getLinkResult,
64
+ SMO_getSystemResult,
65
+ SMO_free,
66
+ SMO_clearError,
67
+ SMO_checkError
68
+ )
69
+
70
+ class UnitSystem(Enum):
71
+ """
72
+ Enumeration of the unit system used in the output file.
73
+
74
+ :ivar US: US customary units.
75
+ :type US: int
76
+ :ivar SI: SI metric units.
77
+ :type SI: int
78
+ """
79
+ US = SMO_unitSystem.SMO_US #: US customary units.
80
+ SI = SMO_unitSystem.SMO_SI #: SI metric units.
81
+
82
+ class FlowUnits(Enum):
83
+ """
84
+ Enumeration of the flow units used in the simulation.
85
+
86
+ :ivar CFS: Cubic feet per second.
87
+ :type CFS: int
88
+ :ivar GPM: Gallons per minute.
89
+ :type GPM: int
90
+ :ivar MGD: Million gallons per day.
91
+ :type MGD: int
92
+ :ivar CMS: Cubic meters per second.
93
+ :type CMS: int
94
+ :ivar LPS: Liters per second.
95
+ :type LPS: int
96
+ :ivar MLD: Million liters per day.
97
+ :type MLD: int
98
+ """
99
+ CFS = SMO_flowUnits.SMO_CFS #: Cubic feet per second.
100
+ GPM = SMO_flowUnits.SMO_GPM #: Gallons per minute.
101
+ MGD = SMO_flowUnits.SMO_MGD #: Million gallons per day.
102
+ CMS = SMO_flowUnits.SMO_CMS #: Cubic meters per second.
103
+ LPS = SMO_flowUnits.SMO_LPS #: Liters per second.
104
+ MLD = SMO_flowUnits.SMO_MLD #: Million liters per day.
105
+
106
+ class ConcentrationUnits(Enum):
107
+ """
108
+ Enumeration of the concentration units used in the simulation.
109
+
110
+ :ivar MG: Milligrams per liter.
111
+ :type MG: int
112
+ :ivar UG: Micrograms per liter.
113
+ :type UG: int
114
+ :ivar COUNT: Counts per liter.
115
+ :type COUNT: int
116
+ :ivar NONE: No units.
117
+ :type NONE: int
118
+ """
119
+ MG = SMO_concUnits.SMO_MG #: Milligrams per liter.
120
+ UG = SMO_concUnits.SMO_UG #: Micrograms per liter.
121
+ COUNT = SMO_concUnits.SMO_COUNT #: Counts per liter.
122
+ NONE = SMO_concUnits.SMO_NONE #: No units.
123
+
124
+ class ElementType(Enum):
125
+ """
126
+ Enumeration of the SWMM element types.
127
+
128
+ :ivar SUBCATCHMENT: Subcatchment.
129
+ :type SUBCATCHMENT: int
130
+ :ivar NODE: Node.
131
+ :type NODE: int
132
+ :ivar LINK: Link.
133
+ :type LINK: int
134
+ :ivar SYS: System.
135
+ :type SYS: int
136
+ :ivar POLLUTANT: Pollutant.
137
+ :type POLLUTANT: int
138
+ """
139
+ SUBCATCHMENT = SMO_elementType.SMO_subcatch #: Subcatchment.
140
+ NODE = SMO_elementType.SMO_node #: Node.
141
+ LINK = SMO_elementType.SMO_link #: Link.
142
+ SYSTEM = SMO_elementType.SMO_sys #: System.
143
+ POLLUTANT = SMO_elementType.SMO_pollut #: Pollutant.
144
+
145
+ class TimeAttribute(Enum):
146
+ """
147
+ Enumeration of the report time related attributes.
148
+
149
+ :ivar REPORT_STEP: Report step size (seconds).
150
+ :type REPORT_STEP: int
151
+ :ivar NUM_PERIODS: Number of reporting periods.
152
+ :type NUM_PERIODS: int
153
+ """
154
+ REPORT_STEP = SMO_time.SMO_reportStep #: Report step size (seconds).
155
+ NUM_PERIODS = SMO_time.SMO_numPeriods #: Number of reporting periods.
156
+
157
+ class SubcatchAttribute(Enum):
158
+ """
159
+ Enumeration of the subcatchment attributes.
160
+
161
+ :ivar RAINFALL: Subcatchment rainfall (in/hr or mm/hr).
162
+ :type RAINFALL: int
163
+ :ivar SNOW_DEPTH: Subcatchment snow depth (in or mm).
164
+ :type SNOW_DEPTH: int
165
+ :ivar EVAPORATION_LOSS: Subcatchment evaporation loss (in/hr or mm/hr).
166
+ :type EVAPORATION_LOSS: int
167
+ :ivar INFILTRATION_LOSS: Subcatchment infiltration loss (in/hr or mm/hr).
168
+ :type INFILTRATION_LOSS: int
169
+ :ivar RUNOFF_RATE: Subcatchment runoff flow (flow units).
170
+ :type RUNOFF_RATE: int
171
+ :ivar GROUNDWATER_OUTFLOW: Subcatchment groundwater flow (flow units).
172
+ :type GROUNDWATER_OUTFLOW: int
173
+ :ivar GW_TABLE: Subcatchment groundwater elevation (ft or m).
174
+ :type GW_TABLE: int
175
+ :ivar SOIL_MOISTURE: Subcatchment soil moisture content (-).
176
+ :type SOIL_MOISTURE: int
177
+ :ivar POLLUTANT_CONCENTRATION: Subcatchment pollutant concentration (-).
178
+ :type POLLUTANT_CONCENTRATION: int
179
+ """
180
+ RAINFALL = SMO_subcatchAttribute.SMO_rainfall_subcatch #: Subcatchment rainfall (in/hr or mm/hr).
181
+ SNOW_DEPTH = SMO_subcatchAttribute.SMO_snow_depth_subcatch #: Subcatchment snow depth (in or mm).
182
+ EVAPORATION_LOSS = SMO_subcatchAttribute.SMO_evap_loss #: Subcatchment evaporation loss (in/hr or mm/hr).
183
+ INFILTRATION_LOSS = SMO_subcatchAttribute.SMO_infil_loss #: Subcatchment infiltration loss (in/hr or mm/hr).
184
+ RUNOFF_RATE = SMO_subcatchAttribute.SMO_runoff_rate #: Subcatchment runoff flow (flow units).
185
+ GROUNDWATER_OUTFLOW = SMO_subcatchAttribute.SMO_gwoutflow_rate #: Subcatchment groundwater flow (flow units).
186
+ GROUNDWATER_TABLE_ELEVATION = SMO_subcatchAttribute.SMO_gwtable_elev #: Subcatchment groundwater elevation (ft or m).
187
+ SOIL_MOISTURE = SMO_subcatchAttribute.SMO_soil_moisture #: Subcatchment soil moisture content (-).
188
+ POLLUTANT_CONCENTRATION = SMO_subcatchAttribute.SMO_pollutant_conc_subcatch #: Subcatchment pollutant concentration (-).
189
+
190
+ class NodeAttribute(Enum):
191
+ """
192
+ Enumeration of the node attributes.
193
+
194
+ :ivar INVERT_DEPTH: Node depth above invert (ft or m).
195
+ :type INVERT_DEPTH: int
196
+ :ivar HYDRAULIC_HEAD: Node hydraulic head (ft or m).
197
+ :type HYDRAULIC_HEAD: int
198
+ :ivar STORED_VOLUME: Node volume stored (ft3 or m3).
199
+ :type STORED_VOLUME: int
200
+ :ivar LATERAL_INFLOW: Node lateral inflow (flow units).
201
+ :type LATERAL_INFLOW: int
202
+ :ivar TOTAL_INFLOW: Node total inflow (flow units).
203
+ :type TOTAL_INFLOW: int
204
+ :ivar FLOODING_LOSSES: Node flooding losses (flow units).
205
+ :type FLOODING_LOSSES: int
206
+ :ivar POLLUTANT_CONCENTRATION: Node pollutant concentration (-).
207
+ :type POLLUTANT_CONCENTRATION: int
208
+ """
209
+ INVERT_DEPTH = SMO_nodeAttribute.SMO_invert_depth #: Node depth above invert (ft or m).
210
+ HYDRAULIC_HEAD = SMO_nodeAttribute.SMO_hydraulic_head #: Node hydraulic head (ft or m).
211
+ STORED_VOLUME = SMO_nodeAttribute.SMO_stored_ponded_volume #: Node volume stored (ft3 or m3).
212
+ LATERAL_INFLOW = SMO_nodeAttribute.SMO_lateral_inflow #: Node lateral inflow (flow units).
213
+ TOTAL_INFLOW = SMO_nodeAttribute.SMO_total_inflow #: Node total inflow (flow units).
214
+ FLOODING_LOSSES = SMO_nodeAttribute.SMO_flooding_losses #: Node flooding losses (flow units).
215
+ POLLUTANT_CONCENTRATION = SMO_nodeAttribute.SMO_pollutant_conc_node #: Node pollutant concentration (-).
216
+
217
+ class LinkAttribute(Enum):
218
+ """
219
+ Enumeration of the link attributes.
220
+
221
+ :ivar FLOW_RATE: Link flow rate (flow units).
222
+ :type FLOW_RATE: int
223
+ :ivar FLOW_DEPTH: Link flow depth (ft or m).
224
+ :type FLOW_DEPTH: int
225
+ :ivar FLOW_VELOCITY: Link flow velocity (ft/s or m/s).
226
+ :type FLOW_VELOCITY: int
227
+ :ivar FLOW_VOLUME: Link flow volume (ft3 or m3).
228
+ :type FLOW_VOLUME: int
229
+ :ivar CAPACITY: Link capacity (fraction of conduit filled).
230
+ :type CAPACITY: int
231
+ :ivar POLLUTANT_CONCENTRATION: Link pollutant concentration (-).
232
+ :type POLLUTANT_CONCENTRATION: int
233
+ """
234
+ FLOW_RATE = SMO_linkAttribute.SMO_flow_rate_link #: Link flow rate (flow units).
235
+ FLOW_DEPTH = SMO_linkAttribute.SMO_flow_depth #: Link flow depth (ft or m).
236
+ FLOW_VELOCITY = SMO_linkAttribute.SMO_flow_velocity #: Link flow velocity (ft/s or m/s).
237
+ FLOW_VOLUME = SMO_linkAttribute.SMO_flow_volume #: Link flow volume (ft3 or m3).
238
+ CAPACITY = SMO_linkAttribute.SMO_capacity #: Link capacity (fraction of conduit filled).
239
+ POLLUTANT_CONCENTRATION = SMO_linkAttribute.SMO_pollutant_conc_link #: Link pollutant concentration (-).
240
+
241
+ class SystemAttribute(Enum):
242
+ """
243
+ Enumeration of the system attributes.
244
+
245
+ :ivar AIR_TEMP: Air temperature (deg. F or deg. C).
246
+ :type AIR_TEMP: int
247
+ :ivar RAINFALL: Rainfall intensity (in/hr or mm/hr).
248
+ :type RAINFALL: int
249
+ :ivar SNOW_DEPTH: Snow depth (in or mm).
250
+ :type SNOW_DEPTH: int
251
+ :ivar EVAP_INFIL_LOSS: Evaporation and infiltration loss rate (in/day or mm/day).
252
+ :type EVAP_INFIL_LOSS: int
253
+ :ivar RUNOFF_FLOW: Runoff flow (flow units).
254
+ :type RUNOFF_FLOW: int
255
+ :ivar DRY_WEATHER_INFLOW: Dry weather inflow (flow units).
256
+ :type DRY_WEATHER_INFLOW: int
257
+ :ivar GROUNDWATER_INFLOW: Groundwater inflow (flow units).
258
+ :type GROUNDWATER_INFLOW: int
259
+ :ivar RDII_INFLOW: Rainfall Derived Infiltration and Inflow (RDII) (flow units).
260
+ :type RDII_INFLOW: int
261
+ :ivar DIRECT_INFLOW: Direct inflow (flow units).
262
+ :type DIRECT_INFLOW: int
263
+ :ivar TOTAL_LATERAL_INFLOW: Total lateral inflow; sum of variables 4 to 8 (flow units).
264
+ :type TOTAL_LATERAL_INFLOW: int
265
+ :ivar FLOOD_LOSSES: Flooding losses (flow units).
266
+ :type FLOOD_LOSSES: int
267
+ :ivar OUTFALL_FLOWS: Outfall flow (flow units).
268
+ :type OUTFALL_FLOWS: int
269
+ :ivar VOLUME_STORED: Volume stored in storage nodes (ft3 or m3).
270
+ :type VOLUME_STORED: int
271
+ :ivar EVAPORATION_RATE: Evaporation rate (in/day or mm/day).
272
+ :type EVAPORATION_RATE: int
273
+ """
274
+ AIR_TEMP = SMO_systemAttribute.SMO_air_temp #: Air temperature (deg. F or deg. C).
275
+ RAINFALL = SMO_systemAttribute.SMO_rainfall_system #: Rainfall intensity (in/hr or mm/hr).
276
+ SNOW_DEPTH = SMO_systemAttribute.SMO_snow_depth_system #: Snow depth (in or mm).
277
+ EVAP_INFIL_LOSS = SMO_systemAttribute.SMO_evap_infil_loss #: Evaporation and infiltration loss rate (in/day or mm/day).
278
+ RUNOFF_FLOW = SMO_systemAttribute.SMO_runoff_flow #: Runoff flow (flow units).
279
+ DRY_WEATHER_INFLOW = SMO_systemAttribute.SMO_dry_weather_inflow #: Dry weather inflow (flow units).
280
+ GROUNDWATER_INFLOW = SMO_systemAttribute.SMO_groundwater_inflow #: Groundwater inflow (flow units).
281
+ RDII_INFLOW = SMO_systemAttribute.SMO_RDII_inflow #: Rainfall Derived Infiltration and Inflow (RDII) (flow units).
282
+ DIRECT_INFLOW = SMO_systemAttribute.SMO_direct_inflow #: Direct inflow (flow units).
283
+ TOTAL_LATERAL_INFLOW = SMO_systemAttribute.SMO_total_lateral_inflow #: Total lateral inflow; sum of variables 4 to 8 (flow units).
284
+ FLOOD_LOSSES = SMO_systemAttribute.SMO_flood_losses #: Flooding losses (flow units).
285
+ OUTFALL_FLOWS = SMO_systemAttribute.SMO_outfall_flows #: Outfall flow (flow units).
286
+ VOLUME_STORED = SMO_systemAttribute.SMO_volume_stored #: Volume stored in storage nodes (ft3 or m3).
287
+ EVAPORATION_RATE = SMO_systemAttribute.SMO_evap_rate #: Evaporation rate (in/day or mm/day).
288
+
289
+ class SWMMOutputException(Exception):
290
+ """
291
+ Exception class for SWMM output file processing errors.
292
+ """
293
+ def __init__(self, message: str) -> None:
294
+ """
295
+ Constructor to initialize the exception message.
296
+
297
+ :param message: Error message.
298
+ :type message: str
299
+ """
300
+ super().__init__(message)
301
+
302
+ cdef class Output:
303
+ """
304
+ Class to read and process the output file generated by the SWMM simulation.
305
+
306
+ :cvar _output_file_handle: Handle to the SWMM output file.
307
+ :cvar _version: Version of the SWMM output file.
308
+ :cvar _units: Unit system used in the SWMM output file.
309
+ :cvar _flow_units: Flow units used in the SWMM output file.
310
+ :cvar _output_size: Size of the project in the SWMM output file.
311
+ :cvar _pollutant_units: Pollutant units used in the SWMM output file.
312
+ :cvar _start_date: Start date of the simulation in the SWMM output file.
313
+ :cvar _report_step: Report step size in seconds.
314
+ :cvar _num_periods: Number of reporting periods.
315
+ :cvar _times: Times of the simulation in the SWMM output file.
316
+ """
317
+ cdef SMO_Handle _output_file_handle
318
+ cdef int _version
319
+ cdef int* _units
320
+ cdef int _units_length
321
+ cdef int _flow_units
322
+ cdef int* _output_size
323
+ cdef int _output_size_length
324
+ cdef list _pollutant_units
325
+ cdef dict _element_name_indexes
326
+ cdef datetime _start_date
327
+ cdef int _report_step
328
+ cdef int _num_periods
329
+ cdef list _times
330
+
331
+ def __cinit__(self, str output_file):
332
+ """
333
+ Constructor to open the SWMM output file.
334
+
335
+ :param output_file: Path to the SWMM output file.
336
+ :type output_file: str
337
+ """
338
+ cdef int i = 0
339
+ cdef int error_code = 0
340
+ cdef bytes path_bytes = output_file.encode('utf-8')
341
+ cdef const char* c_output_file = path_bytes
342
+ self._output_file_handle = NULL
343
+
344
+ self._output_size = NULL
345
+ self._units = NULL
346
+
347
+ error_code = SMO_init(&self._output_file_handle)
348
+
349
+ # get error message if error code is not 0 and print it and prevent any memory leaks
350
+ if error_code != 0:
351
+ # create a buffer to store the error message
352
+ error_message = self.check_error()
353
+ raise SWMMOutputException(f"Error initializing the SWMM output file {output_file}. Error code: {error_code}: {error_message}")
354
+
355
+ # Check if the output file exists
356
+ if not os.path.exists(output_file):
357
+ raise FileNotFoundError(f"Error opening the SWMM output file {output_file}. Error code: 434: The output file does not exist.")
358
+
359
+ error_code = SMO_open(self._output_file_handle, c_output_file)
360
+
361
+ # get error message if error code is not 0 and print it and prevent any memory leaks
362
+ if error_code != 0:
363
+ # create a buffer to store the error message
364
+ error_message = self.check_error()
365
+
366
+ if error_code > 400:
367
+ self._output_file_handle = NULL
368
+
369
+ if error_code == 434:
370
+ raise FileNotFoundError(f"Error opening the SWMM output file {output_file}. Error code: {error_code}: {error_message}. The output file may not exist or may be locked by another process.")
371
+ else:
372
+ raise SWMMOutputException(f"Error opening the SWMM output file {output_file}. Error code: {error_code}: {error_message}")
373
+
374
+ # Read and cache output attributes for faster access
375
+ self._version = self.__get_version()
376
+ self._units, self._units_length = self.__get_units()
377
+ self._flow_units = self.__get_flow_units()
378
+ self._output_size, self._output_size_length = self.__get_output_size()
379
+ self._pollutant_units = [ConcentrationUnits(i) for i in self.__get_pollutant_units()]
380
+ self._element_name_indexes = self.__get_element_name_indexes()
381
+ self._start_date = self.__get_start_date()
382
+ self._report_step = self.get_time_attribute(TimeAttribute.REPORT_STEP)
383
+ self._num_periods = self.get_time_attribute(TimeAttribute.NUM_PERIODS)
384
+ self._times = [self._start_date + timedelta(seconds=self._report_step) * i for i in range(1, self._num_periods + 1)]
385
+
386
+ def __enter__(self):
387
+ """
388
+ Method to return the SWMM output file instance.
389
+ """
390
+ return self
391
+
392
+ def __close(self):
393
+ """
394
+ Method to close the SWMM output file instance.
395
+ """
396
+ if self._output_file_handle != NULL:
397
+ SMO_close(&self._output_file_handle)
398
+ self._output_file_handle = NULL
399
+
400
+ if self._output_size != NULL:
401
+ free(self._output_size)
402
+ self._output_size = NULL
403
+
404
+ if self._units != NULL:
405
+ free(self._units)
406
+ self._units = NULL
407
+
408
+ def __exit__(self, exc_type, exc_value, traceback):
409
+ """
410
+ Method to close the SWMM output file instance.
411
+ """
412
+ self.__close()
413
+
414
+ def __dealloc__(self):
415
+ """
416
+ Destructor to close the SWMM output file.
417
+ """
418
+ self.__close()
419
+
420
+ @property
421
+ def version(self) -> int:
422
+ """
423
+ Method to get the version of the SWMM output file.
424
+
425
+ :return: Version of the SWMM output file.
426
+ :rtype: str
427
+ """
428
+ return self._version
429
+
430
+ cdef int __get_version(self):
431
+ """
432
+ Method to get the version of the SWMM output file.
433
+
434
+ :return: Version of the SWMM output file.
435
+ :rtype: str
436
+ """
437
+ cdef int error_code = 0
438
+ cdef int version = 0
439
+
440
+ error_code = SMO_getVersion(self._output_file_handle, &version)
441
+ self.__validate_error_code(error_code)
442
+
443
+ return version
444
+
445
+ @property
446
+ def output_size(self) -> Dict[str, int]:
447
+ """
448
+ Method to get the size of the project in the SWMM output file.
449
+
450
+ :return: Size of the project in the SWMM output file.
451
+ :rtype: int
452
+ """
453
+ cdef dict output_size_dict = {
454
+ 'subcatchments': self._output_size[0],
455
+ 'nodes': self._output_size[1],
456
+ 'links': self._output_size[2],
457
+ 'system': self._output_size[3],
458
+ 'pollutants': self._output_size[4]
459
+ }
460
+
461
+ return output_size_dict
462
+
463
+ cdef (int*, int) __get_output_size(self):
464
+ """
465
+ Method to get the size of the project in the SWMM output file.
466
+
467
+ :return: Size of the project in the SWMM output file.
468
+ :rtype: int
469
+ """
470
+ cdef int error_code = 0
471
+ cdef int *project_size = NULL
472
+ cdef int length = 0
473
+
474
+ error_code = SMO_getProjectSize(self._output_file_handle, &project_size, &length)
475
+ self.__validate_error_code(error_code)
476
+
477
+ return project_size, length
478
+
479
+ @property
480
+ def units(self) -> Tuple[UnitSystem, FlowUnits, Optional[List[ConcentrationUnits]]]:
481
+ """
482
+ Method to get the unit system used in the SWMM output file.
483
+
484
+ :return: Tuple of the unit system, flow units, and pollutant units used in the SWMM output file.
485
+ :rtype: Tuple[UnitSystem, FlowUnits, Optional[List[ConcentrationUnits]]]
486
+ """
487
+ return (
488
+ UnitSystem(self._units[0]),
489
+ FlowUnits(self._units[1]),
490
+ [ConcentrationUnits(self._units[i]) for i in range(2, self._units_length)]
491
+ )
492
+
493
+ cdef (int*, int) __get_units(self):
494
+ """
495
+ Method to get the unit system used in the SWMM output file.
496
+
497
+ :return: Tuple of comprised of an integer array of unit systems used and its length
498
+ :rtype: Tuple[int*, int]
499
+ """
500
+ cdef int error_code = 0
501
+ cdef int *units = NULL
502
+ cdef int length = 0
503
+
504
+ error_code = SMO_getUnits(self._output_file_handle, &units, &length)
505
+ self.__validate_error_code(error_code)
506
+
507
+ return units, length
508
+
509
+ @property
510
+ def flow_units(self) -> FlowUnits:
511
+ """
512
+ Method to get the flow units used in the SWMM output file.
513
+
514
+ :return: Flow units used in the SWMM output file.
515
+ :rtype: FlowUnits
516
+ """
517
+ return FlowUnits(self._flow_units)
518
+
519
+ cdef int __get_flow_units(self):
520
+ """
521
+ Method to get the flow units used in the SWMM output file.
522
+
523
+ :return: Flow units used in the SWMM output file.
524
+ :rtype: FlowUnits
525
+ """
526
+ cdef int error_code = 0
527
+ cdef int flow_units = 0
528
+
529
+ error_code = SMO_getFlowUnits(self._output_file_handle, &flow_units)
530
+ self.__validate_error_code(error_code)
531
+
532
+ return flow_units
533
+
534
+ @property
535
+ def pollutant_units(self) -> List[ConcentrationUnits]:
536
+ """
537
+ Method to get the pollutant units used in the SWMM output file.
538
+
539
+ :return: Pollutant units used in the SWMM output file.
540
+ :rtype: List[ConcentrationUnits]
541
+ """
542
+ return self._pollutant_units
543
+
544
+ cdef list __get_pollutant_units(self):
545
+ """
546
+ Method to get the pollutant units used in the SWMM output file.
547
+
548
+ :return: Pollutant units used in the SWMM output file.
549
+ :rtype: List[ConcentrationUnits]
550
+ """
551
+ cdef int i = 0
552
+ cdef int error_code = 0
553
+ cdef int *pollutant_units = NULL
554
+ cdef int length = 0
555
+ cdef list pollutant_units_list = []
556
+
557
+ error_code = SMO_getPollutantUnits(self._output_file_handle, &pollutant_units, &length)
558
+ self.__validate_error_code(error_code)
559
+
560
+ pollutant_units_list = [pollutant_units[i] for i in range(length)]
561
+
562
+ if pollutant_units != NULL:
563
+ free(pollutant_units)
564
+
565
+ return pollutant_units_list
566
+
567
+ cdef dict __get_element_name_indexes(self):
568
+ """
569
+ Method to get the indexes of all elements of a given type and name in the SWMM output file.
570
+ :rtype: Dict[str, Dict[str, int]]
571
+ """
572
+ cdef dict element_name_indexes = {}
573
+
574
+ for element_type in ElementType:
575
+ if element_type.value == SMO_elementType.SMO_sys:
576
+ continue
577
+
578
+ num_elements = self._output_size[element_type.value]
579
+ element_names = self.get_element_names(element_type)
580
+
581
+ element_name_indexes[element_type.name] = {element_name: i for i , element_name in enumerate(element_names)}
582
+
583
+ return element_name_indexes
584
+
585
+ @property
586
+ def start_date(self) -> datetime:
587
+ """
588
+ Method to get the start date of the simulation in the SWMM output file.
589
+
590
+ :return: Start date of the simulation in the SWMM output file.
591
+ :rtype: datetime
592
+ """
593
+ return self._start_date
594
+
595
+ cdef datetime __get_start_date(self):
596
+ """
597
+ Method to get the start date of the simulation in the SWMM output file.
598
+
599
+ :return: Start date of the simulation in the SWMM output file.
600
+ :rtype: datetime
601
+ """
602
+ cdef double swmm_datetime = 0
603
+
604
+ error_code = SMO_getStartDate(self._output_file_handle, &swmm_datetime)
605
+ self.__validate_error_code(error_code)
606
+
607
+ return decode_swmm_datetime(swmm_datetime)
608
+
609
+ @property
610
+ def times(self) -> List[datetime]:
611
+ """
612
+ Method to get the times of the simulation in the SWMM output file.
613
+
614
+ :return: Times of the simulation in the SWMM output file.
615
+ :rtype: List[datetime]
616
+ """
617
+ return self._times
618
+
619
+ def get_num_variables(self, element_type: ElementType) -> int:
620
+ """
621
+ Method to get the number of variables for an element type in the SWMM output file.
622
+
623
+ :param element_type: Type of the element.
624
+ :type element_type: int
625
+
626
+ :return: Number of variables for the element type.
627
+ :rtype: int
628
+ """
629
+ cdef int error_code = 0
630
+ cdef int num_vars = 0
631
+
632
+ error_code = SMO_getNumVars(
633
+ p_handle=self._output_file_handle,
634
+ type=<SMO_elementType>element_type.value,
635
+ count=&num_vars
636
+ )
637
+
638
+ self.__validate_error_code(error_code)
639
+
640
+ return num_vars
641
+
642
+ def get_variable_code(self, element_type: ElementType, variable_index: int) -> int:
643
+ """
644
+ Method to get the variable code for an element type in the SWMM output file.
645
+
646
+ :param element_type: Type of the element.
647
+ :type element_type: int
648
+
649
+ :param variable_index: Index of the variable.
650
+ :type variable_index: int
651
+
652
+ :return: Variable code for the element type.
653
+ :rtype: int
654
+ """
655
+ cdef int error_code = 0
656
+ cdef int var_code = 0
657
+
658
+ error_code = SMO_getVarCode(
659
+ p_handle=self._output_file_handle,
660
+ type=<SMO_elementType>element_type.value,
661
+ varIndex=variable_index,
662
+ varCode=&var_code
663
+ )
664
+
665
+ self.__validate_error_code(error_code)
666
+
667
+ return var_code
668
+
669
+ def get_variable_codes(self, element_type: ElementType) -> List[int]:
670
+ """
671
+ Method to get the variable codes for an element type in the SWMM output file.
672
+
673
+ :param element_type: Type of the element.
674
+ :type element_type: int
675
+
676
+ :return: Variable codes for the element type.
677
+ :rtype: List[int]
678
+ """
679
+ cdef int error_code = 0
680
+ cdef int num_vars = 0
681
+ cdef int* var_codes = NULL
682
+ cdef list variable_codes
683
+
684
+ error_code = SMO_getVarCodes(
685
+ p_handle=self._output_file_handle,
686
+ type=<SMO_elementType>element_type.value,
687
+ varCodes=&var_codes,
688
+ size=&num_vars
689
+ )
690
+
691
+ self.__validate_error_code(error_code)
692
+
693
+ variable_codes = [var_codes[i] for i in range(num_vars)]
694
+
695
+ if var_codes != NULL:
696
+ free(var_codes)
697
+
698
+ return variable_codes
699
+
700
+ def get_num_properties(self, element_type: ElementType) -> int:
701
+ """
702
+ Method to get the number of properties for an element type in the SWMM output file.
703
+
704
+ :param element_type: Type of the element.
705
+ :type element_type: int
706
+
707
+ :return: Number of properties for the element type.
708
+ :rtype: int
709
+ """
710
+ cdef int error_code = 0
711
+ cdef int num_properties = 0
712
+
713
+ error_code = SMO_getNumProperties(
714
+ p_handle=self._output_file_handle,
715
+ type=<SMO_elementType>element_type.value,
716
+ count=&num_properties
717
+ )
718
+
719
+ self.__validate_error_code(error_code)
720
+
721
+ return num_properties
722
+
723
+ def get_property_code(self, element_type: ElementType, property_index: int) -> int:
724
+ """
725
+ Method to get the property code for an element type in the SWMM output file.
726
+
727
+ :param element_type: Type of the element.
728
+ :type element_type: int
729
+
730
+ :param property_index: Index of the property.
731
+ :type property_index: int
732
+
733
+ :return: Property code for the element type.
734
+ :rtype: int
735
+ """
736
+ cdef int error_code = 0
737
+ cdef int property_code = 0
738
+
739
+ error_code = SMO_getPropertyCode(
740
+ p_handle=self._output_file_handle,
741
+ type=<SMO_elementType>element_type.value,
742
+ propertyIndex=property_index,
743
+ propertyCode=&property_code
744
+ )
745
+
746
+ self.__validate_error_code(error_code)
747
+
748
+ return property_code
749
+
750
+ def get_property_codes(self, element_type: ElementType) -> List[int]:
751
+ """
752
+ Method to get the property codes for an element type in the SWMM output file.
753
+
754
+ :param element_type: Type of the element.
755
+ :type element_type: int
756
+
757
+ :return: Property codes for the element type.
758
+ :rtype: List[int]
759
+ """
760
+ cdef int error_code = 0
761
+ cdef int num_properties = 0
762
+ cdef int* property_codes = NULL
763
+
764
+ error_code = SMO_getPropertyCodes(
765
+ p_handle=self._output_file_handle,
766
+ type=<SMO_elementType>element_type.value,
767
+ propertyCodes=&property_codes,
768
+ size=&num_properties
769
+ )
770
+
771
+ self.__validate_error_code(error_code)
772
+
773
+ property_codes_list = [property_codes[i] for i in range(num_properties)]
774
+
775
+ if property_codes != NULL:
776
+ free(property_codes)
777
+
778
+ return property_codes_list
779
+
780
+ def get_property_value(self, element_type: ElementType, element_index: Union[int, str], property_code: int) -> float:
781
+ """
782
+ Method to get the property value for an element in the SWMM output file.
783
+
784
+ :param element_type: Type of the element.
785
+ :type element_type: int
786
+
787
+ :param element_index: Index of the element.
788
+ :type element_index: int
789
+
790
+ :param property_code: Property code.
791
+ :type property_code: int
792
+
793
+ :return: Property value for the element.
794
+ :rtype: float
795
+ """
796
+ cdef int error_code = 0
797
+ cdef float property_value = 0
798
+ cdef int l_element_index = element_index if isinstance(element_index, int) \
799
+ else self._element_name_indexes[ElementType.NODE.name][element_index]
800
+
801
+ error_code = SMO_getPropertyValue(
802
+ p_handle=self._output_file_handle,
803
+ type=<SMO_elementType>element_type.value,
804
+ propertyIndex=property_code,
805
+ elementIndex=l_element_index,
806
+ value=&property_value
807
+ )
808
+
809
+ self.__validate_error_code(error_code)
810
+
811
+ return property_value
812
+
813
+ def get_property_values(self, element_type: ElementType, element_index: Union[int, str]) -> List[float]:
814
+ """
815
+ Method to get the property values for an element type in the SWMM output file.
816
+
817
+ :param element_type: Type of the element.
818
+ :type element_type: int
819
+
820
+ :param element_index: Element index.
821
+ :type element_index: int
822
+
823
+ :return: Property values for the element type.
824
+ :rtype: List[float]
825
+ """
826
+ cdef int error_code = 0
827
+ cdef int num_elements = 0
828
+ cdef int i = 0
829
+ cdef float* property_values = NULL
830
+ cdef list property_values_list
831
+ cdef int l_element_index = element_index if isinstance(element_index, int) \
832
+ else self._element_name_indexes[ElementType.NODE.name][element_index]
833
+
834
+ error_code = SMO_getPropertyValues(
835
+ p_handle=self._output_file_handle,
836
+ type=<SMO_elementType>element_type.value,
837
+ elementIndex=l_element_index,
838
+ outValueArray=&property_values,
839
+ length=&num_elements
840
+ )
841
+
842
+ self.__validate_error_code(error_code)
843
+ property_values_list = [*<float[:num_elements]>property_values]
844
+
845
+ if property_values != NULL:
846
+ free(property_values)
847
+
848
+ return property_values_list
849
+
850
+ def get_time_attribute(self, time_attribute: TimeAttribute) -> int:
851
+ """
852
+ Method to get the temporal attributes of the simulation in the SWMM output file.
853
+
854
+ :param time_attribute: Temporal attribute.
855
+ :type time_attribute: TimeAttribute
856
+
857
+ :return: Temporal attributes of the simulation in the SWMM output file.
858
+ :rtype: int
859
+
860
+ """
861
+ cdef int error_code = 0
862
+ cdef int temporal_attribute = -1
863
+
864
+ error_code = SMO_getTimes(self._output_file_handle, <SMO_time>time_attribute.value, &temporal_attribute)
865
+ self.__validate_error_code(error_code)
866
+
867
+ return temporal_attribute
868
+
869
+ def get_element_name(self, element_type: ElementType, element_index: int) -> str:
870
+ """
871
+ Method to get the name of an element in the SWMM output file.
872
+
873
+ :param element_type: Type of the element.
874
+ :type element_type: int
875
+
876
+ :param indelement_indexex: Index of the element.
877
+ :type element_index: int
878
+
879
+ :return: Name of the element.
880
+ :rtype: str
881
+ """
882
+ cdef int error_code = 0
883
+ cdef int strlen = 0
884
+ cdef char* element_name = NULL
885
+
886
+ error_code = SMO_getElementName(self._output_file_handle, <SMO_elementType>element_type.value, element_index, &element_name, &strlen)
887
+ self.__validate_error_code(error_code)
888
+
889
+ # Convert the C string to a Python string and delete the C string
890
+ element_name_str = element_name.decode('utf-8')
891
+
892
+ if element_name != NULL:
893
+ free(element_name)
894
+
895
+ return element_name_str
896
+
897
+ def get_element_names(self, element_type: ElementType) -> List[str]:
898
+ """
899
+ Method to get the names of all elements of a given type in the SWMM output file.
900
+
901
+ :param element_type: Type of the element.
902
+ :type element_type: int
903
+
904
+ :return: Names of all elements of the given type.
905
+ :rtype: List[str]
906
+ """
907
+ cdef int error_code = 0
908
+ cdef int num_elements = 0
909
+ cdef int i = 0
910
+ cdef int strlen = 0
911
+ cdef char** c_element_names = NULL
912
+ cdef list element_names
913
+
914
+ if element_type.value == SMO_elementType.SMO_sys:
915
+ raise SWMMOutputException(f"Cannot get element names for the system element type {ElementType.SYSTEM}.")
916
+ elif element_type.value > SMO_elementType.SMO_pollut:
917
+ raise SWMMOutputException("Invalid element type.")
918
+
919
+ num_elements = self._output_size[element_type.value]
920
+
921
+ c_element_names = <char**>malloc(num_elements * sizeof(char*))
922
+
923
+ for i in range(num_elements):
924
+ error_code = SMO_getElementName(self._output_file_handle, <SMO_elementType>element_type.value, i, &c_element_names[i], &strlen)
925
+ self.__validate_error_code(error_code)
926
+
927
+ element_names = [c_element_names[i].decode('utf-8') for i in range(num_elements)]
928
+
929
+ if c_element_names != NULL:
930
+ for i in range(num_elements):
931
+ if c_element_names[i] != NULL:
932
+ free(c_element_names[i])
933
+ c_element_names[i] = NULL
934
+
935
+ free(c_element_names)
936
+
937
+ return element_names
938
+
939
+ def get_subcatchment_timeseries(
940
+ self, element_index: Union[int, str],
941
+ attribute: SubcatchAttribute,
942
+ start_date_index: int = 0,
943
+ end_date_index: int = -1,
944
+ sub_index: int = 0
945
+ ) -> Dict[datetime, float]:
946
+ """
947
+ Method to get the time series data for a subcatchment attribute in the SWMM output file.
948
+
949
+ :param element_index: Index of the subcatchment.
950
+ :type element_index: int or str
951
+
952
+ :param attribute: Subcatchment attribute.
953
+ :type attribute: SubcatchAttribute
954
+
955
+ :param start_date_index: Start date index. Default is 0.
956
+ :type start_date_index: int
957
+
958
+ :param end_date_index: End date index. Default is the last date index.
959
+ :type end_date_index: int
960
+
961
+ :param sub_index: Attribute index for the subcatchment non enumerated attributes primarily for the pollutants
962
+ :type sub_index: int
963
+
964
+ :return: Time series data for the subcatchment attribute.
965
+ :rtype: Dict[datetime, double]
966
+ """
967
+ cdef int error_code = 0
968
+ cdef float* values = NULL
969
+ cdef int length = 0
970
+ cdef int attribute_index = attribute.value + sub_index
971
+ cdef int l_element_index = element_index if isinstance(element_index, int) \
972
+ else self._element_name_indexes[ElementType.SUBCATCHMENT.name][element_index]
973
+
974
+ if end_date_index == -1:
975
+ end_date_index = self._num_periods
976
+
977
+ error_code = SMO_getSubcatchSeries(
978
+ p_handle=self._output_file_handle,
979
+ subcatchIndex=l_element_index,
980
+ attr=<SMO_subcatchAttribute>attribute_index,
981
+ startPeriod=start_date_index,
982
+ endPeriod=end_date_index,
983
+ outValueArray=&values,
984
+ length=&length
985
+ )
986
+ self.__validate_error_code(error_code)
987
+
988
+ results = dict(zip(self._times[start_date_index:end_date_index], <float[:length]>values))
989
+
990
+ if values != NULL:
991
+ free(values)
992
+
993
+ return results
994
+
995
+ def get_node_timeseries(
996
+ self,
997
+ element_index: Union[int, str],
998
+ attribute: NodeAttribute,
999
+ start_date_index: int = 0,
1000
+ end_date_index: int = -1,
1001
+ sub_index: int = 0
1002
+ ) -> Dict[datetime, float]:
1003
+ """
1004
+ Method to get the time series data for a node attribute in the SWMM output file.
1005
+
1006
+ :param element_index: Index of the node.
1007
+ :type element_index: int or str
1008
+
1009
+ :param attribute: Node attribute.
1010
+ :type attribute: NodeAttribute
1011
+
1012
+ :param start_date_index: Start date index. Default is 0.
1013
+ :type start_date_index: int
1014
+
1015
+ :param end_date_index: End date index. Default is the last date index.
1016
+ :type end_date_index: int
1017
+
1018
+ :param sub_index: Attribute index for the subcatchment non enumerated attributes primarily for the pollutants
1019
+ :type sub_index: int
1020
+
1021
+ :return: Time series data for the node attribute.
1022
+ :rtype: Dict[datetime, double]
1023
+ """
1024
+ cdef int error_code = 0
1025
+ cdef float* values = NULL
1026
+ cdef int length = 0
1027
+ cdef int attribute_index = attribute.value + sub_index
1028
+ cdef int l_element_index = element_index if isinstance(element_index, int) \
1029
+ else self._element_name_indexes[ElementType.NODE.name][element_index]
1030
+
1031
+ if end_date_index == -1:
1032
+ end_date_index = self._num_periods
1033
+
1034
+ error_code = SMO_getNodeSeries(
1035
+ p_handle=self._output_file_handle,
1036
+ nodeIndex=l_element_index,
1037
+ attr=<SMO_nodeAttribute>attribute_index,
1038
+ startPeriod=start_date_index,
1039
+ endPeriod=end_date_index,
1040
+ outValueArray=&values,
1041
+ length=&length
1042
+ )
1043
+
1044
+ self.__validate_error_code(error_code)
1045
+
1046
+ results = dict(zip(self._times[start_date_index:end_date_index], <float[:length]>values))
1047
+
1048
+ if values != NULL:
1049
+ free(values)
1050
+
1051
+ return results
1052
+
1053
+ def get_link_timeseries(
1054
+ self,
1055
+ element_index: Union[int, str],
1056
+ attribute: LinkAttribute,
1057
+ start_date_index: int = 0,
1058
+ end_date_index: int = -1,
1059
+ sub_index: int = 0
1060
+ ) -> Dict[datetime, float]:
1061
+ """
1062
+ Method to get the time series data for a link attribute in the SWMM output file.
1063
+
1064
+ :param element_index: Index of the link.
1065
+ :type element_index: int
1066
+
1067
+ :param attribute: Link attribute.
1068
+ :type attribute: LinkAttribute
1069
+
1070
+ :param start_date_index: Start date index. Default is 0.
1071
+ :type start_date_index: int
1072
+
1073
+ :param end_date_index: End date index. Default is the last date index.
1074
+ :type end_date_index: int
1075
+
1076
+ :param sub_index: Attribute index for the subcatchment non enumerated attributes primarily for the pollutants
1077
+ :type sub_index: int
1078
+
1079
+ :return: Time series data for the link attribute.
1080
+ :rtype: Dict[datetime, double]
1081
+ """
1082
+ cdef int error_code = 0
1083
+ cdef float* values = NULL
1084
+ cdef int length = 0
1085
+ cdef int attribute_index = attribute.value + sub_index
1086
+ cdef int l_element_index = element_index if isinstance(element_index, int) \
1087
+ else self._element_name_indexes[ElementType.LINK.name][element_index]
1088
+
1089
+ if end_date_index == -1:
1090
+ end_date_index = self._num_periods
1091
+
1092
+ error_code = SMO_getLinkSeries(
1093
+ p_handle=self._output_file_handle,
1094
+ linkIndex=l_element_index,
1095
+ attr=<SMO_linkAttribute>(attribute_index),
1096
+ startPeriod=start_date_index,
1097
+ endPeriod=end_date_index,
1098
+ outValueArray=&values,
1099
+ length=&length
1100
+ )
1101
+
1102
+ self.__validate_error_code(error_code)
1103
+
1104
+ results = dict(zip(self._times[start_date_index:end_date_index], <float[:length]>values))
1105
+
1106
+ if values != NULL:
1107
+ free(values)
1108
+
1109
+ return results
1110
+
1111
+ def get_system_timeseries(
1112
+ self,
1113
+ attribute: SystemAttribute,
1114
+ start_date_index: int = 0,
1115
+ end_date_index: int = -1,
1116
+ sub_index: int = 0
1117
+ ) -> Dict[datetime, float]:
1118
+ """
1119
+ Method to get the time series data for a system attribute in the SWMM output file.
1120
+
1121
+ :param attribute: System attribute.
1122
+ :type attribute: SystemAttribute
1123
+
1124
+ :param start_date_index: Start date index. Default is 0.
1125
+ :type start_date_index: int
1126
+
1127
+ :param end_date_index: End date index. Default is the last date index.
1128
+ :type end_date_index: int
1129
+
1130
+ :param sub_index: Attribute index for the subcatchment non enumerated attributes primarily for the pollutants
1131
+ :type sub_index: int
1132
+
1133
+ :return: Time series data for the system attribute.
1134
+ :rtype: Dict[datetime, double]
1135
+ """
1136
+ cdef int error_code = 0
1137
+ cdef float* values = NULL
1138
+ cdef int length = 0
1139
+ cdef int attribute_index = attribute.value + sub_index
1140
+
1141
+ if end_date_index == -1:
1142
+ end_date_index = self._num_periods
1143
+
1144
+ error_code = SMO_getSystemSeries(
1145
+ p_handle=self._output_file_handle,
1146
+ attr=<SMO_systemAttribute>attribute_index,
1147
+ startPeriod=start_date_index,
1148
+ endPeriod=end_date_index,
1149
+ outValueArray=&values,
1150
+ length=&length
1151
+ )
1152
+
1153
+ self.__validate_error_code(error_code)
1154
+
1155
+ results = dict(zip(self._times[start_date_index:end_date_index], <float[:length]>values))
1156
+
1157
+ if values != NULL:
1158
+ free(values)
1159
+
1160
+ return results
1161
+
1162
+ def get_subcatchment_values_by_time_and_attribute(
1163
+ self,
1164
+ time_index: int,
1165
+ attribute: SubcatchAttribute,
1166
+ sub_index: int = 0
1167
+ ) -> Dict[str, float]:
1168
+ """
1169
+ Method to get the subcatchment values for all subcatchments for a given time index and attribute.
1170
+
1171
+ :param time_index: Time index.
1172
+ :type time_index: int
1173
+
1174
+ :param attribute: Subcatchment attribute.
1175
+ :type attribute: SubcatchAttribute
1176
+
1177
+ :param sub_index: Attribute index for the subcatchment non enumerated attributes primarily for the pollutants
1178
+ :type sub_index: int
1179
+
1180
+ :return: Subcatchment values for all subcatchments.
1181
+ :rtype: Dict[str, float]
1182
+ """
1183
+
1184
+ cdef int error_code = 0
1185
+ cdef float* values = NULL
1186
+ cdef int length = 0
1187
+ cdef int attribute_index = attribute.value + sub_index
1188
+
1189
+ error_code = SMO_getSubcatchAttribute(
1190
+ p_handle=self._output_file_handle,
1191
+ timeIndex=time_index,
1192
+ attr=<SMO_subcatchAttribute>attribute_index,
1193
+ outValueArray=&values,
1194
+ length=&length
1195
+ )
1196
+
1197
+ self.__validate_error_code(error_code)
1198
+
1199
+ subcatchment_values = dict(zip(self.get_element_names(ElementType.SUBCATCHMENT), <float[:length]>values))
1200
+
1201
+ if values != NULL:
1202
+ free(values)
1203
+
1204
+ return subcatchment_values
1205
+
1206
+ def get_node_values_by_time_and_attribute(
1207
+ self,
1208
+ time_index: int,
1209
+ attribute: NodeAttribute,
1210
+ sub_index: int = 0
1211
+ ) -> Dict[str, float]:
1212
+ """
1213
+ Method to get the node values for all nodes for a given time index and attribute.
1214
+
1215
+ :param time_index: Time index.
1216
+ :type time_index: int
1217
+
1218
+ :param attribute: Node attribute.
1219
+ :type attribute: NodeAttribute
1220
+
1221
+ :param sub_index: Attribute index for the subcatchment non enumerated attributes primarily for the pollutants
1222
+ :type sub_index: int
1223
+
1224
+ :return: Node values for all nodes.
1225
+ :rtype: Dict[str, float]
1226
+ """
1227
+
1228
+ cdef int error_code = 0
1229
+ cdef float* values = NULL
1230
+ cdef int length = 0
1231
+ cdef int attribute_index = attribute.value + sub_index
1232
+
1233
+ error_code = SMO_getNodeAttribute(
1234
+ p_handle=self._output_file_handle,
1235
+ timeIndex=time_index,
1236
+ attr=<SMO_nodeAttribute>attribute_index,
1237
+ outValueArray=&values,
1238
+ length=&length
1239
+ )
1240
+
1241
+ self.__validate_error_code(error_code)
1242
+
1243
+ node_values = dict(zip(self.get_element_names(ElementType.NODE), <float[:length]>values))
1244
+
1245
+ if values != NULL:
1246
+ free(values)
1247
+
1248
+ return node_values
1249
+
1250
+ def get_link_values_by_time_and_attribute(
1251
+ self,
1252
+ time_index: int,
1253
+ attribute: LinkAttribute,
1254
+ sub_index: int = 0
1255
+ ) -> Dict[str, float]:
1256
+ """
1257
+ Method to get the link values for all links for a given time index and attribute.
1258
+
1259
+ :param time_index: Time index.
1260
+ :type time_index: int
1261
+
1262
+ :param attribute: Link attribute.
1263
+ :type attribute: LinkAttribute
1264
+
1265
+ :param sub_index: Attribute index for the subcatchment non enumerated attributes primarily for the pollutants
1266
+ :type sub_index: int
1267
+
1268
+ :return: Link values for all links.
1269
+ :rtype: Dict[str, float]
1270
+ """
1271
+
1272
+ cdef int error_code = 0
1273
+ cdef float* values = NULL
1274
+ cdef int length = 0
1275
+ cdef int attribute_index = attribute.value + sub_index
1276
+
1277
+ error_code = SMO_getLinkAttribute(
1278
+ p_handle=self._output_file_handle,
1279
+ timeIndex=time_index,
1280
+ attr=<SMO_linkAttribute>attribute_index,
1281
+ outValueArray=&values,
1282
+ length=&length
1283
+ )
1284
+
1285
+ self.__validate_error_code(error_code)
1286
+
1287
+ link_values = dict(zip(self.get_element_names(ElementType.LINK), <float[:length]>values))
1288
+
1289
+ if values != NULL:
1290
+ free(values)
1291
+
1292
+ return link_values
1293
+
1294
+ def get_system_values_by_time_and_attribute(
1295
+ self,
1296
+ time_index: int,
1297
+ attribute: SystemAttribute,
1298
+ sub_index: int = 0
1299
+ ) -> Dict[str, float]:
1300
+ """
1301
+ Method to get the system values for a given time index and attribute.
1302
+
1303
+ :param time_index: Time index.
1304
+ :type time_index: int
1305
+
1306
+ :param attribute: System attribute.
1307
+ :type attribute: SystemAttribute
1308
+
1309
+ :param sub_index: Attribute index for the subcatchment non enumerated attributes primarily for the pollutants
1310
+ :type sub_index: int
1311
+
1312
+ :return: System values.
1313
+ :rtype: Dict[str, float]
1314
+ """
1315
+
1316
+ cdef int error_code = 0
1317
+ cdef float* values = NULL
1318
+ cdef int length = 0
1319
+ cdef int attribute_index = attribute.value + sub_index
1320
+
1321
+ error_code = SMO_getSystemAttribute(
1322
+ p_handle=self._output_file_handle,
1323
+ timeIndex=time_index,
1324
+ attr=<SMO_systemAttribute>attribute_index,
1325
+ outValueArray=&values,
1326
+ length=&length
1327
+ )
1328
+
1329
+ self.__validate_error_code(error_code)
1330
+
1331
+ system_values = dict(zip([SystemAttribute(attribute).name], <float[:length]>values))
1332
+
1333
+
1334
+ if values != NULL:
1335
+ free(values)
1336
+
1337
+ return system_values
1338
+
1339
+ def get_subcatchment_values_by_time_and_element_index(
1340
+ self,
1341
+ time_index: int,
1342
+ element_index: Union[int, str]
1343
+ ) -> Dict[str, float]:
1344
+ """
1345
+ Method to get all attributes of a given subcatchment for specified time.
1346
+
1347
+ :param time_index: Time index.
1348
+ :type time_index: int
1349
+
1350
+ :param element_index: Index of the subcatchment.
1351
+ :type element_index: int or str
1352
+
1353
+ :return: Dictionary of subcatchment attributes.
1354
+ :rtype: Dict[str, float]
1355
+ """
1356
+ cdef int error_code = 0
1357
+ cdef float* values = NULL
1358
+ cdef int length = 0
1359
+ cdef int enum_values_length = len(SubcatchAttribute) - 1
1360
+ cdef list pollutant_names = self.get_element_names(ElementType.POLLUTANT)
1361
+ cdef list attrib_names = []
1362
+ cdef int l_element_index = element_index if isinstance(element_index, int) \
1363
+ else self._element_name_indexes[ElementType.SUBCATCHMENT.name][element_index]
1364
+
1365
+ error_code = SMO_getSubcatchResult(
1366
+ p_handle=self._output_file_handle,
1367
+ timeIndex=time_index,
1368
+ subcatchIndex=l_element_index,
1369
+ outValueArray=&values,
1370
+ length=&length
1371
+ )
1372
+
1373
+ self.__validate_error_code(error_code)
1374
+
1375
+ for i in range(length):
1376
+ if i < enum_values_length:
1377
+ attrib_names.append(SubcatchAttribute(i).name)
1378
+ else:
1379
+ attrib_names.append(pollutant_names[i - enum_values_length])
1380
+
1381
+ subcatchment_values = dict(zip(attrib_names, <float[:length]>values))
1382
+
1383
+ if values != NULL:
1384
+ free(values)
1385
+
1386
+ return subcatchment_values
1387
+
1388
+ def get_node_values_by_time_and_element_index(
1389
+ self,
1390
+ time_index: int,
1391
+ element_index: Union[int, str]
1392
+ ) -> Dict[str, float]:
1393
+ """
1394
+ Method to get all attributes of a given node for specified time.
1395
+
1396
+ :param time_index: Time index.
1397
+ :type time_index: int
1398
+
1399
+ :param element_index: Index of the node.
1400
+ :type element_index: int
1401
+
1402
+ :return: Dictionary of node attributes.
1403
+ :rtype: Dict[str, float]
1404
+ """
1405
+
1406
+ cdef int error_code = 0
1407
+ cdef float* values = NULL
1408
+ cdef int length = 0
1409
+ cdef int enum_values_length = len(NodeAttribute) - 1
1410
+ cdef list pollutant_names = self.get_element_names(ElementType.POLLUTANT)
1411
+ cdef list attrib_names = []
1412
+ cdef int l_element_index = element_index if isinstance(element_index, int) \
1413
+ else self._element_name_indexes[ElementType.NODE.name][element_index]
1414
+
1415
+ error_code = SMO_getNodeResult(
1416
+ p_handle=self._output_file_handle,
1417
+ timeIndex=time_index,
1418
+ nodeIndex=l_element_index,
1419
+ outValueArray=&values,
1420
+ length=&length
1421
+ )
1422
+
1423
+ self.__validate_error_code(error_code)
1424
+
1425
+ for i in range(length):
1426
+ if i < enum_values_length:
1427
+ attrib_names.append(NodeAttribute(i).name)
1428
+ else:
1429
+ attrib_names.append(pollutant_names[i - enum_values_length])
1430
+
1431
+ node_values = dict(zip(attrib_names, <float[:length]>values))
1432
+
1433
+ if values != NULL:
1434
+ free(values)
1435
+
1436
+ return node_values
1437
+
1438
+ def get_link_values_by_time_and_element_index(
1439
+ self,
1440
+ time_index: int,
1441
+ element_index: Union[int, str]
1442
+ ) -> Dict[str, float]:
1443
+ """
1444
+ Method to get all attributes of a given link for specified time.
1445
+
1446
+ :param time_index: Time index.
1447
+ :type time_index: int
1448
+
1449
+ :param element_index: Index of the link.
1450
+ :type element_index: int, str
1451
+
1452
+ :return: Dictionary of link attributes.
1453
+ :rtype: Dict[str, float]
1454
+ """
1455
+
1456
+ cdef int error_code = 0
1457
+ cdef float* values = NULL
1458
+ cdef int length = 0
1459
+ cdef int enum_values_length = len(LinkAttribute) - 1
1460
+ cdef list pollutant_names = self.get_element_names(ElementType.POLLUTANT)
1461
+ cdef list attrib_names = []
1462
+ cdef int l_element_index = element_index if isinstance(element_index, int) \
1463
+ else self._element_name_indexes[ElementType.LINK.name][element_index]
1464
+
1465
+ error_code = SMO_getLinkResult(
1466
+ p_handle=self._output_file_handle,
1467
+ timeIndex=time_index,
1468
+ linkIndex=l_element_index,
1469
+ outValueArray=&values,
1470
+ length=&length
1471
+ )
1472
+
1473
+ self.__validate_error_code(error_code)
1474
+
1475
+ for i in range(length):
1476
+ if i < enum_values_length:
1477
+ attrib_names.append(LinkAttribute(i).name)
1478
+ else:
1479
+ attrib_names.append(pollutant_names[i - enum_values_length])
1480
+
1481
+ link_values = dict(zip(attrib_names, <float[:length]>values))
1482
+
1483
+ if values != NULL:
1484
+ free(values)
1485
+
1486
+ return link_values
1487
+
1488
+ def get_system_values_by_time(self, time_index: int) -> Dict[str, float]:
1489
+ """
1490
+ Method to get all attributes of the system for specified time.
1491
+
1492
+ :param time_index: Time index.
1493
+ :type time_index: int
1494
+
1495
+ :return: Dictionary of system attributes.
1496
+ :rtype: Dict[str, float]
1497
+ """
1498
+
1499
+ cdef int error_code = 0
1500
+ cdef float* values = NULL
1501
+ cdef int length = 0
1502
+ cdef int enum_values_length = len(SystemAttribute) - 1
1503
+
1504
+ error_code = SMO_getSystemResult(
1505
+ p_handle=self._output_file_handle,
1506
+ timeIndex=time_index,
1507
+ dummyIndex=0,
1508
+ outValueArray=&values,
1509
+ length=&length
1510
+ )
1511
+
1512
+ self.__validate_error_code(error_code)
1513
+
1514
+ system_values = dict(zip([SystemAttribute(i).name for i in range(enum_values_length)], <float[:length]>values))
1515
+
1516
+ if values != NULL:
1517
+ free(values)
1518
+
1519
+ return system_values
1520
+
1521
+ cdef str check_error(self):
1522
+ """
1523
+ Method to check if there is an error in the SWMM output file instance reader.
1524
+
1525
+ :return: Error message if there is an error, otherwise None.
1526
+ :rtype: str
1527
+ """
1528
+ cdef char* msg_buffer = NULL
1529
+ cdef int error_code
1530
+
1531
+ error_code = SMO_checkError(self._output_file_handle, &msg_buffer)
1532
+
1533
+ if error_code != 0 and msg_buffer != NULL:
1534
+ # Convert the C string to a Python string
1535
+ error_message = msg_buffer.decode('utf-8')
1536
+ # Free the allocated memory for the message buffer
1537
+ free(msg_buffer)
1538
+ return error_message
1539
+ else:
1540
+ return u""
1541
+
1542
+ cdef str __validate_error_code(self, int error_code):
1543
+ """
1544
+ Method to validate the error code and return the error message if there is an error.
1545
+
1546
+ :param error_code: Error code to validate.
1547
+ :type error_code: int
1548
+
1549
+ :return: Error message if there is an error, otherwise None.
1550
+ :rtype: str
1551
+ """
1552
+
1553
+ if error_code != 0:
1554
+ error_message = self.check_error()
1555
+ raise SWMMOutputException(f"Error code: {error_code}: {error_message}")
1556
+ else:
1557
+ return u""