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,1048 @@
1
+ # Description: Unit tests for the SWMM output module
2
+ # Created by: Caleb Buahin (EPA/ORD/CESER/WID)
3
+ # Created on: 2024-11-19
4
+
5
+ # python imports
6
+ import json
7
+ import unittest
8
+ from datetime import datetime
9
+ import pickle
10
+
11
+ # third party imports
12
+
13
+ # local imports
14
+ from .data import output as example_output_data
15
+ from openswmmcore import output
16
+ from python.output import Output, SWMMOutputException
17
+
18
+
19
+ class TestSWMMOutput(unittest.TestCase):
20
+ """
21
+ Test the SWMM solver functions
22
+
23
+ TODO: Add tests to check for exceptions and errors
24
+ """
25
+
26
+ def setUp(self):
27
+ """
28
+ Set up the test case with the test artifacts
29
+ :return:
30
+ """
31
+
32
+ self.test_artifacts = {}
33
+
34
+ with open(file=example_output_data.JSON_TIME_SERIES_FILE, mode='rb') as f:
35
+ self.test_artifacts = pickle.load(f)
36
+
37
+ def test_output_unit_system_enum(self):
38
+ """
39
+ Test the output unit system enum
40
+ :return:
41
+ """
42
+
43
+ self.assertEqual(
44
+ first=output.UnitSystem.US.value, second=0, msg="US unit system value should be 0"
45
+ )
46
+
47
+ self.assertEqual(
48
+ first=output.UnitSystem.SI.value, second=1, msg="SI unit system value should be 1"
49
+ )
50
+
51
+ def test_output_flow_units_enum(self):
52
+ """
53
+ Test the output flow units enum
54
+ :return:
55
+ """
56
+
57
+ self.assertEqual(
58
+ first=output.FlowUnits.CFS.value, second=0, msg="CFS flow unit value should be 0"
59
+ )
60
+ self.assertEqual(
61
+ first=output.FlowUnits.GPM.value, second=1, msg="GPM flow unit value should be 1"
62
+ )
63
+ self.assertEqual(
64
+ first=output.FlowUnits.MGD.value, second=2, msg="MGD flow unit value should be 2"
65
+ )
66
+ self.assertEqual(
67
+ first=output.FlowUnits.CMS.value, second=3, msg="CMS flow unit value should be 3"
68
+ )
69
+ self.assertEqual(
70
+ first=output.FlowUnits.LPS.value, second=4, msg="LPS flow unit value should be 4"
71
+ )
72
+ self.assertEqual(
73
+ first=output.FlowUnits.MLD.value, second=5, msg="MLD flow unit value should be 5"
74
+ )
75
+
76
+ def test_output_concentration_units_enum(self):
77
+ """
78
+ Test the output concentration units enum
79
+ :return:
80
+ """
81
+
82
+ self.assertEqual(
83
+ first=output.ConcentrationUnits.MG.value, second=0, msg="MG concentration unit value should be 0"
84
+ )
85
+ self.assertEqual(
86
+ first=output.ConcentrationUnits.UG.value, second=1, msg="UG concentration unit value should be 1"
87
+ )
88
+ self.assertEqual(
89
+ first=output.ConcentrationUnits.COUNT.value, second=2, msg="COUNT concentration unit value should be 2"
90
+ )
91
+ self.assertEqual(
92
+ first=output.ConcentrationUnits.NONE.value, second=3, msg="NONE concentration unit value should be 3"
93
+ )
94
+
95
+ def test_output_element_type_enum(self):
96
+ """
97
+ Test the output element type enum
98
+ :return:
99
+ """
100
+
101
+ self.assertEqual(
102
+ first=output.ElementType.SUBCATCHMENT.value, second=0, msg="SUBCATCHMENT element type value should be 0"
103
+ )
104
+ self.assertEqual(
105
+ first=output.ElementType.NODE.value, second=1, msg="NODE element type value should be 1"
106
+ )
107
+ self.assertEqual(
108
+ first=output.ElementType.LINK.value, second=2, msg="LINK element type value should be 2"
109
+ )
110
+ self.assertEqual(
111
+ first=output.ElementType.SYSTEM.value, second=3, msg="SYSTEM element type value should be 3"
112
+ )
113
+ self.assertEqual(
114
+ first=output.ElementType.POLLUTANT.value, second=4, msg="POLLUTANT element type value should be 4"
115
+ )
116
+
117
+ def test_output_time_enum(self):
118
+ """
119
+ Test the output time enum
120
+ :return:
121
+ """
122
+
123
+ self.assertEqual(
124
+ first=output.TimeAttribute.REPORT_STEP.value, second=0, msg="REPORT_STEP time value should be 0"
125
+ )
126
+ self.assertEqual(
127
+ first=output.TimeAttribute.NUM_PERIODS.value, second=1, msg="NUM_PERIODS time value should be 1"
128
+ )
129
+
130
+ def test_output_sub_catch_attribute_enum(self):
131
+ """
132
+ Test the output sub-catchment attribute enum
133
+ :return:
134
+ """
135
+
136
+ self.assertEqual(
137
+ first=output.SubcatchAttribute.RAINFALL.value, second=0,
138
+ msg="RAINFALL sub-catchment attribute value should be 0"
139
+ )
140
+ self.assertEqual(
141
+ first=output.SubcatchAttribute.SNOW_DEPTH.value, second=1,
142
+ msg="SNOW_DEPTH sub-catchment attribute value should be 1"
143
+ )
144
+ self.assertEqual(
145
+ first=output.SubcatchAttribute.EVAPORATION_LOSS.value, second=2,
146
+ msg="EVAPORATION_LOSS sub-catchment attribute value should be 2"
147
+ )
148
+ self.assertEqual(
149
+ first=output.SubcatchAttribute.INFILTRATION_LOSS.value, second=3,
150
+ msg="INFILTRATION_LOSS sub-catchment attribute value should be 3"
151
+ )
152
+ self.assertEqual(
153
+ first=output.SubcatchAttribute.RUNOFF_RATE.value, second=4,
154
+ msg="RUNOFF_RATE sub-catchment attribute value should be 4"
155
+ )
156
+ self.assertEqual(
157
+ first=output.SubcatchAttribute.GROUNDWATER_OUTFLOW.value, second=5,
158
+ msg="GROUNDWATER_OUTFLOW sub-catchment attribute value should be 5"
159
+ )
160
+ self.assertEqual(
161
+ first=output.SubcatchAttribute.GROUNDWATER_TABLE_ELEVATION.value, second=6,
162
+ msg="GROUNDWATER_TABLE sub-catchment attribute value should be 6"
163
+ )
164
+ self.assertEqual(
165
+ first=output.SubcatchAttribute.SOIL_MOISTURE.value, second=7,
166
+ msg="SOIL_MOISTURE sub-catchment attribute value should be 7"
167
+ )
168
+ self.assertEqual(
169
+ first=output.SubcatchAttribute.POLLUTANT_CONCENTRATION.value, second=8,
170
+ msg="POLLUTANT_CONCENTRATION sub-catchment attribute value should be 8"
171
+ )
172
+
173
+ def test_output_node_attribute_enum(self):
174
+ """
175
+ Test the output node attribute enum
176
+ :return:
177
+ """
178
+
179
+ self.assertEqual(
180
+ first=output.NodeAttribute.INVERT_DEPTH.value, second=0,
181
+ msg="INVERT_DEPTH node attribute value should be 0"
182
+ )
183
+ self.assertEqual(
184
+ first=output.NodeAttribute.HYDRAULIC_HEAD.value, second=1,
185
+ msg="HYDRAULIC_HEAD node attribute value should be 1"
186
+ )
187
+ self.assertEqual(
188
+ first=output.NodeAttribute.STORED_VOLUME.value, second=2,
189
+ msg="STORED_VOLUME node attribute value should be 2"
190
+ )
191
+ self.assertEqual(
192
+ first=output.NodeAttribute.LATERAL_INFLOW.value, second=3,
193
+ msg="LATERAL_INFLOW node attribute value should be 3"
194
+ )
195
+ self.assertEqual(
196
+ first=output.NodeAttribute.TOTAL_INFLOW.value, second=4,
197
+ msg="TOTAL_INFLOW node attribute value should be 4"
198
+ )
199
+ self.assertEqual(
200
+ first=output.NodeAttribute.FLOODING_LOSSES.value, second=5,
201
+ msg="FLOODING_LOSSES node attribute value should be 5"
202
+ )
203
+ self.assertEqual(
204
+ first=output.NodeAttribute.POLLUTANT_CONCENTRATION.value, second=6,
205
+ msg="POLLUTANT_CONCENTRATION node attribute value should be 6"
206
+ )
207
+
208
+ def test_output_link_attribute_enum(self):
209
+ """
210
+ Test the output link attribute enum
211
+ :return:
212
+ """
213
+
214
+ self.assertEqual(
215
+ first=output.LinkAttribute.FLOW_RATE.value, second=0,
216
+ msg="FLOW_RATE link attribute value should be 0"
217
+ )
218
+ self.assertEqual(
219
+ first=output.LinkAttribute.FLOW_DEPTH.value, second=1,
220
+ msg="FLOW_DEPTH link attribute value should be 1"
221
+ )
222
+ self.assertEqual(
223
+ first=output.LinkAttribute.FLOW_VELOCITY.value, second=2,
224
+ msg="FLOW_VELOCITY link attribute value should be 2"
225
+ )
226
+ self.assertEqual(
227
+ first=output.LinkAttribute.FLOW_VOLUME.value, second=3,
228
+ msg="FLOW_VOLUME link attribute value should be 3"
229
+ )
230
+ self.assertEqual(
231
+ first=output.LinkAttribute.CAPACITY.value, second=4,
232
+ msg="CAPACITY link attribute value should be 4"
233
+ )
234
+ self.assertEqual(
235
+ first=output.LinkAttribute.POLLUTANT_CONCENTRATION.value, second=5,
236
+ msg="POLLUTANT_CONCENTRATION link attribute value should be 5"
237
+ )
238
+
239
+ def test_output_system_attribute_enum(self):
240
+ """
241
+ Test the output system attribute enum
242
+ :return:
243
+ """
244
+
245
+ self.assertEqual(
246
+ first=output.SystemAttribute.AIR_TEMP.value, second=0,
247
+ msg="AIR_TEMP system attribute value should be 0"
248
+ )
249
+ self.assertEqual(
250
+ first=output.SystemAttribute.RAINFALL.value, second=1,
251
+ msg="RAINFALL system attribute value should be 1"
252
+ )
253
+ self.assertEqual(
254
+ first=output.SystemAttribute.SNOW_DEPTH.value, second=2,
255
+ msg="SNOW_DEPTH system attribute value should be 2"
256
+ )
257
+ self.assertEqual(
258
+ first=output.SystemAttribute.EVAP_INFIL_LOSS.value, second=3,
259
+ msg="EVAP_INFIL_LOSS system attribute value should be 3"
260
+ )
261
+ self.assertEqual(
262
+ first=output.SystemAttribute.RUNOFF_FLOW.value, second=4,
263
+ msg="RUNOFF_FLOW system attribute value should be 4"
264
+ )
265
+ self.assertEqual(
266
+ first=output.SystemAttribute.DRY_WEATHER_INFLOW.value, second=5,
267
+ msg="DRY_WEATHER_INFLOW system attribute value should be 5"
268
+ )
269
+ self.assertEqual(
270
+ first=output.SystemAttribute.GROUNDWATER_INFLOW.value, second=6,
271
+ msg="GROUNDWATER_INFLOW system attribute value should be 6"
272
+ )
273
+ self.assertEqual(
274
+ first=output.SystemAttribute.RDII_INFLOW.value, second=7,
275
+ msg="RDII_INFLOW system attribute value should be 7"
276
+ )
277
+ self.assertEqual(
278
+ first=output.SystemAttribute.DIRECT_INFLOW.value, second=8,
279
+ msg="DIRECT_INFLOW system attribute value should be 8"
280
+ )
281
+ self.assertEqual(
282
+ first=output.SystemAttribute.TOTAL_LATERAL_INFLOW.value, second=9,
283
+ msg="TOTAL_LATERAL_INFLOW system attribute value should be 9"
284
+ )
285
+ self.assertEqual(
286
+ first=output.SystemAttribute.FLOOD_LOSSES.value, second=10,
287
+ msg="FLOOD_LOSSES system attribute value should be 10"
288
+ )
289
+ self.assertEqual(
290
+ first=output.SystemAttribute.OUTFALL_FLOWS.value, second=11,
291
+ msg="OUTFALL_FLOWS system attribute value should be 11"
292
+ )
293
+ self.assertEqual(
294
+ first=output.SystemAttribute.VOLUME_STORED.value, second=12,
295
+ msg="VOLUME_STORED system attribute value should be 12"
296
+ )
297
+ self.assertEqual(
298
+ first=output.SystemAttribute.EVAPORATION_RATE.value, second=13,
299
+ msg="EVAPORATION_RATE system attribute value should be 13"
300
+ )
301
+
302
+ def test_output_open_and_close(self):
303
+ """
304
+ Test the output open and close functions
305
+ :return:
306
+ """
307
+ with Output(output_file=example_output_data.EXAMPLE_OUTPUT_FILE_1) as swmm_output:
308
+ pass
309
+
310
+ swmm_output = Output(output_file=example_output_data.EXAMPLE_OUTPUT_FILE_1)
311
+
312
+ def test_output_open_error(self):
313
+ """
314
+ Test the output open error function
315
+ :return:
316
+ """
317
+ with self.assertRaises(expected_exception=FileNotFoundError) as context:
318
+ swmm_output = Output(output_file=example_output_data.NON_EXISTENT_OUTPUT_FILE)
319
+
320
+ self.assertIn(
321
+ member="Error opening the SWMM output file",
322
+ container=str(context.exception),
323
+ msg="Error message should be 'Error opening the SWMM output file'"
324
+ )
325
+
326
+ def test_output_get_version(self):
327
+ """
328
+ Test the output get version function
329
+ :return:
330
+ """
331
+ swmm_output = Output(output_file=example_output_data.EXAMPLE_OUTPUT_FILE_1)
332
+ version = swmm_output.version
333
+
334
+ self.assertEqual(first=version, second=51000, msg="Version should be 51000")
335
+
336
+ def test_output_get_size(self):
337
+ """
338
+ Test the output get size function
339
+ :return:
340
+ """
341
+ swmm_output = Output(output_file=example_output_data.EXAMPLE_OUTPUT_FILE_1)
342
+ self.assertDictEqual(
343
+ d1={'subcatchments': 8, 'nodes': 14, 'links': 13, 'system': 1, 'pollutants': 2},
344
+ d2=swmm_output.output_size,
345
+ msg="Output size should be {'subcatchments': 8, 'nodes': 14, 'links': 13, 'system': 1, 'pollutants': 2}"
346
+ )
347
+
348
+ def test_output_get_units(self):
349
+ """
350
+ Test the output get units function
351
+ :return:
352
+ """
353
+ swmm_output = Output(output_file=example_output_data.EXAMPLE_OUTPUT_FILE_1)
354
+ units = swmm_output.units
355
+
356
+ self.assertListEqual(
357
+ list1=list(units),
358
+ list2=[
359
+ output.UnitSystem.US,
360
+ output.FlowUnits.CFS,
361
+ [output.ConcentrationUnits.MG, output.ConcentrationUnits.UG]
362
+ ],
363
+ msg="Units should be [US, CFS, [MG, UG]]"
364
+ )
365
+
366
+ def test_output_get_flow_units(self):
367
+ """
368
+ Test the output get flow units function
369
+ :return:
370
+ """
371
+ swmm_output = Output(output_file=example_output_data.EXAMPLE_OUTPUT_FILE_1)
372
+ flow_units = swmm_output.flow_units
373
+
374
+ self.assertEqual(
375
+ first=flow_units, second=output.FlowUnits.CFS, msg="Flow units should be CFS"
376
+ )
377
+
378
+ def test_output_get_element_properties(self):
379
+ """
380
+ Test the output get element properties function
381
+ :return:
382
+ """
383
+ swmm_output = Output(output_file=example_output_data.EXAMPLE_OUTPUT_FILE_1)
384
+
385
+ sub_catchment_properties = swmm_output.get_num_properties(
386
+ element_type=output.ElementType.SUBCATCHMENT
387
+ )
388
+ node_properties = swmm_output.get_num_properties(
389
+ element_type=output.ElementType.NODE
390
+ )
391
+ link_properties = swmm_output.get_num_properties(
392
+ element_type=output.ElementType.LINK
393
+ )
394
+
395
+ self.assertEqual(
396
+ first=sub_catchment_properties, second=1,
397
+ msg="Number of sub-catchment properties should be 1"
398
+ )
399
+
400
+ self.assertEqual(
401
+ first=node_properties, second=3,
402
+ msg="Number of node properties should be 3"
403
+ )
404
+
405
+ self.assertEqual(
406
+ first=link_properties, second=5,
407
+ msg="Number of link properties should be 5"
408
+ )
409
+
410
+ sub_catchment_property_codes = [
411
+ swmm_output.get_property_code(
412
+ element_type=output.ElementType.SUBCATCHMENT,
413
+ property_index=i
414
+ ) for i in range(sub_catchment_properties)
415
+ ]
416
+
417
+ sub_catchment_property_codes_all = swmm_output.get_property_codes(
418
+ element_type=output.ElementType.SUBCATCHMENT
419
+ )
420
+
421
+ self.assertListEqual(
422
+ list1=sub_catchment_property_codes,
423
+ list2=[1],
424
+ msg="Sub-catchment property codes should be [1]"
425
+ )
426
+
427
+ self.assertListEqual(
428
+ list1=sub_catchment_property_codes,
429
+ list2=sub_catchment_property_codes_all,
430
+ msg="Sub-catchment property codes should be [1]"
431
+ )
432
+
433
+ node_property_codes = [
434
+ swmm_output.get_property_code(
435
+ element_type=output.ElementType.NODE,
436
+ property_index=i
437
+ ) for i in range(node_properties)
438
+ ]
439
+
440
+ node_property_codes_all = swmm_output.get_property_codes(
441
+ element_type=output.ElementType.NODE
442
+ )
443
+
444
+ self.assertListEqual(
445
+ list1=node_property_codes,
446
+ list2=[0, 2, 3],
447
+ msg="Node property codes should be [0, 2, 3]"
448
+ )
449
+
450
+ self.assertListEqual(
451
+ list1=node_property_codes,
452
+ list2=node_property_codes_all,
453
+ msg="Node property codes should be [0, 2, 3]"
454
+ )
455
+
456
+ link_property_codes = [
457
+ swmm_output.get_property_code(
458
+ element_type=output.ElementType.LINK,
459
+ property_index=i
460
+ ) for i in range(link_properties)
461
+ ]
462
+
463
+ link_property_codes_all = swmm_output.get_property_codes(
464
+ element_type=output.ElementType.LINK
465
+ )
466
+
467
+ self.assertListEqual(
468
+ list1=link_property_codes,
469
+ list2=[0, 4, 4, 3, 5],
470
+ msg="Link property codes should be [0, 4, 4, 3, 5]"
471
+ )
472
+
473
+ self.assertListEqual(
474
+ list1=link_property_codes,
475
+ list2=link_property_codes_all,
476
+ msg="Link property codes should be [0, 4, 4, 3, 5]"
477
+ )
478
+
479
+ def test_get_element_property_values(self):
480
+
481
+ swmm_output = Output(output_file=example_output_data.EXAMPLE_OUTPUT_FILE_1)
482
+
483
+ sub_catchment_property_values = [
484
+ swmm_output.get_property_value(
485
+ element_type=output.ElementType.SUBCATCHMENT,
486
+ element_index=0,
487
+ property_code=i
488
+ ) for i in range(1)
489
+ ]
490
+
491
+ sub_catchment_property_values_all = swmm_output.get_property_values(
492
+ element_type=output.ElementType.SUBCATCHMENT,
493
+ element_index=0
494
+ )
495
+
496
+ self.assertListEqual(
497
+ list1=sub_catchment_property_values,
498
+ list2=[10.0],
499
+ msg="Sub-catchment property values should be [10.0]"
500
+ )
501
+
502
+ self.assertListEqual(
503
+ list1=sub_catchment_property_values,
504
+ list2=sub_catchment_property_values_all,
505
+ msg="Sub-catchment property values should be [10.0]"
506
+ )
507
+
508
+ node_property_values = [
509
+ swmm_output.get_property_value(
510
+ element_type=output.ElementType.NODE,
511
+ element_index=0,
512
+ property_code=i
513
+ ) for i in range(3)
514
+ ]
515
+
516
+ node_property_values_all = swmm_output.get_property_values(
517
+ element_type=output.ElementType.NODE,
518
+ element_index=0
519
+ )
520
+
521
+ self.assertListEqual(
522
+ list1=node_property_values,
523
+ list2=[0.0, 1000.0, 3.0],
524
+ msg="Node property values should be [0.0, 1000.0, 3.0]"
525
+ )
526
+
527
+ self.assertListEqual(
528
+ list1=node_property_values,
529
+ list2=node_property_values_all,
530
+ msg="Node property values should be [0.0, 1000.0, 3.0]"
531
+ )
532
+
533
+ link_property_values = [
534
+ swmm_output.get_property_value(
535
+ element_type=output.ElementType.LINK,
536
+ element_index=0,
537
+ property_code=i
538
+ ) for i in range(5)
539
+ ]
540
+
541
+ link_property_values_all = swmm_output.get_property_values(
542
+ element_type=output.ElementType.LINK,
543
+ element_index=0
544
+ )
545
+
546
+ self.assertListEqual(
547
+ list1=link_property_values,
548
+ list2=[0.0, 0.0, 0.0, 1.5, 400.0],
549
+ msg="Link property values should be [0.0, 0.0, 0.0, 1.5, 400.0]"
550
+ )
551
+
552
+ self.assertListEqual(
553
+ list1=link_property_values,
554
+ list2=link_property_values_all,
555
+ msg="Link property values should be [0.0, 0.0, 0.0, 1.5, 400.0]"
556
+ )
557
+
558
+ def test_output_get_element_variables(self):
559
+
560
+ swmm_output = Output(output_file=example_output_data.EXAMPLE_OUTPUT_FILE_1)
561
+ num_sub_catch_vars = swmm_output.get_num_variables(output.ElementType.SUBCATCHMENT)
562
+ num_node_vars = swmm_output.get_num_variables(output.ElementType.NODE)
563
+ num_link_vars = swmm_output.get_num_variables(output.ElementType.LINK)
564
+ num_system_vars = swmm_output.get_num_variables(output.ElementType.SYSTEM)
565
+
566
+ self.assertEqual(
567
+ first=num_sub_catch_vars, second=10, msg="Number of sub-catchment variables should be 10"
568
+ )
569
+
570
+ self.assertEqual(
571
+ first=num_node_vars, second=8, msg="Number of node variables should be 8"
572
+ )
573
+
574
+ self.assertEqual(
575
+ first=num_link_vars, second=7, msg="Number of link variables should be 7"
576
+ )
577
+
578
+ self.assertEqual(
579
+ first=num_system_vars, second=14, msg="Number of system variables should be 14"
580
+ )
581
+
582
+ def test_output_get_start_date(self):
583
+ """
584
+ Test the output get start date function
585
+ :return:
586
+ """
587
+ swmm_output = Output(output_file=example_output_data.EXAMPLE_OUTPUT_FILE_1)
588
+ start_date = swmm_output.start_date
589
+
590
+ self.assertEqual(
591
+ first=start_date,
592
+ second=datetime(year=1998, month=1, day=1),
593
+ msg="Start date should be 01/01/1998"
594
+ )
595
+
596
+ def test_output_get_time_attributes(self):
597
+ """
598
+ Test the output get temporal attributes function
599
+ :return:
600
+ """
601
+ swmm_output = Output(output_file=example_output_data.EXAMPLE_OUTPUT_FILE_1)
602
+ report_step = swmm_output.get_time_attribute(time_attribute=output.TimeAttribute.REPORT_STEP)
603
+ num_periods = swmm_output.get_time_attribute(time_attribute=output.TimeAttribute.NUM_PERIODS)
604
+
605
+ self.assertEqual(
606
+ first=report_step, second=3600,
607
+ msg="Report step should be 3600"
608
+ )
609
+ self.assertEqual(
610
+ first=num_periods, second=36,
611
+ msg="Number of periods should be 365"
612
+ )
613
+
614
+ def test_output_get_element_name(self):
615
+ """
616
+ Test the output get element names function
617
+ :return:
618
+ """
619
+ swmm_output = Output(output_file=example_output_data.EXAMPLE_OUTPUT_FILE_1)
620
+
621
+ retrieved_sub_catch_names = [
622
+ swmm_output.get_element_name(
623
+ element_type=output.ElementType.SUBCATCHMENT, element_index=i
624
+ )
625
+ for i in range(8)
626
+ ]
627
+ sub_catch_names = ['1', '2', '3', '4', '5', '6', '7', '8']
628
+
629
+ self.assertListEqual(
630
+ list1=retrieved_sub_catch_names, list2=sub_catch_names,
631
+ msg="Sub-catchment names should be [1, 2, 3, 4, 5, 6, 7, 8]"
632
+ )
633
+
634
+ retrieved_node_names = [
635
+ swmm_output.get_element_name(
636
+ element_type=output.ElementType.NODE, element_index=i
637
+ )
638
+ for i in range(14)
639
+ ]
640
+
641
+ node_names = ['9', '10', '13', '14', '15', '16', '17', '19', '20', '21', '22', '23', '24', '18']
642
+
643
+ self.assertListEqual(
644
+ list1=retrieved_node_names, list2=node_names,
645
+ msg="Node names should be [9, 10, 13, 14, 15, 16, 17, 19, 20, 21, 22, 23, 24, 18]"
646
+ )
647
+
648
+ retrieved_link_names = [
649
+ swmm_output.get_element_name(
650
+ element_type=output.ElementType.LINK, element_index=i
651
+ ) for i in range(13)
652
+ ]
653
+
654
+ link_names = ['1', '4', '5', '6', '7', '8', '10', '11', '12', '13', '14', '15', '16']
655
+ self.assertListEqual(
656
+ list1=retrieved_link_names, list2=link_names,
657
+ msg="Link names should be [1, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16]"
658
+ )
659
+
660
+ retrieved_pollutant_names = [
661
+ swmm_output.get_element_name(
662
+ element_type=output.ElementType.POLLUTANT, element_index=i
663
+ )
664
+ for i in range(2)
665
+ ]
666
+
667
+ pollutant_names = ['TSS', 'Lead']
668
+ self.assertListEqual(
669
+ list1=retrieved_pollutant_names, list2=pollutant_names,
670
+ msg="Pollutant names should be [TSS, TSS]"
671
+ )
672
+
673
+ def test_get_element_name_errors(self):
674
+ """
675
+ Test the output get element name error function
676
+ :return:
677
+ """
678
+ swmm_output = Output(output_file=example_output_data.EXAMPLE_OUTPUT_FILE_1)
679
+
680
+ with self.assertRaises(expected_exception=Exception) as context:
681
+ swmm_output.get_element_name(
682
+ element_type=output.ElementType.SYSTEM, element_index=0
683
+ )
684
+
685
+ self.assertIn(
686
+ member="invalid parameter code", container=str(context.exception),
687
+ msg="Error message should be 'Invalid element type'"
688
+ )
689
+
690
+ with self.assertRaises(expected_exception=Exception) as context:
691
+ swmm_output.get_element_name(
692
+ element_type=output.ElementType.SUBCATCHMENT, element_index=8
693
+ )
694
+
695
+ self.assertIn(
696
+ member="element index out of range",
697
+ container=str(context.exception),
698
+ msg="Error message should be 'Index out of range'"
699
+ )
700
+
701
+ def test_output_get_element_names(self):
702
+ """
703
+ Test the output get element names error function
704
+ :return:
705
+ """
706
+ swmm_output = Output(output_file=example_output_data.EXAMPLE_OUTPUT_FILE_1)
707
+
708
+ retrieved_sub_catch_names = swmm_output.get_element_names(element_type=output.ElementType.SUBCATCHMENT)
709
+ sub_catch_names = ['1', '2', '3', '4', '5', '6', '7', '8']
710
+
711
+ self.assertListEqual(
712
+ list1=retrieved_sub_catch_names,
713
+ list2=sub_catch_names,
714
+ msg="Sub-catchment names should be [1, 2, 3, 4, 5, 6, 7, 8]"
715
+ )
716
+
717
+ retrieved_node_names = swmm_output.get_element_names(element_type=output.ElementType.NODE)
718
+ node_names = [
719
+ '9', '10', '13', '14', '15', '16', '17', '19', '20', '21', '22', '23', '24', '18'
720
+ ]
721
+ self.assertListEqual(
722
+ list1=retrieved_node_names, list2=node_names,
723
+ msg="Node names should be [9, 10, 13, 14, 15, 16, 17, 19, 20, 21, 22, 23, 24, 18]"
724
+ )
725
+
726
+ retrieved_link_names = swmm_output.get_element_names(element_type=output.ElementType.LINK)
727
+ link_names = ['1', '4', '5', '6', '7', '8', '10', '11', '12', '13', '14', '15', '16']
728
+ self.assertListEqual(
729
+ list1=retrieved_link_names, list2=link_names,
730
+ msg="Link names should be [1, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16]"
731
+ )
732
+
733
+ retrieved_pollutant_names = swmm_output.get_element_names(element_type=output.ElementType.POLLUTANT)
734
+ pollutant_names = ['TSS', 'Lead']
735
+ self.assertListEqual(
736
+ list1=retrieved_pollutant_names, list2=pollutant_names,
737
+ msg="Pollutant names should be [TSS, TSS]"
738
+ )
739
+
740
+ with self.assertRaises(expected_exception=SWMMOutputException) as context:
741
+ swmm_output.get_element_names(element_type=output.ElementType.SYSTEM)
742
+
743
+ self.assertIn(
744
+ member="Cannot get element names for the system element type",
745
+ container=str(context.exception),
746
+ msg="Error message should be 'Invalid element type'"
747
+ )
748
+
749
+ def test_get_times(self):
750
+ """
751
+ Test the output get timeseries function
752
+ :return:
753
+ """
754
+ swmm_output = Output(output_file=example_output_data.EXAMPLE_OUTPUT_FILE_1)
755
+ times = swmm_output.times
756
+
757
+ self.assertEqual(first=len(times), second=36, msg="Number of times should be 36")
758
+
759
+ self.assertEqual(
760
+ first=times[0], second=datetime(year=1998, month=1, day=1, hour=1),
761
+ msg="First time should be 01/01/1998 01:00"
762
+ )
763
+
764
+ self.assertEqual(
765
+ first=times[16], second=datetime(year=1998, month=1, day=1, hour=17),
766
+ msg="Middle time should be 01/01/1998 14:00"
767
+ )
768
+
769
+ self.assertEqual(
770
+ first=times[-1], second=datetime(year=1998, month=1, day=2, hour=12),
771
+ msg="Last time should be 01/02/1998 12:00"
772
+ )
773
+
774
+ def test_get_sub_catchment_timeseries(self):
775
+ """
776
+ Test the output get sub-catchment timeseries function
777
+ :return:
778
+ """
779
+ swmm_output = Output(output_file=example_output_data.EXAMPLE_OUTPUT_FILE_1)
780
+ sub_catchment_timeseries = swmm_output.get_subcatchment_timeseries(
781
+ element_index=5,
782
+ attribute=output.SubcatchAttribute.RUNOFF_RATE,
783
+ )
784
+
785
+ sub_catchment_timeseries_by_name = swmm_output.get_subcatchment_timeseries(
786
+ element_index='6',
787
+ attribute=output.SubcatchAttribute.RUNOFF_RATE,
788
+ )
789
+
790
+ TestSWMMOutput.assert_dict_almost_equal(
791
+ d1=sub_catchment_timeseries,
792
+ d2=self.test_artifacts['test_get_subcatchment_timeseries'],
793
+ )
794
+
795
+ TestSWMMOutput.assert_dict_almost_equal(
796
+ d1=sub_catchment_timeseries_by_name,
797
+ d2=sub_catchment_timeseries,
798
+ )
799
+
800
+ def test_get_node_timeseries(self):
801
+ """
802
+ Test the output get node timeseries function
803
+ :return:
804
+ """
805
+ swmm_output = Output(output_file=example_output_data.EXAMPLE_OUTPUT_FILE_1)
806
+ node_timeseries = swmm_output.get_node_timeseries(
807
+ element_index=7,
808
+ attribute=output.NodeAttribute.TOTAL_INFLOW,
809
+ )
810
+
811
+ node_timeseries_by_name = swmm_output.get_node_timeseries(
812
+ element_index='19',
813
+ attribute=output.NodeAttribute.TOTAL_INFLOW,
814
+ )
815
+
816
+ TestSWMMOutput.assert_dict_almost_equal(
817
+ d1=node_timeseries,
818
+ d2=self.test_artifacts['test_get_node_timeseries'],
819
+ )
820
+
821
+ TestSWMMOutput.assert_dict_almost_equal(
822
+ d1=node_timeseries_by_name,
823
+ d2=node_timeseries,
824
+ )
825
+
826
+ def test_get_link_timeseries(self):
827
+ """
828
+ Test the output get link timeseries function
829
+ :return:
830
+ """
831
+ swmm_output = Output(output_file=example_output_data.EXAMPLE_OUTPUT_FILE_1)
832
+ link_timeseries = swmm_output.get_link_timeseries(
833
+ element_index=5,
834
+ attribute=output.LinkAttribute.FLOW_RATE,
835
+ )
836
+
837
+ link_timeseries_by_name = swmm_output.get_link_timeseries(
838
+ element_index='8',
839
+ attribute=output.LinkAttribute.FLOW_RATE,
840
+ )
841
+
842
+ TestSWMMOutput.assert_dict_almost_equal(
843
+ link_timeseries,
844
+ self.test_artifacts['test_get_link_timeseries'],
845
+ )
846
+
847
+ TestSWMMOutput.assert_dict_almost_equal(
848
+ link_timeseries_by_name,
849
+ link_timeseries,
850
+ )
851
+
852
+ def test_get_system_timeseries(self):
853
+ """
854
+ Test the output get system timeseries function
855
+ :return:
856
+ """
857
+ swmm_output = Output(example_output_data.EXAMPLE_OUTPUT_FILE_1)
858
+ system_timeseries = swmm_output.get_system_timeseries(
859
+ attribute=output.SystemAttribute.RUNOFF_FLOW
860
+ )
861
+
862
+ TestSWMMOutput.assert_dict_almost_equal(
863
+ system_timeseries,
864
+ self.test_artifacts['test_get_system_timeseries'],
865
+ )
866
+
867
+ def test_get_sub_catchment_values_by_time_and_attributes(self):
868
+ """
869
+ Test the output get sub-catchment values by time and attributes function
870
+ :return:
871
+ """
872
+ swmm_output = Output(example_output_data.EXAMPLE_OUTPUT_FILE_1)
873
+ sub_catchment_values = swmm_output.get_subcatchment_values_by_time_and_attribute(
874
+ time_index=5,
875
+ attribute=output.SubcatchAttribute.RUNOFF_RATE
876
+ )
877
+
878
+ TestSWMMOutput.assert_dict_almost_equal(
879
+ sub_catchment_values,
880
+ self.test_artifacts['test_get_subcatchment_values_by_time_and_attributes'],
881
+ )
882
+
883
+ def test_get_node_values_by_time_and_attributes(self):
884
+ """
885
+ Test the output get node values by time and attributes function
886
+ :return:
887
+ """
888
+ swmm_output = Output(example_output_data.EXAMPLE_OUTPUT_FILE_1)
889
+ node_values = swmm_output.get_node_values_by_time_and_attribute(
890
+ time_index=8,
891
+ attribute=output.NodeAttribute.TOTAL_INFLOW
892
+ )
893
+
894
+ TestSWMMOutput.assert_dict_almost_equal(
895
+ node_values,
896
+ self.test_artifacts['test_get_node_values_by_time_and_attributes'],
897
+ )
898
+
899
+ def test_get_link_values_by_time_and_attributes(self):
900
+ """
901
+ Test the output get link values by time and attributes function
902
+ :return:
903
+ """
904
+ swmm_output = Output(example_output_data.EXAMPLE_OUTPUT_FILE_1)
905
+ link_values = swmm_output.get_link_values_by_time_and_attribute(
906
+ time_index=10,
907
+ attribute=output.LinkAttribute.FLOW_RATE
908
+ )
909
+
910
+ TestSWMMOutput.assert_dict_almost_equal(
911
+ d1=link_values,
912
+ d2=self.test_artifacts['test_get_link_values_by_time_and_attributes'],
913
+ )
914
+
915
+ def test_get_system_values_by_time_and_attributes(self):
916
+ """
917
+ Test the output get system values by time and attributes function
918
+ :return:
919
+ """
920
+ swmm_output = Output(output_file=example_output_data.EXAMPLE_OUTPUT_FILE_1)
921
+ system_values = swmm_output.get_system_values_by_time_and_attribute(
922
+ time_index=12,
923
+ attribute=output.SystemAttribute.RUNOFF_FLOW
924
+ )
925
+
926
+ TestSWMMOutput.assert_dict_almost_equal(
927
+ d1=system_values,
928
+ d2=self.test_artifacts['test_get_system_values_by_time_and_attributes'],
929
+ )
930
+
931
+ def test_get_sub_catchment_values_by_time_and_index(self):
932
+ """
933
+ Test the output get sub-catchment values by time and index function
934
+ :return:
935
+ """
936
+ swmm_output = Output(output_file=example_output_data.EXAMPLE_OUTPUT_FILE_1)
937
+ sub_catchment_values = swmm_output.get_subcatchment_values_by_time_and_element_index(
938
+ time_index=5,
939
+ element_index=3
940
+ )
941
+
942
+ sub_catchment_values_by_name = swmm_output.get_subcatchment_values_by_time_and_element_index(
943
+ time_index=5,
944
+ element_index='4'
945
+ )
946
+
947
+ TestSWMMOutput.assert_dict_almost_equal(
948
+ d1=sub_catchment_values,
949
+ d2=self.test_artifacts['test_get_subcatchment_values_by_time_and_index'],
950
+ )
951
+
952
+ TestSWMMOutput.assert_dict_almost_equal(
953
+ d1=sub_catchment_values_by_name,
954
+ d2=sub_catchment_values,
955
+ )
956
+
957
+ def test_get_node_values_by_time_and_index(self):
958
+ """
959
+ Test the output get node values by time and index function
960
+ :return:
961
+ """
962
+ swmm_output = Output(output_file=example_output_data.EXAMPLE_OUTPUT_FILE_1)
963
+ node_values = swmm_output.get_node_values_by_time_and_element_index(
964
+ time_index=8,
965
+ element_index=4
966
+ )
967
+
968
+ node_values_by_name = swmm_output.get_node_values_by_time_and_element_index(
969
+ time_index=8,
970
+ element_index='15'
971
+ )
972
+
973
+ TestSWMMOutput.assert_dict_almost_equal(
974
+ d1=node_values,
975
+ d2=self.test_artifacts['test_get_node_values_by_time_and_index'],
976
+ )
977
+
978
+ TestSWMMOutput.assert_dict_almost_equal(
979
+ d1=node_values_by_name,
980
+ d2=node_values,
981
+ )
982
+
983
+ def test_get_link_values_by_time_and_index(self):
984
+ """
985
+ Test the output get link values by time and index function
986
+ :return:
987
+ """
988
+ swmm_output = Output(output_file=example_output_data.EXAMPLE_OUTPUT_FILE_1)
989
+ link_values = swmm_output.get_link_values_by_time_and_element_index(
990
+ time_index=10,
991
+ element_index=5
992
+ )
993
+
994
+ link_values_by_name = swmm_output.get_link_values_by_time_and_element_index(
995
+ time_index=10,
996
+ element_index='8'
997
+ )
998
+
999
+ TestSWMMOutput.assert_dict_almost_equal(
1000
+ d1=link_values,
1001
+ d2=self.test_artifacts['test_get_link_values_by_time_and_index'],
1002
+ )
1003
+
1004
+ TestSWMMOutput.assert_dict_almost_equal(
1005
+ d1=link_values_by_name,
1006
+ d2=link_values,
1007
+ )
1008
+
1009
+ def test_get_system_values_by_time(self):
1010
+ """
1011
+ Test the output get system values by time
1012
+ :return:
1013
+ """
1014
+ swmm_output = Output(output_file=example_output_data.EXAMPLE_OUTPUT_FILE_1)
1015
+ system_values = swmm_output.get_system_values_by_time(time_index=12)
1016
+
1017
+ TestSWMMOutput.assert_dict_almost_equal(
1018
+ d1=system_values,
1019
+ d2=self.test_artifacts['test_get_system_values_by_time']
1020
+ )
1021
+
1022
+ @staticmethod
1023
+ def assert_dict_almost_equal(d1: dict, d2: dict, rtol: float = 1e-5, atol: float = 1e-8):
1024
+ """
1025
+ Assert that two dictionaries are almost equal
1026
+ :param d1: First dictionary to compare with d2 dictionary
1027
+ :param d2: Second dictionary to compare with d1 dictionary
1028
+ :param rtol: Relative error tolerance for floating point values
1029
+ :param atol: Absolute error tolerance for floating point values
1030
+ :return:
1031
+ """
1032
+ """Assert that two dictionaries are almost equal (with tolerance)."""
1033
+
1034
+ assert set(d1.keys()) == set(d2.keys()) # Check if keys are the same
1035
+
1036
+ for key in d1.keys():
1037
+ value1 = d1[key]
1038
+ value2 = d2[key]
1039
+
1040
+ if isinstance(value1, dict):
1041
+ # If the values are dictionaries, recursively compare them
1042
+ TestSWMMOutput.assert_dict_almost_equal(value1, value2, rtol, atol)
1043
+ elif isinstance(value1, float):
1044
+ # If the values are floats, compare them with tolerance
1045
+ assert abs(value1 - value2) <= atol + rtol * abs(value2)
1046
+ else:
1047
+ # Otherwise, compare them directly
1048
+ assert value1 == value2