epyt-flow 0.1.0__py3-none-any.whl → 0.2.0__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.
- epyt_flow/EPANET/compile_linux.sh +4 -0
- epyt_flow/VERSION +1 -1
- epyt_flow/__init__.py +21 -8
- epyt_flow/rest_api/scada_data/__init__.py +0 -0
- epyt_flow/rest_api/{scada_data_handler.py → scada_data/data_handlers.py} +3 -162
- epyt_flow/rest_api/scada_data/export_handlers.py +140 -0
- epyt_flow/rest_api/scada_data/handlers.py +167 -0
- epyt_flow/rest_api/scenario/__init__.py +0 -0
- epyt_flow/rest_api/scenario/event_handlers.py +118 -0
- epyt_flow/rest_api/{scenario_handler.py → scenario/handlers.py} +86 -67
- epyt_flow/rest_api/scenario/simulation_handlers.py +174 -0
- epyt_flow/rest_api/scenario/uncertainty_handlers.py +118 -0
- epyt_flow/rest_api/server.py +59 -24
- epyt_flow/simulation/scada/scada_data.py +2 -2
- epyt_flow/simulation/scada/scada_data_export.py +1 -7
- epyt_flow/simulation/scenario_config.py +12 -18
- epyt_flow/simulation/scenario_simulator.py +387 -132
- epyt_flow/simulation/sensor_config.py +370 -9
- epyt_flow/topology.py +47 -6
- epyt_flow/utils.py +75 -18
- {epyt_flow-0.1.0.dist-info → epyt_flow-0.2.0.dist-info}/METADATA +29 -5
- {epyt_flow-0.1.0.dist-info → epyt_flow-0.2.0.dist-info}/RECORD +25 -18
- epyt_flow/EPANET/compile.sh +0 -4
- {epyt_flow-0.1.0.dist-info → epyt_flow-0.2.0.dist-info}/LICENSE +0 -0
- {epyt_flow-0.1.0.dist-info → epyt_flow-0.2.0.dist-info}/WHEEL +0 -0
- {epyt_flow-0.1.0.dist-info → epyt_flow-0.2.0.dist-info}/top_level.txt +0 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Module provides a class for implementing sensor configurations.
|
|
3
3
|
"""
|
|
4
4
|
from copy import deepcopy
|
|
5
|
+
import warnings
|
|
5
6
|
import numpy as np
|
|
6
7
|
import epyt
|
|
7
8
|
|
|
@@ -20,6 +21,115 @@ SENSOR_TYPE_NODE_BULK_SPECIES = 9
|
|
|
20
21
|
SENSOR_TYPE_LINK_BULK_SPECIES = 10
|
|
21
22
|
SENSOR_TYPE_SURFACE_SPECIES = 11
|
|
22
23
|
|
|
24
|
+
AREA_UNIT_FT2 = 1
|
|
25
|
+
AREA_UNIT_M2 = 2
|
|
26
|
+
AREA_UNIT_CM2 = 3
|
|
27
|
+
MASS_UNIT_MG = 4
|
|
28
|
+
MASS_UNIT_UG = 5
|
|
29
|
+
MASS_UNIT_MOL = 6
|
|
30
|
+
MASS_UNIT_MMOL = 7
|
|
31
|
+
TIME_UNIT_HRS = 8
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def areaunit_to_id(unit_desc: str) -> int:
|
|
35
|
+
"""
|
|
36
|
+
Converts a given area units string to the corresponding ID.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
unit_desc : `str`
|
|
41
|
+
Area units string.
|
|
42
|
+
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
`int`
|
|
46
|
+
Corresponding area unit ID.
|
|
47
|
+
"""
|
|
48
|
+
return {"FT2": AREA_UNIT_FT2,
|
|
49
|
+
"M2": AREA_UNIT_M2,
|
|
50
|
+
"CM2": AREA_UNIT_CM2}[unit_desc]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def massunit_to_id(unit_desc: str) -> int:
|
|
54
|
+
"""
|
|
55
|
+
Converts a given mass units string to the corresponding ID.
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
unit_desc : `str`
|
|
60
|
+
Mass units string.
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
`int`
|
|
65
|
+
Corresponding mass unit ID.
|
|
66
|
+
"""
|
|
67
|
+
return {"MG": MASS_UNIT_MG,
|
|
68
|
+
"UG": MASS_UNIT_UG,
|
|
69
|
+
"MOL": MASS_UNIT_MOL,
|
|
70
|
+
"MMOL": MASS_UNIT_MMOL}[unit_desc]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def qualityunits_to_id(unit_desc: str) -> int:
|
|
74
|
+
"""
|
|
75
|
+
Converts a given measurement unit description to the corresponding mass unit ID.
|
|
76
|
+
|
|
77
|
+
Parameters
|
|
78
|
+
----------
|
|
79
|
+
unit_desc : `str`
|
|
80
|
+
Mass unit.
|
|
81
|
+
|
|
82
|
+
Returns
|
|
83
|
+
-------
|
|
84
|
+
`int`
|
|
85
|
+
Mass unit ID.
|
|
86
|
+
|
|
87
|
+
Will be either None (if no water quality analysis was set up) or
|
|
88
|
+
one of the following constants:
|
|
89
|
+
|
|
90
|
+
- MASS_UNIT_MG = 4 (mg/L)
|
|
91
|
+
- MASS_UNIT_UG = 5 (ug/L)
|
|
92
|
+
- TIME_UNIT_HRS = 8 (hrs)
|
|
93
|
+
"""
|
|
94
|
+
if unit_desc == "mg/L":
|
|
95
|
+
return MASS_UNIT_MG
|
|
96
|
+
elif unit_desc == "ug/L":
|
|
97
|
+
return MASS_UNIT_UG
|
|
98
|
+
elif unit_desc == "hrs":
|
|
99
|
+
return TIME_UNIT_HRS
|
|
100
|
+
else:
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def qualityunits_to_str(unit_id: int) -> str:
|
|
105
|
+
"""
|
|
106
|
+
Converts a given measurement unit ID to the corresponding description.
|
|
107
|
+
|
|
108
|
+
Parameters
|
|
109
|
+
----------
|
|
110
|
+
unit_id : `int`
|
|
111
|
+
ID of the mass unit.
|
|
112
|
+
|
|
113
|
+
Must be one of the following constants:
|
|
114
|
+
|
|
115
|
+
- MASS_UNIT_MG = 4 (mg/L)
|
|
116
|
+
- MASS_UNIT_UG = 5 (ug/L)
|
|
117
|
+
- TIME_UNIT_HRS = 8 (hrs)
|
|
118
|
+
|
|
119
|
+
Returns
|
|
120
|
+
-------
|
|
121
|
+
`str`
|
|
122
|
+
Mass unit description.
|
|
123
|
+
"""
|
|
124
|
+
if unit_id == MASS_UNIT_MG:
|
|
125
|
+
return "mg/L"
|
|
126
|
+
elif unit_id == MASS_UNIT_UG:
|
|
127
|
+
return "ug/L"
|
|
128
|
+
elif unit_id == TIME_UNIT_HRS:
|
|
129
|
+
return "hrs"
|
|
130
|
+
else:
|
|
131
|
+
raise ValueError(f"Unknown unit ID '{unit_id}'")
|
|
132
|
+
|
|
23
133
|
|
|
24
134
|
@serializable(SENSOR_CONFIG_ID, ".epytflow_sensor_config")
|
|
25
135
|
class SensorConfig(JsonSerializable):
|
|
@@ -39,7 +149,7 @@ class SensorConfig(JsonSerializable):
|
|
|
39
149
|
tanks : `list[str]`
|
|
40
150
|
List of all tanks (i.e. IDs) in the network.
|
|
41
151
|
species : `list[str]`
|
|
42
|
-
List of all (EPANET-MSX) species (i.e. IDs) in the network
|
|
152
|
+
List of all (EPANET-MSX) species (i.e. IDs) in the network
|
|
43
153
|
pressure_sensors : `list[str]`, optional
|
|
44
154
|
List of all nodes (i.e. IDs) at which a pressure sensor is placed.
|
|
45
155
|
|
|
@@ -131,9 +241,65 @@ class SensorConfig(JsonSerializable):
|
|
|
131
241
|
sorted according to their EPANET index.
|
|
132
242
|
|
|
133
243
|
The default is None.
|
|
244
|
+
flow_unit : `int`
|
|
245
|
+
Specifies the flow units and consequently all other hydraulic units
|
|
246
|
+
(US CUSTOMARY or SI METRIC) as well.
|
|
247
|
+
|
|
248
|
+
Must be one of the following EPANET toolkit constants:
|
|
249
|
+
|
|
250
|
+
- EN_CFS = 0 (cu foot/sec)
|
|
251
|
+
- EN_GPM = 1 (gal/min)
|
|
252
|
+
- EN_MGD = 2 (Million gal/day)
|
|
253
|
+
- EN_IMGD = 3 (Imperial MGD)
|
|
254
|
+
- EN_AFD = 4 (ac-foot/day)
|
|
255
|
+
- EN_LPS = 5 (liter/sec)
|
|
256
|
+
- EN_LPM = 6 (liter/min)
|
|
257
|
+
- EN_MLD = 7 (Megaliter/day)
|
|
258
|
+
- EN_CMH = 8 (cubic meter/hr)
|
|
259
|
+
- EN_CMD = 9 (cubic meter/day)
|
|
260
|
+
quality_unit : `str`, optional
|
|
261
|
+
Measurement unit (in a basic quality analysis) -- only relevant
|
|
262
|
+
if basic water quality is enabled.
|
|
263
|
+
|
|
264
|
+
Must be one of the following constants:
|
|
265
|
+
|
|
266
|
+
- MASS_UNIT_MG = 4 (mg/L)
|
|
267
|
+
- MASS_UNIT_UG = 5 (ug/L)
|
|
268
|
+
- TIME_UNIT_HRS = 8 (hrs)
|
|
269
|
+
|
|
270
|
+
bulk_species_mass_unit : `list[int]`, optional
|
|
271
|
+
Specifies the mass unit for each bulk species -- only relevant if EPANET-MSX is used.
|
|
272
|
+
|
|
273
|
+
Must be one of the following constants:
|
|
274
|
+
|
|
275
|
+
- MASS_UNIT_MG = 4 (milligram)
|
|
276
|
+
- MASS_UNIT_UG = 5 (microgram)
|
|
277
|
+
- MASS_UNIT_MOL = 6 (mole)
|
|
278
|
+
- MASS_UNIT_MMOL = 7 (millimole)
|
|
279
|
+
|
|
280
|
+
Note that the assumed ordering is the same as given in 'bulk_species'.
|
|
281
|
+
surface_species_mass_unit : `list[int]`, optional
|
|
282
|
+
Specifies the mass unit for each surface species -- only relevant if EPANET-MSX is used.
|
|
283
|
+
|
|
284
|
+
Must be one of the following constants:
|
|
285
|
+
|
|
286
|
+
- MASS_UNIT_MG = 4 (milligram)
|
|
287
|
+
- MASS_UNIT_UG = 5 (microgram)
|
|
288
|
+
- MASS_UNIT_MOL = 6 (mole)
|
|
289
|
+
- MASS_UNIT_MMOL = 7 (millimole)
|
|
290
|
+
|
|
291
|
+
Note that the assumed ordering is the same as given in 'surface_species'.
|
|
292
|
+
surface_species_area_unit : `int`, optional
|
|
293
|
+
Species the area unit of all surface species -- only relevant if EPANET-MSX is used.
|
|
294
|
+
Must be one of the following constants:
|
|
295
|
+
|
|
296
|
+
- AREA_UNIT_FT2 = 1 (square feet)
|
|
297
|
+
- AREA_UNIT_M2 = 2 (square meters)
|
|
298
|
+
- AREA_UNIT_CM2 = 3 (square centimeters)
|
|
134
299
|
"""
|
|
135
300
|
def __init__(self, nodes: list[str], links: list[str], valves: list[str], pumps: list[str],
|
|
136
301
|
tanks: list[str], bulk_species: list[str], surface_species: list[str],
|
|
302
|
+
flow_unit: int = None,
|
|
137
303
|
pressure_sensors: list[str] = [],
|
|
138
304
|
flow_sensors: list[str] = [],
|
|
139
305
|
demand_sensors: list[str] = [],
|
|
@@ -148,7 +314,12 @@ class SensorConfig(JsonSerializable):
|
|
|
148
314
|
node_id_to_idx: dict = None, link_id_to_idx: dict = None,
|
|
149
315
|
valve_id_to_idx: dict = None, pump_id_to_idx: dict = None,
|
|
150
316
|
tank_id_to_idx: dict = None, bulkspecies_id_to_idx: dict = None,
|
|
151
|
-
surfacespecies_id_to_idx: dict = None,
|
|
317
|
+
surfacespecies_id_to_idx: dict = None,
|
|
318
|
+
quality_unit: int = None,
|
|
319
|
+
bulk_species_mass_unit : list[int] = [],
|
|
320
|
+
surface_species_mass_unit : list[int] = [],
|
|
321
|
+
surface_species_area_unit : int = None,
|
|
322
|
+
**kwds):
|
|
152
323
|
if not isinstance(nodes, list):
|
|
153
324
|
raise TypeError("'nodes' must be an instance of 'list[str]' " +
|
|
154
325
|
f"but not of '{type(nodes)}'")
|
|
@@ -329,6 +500,48 @@ class SensorConfig(JsonSerializable):
|
|
|
329
500
|
if any(s not in surface_species for s in surfacespecies_id_to_idx.keys()):
|
|
330
501
|
raise ValueError("Unknown surface species ID in 'surfacespecies_id_to_idx'")
|
|
331
502
|
|
|
503
|
+
if flow_unit is not None:
|
|
504
|
+
if not isinstance(flow_unit, int):
|
|
505
|
+
raise TypeError("'flow_unit' must be a an instance of 'int' " +
|
|
506
|
+
f"but not of '{type(flow_unit)}'")
|
|
507
|
+
if flow_unit not in range(10):
|
|
508
|
+
raise ValueError("Invalid value of 'flow_unit'")
|
|
509
|
+
else:
|
|
510
|
+
warnings.warn("Loading a file that was created with an outdated version of EPyT-Flow" +
|
|
511
|
+
" -- support of such old files will be removed in the next release!",
|
|
512
|
+
DeprecationWarning)
|
|
513
|
+
|
|
514
|
+
if quality_unit is not None:
|
|
515
|
+
if not isinstance(quality_unit, int):
|
|
516
|
+
raise TypeError("'quality_mass_unit' must be an instance of 'int' " +
|
|
517
|
+
f"but not of '{type(quality_unit)}'")
|
|
518
|
+
if quality_unit not in [MASS_UNIT_MG, MASS_UNIT_UG, TIME_UNIT_HRS]:
|
|
519
|
+
raise ValueError("Invalid value of 'quality_unit'")
|
|
520
|
+
|
|
521
|
+
if len(bulk_species_mass_unit) != len(bulk_species):
|
|
522
|
+
raise ValueError("Inconsistency between 'bulk_species_mass_unit' and 'bulk_species'")
|
|
523
|
+
if any(not isinstance(mass_unit, int) for mass_unit in bulk_species_mass_unit):
|
|
524
|
+
raise TypeError("All items in 'bulk_species_mass_unit' must be an instance of 'int'")
|
|
525
|
+
if any(mass_unit not in [MASS_UNIT_MG, MASS_UNIT_UG, MASS_UNIT_MOL, MASS_UNIT_MMOL]
|
|
526
|
+
for mass_unit in bulk_species_mass_unit):
|
|
527
|
+
raise ValueError("Invalid mass unit in 'bulk_species_mass_unit'")
|
|
528
|
+
|
|
529
|
+
if len(surface_species_mass_unit) != len(surface_species):
|
|
530
|
+
raise ValueError("Inconsistency between 'surface_species_mass_unit' " +
|
|
531
|
+
"and 'surface_species'")
|
|
532
|
+
if any(not isinstance(mass_unit, int) for mass_unit in surface_species_mass_unit):
|
|
533
|
+
raise TypeError("All items in 'surface_species_mass_unit' must be an instance of 'int'")
|
|
534
|
+
if any(mass_unit not in [MASS_UNIT_MG, MASS_UNIT_UG, MASS_UNIT_MOL, MASS_UNIT_MMOL]
|
|
535
|
+
for mass_unit in surface_species_mass_unit):
|
|
536
|
+
raise ValueError("Invalid mass unit in 'surface_species_mass_unit'")
|
|
537
|
+
|
|
538
|
+
if surface_species_area_unit is not None:
|
|
539
|
+
if not isinstance(surface_species_area_unit, int):
|
|
540
|
+
raise TypeError("'surface_species_area_unit' must be a an instance of 'int' " +
|
|
541
|
+
f"but not of '{type(surface_species_area_unit)}'")
|
|
542
|
+
if surface_species_area_unit not in [AREA_UNIT_FT2, AREA_UNIT_M2, AREA_UNIT_CM2]:
|
|
543
|
+
raise ValueError("Invalid area unit 'surface_species_area_unit'")
|
|
544
|
+
|
|
332
545
|
self.__nodes = nodes
|
|
333
546
|
self.__links = links
|
|
334
547
|
self.__valves = valves
|
|
@@ -354,11 +567,45 @@ class SensorConfig(JsonSerializable):
|
|
|
354
567
|
self.__tank_id_to_idx = tank_id_to_idx
|
|
355
568
|
self.__bulkspecies_id_to_idx = bulkspecies_id_to_idx
|
|
356
569
|
self.__surfacespecies_id_to_idx = surfacespecies_id_to_idx
|
|
570
|
+
self.__flow_unit = flow_unit
|
|
571
|
+
self.__quality_unit = quality_unit
|
|
572
|
+
self.__bulk_species_mass_unit = bulk_species_mass_unit
|
|
573
|
+
self.__surface_species_mass_unit = surface_species_mass_unit
|
|
574
|
+
self.__surface_species_area_unit = surface_species_area_unit
|
|
357
575
|
|
|
358
576
|
self.__compute_indices() # Compute indices
|
|
359
577
|
|
|
360
578
|
super().__init__(**kwds)
|
|
361
579
|
|
|
580
|
+
@staticmethod
|
|
581
|
+
def create_empty_sensor_config(sensor_config):
|
|
582
|
+
"""
|
|
583
|
+
Creates an empty sensor configuration from a given sensor configuration
|
|
584
|
+
-- i.e. a clone of the given sensor configuration except that no sensors are set.
|
|
585
|
+
|
|
586
|
+
Parameters
|
|
587
|
+
----------
|
|
588
|
+
sensor_config : :class:`epyt_flow.simulation.sensor_config.SensorConfig`
|
|
589
|
+
Sensor configuration used as a basis.
|
|
590
|
+
|
|
591
|
+
Returns
|
|
592
|
+
-------
|
|
593
|
+
:class:`epyt_flow.simulation.sensor_config.SensorConfig`
|
|
594
|
+
Empty sensor configuration.
|
|
595
|
+
"""
|
|
596
|
+
return SensorConfig(nodes=sensor_config.nodes,
|
|
597
|
+
links=sensor_config.links,
|
|
598
|
+
valves=sensor_config.valves,
|
|
599
|
+
pumps=sensor_config.pumps,
|
|
600
|
+
tanks=sensor_config.tanks,
|
|
601
|
+
flow_unit=sensor_config.flow_unit,
|
|
602
|
+
quality_unit=sensor_config.quality_unit,
|
|
603
|
+
bulk_species=sensor_config.bulk_species,
|
|
604
|
+
surface_species=sensor_config.surface_species,
|
|
605
|
+
bulk_species_mass_unit=sensor_config.bulk_species_mass_unit,
|
|
606
|
+
surface_species_mass_unit=sensor_config.surface_species_mass_unit,
|
|
607
|
+
surface_species_area_unit=sensor_config.surface_species_area_unit)
|
|
608
|
+
|
|
362
609
|
def node_id_to_idx(self, node_id: str) -> int:
|
|
363
610
|
"""
|
|
364
611
|
Gets the index of a given node ID.
|
|
@@ -612,7 +859,7 @@ class SensorConfig(JsonSerializable):
|
|
|
612
859
|
|
|
613
860
|
bulk_species = []
|
|
614
861
|
surface_species = []
|
|
615
|
-
if
|
|
862
|
+
if epanet_api.msx is not None:
|
|
616
863
|
for species_id, species_type in zip(epanet_api.getMSXSpeciesNameID(),
|
|
617
864
|
epanet_api.getMSXSpeciesType()):
|
|
618
865
|
if species_type == "BULK":
|
|
@@ -701,6 +948,50 @@ class SensorConfig(JsonSerializable):
|
|
|
701
948
|
"""
|
|
702
949
|
return self.__tanks.copy()
|
|
703
950
|
|
|
951
|
+
@property
|
|
952
|
+
def flow_unit(self) -> int:
|
|
953
|
+
"""
|
|
954
|
+
Gets the flow units.
|
|
955
|
+
Note that this specifies all other hydraulic units as well.
|
|
956
|
+
|
|
957
|
+
Will be one of the following EPANET toolkit constants:
|
|
958
|
+
|
|
959
|
+
- EN_CFS = 0 (cu foot/sec)
|
|
960
|
+
- EN_GPM = 1 (gal/min)
|
|
961
|
+
- EN_MGD = 2 (Million gal/day)
|
|
962
|
+
- EN_IMGD = 3 (Imperial MGD)
|
|
963
|
+
- EN_AFD = 4 (ac-foot/day)
|
|
964
|
+
- EN_LPS = 5 (liter/sec)
|
|
965
|
+
- EN_LPM = 6 (liter/min)
|
|
966
|
+
- EN_MLD = 7 (Megaliter/day)
|
|
967
|
+
- EN_CMH = 8 (cubic meter/hr)
|
|
968
|
+
- EN_CMD = 9 (cubic meter/day)
|
|
969
|
+
|
|
970
|
+
Returns
|
|
971
|
+
-------
|
|
972
|
+
`int`
|
|
973
|
+
Flow unit ID.
|
|
974
|
+
"""
|
|
975
|
+
return self.__flow_unit
|
|
976
|
+
|
|
977
|
+
@property
|
|
978
|
+
def quality_unit(self) -> int:
|
|
979
|
+
"""
|
|
980
|
+
Gets the measurement unit ID used in the basic quality analysis.
|
|
981
|
+
|
|
982
|
+
Will be one of the following constants:
|
|
983
|
+
|
|
984
|
+
- MASS_UNIT_MG = 4 (milligram)
|
|
985
|
+
- MASS_UNIT_UG = 5 (microgram)
|
|
986
|
+
- TIME_UNIT_HRS = 6 (hours)
|
|
987
|
+
|
|
988
|
+
Returns
|
|
989
|
+
-------
|
|
990
|
+
`int`
|
|
991
|
+
Mass unit ID.
|
|
992
|
+
"""
|
|
993
|
+
return self.__quality_unit
|
|
994
|
+
|
|
704
995
|
@property
|
|
705
996
|
def bulk_species(self) -> list[str]:
|
|
706
997
|
"""
|
|
@@ -725,6 +1016,62 @@ class SensorConfig(JsonSerializable):
|
|
|
725
1016
|
"""
|
|
726
1017
|
return self.__surface_species.copy()
|
|
727
1018
|
|
|
1019
|
+
@property
|
|
1020
|
+
def bulk_species_mass_unit(self) -> list[int]:
|
|
1021
|
+
"""
|
|
1022
|
+
Gets the mass unit of each bulk species.
|
|
1023
|
+
|
|
1024
|
+
Will be one of the following constants:
|
|
1025
|
+
|
|
1026
|
+
- MASS_UNIT_MG = 4 (milligram)
|
|
1027
|
+
- MASS_UNIT_UG = 5 (microgram)
|
|
1028
|
+
- MASS_UNIT_MOL = 6 (mole)
|
|
1029
|
+
- MASS_UNIT_MMOL = 7 (millimole)
|
|
1030
|
+
|
|
1031
|
+
Returns
|
|
1032
|
+
-------
|
|
1033
|
+
`int`
|
|
1034
|
+
Mass unit ID.
|
|
1035
|
+
"""
|
|
1036
|
+
return self.__bulk_species_mass_unit
|
|
1037
|
+
|
|
1038
|
+
@property
|
|
1039
|
+
def surface_species_mass_unit(self) -> list[int]:
|
|
1040
|
+
"""
|
|
1041
|
+
Gets the mass unit of each surface species.
|
|
1042
|
+
|
|
1043
|
+
Will be one of the following constants:
|
|
1044
|
+
|
|
1045
|
+
- MASS_UNIT_MG = 4 (milligram)
|
|
1046
|
+
- MASS_UNIT_UG = 5 (microgram)
|
|
1047
|
+
- MASS_UNIT_MOL = 6 (mole)
|
|
1048
|
+
- MASS_UNIT_MMOL = 7 (millimole)
|
|
1049
|
+
|
|
1050
|
+
Returns
|
|
1051
|
+
-------
|
|
1052
|
+
`int`
|
|
1053
|
+
Mass unit ID.
|
|
1054
|
+
"""
|
|
1055
|
+
return self.__surface_species_mass_unit
|
|
1056
|
+
|
|
1057
|
+
@property
|
|
1058
|
+
def surface_species_area_unit(self) -> int:
|
|
1059
|
+
"""
|
|
1060
|
+
Gets the surface species area unit.
|
|
1061
|
+
|
|
1062
|
+
Will be one of the following constants:
|
|
1063
|
+
|
|
1064
|
+
- AREA_UNIT_FT2 = 1 (square feet)
|
|
1065
|
+
- AREA_UNIT_M2 = 2 (square meters)
|
|
1066
|
+
- AREA_UNIT_CM2 = 3 (square centimeters)
|
|
1067
|
+
|
|
1068
|
+
Returns
|
|
1069
|
+
-------
|
|
1070
|
+
`int`
|
|
1071
|
+
Area unit ID.
|
|
1072
|
+
"""
|
|
1073
|
+
return self.__surface_species_area_unit
|
|
1074
|
+
|
|
728
1075
|
@property
|
|
729
1076
|
def pressure_sensors(self) -> list[str]:
|
|
730
1077
|
"""
|
|
@@ -1042,7 +1389,12 @@ class SensorConfig(JsonSerializable):
|
|
|
1042
1389
|
"pump_id_to_idx": self.__pump_id_to_idx,
|
|
1043
1390
|
"tank_id_to_idx": self.__tank_id_to_idx,
|
|
1044
1391
|
"bulkspecies_id_to_idx": self.__bulkspecies_id_to_idx,
|
|
1045
|
-
"surfacespecies_id_to_idx": self.__surfacespecies_id_to_idx
|
|
1392
|
+
"surfacespecies_id_to_idx": self.__surfacespecies_id_to_idx,
|
|
1393
|
+
"flow_unit": self.__flow_unit,
|
|
1394
|
+
"quality_unit": self.__quality_unit,
|
|
1395
|
+
"bulk_species_mass_unit": self.__bulk_species_mass_unit,
|
|
1396
|
+
"surface_species_mass_unit": self.__surface_species_mass_unit,
|
|
1397
|
+
"surface_species_area_unit": self.__surface_species_area_unit}
|
|
1046
1398
|
|
|
1047
1399
|
return super().get_attributes() | attr
|
|
1048
1400
|
|
|
@@ -1065,7 +1417,12 @@ class SensorConfig(JsonSerializable):
|
|
|
1065
1417
|
and self.__tank_volume_sensors == other.tank_volume_sensors \
|
|
1066
1418
|
and self.__bulk_species_node_sensors == other.bulk_species_node_sensors \
|
|
1067
1419
|
and self.__bulk_species_link_sensors == other.bulk_species_link_sensors \
|
|
1068
|
-
and self.__surface_species_sensors == other.surface_species_sensors
|
|
1420
|
+
and self.__surface_species_sensors == other.surface_species_sensors \
|
|
1421
|
+
and self.__flow_unit == other.flow_unit \
|
|
1422
|
+
and self.__quality_unit == other.quality_unit \
|
|
1423
|
+
and self.__bulk_species_mass_unit == other.bulk_species_mass_unit \
|
|
1424
|
+
and self.__surface_species_mass_unit == other.surface_species_mass_unit \
|
|
1425
|
+
and self.__surface_species_area_unit == other.surface_species_area_unit
|
|
1069
1426
|
|
|
1070
1427
|
def __str__(self) -> str:
|
|
1071
1428
|
return f"nodes: {self.__nodes} links: {self.__links} valves: {self.__valves} " +\
|
|
@@ -1077,10 +1434,14 @@ class SensorConfig(JsonSerializable):
|
|
|
1077
1434
|
f"quality_link_sensors: {self.__quality_link_sensors} " +\
|
|
1078
1435
|
f"valve_state_sensors: {self.__valve_state_sensors} " +\
|
|
1079
1436
|
f"pump_state_sensors: {self.__pump_state_sensors} " +\
|
|
1080
|
-
f"tank_volume_sensors: {self.__tank_volume_sensors}" +\
|
|
1081
|
-
f"bulk_species_node_sensors: {self.__bulk_species_node_sensors}" +\
|
|
1082
|
-
f"bulk_species_link_sensors: {self.__bulk_species_link_sensors}" +\
|
|
1083
|
-
f"surface_species_sensors: {self.__surface_species_sensors}"
|
|
1437
|
+
f"tank_volume_sensors: {self.__tank_volume_sensors} " +\
|
|
1438
|
+
f"bulk_species_node_sensors: {self.__bulk_species_node_sensors} " +\
|
|
1439
|
+
f"bulk_species_link_sensors: {self.__bulk_species_link_sensors} " +\
|
|
1440
|
+
f"surface_species_sensors: {self.__surface_species_sensors} " +\
|
|
1441
|
+
f"flow_unit: {self.__flow_unit} quality_unit: {self.__quality_unit}" +\
|
|
1442
|
+
f"bulk_species_mass_unit: {self.__bulk_species_mass_unit} " +\
|
|
1443
|
+
f"surface_species_mass_unit: {self.__surface_species_mass_unit} " +\
|
|
1444
|
+
f"surface_species_area_unit: {self.__surface_species_area_unit}"
|
|
1084
1445
|
|
|
1085
1446
|
def compute_readings(self, pressures: np.ndarray, flows: np.ndarray, demands: np.ndarray,
|
|
1086
1447
|
nodes_quality: np.ndarray, links_quality: np.ndarray,
|
epyt_flow/topology.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Module provides a class for representing the topology of WDN.
|
|
3
3
|
"""
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
import warnings
|
|
4
6
|
import numpy as np
|
|
5
7
|
import networkx as nx
|
|
6
8
|
from scipy.sparse import bsr_array
|
|
@@ -8,6 +10,10 @@ from scipy.sparse import bsr_array
|
|
|
8
10
|
from .serialization import serializable, JsonSerializable, NETWORK_TOPOLOGY_ID
|
|
9
11
|
|
|
10
12
|
|
|
13
|
+
UNITS_USCUSTOM = 0
|
|
14
|
+
UNITS_SIMETRIC = 1
|
|
15
|
+
|
|
16
|
+
|
|
11
17
|
@serializable(NETWORK_TOPOLOGY_ID, ".epytflow_topology")
|
|
12
18
|
class NetworkTopology(nx.Graph, JsonSerializable):
|
|
13
19
|
"""
|
|
@@ -22,13 +28,28 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
22
28
|
links : `list[tuple[tuple[str, str], dict]]`
|
|
23
29
|
List of all links/pipes -- i.e. link ID, ID of connecting nodes, and link information
|
|
24
30
|
such as pipe diameter, length, etc.
|
|
31
|
+
units : `int`
|
|
32
|
+
Measurement units category.
|
|
33
|
+
|
|
34
|
+
Must be one of the following constants:
|
|
35
|
+
|
|
36
|
+
- UNITS_USCUSTOM = 0 (US Customary)
|
|
37
|
+
- UNITS_SIMETRIC = 1 (SI Metric)
|
|
25
38
|
"""
|
|
26
39
|
def __init__(self, f_inp: str, nodes: list[tuple[str, dict]],
|
|
27
|
-
links: list[tuple[str, tuple[str, str], dict]],
|
|
40
|
+
links: list[tuple[str, tuple[str, str], dict]],
|
|
41
|
+
units: int = None,
|
|
42
|
+
**kwds):
|
|
28
43
|
super().__init__(name=f_inp, **kwds)
|
|
29
44
|
|
|
30
45
|
self.__nodes = nodes
|
|
31
46
|
self.__links = links
|
|
47
|
+
self.__units = units
|
|
48
|
+
|
|
49
|
+
if units is None:
|
|
50
|
+
warnings.warn("Loading a file that was created with an outdated version of EPyT-Flow" +
|
|
51
|
+
" -- support of such old files will be removed in the next release!",
|
|
52
|
+
DeprecationWarning)
|
|
32
53
|
|
|
33
54
|
for node_id, node_info in nodes:
|
|
34
55
|
node_elevation = node_info["elevation"]
|
|
@@ -104,23 +125,43 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
104
125
|
|
|
105
126
|
raise ValueError(f"Unknown link '{link_id}'")
|
|
106
127
|
|
|
128
|
+
@property
|
|
129
|
+
def units(self) -> int:
|
|
130
|
+
"""
|
|
131
|
+
Gets the used measurement units category.
|
|
132
|
+
|
|
133
|
+
Will be one of the following constants:
|
|
134
|
+
|
|
135
|
+
- UNITS_USCUSTOM = 0 (US Customary)
|
|
136
|
+
- UNITS_SIMETRIC = 1 (SI Metric)
|
|
137
|
+
|
|
138
|
+
Returns
|
|
139
|
+
-------
|
|
140
|
+
`int`
|
|
141
|
+
Measurement units category.
|
|
142
|
+
"""
|
|
143
|
+
return self.__units
|
|
144
|
+
|
|
107
145
|
def __eq__(self, other) -> bool:
|
|
108
146
|
if not isinstance(other, NetworkTopology):
|
|
109
147
|
raise TypeError("Can not compare 'NetworkTopology' instance to " +
|
|
110
148
|
f"'{type(other)}' instance")
|
|
111
149
|
|
|
112
150
|
return super().__eq__(other) and \
|
|
113
|
-
self.get_all_nodes() == other.get_all_nodes()
|
|
114
|
-
all(link_a[0] == link_b[0] and all(link_a[1] == link_b[1])
|
|
115
|
-
|
|
151
|
+
self.get_all_nodes() == other.get_all_nodes() \
|
|
152
|
+
and all(link_a[0] == link_b[0] and all(link_a[1] == link_b[1])
|
|
153
|
+
for link_a, link_b in zip(self.get_all_links(), other.get_all_links())) \
|
|
154
|
+
and self.__units == other.units
|
|
116
155
|
|
|
117
156
|
def __str__(self) -> str:
|
|
118
|
-
return f"f_inp: {self.name} nodes: {self.__nodes} links: {self.__links}"
|
|
157
|
+
return f"f_inp: {self.name} nodes: {self.__nodes} links: {self.__links} " +\
|
|
158
|
+
f"units: {self.__units}"
|
|
119
159
|
|
|
120
160
|
def get_attributes(self) -> dict:
|
|
121
161
|
return super().get_attributes() | {"f_inp": self.name,
|
|
122
162
|
"nodes": self.__nodes,
|
|
123
|
-
"links": self.__links
|
|
163
|
+
"links": self.__links,
|
|
164
|
+
"units": self.__units}
|
|
124
165
|
|
|
125
166
|
def get_adj_matrix(self) -> bsr_array:
|
|
126
167
|
"""
|