epyt-flow 0.1.1__py3-none-any.whl → 0.3.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/EPANET/compile_macos.sh +4 -0
- epyt_flow/VERSION +1 -1
- epyt_flow/__init__.py +29 -18
- epyt_flow/data/benchmarks/leakdb.py +7 -12
- epyt_flow/data/networks.py +404 -40
- epyt_flow/rest_api/base_handler.py +14 -0
- 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 +209 -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 +61 -24
- epyt_flow/simulation/events/leakages.py +27 -17
- epyt_flow/simulation/scada/scada_data.py +545 -14
- epyt_flow/simulation/scada/scada_data_export.py +39 -12
- epyt_flow/simulation/scenario_config.py +14 -20
- epyt_flow/simulation/scenario_simulator.py +358 -114
- epyt_flow/simulation/sensor_config.py +693 -37
- epyt_flow/topology.py +149 -8
- epyt_flow/utils.py +75 -18
- {epyt_flow-0.1.1.dist-info → epyt_flow-0.3.0.dist-info}/METADATA +33 -5
- {epyt_flow-0.1.1.dist-info → epyt_flow-0.3.0.dist-info}/RECORD +30 -22
- epyt_flow/EPANET/compile.sh +0 -4
- {epyt_flow-0.1.1.dist-info → epyt_flow-0.3.0.dist-info}/LICENSE +0 -0
- {epyt_flow-0.1.1.dist-info → epyt_flow-0.3.0.dist-info}/WHEEL +0 -0
- {epyt_flow-0.1.1.dist-info → epyt_flow-0.3.0.dist-info}/top_level.txt +0 -0
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
Module provides a class for implementing sensor configurations.
|
|
3
3
|
"""
|
|
4
4
|
from copy import deepcopy
|
|
5
|
+
import warnings
|
|
6
|
+
import itertools
|
|
5
7
|
import numpy as np
|
|
6
8
|
import epyt
|
|
9
|
+
from epyt.epanet import ToolkitConstants
|
|
7
10
|
|
|
8
11
|
from ..serialization import SENSOR_CONFIG_ID, JsonSerializable, serializable
|
|
9
12
|
|
|
@@ -20,6 +23,268 @@ SENSOR_TYPE_NODE_BULK_SPECIES = 9
|
|
|
20
23
|
SENSOR_TYPE_LINK_BULK_SPECIES = 10
|
|
21
24
|
SENSOR_TYPE_SURFACE_SPECIES = 11
|
|
22
25
|
|
|
26
|
+
AREA_UNIT_FT2 = 1
|
|
27
|
+
AREA_UNIT_M2 = 2
|
|
28
|
+
AREA_UNIT_CM2 = 3
|
|
29
|
+
MASS_UNIT_MG = 4
|
|
30
|
+
MASS_UNIT_UG = 5
|
|
31
|
+
MASS_UNIT_MOL = 6
|
|
32
|
+
MASS_UNIT_MMOL = 7
|
|
33
|
+
TIME_UNIT_HRS = 8
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def areaunit_to_id(unit_desc: str) -> int:
|
|
37
|
+
"""
|
|
38
|
+
Converts a given area units string to the corresponding ID.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
unit_desc : `str`
|
|
43
|
+
Area units string.
|
|
44
|
+
|
|
45
|
+
Returns
|
|
46
|
+
-------
|
|
47
|
+
`int`
|
|
48
|
+
Corresponding area unit ID.
|
|
49
|
+
"""
|
|
50
|
+
return {"FT2": AREA_UNIT_FT2,
|
|
51
|
+
"M2": AREA_UNIT_M2,
|
|
52
|
+
"CM2": AREA_UNIT_CM2}[unit_desc]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def massunit_to_id(unit_desc: str) -> int:
|
|
56
|
+
"""
|
|
57
|
+
Converts a given mass units string to the corresponding ID.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
unit_desc : `str`
|
|
62
|
+
Mass units string.
|
|
63
|
+
|
|
64
|
+
Returns
|
|
65
|
+
-------
|
|
66
|
+
`int`
|
|
67
|
+
Corresponding mass unit ID.
|
|
68
|
+
"""
|
|
69
|
+
return {"MG": MASS_UNIT_MG,
|
|
70
|
+
"UG": MASS_UNIT_UG,
|
|
71
|
+
"MOL": MASS_UNIT_MOL,
|
|
72
|
+
"MMOL": MASS_UNIT_MMOL}[unit_desc]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def qualityunit_to_id(unit_desc: str) -> int:
|
|
76
|
+
"""
|
|
77
|
+
Converts a given measurement unit description to the corresponding mass unit ID.
|
|
78
|
+
|
|
79
|
+
Parameters
|
|
80
|
+
----------
|
|
81
|
+
unit_desc : `str`
|
|
82
|
+
Mass unit.
|
|
83
|
+
|
|
84
|
+
Returns
|
|
85
|
+
-------
|
|
86
|
+
`int`
|
|
87
|
+
Mass unit ID.
|
|
88
|
+
|
|
89
|
+
Will be either None (if no water quality analysis was set up) or
|
|
90
|
+
one of the following constants:
|
|
91
|
+
|
|
92
|
+
- MASS_UNIT_MG = 4 (mg/L)
|
|
93
|
+
- MASS_UNIT_UG = 5 (ug/L)
|
|
94
|
+
- TIME_UNIT_HRS = 8 (hrs)
|
|
95
|
+
"""
|
|
96
|
+
if unit_desc == "mg/L":
|
|
97
|
+
return MASS_UNIT_MG
|
|
98
|
+
elif unit_desc == "ug/L":
|
|
99
|
+
return MASS_UNIT_UG
|
|
100
|
+
elif unit_desc == "hrs":
|
|
101
|
+
return TIME_UNIT_HRS
|
|
102
|
+
else:
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def massunit_to_str(unit_id: int) -> str:
|
|
107
|
+
"""
|
|
108
|
+
Converts a given mass unit ID to the corresponding description.
|
|
109
|
+
|
|
110
|
+
Parameters
|
|
111
|
+
----------
|
|
112
|
+
unit_id : `int`
|
|
113
|
+
ID of the flow unit.
|
|
114
|
+
|
|
115
|
+
Must be one of the following constant:
|
|
116
|
+
|
|
117
|
+
- MASS_UNIT_MG = 4
|
|
118
|
+
- MASS_UNIT_UG = 5
|
|
119
|
+
- MASS_UNIT_MOL = 6
|
|
120
|
+
- MASS_UNIT_MMOL = 7
|
|
121
|
+
|
|
122
|
+
Returns
|
|
123
|
+
-------
|
|
124
|
+
`str`
|
|
125
|
+
Mass unit description.
|
|
126
|
+
"""
|
|
127
|
+
if unit_id is None:
|
|
128
|
+
return ""
|
|
129
|
+
elif unit_id == MASS_UNIT_MG:
|
|
130
|
+
return "MG"
|
|
131
|
+
elif unit_id == MASS_UNIT_UG:
|
|
132
|
+
return "UG"
|
|
133
|
+
elif unit_id == MASS_UNIT_MOL:
|
|
134
|
+
return "MOL"
|
|
135
|
+
elif unit_id == MASS_UNIT_MMOL:
|
|
136
|
+
return "MMOL"
|
|
137
|
+
else:
|
|
138
|
+
raise ValueError(f"Unknown mass unit ID '{unit_id}'")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def flowunit_to_str(unit_id: int) -> str:
|
|
142
|
+
"""
|
|
143
|
+
Converts a given flow unit ID to the corresponding description.
|
|
144
|
+
|
|
145
|
+
Parameters
|
|
146
|
+
----------
|
|
147
|
+
unit_id : `int`
|
|
148
|
+
ID of the flow unit.
|
|
149
|
+
|
|
150
|
+
Must be one of the following EPANET toolkit constants:
|
|
151
|
+
|
|
152
|
+
- EN_CFS = 0 (cubic foot/sec)
|
|
153
|
+
- EN_GPM = 1 (gal/min)
|
|
154
|
+
- EN_MGD = 2 (Million gal/day)
|
|
155
|
+
- EN_IMGD = 3 (Imperial MGD)
|
|
156
|
+
- EN_AFD = 4 (ac-foot/day)
|
|
157
|
+
- EN_LPS = 5 (liter/sec)
|
|
158
|
+
- EN_LPM = 6 (liter/min)
|
|
159
|
+
- EN_MLD = 7 (Megaliter/day)
|
|
160
|
+
- EN_CMH = 8 (cubic meter/hr)
|
|
161
|
+
- EN_CMD = 9 (cubic meter/day)
|
|
162
|
+
|
|
163
|
+
Returns
|
|
164
|
+
-------
|
|
165
|
+
`str`
|
|
166
|
+
Flow unit description.
|
|
167
|
+
"""
|
|
168
|
+
if unit_id is None:
|
|
169
|
+
return ""
|
|
170
|
+
elif unit_id == ToolkitConstants.EN_CFS:
|
|
171
|
+
return "cubic foot/sec"
|
|
172
|
+
elif unit_id == ToolkitConstants.EN_GPM:
|
|
173
|
+
return "gal/min"
|
|
174
|
+
elif unit_id == ToolkitConstants.EN_MGD:
|
|
175
|
+
return "Million gal/day"
|
|
176
|
+
elif unit_id == ToolkitConstants.EN_IMGD:
|
|
177
|
+
return "Imperial MGD"
|
|
178
|
+
elif unit_id == ToolkitConstants.EN_AFD:
|
|
179
|
+
return "ac-foot/day"
|
|
180
|
+
elif unit_id == ToolkitConstants.EN_LPS:
|
|
181
|
+
return "liter/sec"
|
|
182
|
+
elif unit_id == ToolkitConstants.EN_LPM:
|
|
183
|
+
return "liter/min"
|
|
184
|
+
elif unit_id == ToolkitConstants.EN_MLD:
|
|
185
|
+
return "Megaliter/day"
|
|
186
|
+
elif unit_id == ToolkitConstants.EN_CMH:
|
|
187
|
+
return "cubic meter/hr"
|
|
188
|
+
elif unit_id == ToolkitConstants.EN_CMD:
|
|
189
|
+
return "cubic meter/day"
|
|
190
|
+
else:
|
|
191
|
+
raise ValueError(f"Unknown unit ID '{unit_id}'")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def qualityunit_to_str(unit_id: int) -> str:
|
|
195
|
+
"""
|
|
196
|
+
Converts a given quality measurement unit ID to the corresponding description.
|
|
197
|
+
|
|
198
|
+
Parameters
|
|
199
|
+
----------
|
|
200
|
+
unit_id : `int`
|
|
201
|
+
ID of the quality unit.
|
|
202
|
+
|
|
203
|
+
Must be one of the following constants:
|
|
204
|
+
|
|
205
|
+
- MASS_UNIT_MG = 4 (mg/L)
|
|
206
|
+
- MASS_UNIT_UG = 5 (ug/L)
|
|
207
|
+
- TIME_UNIT_HRS = 8 (hrs)
|
|
208
|
+
|
|
209
|
+
Returns
|
|
210
|
+
-------
|
|
211
|
+
`str`
|
|
212
|
+
Mass unit description.
|
|
213
|
+
"""
|
|
214
|
+
if unit_id is None:
|
|
215
|
+
return ""
|
|
216
|
+
elif unit_id == MASS_UNIT_MG:
|
|
217
|
+
return "mg/L"
|
|
218
|
+
elif unit_id == MASS_UNIT_UG:
|
|
219
|
+
return "ug/L"
|
|
220
|
+
elif unit_id == TIME_UNIT_HRS:
|
|
221
|
+
return "hrs"
|
|
222
|
+
else:
|
|
223
|
+
raise ValueError(f"Unknown unit ID '{unit_id}'")
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def areaunit_to_str(unit_id: int) -> str:
|
|
227
|
+
"""
|
|
228
|
+
Converts a given area measurement unit ID to the corresponding description.
|
|
229
|
+
|
|
230
|
+
Parameters
|
|
231
|
+
----------
|
|
232
|
+
unit_id : `int`
|
|
233
|
+
ID of the area unit.
|
|
234
|
+
|
|
235
|
+
Must be one of the following constants:
|
|
236
|
+
|
|
237
|
+
- AREA_UNIT_FT2 = 1
|
|
238
|
+
- AREA_UNIT_M2 = 2
|
|
239
|
+
- AREA_UNIT_CM2 = 3
|
|
240
|
+
|
|
241
|
+
Returns
|
|
242
|
+
-------
|
|
243
|
+
`str`
|
|
244
|
+
Area unit description.
|
|
245
|
+
"""
|
|
246
|
+
if unit_id is None:
|
|
247
|
+
return None
|
|
248
|
+
elif unit_id == AREA_UNIT_FT2:
|
|
249
|
+
return "FT2"
|
|
250
|
+
elif unit_id == AREA_UNIT_M2:
|
|
251
|
+
return "M2"
|
|
252
|
+
elif unit_id == AREA_UNIT_CM2:
|
|
253
|
+
return "CM2"
|
|
254
|
+
else:
|
|
255
|
+
raise ValueError(f"Unknown unit ID '{unit_id}'")
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def is_flowunit_simetric(unit_id: int) -> bool:
|
|
259
|
+
"""
|
|
260
|
+
Checks if a given flow unit belongs to SI metric units.
|
|
261
|
+
|
|
262
|
+
Parameters
|
|
263
|
+
----------
|
|
264
|
+
unit_id : `int`
|
|
265
|
+
ID of the flow unit.
|
|
266
|
+
|
|
267
|
+
Must be one of the following EPANET toolkit constants:
|
|
268
|
+
|
|
269
|
+
- EN_CFS = 0 (cubic foot/sec)
|
|
270
|
+
- EN_GPM = 1 (gal/min)
|
|
271
|
+
- EN_MGD = 2 (Million gal/day)
|
|
272
|
+
- EN_IMGD = 3 (Imperial MGD)
|
|
273
|
+
- EN_AFD = 4 (ac-foot/day)
|
|
274
|
+
- EN_LPS = 5 (liter/sec)
|
|
275
|
+
- EN_LPM = 6 (liter/min)
|
|
276
|
+
- EN_MLD = 7 (Megaliter/day)
|
|
277
|
+
- EN_CMH = 8 (cubic meter/hr)
|
|
278
|
+
- EN_CMD = 9 (cubic meter/day)
|
|
279
|
+
|
|
280
|
+
Returns
|
|
281
|
+
-------
|
|
282
|
+
`bool`
|
|
283
|
+
True if the fiven unit is a SI metric unit, False otherwise.
|
|
284
|
+
"""
|
|
285
|
+
return unit_id in [ToolkitConstants.EN_LPM, ToolkitConstants.EN_MLD,
|
|
286
|
+
ToolkitConstants.EN_CMH, ToolkitConstants.EN_CMD]
|
|
287
|
+
|
|
23
288
|
|
|
24
289
|
@serializable(SENSOR_CONFIG_ID, ".epytflow_sensor_config")
|
|
25
290
|
class SensorConfig(JsonSerializable):
|
|
@@ -39,7 +304,7 @@ class SensorConfig(JsonSerializable):
|
|
|
39
304
|
tanks : `list[str]`
|
|
40
305
|
List of all tanks (i.e. IDs) in the network.
|
|
41
306
|
species : `list[str]`
|
|
42
|
-
List of all (EPANET-MSX) species (i.e. IDs) in the network
|
|
307
|
+
List of all (EPANET-MSX) species (i.e. IDs) in the network
|
|
43
308
|
pressure_sensors : `list[str]`, optional
|
|
44
309
|
List of all nodes (i.e. IDs) at which a pressure sensor is placed.
|
|
45
310
|
|
|
@@ -131,9 +396,65 @@ class SensorConfig(JsonSerializable):
|
|
|
131
396
|
sorted according to their EPANET index.
|
|
132
397
|
|
|
133
398
|
The default is None.
|
|
399
|
+
flow_unit : `int`
|
|
400
|
+
Specifies the flow units and consequently all other hydraulic units
|
|
401
|
+
(US CUSTOMARY or SI METRIC) as well.
|
|
402
|
+
|
|
403
|
+
Must be one of the following EPANET toolkit constants:
|
|
404
|
+
|
|
405
|
+
- EN_CFS = 0 (cubic foot/sec)
|
|
406
|
+
- EN_GPM = 1 (gal/min)
|
|
407
|
+
- EN_MGD = 2 (Million gal/day)
|
|
408
|
+
- EN_IMGD = 3 (Imperial MGD)
|
|
409
|
+
- EN_AFD = 4 (ac-foot/day)
|
|
410
|
+
- EN_LPS = 5 (liter/sec)
|
|
411
|
+
- EN_LPM = 6 (liter/min)
|
|
412
|
+
- EN_MLD = 7 (Megaliter/day)
|
|
413
|
+
- EN_CMH = 8 (cubic meter/hr)
|
|
414
|
+
- EN_CMD = 9 (cubic meter/day)
|
|
415
|
+
quality_unit : `str`, optional
|
|
416
|
+
Measurement unit (in a basic quality analysis) -- only relevant
|
|
417
|
+
if basic water quality is enabled.
|
|
418
|
+
|
|
419
|
+
Must be one of the following constants:
|
|
420
|
+
|
|
421
|
+
- MASS_UNIT_MG = 4 (mg/L)
|
|
422
|
+
- MASS_UNIT_UG = 5 (ug/L)
|
|
423
|
+
- TIME_UNIT_HRS = 8 (hrs)
|
|
424
|
+
|
|
425
|
+
bulk_species_mass_unit : `list[int]`, optional
|
|
426
|
+
Specifies the mass unit for each bulk species -- only relevant if EPANET-MSX is used.
|
|
427
|
+
|
|
428
|
+
Must be one of the following constants:
|
|
429
|
+
|
|
430
|
+
- MASS_UNIT_MG = 4 (milligram)
|
|
431
|
+
- MASS_UNIT_UG = 5 (microgram)
|
|
432
|
+
- MASS_UNIT_MOL = 6 (mole)
|
|
433
|
+
- MASS_UNIT_MMOL = 7 (millimole)
|
|
434
|
+
|
|
435
|
+
Note that the assumed ordering is the same as given in 'bulk_species'.
|
|
436
|
+
surface_species_mass_unit : `list[int]`, optional
|
|
437
|
+
Specifies the mass unit for each surface species -- only relevant if EPANET-MSX is used.
|
|
438
|
+
|
|
439
|
+
Must be one of the following constants:
|
|
440
|
+
|
|
441
|
+
- MASS_UNIT_MG = 4 (milligram)
|
|
442
|
+
- MASS_UNIT_UG = 5 (microgram)
|
|
443
|
+
- MASS_UNIT_MOL = 6 (mole)
|
|
444
|
+
- MASS_UNIT_MMOL = 7 (millimole)
|
|
445
|
+
|
|
446
|
+
Note that the assumed ordering is the same as given in 'surface_species'.
|
|
447
|
+
surface_species_area_unit : `int`, optional
|
|
448
|
+
Species the area unit of all surface species -- only relevant if EPANET-MSX is used.
|
|
449
|
+
Must be one of the following constants:
|
|
450
|
+
|
|
451
|
+
- AREA_UNIT_FT2 = 1 (square feet)
|
|
452
|
+
- AREA_UNIT_M2 = 2 (square meters)
|
|
453
|
+
- AREA_UNIT_CM2 = 3 (square centimeters)
|
|
134
454
|
"""
|
|
135
455
|
def __init__(self, nodes: list[str], links: list[str], valves: list[str], pumps: list[str],
|
|
136
456
|
tanks: list[str], bulk_species: list[str], surface_species: list[str],
|
|
457
|
+
flow_unit: int = None,
|
|
137
458
|
pressure_sensors: list[str] = [],
|
|
138
459
|
flow_sensors: list[str] = [],
|
|
139
460
|
demand_sensors: list[str] = [],
|
|
@@ -148,7 +469,12 @@ class SensorConfig(JsonSerializable):
|
|
|
148
469
|
node_id_to_idx: dict = None, link_id_to_idx: dict = None,
|
|
149
470
|
valve_id_to_idx: dict = None, pump_id_to_idx: dict = None,
|
|
150
471
|
tank_id_to_idx: dict = None, bulkspecies_id_to_idx: dict = None,
|
|
151
|
-
surfacespecies_id_to_idx: dict = None,
|
|
472
|
+
surfacespecies_id_to_idx: dict = None,
|
|
473
|
+
quality_unit: int = None,
|
|
474
|
+
bulk_species_mass_unit : list[int] = [],
|
|
475
|
+
surface_species_mass_unit : list[int] = [],
|
|
476
|
+
surface_species_area_unit : int = None,
|
|
477
|
+
**kwds):
|
|
152
478
|
if not isinstance(nodes, list):
|
|
153
479
|
raise TypeError("'nodes' must be an instance of 'list[str]' " +
|
|
154
480
|
f"but not of '{type(nodes)}'")
|
|
@@ -259,7 +585,8 @@ class SensorConfig(JsonSerializable):
|
|
|
259
585
|
if any(bulk_species_id not in bulk_species
|
|
260
586
|
for bulk_species_id in bulk_species_node_sensors.keys()):
|
|
261
587
|
raise ValueError("Unknown bulk species ID in 'bulk_species_node_sensors'")
|
|
262
|
-
if any(node_id not in nodes for node_id in
|
|
588
|
+
if any(node_id not in nodes for node_id in list(itertools.chain(
|
|
589
|
+
*bulk_species_node_sensors.values()))):
|
|
263
590
|
raise ValueError("Unknown node ID in 'bulk_species_node_sensors'")
|
|
264
591
|
|
|
265
592
|
if not isinstance(bulk_species_link_sensors, dict):
|
|
@@ -268,7 +595,8 @@ class SensorConfig(JsonSerializable):
|
|
|
268
595
|
if any(bulk_species_id not in bulk_species
|
|
269
596
|
for bulk_species_id in bulk_species_link_sensors.keys()):
|
|
270
597
|
raise ValueError("Unknown bulk species ID in 'bulk_species_link_sensors'")
|
|
271
|
-
if any(link_id not in links for link_id in
|
|
598
|
+
if any(link_id not in links for link_id in list(itertools.chain(
|
|
599
|
+
*bulk_species_link_sensors.values()))):
|
|
272
600
|
raise ValueError("Unknown link/pipe ID in 'bulk_species_link_sensors'")
|
|
273
601
|
|
|
274
602
|
if not isinstance(surface_species_sensors, dict):
|
|
@@ -277,7 +605,8 @@ class SensorConfig(JsonSerializable):
|
|
|
277
605
|
if any(surface_species_id not in surface_species_sensors
|
|
278
606
|
for surface_species_id in surface_species_sensors.keys()):
|
|
279
607
|
raise ValueError("Unknown surface species ID in 'surface_species_sensors'")
|
|
280
|
-
if any(link_id not in links for link_id in
|
|
608
|
+
if any(link_id not in links for link_id in list(itertools.chain(
|
|
609
|
+
*surface_species_sensors.values()))):
|
|
281
610
|
raise ValueError("Unknown link ID in 'surface_species_sensors'")
|
|
282
611
|
|
|
283
612
|
if node_id_to_idx is not None:
|
|
@@ -329,6 +658,48 @@ class SensorConfig(JsonSerializable):
|
|
|
329
658
|
if any(s not in surface_species for s in surfacespecies_id_to_idx.keys()):
|
|
330
659
|
raise ValueError("Unknown surface species ID in 'surfacespecies_id_to_idx'")
|
|
331
660
|
|
|
661
|
+
if flow_unit is not None:
|
|
662
|
+
if not isinstance(flow_unit, int):
|
|
663
|
+
raise TypeError("'flow_unit' must be a an instance of 'int' " +
|
|
664
|
+
f"but not of '{type(flow_unit)}'")
|
|
665
|
+
if flow_unit not in range(10):
|
|
666
|
+
raise ValueError("Invalid value of 'flow_unit'")
|
|
667
|
+
else:
|
|
668
|
+
warnings.warn("Loading a file that was created with an outdated version of EPyT-Flow" +
|
|
669
|
+
" -- support of such old files will be removed in the next release!",
|
|
670
|
+
DeprecationWarning)
|
|
671
|
+
|
|
672
|
+
if quality_unit is not None:
|
|
673
|
+
if not isinstance(quality_unit, int):
|
|
674
|
+
raise TypeError("'quality_mass_unit' must be an instance of 'int' " +
|
|
675
|
+
f"but not of '{type(quality_unit)}'")
|
|
676
|
+
if quality_unit not in [MASS_UNIT_MG, MASS_UNIT_UG, TIME_UNIT_HRS]:
|
|
677
|
+
raise ValueError("Invalid value of 'quality_unit'")
|
|
678
|
+
|
|
679
|
+
if len(bulk_species_mass_unit) != len(bulk_species):
|
|
680
|
+
raise ValueError("Inconsistency between 'bulk_species_mass_unit' and 'bulk_species'")
|
|
681
|
+
if any(not isinstance(mass_unit, int) for mass_unit in bulk_species_mass_unit):
|
|
682
|
+
raise TypeError("All items in 'bulk_species_mass_unit' must be an instance of 'int'")
|
|
683
|
+
if any(mass_unit not in [MASS_UNIT_MG, MASS_UNIT_UG, MASS_UNIT_MOL, MASS_UNIT_MMOL]
|
|
684
|
+
for mass_unit in bulk_species_mass_unit):
|
|
685
|
+
raise ValueError("Invalid mass unit in 'bulk_species_mass_unit'")
|
|
686
|
+
|
|
687
|
+
if len(surface_species_mass_unit) != len(surface_species):
|
|
688
|
+
raise ValueError("Inconsistency between 'surface_species_mass_unit' " +
|
|
689
|
+
"and 'surface_species'")
|
|
690
|
+
if any(not isinstance(mass_unit, int) for mass_unit in surface_species_mass_unit):
|
|
691
|
+
raise TypeError("All items in 'surface_species_mass_unit' must be an instance of 'int'")
|
|
692
|
+
if any(mass_unit not in [MASS_UNIT_MG, MASS_UNIT_UG, MASS_UNIT_MOL, MASS_UNIT_MMOL]
|
|
693
|
+
for mass_unit in surface_species_mass_unit):
|
|
694
|
+
raise ValueError("Invalid mass unit in 'surface_species_mass_unit'")
|
|
695
|
+
|
|
696
|
+
if surface_species_area_unit is not None:
|
|
697
|
+
if not isinstance(surface_species_area_unit, int):
|
|
698
|
+
raise TypeError("'surface_species_area_unit' must be a an instance of 'int' " +
|
|
699
|
+
f"but not of '{type(surface_species_area_unit)}'")
|
|
700
|
+
if surface_species_area_unit not in [AREA_UNIT_FT2, AREA_UNIT_M2, AREA_UNIT_CM2]:
|
|
701
|
+
raise ValueError("Invalid area unit 'surface_species_area_unit'")
|
|
702
|
+
|
|
332
703
|
self.__nodes = nodes
|
|
333
704
|
self.__links = links
|
|
334
705
|
self.__valves = valves
|
|
@@ -354,12 +725,165 @@ class SensorConfig(JsonSerializable):
|
|
|
354
725
|
self.__tank_id_to_idx = tank_id_to_idx
|
|
355
726
|
self.__bulkspecies_id_to_idx = bulkspecies_id_to_idx
|
|
356
727
|
self.__surfacespecies_id_to_idx = surfacespecies_id_to_idx
|
|
728
|
+
self.__flow_unit = flow_unit
|
|
729
|
+
self.__quality_unit = quality_unit
|
|
730
|
+
self.__bulk_species_mass_unit = bulk_species_mass_unit
|
|
731
|
+
self.__surface_species_mass_unit = surface_species_mass_unit
|
|
732
|
+
self.__surface_species_area_unit = surface_species_area_unit
|
|
357
733
|
|
|
358
734
|
self.__compute_indices() # Compute indices
|
|
359
735
|
|
|
360
736
|
super().__init__(**kwds)
|
|
361
737
|
|
|
362
|
-
|
|
738
|
+
@staticmethod
|
|
739
|
+
def create_empty_sensor_config(sensor_config):
|
|
740
|
+
"""
|
|
741
|
+
Creates an empty sensor configuration from a given sensor configuration
|
|
742
|
+
-- i.e. a clone of the given sensor configuration except that no sensors are set.
|
|
743
|
+
|
|
744
|
+
Parameters
|
|
745
|
+
----------
|
|
746
|
+
sensor_config : :class:`~epyt_flow.simulation.sensor_config.SensorConfig`
|
|
747
|
+
Sensor configuration used as a basis.
|
|
748
|
+
|
|
749
|
+
Returns
|
|
750
|
+
-------
|
|
751
|
+
:class:`epyt_flow.simulation.sensor_config.SensorConfig`
|
|
752
|
+
Empty sensor configuration.
|
|
753
|
+
"""
|
|
754
|
+
return SensorConfig(nodes=sensor_config.nodes,
|
|
755
|
+
links=sensor_config.links,
|
|
756
|
+
valves=sensor_config.valves,
|
|
757
|
+
pumps=sensor_config.pumps,
|
|
758
|
+
tanks=sensor_config.tanks,
|
|
759
|
+
flow_unit=sensor_config.flow_unit,
|
|
760
|
+
quality_unit=sensor_config.quality_unit,
|
|
761
|
+
bulk_species=sensor_config.bulk_species,
|
|
762
|
+
surface_species=sensor_config.surface_species,
|
|
763
|
+
bulk_species_mass_unit=sensor_config.bulk_species_mass_unit,
|
|
764
|
+
surface_species_mass_unit=sensor_config.surface_species_mass_unit,
|
|
765
|
+
surface_species_area_unit=sensor_config.surface_species_area_unit,
|
|
766
|
+
node_id_to_idx=sensor_config.node_id_to_idx,
|
|
767
|
+
link_id_to_idx=sensor_config.link_id_to_idx,
|
|
768
|
+
valve_id_to_idx=sensor_config.valve_id_to_idx,
|
|
769
|
+
pump_id_to_idx=sensor_config.pump_id_to_idx,
|
|
770
|
+
tank_id_to_idx=sensor_config.tank_id_to_idx,
|
|
771
|
+
bulkspecies_id_to_idx=sensor_config.bulkspecies_id_to_idx,
|
|
772
|
+
surfacespecies_id_to_idx=sensor_config.surfacespecies_id_to_idx)
|
|
773
|
+
|
|
774
|
+
@property
|
|
775
|
+
def node_id_to_idx(self) -> dict:
|
|
776
|
+
"""
|
|
777
|
+
Mapping of a surface node ID to the EPANET index
|
|
778
|
+
(i.e. position in the raw sensor reading data).
|
|
779
|
+
|
|
780
|
+
If None, it is assumed that the nodes (in 'nodes') are
|
|
781
|
+
sorted according to their EPANET index.
|
|
782
|
+
|
|
783
|
+
Returns
|
|
784
|
+
-------
|
|
785
|
+
`dict`
|
|
786
|
+
Node ID to index mapping.
|
|
787
|
+
"""
|
|
788
|
+
return self.__node_id_to_idx
|
|
789
|
+
|
|
790
|
+
@property
|
|
791
|
+
def link_id_to_idx(self) -> dict:
|
|
792
|
+
"""
|
|
793
|
+
Mapping of a link/pipe ID to the EPANET index
|
|
794
|
+
(i.e. position in the raw sensor reading data).
|
|
795
|
+
|
|
796
|
+
If None is given, it is assumed that the links/pipes (in 'links') are
|
|
797
|
+
sorted according to their EPANET index.
|
|
798
|
+
|
|
799
|
+
Returns
|
|
800
|
+
-------
|
|
801
|
+
`dict`
|
|
802
|
+
Link/Pipe ID to index mapping.
|
|
803
|
+
"""
|
|
804
|
+
return self.__link_id_to_idx
|
|
805
|
+
|
|
806
|
+
@property
|
|
807
|
+
def valve_id_to_idx(self) -> dict:
|
|
808
|
+
"""
|
|
809
|
+
Mapping of a valve ID to the EPANET index
|
|
810
|
+
(i.e. position in the raw sensor reading data).
|
|
811
|
+
|
|
812
|
+
If None, it is assumed that the valves (in 'valves') are
|
|
813
|
+
sorted according to their EPANET index.
|
|
814
|
+
|
|
815
|
+
Returns
|
|
816
|
+
-------
|
|
817
|
+
`dict`
|
|
818
|
+
Valve ID to index mapping.
|
|
819
|
+
"""
|
|
820
|
+
return self.__valve_id_to_idx
|
|
821
|
+
|
|
822
|
+
@property
|
|
823
|
+
def pump_id_to_idx(self) -> dict:
|
|
824
|
+
"""
|
|
825
|
+
Mapping of a pump ID to the EPANET index
|
|
826
|
+
(i.e. position in the raw sensor reading data).
|
|
827
|
+
|
|
828
|
+
If None, it is assumed that the pumps (in 'pumps') are
|
|
829
|
+
sorted according to their EPANET index.
|
|
830
|
+
|
|
831
|
+
Returns
|
|
832
|
+
-------
|
|
833
|
+
`dict`
|
|
834
|
+
Pump ID to index mapping.
|
|
835
|
+
"""
|
|
836
|
+
return self.__pump_id_to_idx
|
|
837
|
+
|
|
838
|
+
@property
|
|
839
|
+
def tank_id_to_idx(self) -> dict:
|
|
840
|
+
"""
|
|
841
|
+
Mapping of a tank ID to the EPANET index
|
|
842
|
+
(i.e. position in the raw sensor reading data).
|
|
843
|
+
|
|
844
|
+
If None, it is assumed that the tanks (in 'tanks') are
|
|
845
|
+
sorted according to their EPANET index.
|
|
846
|
+
|
|
847
|
+
Returns
|
|
848
|
+
-------
|
|
849
|
+
`dict`
|
|
850
|
+
Tank ID to index mapping.
|
|
851
|
+
"""
|
|
852
|
+
return self.__tank_id_to_idx
|
|
853
|
+
|
|
854
|
+
@property
|
|
855
|
+
def bulkspecies_id_to_idx(self) -> dict:
|
|
856
|
+
"""
|
|
857
|
+
Mapping of a bulk species ID to the EPANET index
|
|
858
|
+
(i.e. position in the raw sensor reading data).
|
|
859
|
+
|
|
860
|
+
If None, it is assumed that the bulk species (in 'bulk_species') are
|
|
861
|
+
sorted according to their EPANET index.
|
|
862
|
+
|
|
863
|
+
Returns
|
|
864
|
+
-------
|
|
865
|
+
`dict`
|
|
866
|
+
Bulk species ID to index mapping.
|
|
867
|
+
"""
|
|
868
|
+
return self.__bulkspecies_id_to_idx
|
|
869
|
+
|
|
870
|
+
@property
|
|
871
|
+
def surfacespecies_id_to_idx(self) -> dict:
|
|
872
|
+
"""
|
|
873
|
+
Mapping of a surface species ID to the EPANET index
|
|
874
|
+
(i.e. position in the raw sensor reading data).
|
|
875
|
+
|
|
876
|
+
If None, it is assumed that the surface species (in 'surface_species') are
|
|
877
|
+
sorted according to their EPANET index.
|
|
878
|
+
|
|
879
|
+
Returns
|
|
880
|
+
-------
|
|
881
|
+
`dict`
|
|
882
|
+
Surface species ID to index mapping.
|
|
883
|
+
"""
|
|
884
|
+
return self.__surfacespecies_id_to_idx
|
|
885
|
+
|
|
886
|
+
def map_node_id_to_idx(self, node_id: str) -> int:
|
|
363
887
|
"""
|
|
364
888
|
Gets the index of a given node ID.
|
|
365
889
|
|
|
@@ -378,7 +902,7 @@ class SensorConfig(JsonSerializable):
|
|
|
378
902
|
else:
|
|
379
903
|
return self.__nodes.index(node_id)
|
|
380
904
|
|
|
381
|
-
def
|
|
905
|
+
def map_link_id_to_idx(self, link_id: str) -> int:
|
|
382
906
|
"""
|
|
383
907
|
Gets the index of a given link ID.
|
|
384
908
|
|
|
@@ -397,7 +921,7 @@ class SensorConfig(JsonSerializable):
|
|
|
397
921
|
else:
|
|
398
922
|
return self.__links.index(link_id)
|
|
399
923
|
|
|
400
|
-
def
|
|
924
|
+
def map_valve_id_to_idx(self, valve_id: str) -> int:
|
|
401
925
|
"""
|
|
402
926
|
Gets the index of a given valve ID.
|
|
403
927
|
|
|
@@ -416,7 +940,7 @@ class SensorConfig(JsonSerializable):
|
|
|
416
940
|
else:
|
|
417
941
|
return self.__valves.index(valve_id)
|
|
418
942
|
|
|
419
|
-
def
|
|
943
|
+
def map_pump_id_to_idx(self, pump_id: str) -> int:
|
|
420
944
|
"""
|
|
421
945
|
Gets the index of a given pump ID.
|
|
422
946
|
|
|
@@ -435,7 +959,7 @@ class SensorConfig(JsonSerializable):
|
|
|
435
959
|
else:
|
|
436
960
|
return self.__pumps.index(pump_id)
|
|
437
961
|
|
|
438
|
-
def
|
|
962
|
+
def map_tank_id_to_idx(self, tank_id: str) -> int:
|
|
439
963
|
"""
|
|
440
964
|
Gets the index of a given tank ID.
|
|
441
965
|
|
|
@@ -454,7 +978,7 @@ class SensorConfig(JsonSerializable):
|
|
|
454
978
|
else:
|
|
455
979
|
return self.__tanks.index(tank_id)
|
|
456
980
|
|
|
457
|
-
def
|
|
981
|
+
def map_bulkspecies_id_to_idx(self, bulk_species_id: str) -> int:
|
|
458
982
|
"""
|
|
459
983
|
Gets the index of a given bulk species ID.
|
|
460
984
|
|
|
@@ -473,7 +997,7 @@ class SensorConfig(JsonSerializable):
|
|
|
473
997
|
else:
|
|
474
998
|
return self.__bulk_species.index(bulk_species_id)
|
|
475
999
|
|
|
476
|
-
def
|
|
1000
|
+
def map_surfacespecies_id_to_idx(self, surface_species_id: str) -> int:
|
|
477
1001
|
"""
|
|
478
1002
|
Gets the index of a given surface species ID.
|
|
479
1003
|
|
|
@@ -493,35 +1017,35 @@ class SensorConfig(JsonSerializable):
|
|
|
493
1017
|
return self.__surface_species.index(surface_species_id)
|
|
494
1018
|
|
|
495
1019
|
def __compute_indices(self):
|
|
496
|
-
self.__pressure_idx = np.array([self.
|
|
1020
|
+
self.__pressure_idx = np.array([self.map_node_id_to_idx(n)
|
|
497
1021
|
for n in self.__pressure_sensors], dtype=np.int32)
|
|
498
|
-
self.__flow_idx = np.array([self.
|
|
1022
|
+
self.__flow_idx = np.array([self.map_link_id_to_idx(link)
|
|
499
1023
|
for link in self.__flow_sensors], dtype=np.int32)
|
|
500
|
-
self.__demand_idx = np.array([self.
|
|
1024
|
+
self.__demand_idx = np.array([self.map_node_id_to_idx(n)
|
|
501
1025
|
for n in self.__demand_sensors], dtype=np.int32)
|
|
502
|
-
self.__quality_node_idx = np.array([self.
|
|
1026
|
+
self.__quality_node_idx = np.array([self.map_node_id_to_idx(n)
|
|
503
1027
|
for n in self.__quality_node_sensors], dtype=np.int32)
|
|
504
|
-
self.__quality_link_idx = np.array([self.
|
|
1028
|
+
self.__quality_link_idx = np.array([self.map_link_id_to_idx(link)
|
|
505
1029
|
for link in self.__quality_link_sensors],
|
|
506
1030
|
dtype=np.int32)
|
|
507
|
-
self.__valve_state_idx = np.array([self.
|
|
1031
|
+
self.__valve_state_idx = np.array([self.map_valve_id_to_idx(v)
|
|
508
1032
|
for v in self.__valve_state_sensors], dtype=np.int32)
|
|
509
|
-
self.__pump_state_idx = np.array([self.
|
|
1033
|
+
self.__pump_state_idx = np.array([self.map_pump_id_to_idx(p)
|
|
510
1034
|
for p in self.__pump_state_sensors], dtype=np.int32)
|
|
511
|
-
self.__tank_volume_idx = np.array([self.
|
|
1035
|
+
self.__tank_volume_idx = np.array([self.map_tank_id_to_idx(t)
|
|
512
1036
|
for t in self.__tank_volume_sensors], dtype=np.int32)
|
|
513
|
-
self.__bulk_species_node_idx = np.array([(self.
|
|
514
|
-
[self.
|
|
1037
|
+
self.__bulk_species_node_idx = np.array([(self.map_bulkspecies_id_to_idx(s),
|
|
1038
|
+
[self.map_node_id_to_idx(node_id)
|
|
515
1039
|
for node_id in self.__bulk_species_node_sensors[s]])
|
|
516
1040
|
for s in self.__bulk_species_node_sensors.keys()],
|
|
517
1041
|
dtype=object)
|
|
518
|
-
self.__bulk_species_link_idx = np.array([(self.
|
|
519
|
-
[self.
|
|
1042
|
+
self.__bulk_species_link_idx = np.array([(self.map_bulkspecies_id_to_idx(s),
|
|
1043
|
+
[self.map_link_id_to_idx(link_id)
|
|
520
1044
|
for link_id in self.__bulk_species_link_sensors[s]])
|
|
521
1045
|
for s in self.__bulk_species_link_sensors.keys()],
|
|
522
1046
|
dtype=object)
|
|
523
|
-
self.__surface_species_idx = np.array([(self.
|
|
524
|
-
[self.
|
|
1047
|
+
self.__surface_species_idx = np.array([(self.map_surfacespecies_id_to_idx(s),
|
|
1048
|
+
[self.map_link_id_to_idx(link_id)
|
|
525
1049
|
for link_id in self.__surface_species_sensors[s]])
|
|
526
1050
|
for s in self.__surface_species_sensors.keys()],
|
|
527
1051
|
dtype=object)
|
|
@@ -534,8 +1058,10 @@ class SensorConfig(JsonSerializable):
|
|
|
534
1058
|
n_valve_state_sensors = len(self.__valve_state_sensors)
|
|
535
1059
|
n_pump_state_sensors = len(self.__pump_state_sensors)
|
|
536
1060
|
n_tank_volume_sensors = len(self.__tank_volume_sensors)
|
|
537
|
-
n_bulk_species_node_sensors = len(
|
|
538
|
-
|
|
1061
|
+
n_bulk_species_node_sensors = len(list(itertools.chain(
|
|
1062
|
+
*self.__bulk_species_node_sensors.values())))
|
|
1063
|
+
n_bulk_species_link_sensors = len(list(itertools.chain(
|
|
1064
|
+
*self.__bulk_species_link_sensors.values())))
|
|
539
1065
|
|
|
540
1066
|
pressure_idx_shift = 0
|
|
541
1067
|
flow_idx_shift = pressure_idx_shift + n_pressure_sensors
|
|
@@ -701,6 +1227,50 @@ class SensorConfig(JsonSerializable):
|
|
|
701
1227
|
"""
|
|
702
1228
|
return self.__tanks.copy()
|
|
703
1229
|
|
|
1230
|
+
@property
|
|
1231
|
+
def flow_unit(self) -> int:
|
|
1232
|
+
"""
|
|
1233
|
+
Gets the flow units.
|
|
1234
|
+
Note that this specifies all other hydraulic units as well.
|
|
1235
|
+
|
|
1236
|
+
Will be one of the following EPANET toolkit constants:
|
|
1237
|
+
|
|
1238
|
+
- EN_CFS = 0 (cubic foot/sec)
|
|
1239
|
+
- EN_GPM = 1 (gal/min)
|
|
1240
|
+
- EN_MGD = 2 (Million gal/day)
|
|
1241
|
+
- EN_IMGD = 3 (Imperial MGD)
|
|
1242
|
+
- EN_AFD = 4 (ac-foot/day)
|
|
1243
|
+
- EN_LPS = 5 (liter/sec)
|
|
1244
|
+
- EN_LPM = 6 (liter/min)
|
|
1245
|
+
- EN_MLD = 7 (Megaliter/day)
|
|
1246
|
+
- EN_CMH = 8 (cubic meter/hr)
|
|
1247
|
+
- EN_CMD = 9 (cubic meter/day)
|
|
1248
|
+
|
|
1249
|
+
Returns
|
|
1250
|
+
-------
|
|
1251
|
+
`int`
|
|
1252
|
+
Flow unit ID.
|
|
1253
|
+
"""
|
|
1254
|
+
return self.__flow_unit
|
|
1255
|
+
|
|
1256
|
+
@property
|
|
1257
|
+
def quality_unit(self) -> int:
|
|
1258
|
+
"""
|
|
1259
|
+
Gets the measurement unit ID used in the basic quality analysis.
|
|
1260
|
+
|
|
1261
|
+
Will be one of the following constants:
|
|
1262
|
+
|
|
1263
|
+
- MASS_UNIT_MG = 4 (milligram)
|
|
1264
|
+
- MASS_UNIT_UG = 5 (microgram)
|
|
1265
|
+
- TIME_UNIT_HRS = 6 (hours)
|
|
1266
|
+
|
|
1267
|
+
Returns
|
|
1268
|
+
-------
|
|
1269
|
+
`int`
|
|
1270
|
+
Mass unit ID.
|
|
1271
|
+
"""
|
|
1272
|
+
return self.__quality_unit
|
|
1273
|
+
|
|
704
1274
|
@property
|
|
705
1275
|
def bulk_species(self) -> list[str]:
|
|
706
1276
|
"""
|
|
@@ -725,6 +1295,62 @@ class SensorConfig(JsonSerializable):
|
|
|
725
1295
|
"""
|
|
726
1296
|
return self.__surface_species.copy()
|
|
727
1297
|
|
|
1298
|
+
@property
|
|
1299
|
+
def bulk_species_mass_unit(self) -> list[int]:
|
|
1300
|
+
"""
|
|
1301
|
+
Gets the mass unit of each bulk species.
|
|
1302
|
+
|
|
1303
|
+
Will be one of the following constants:
|
|
1304
|
+
|
|
1305
|
+
- MASS_UNIT_MG = 4 (milligram)
|
|
1306
|
+
- MASS_UNIT_UG = 5 (microgram)
|
|
1307
|
+
- MASS_UNIT_MOL = 6 (mole)
|
|
1308
|
+
- MASS_UNIT_MMOL = 7 (millimole)
|
|
1309
|
+
|
|
1310
|
+
Returns
|
|
1311
|
+
-------
|
|
1312
|
+
`int`
|
|
1313
|
+
Mass unit ID.
|
|
1314
|
+
"""
|
|
1315
|
+
return self.__bulk_species_mass_unit
|
|
1316
|
+
|
|
1317
|
+
@property
|
|
1318
|
+
def surface_species_mass_unit(self) -> list[int]:
|
|
1319
|
+
"""
|
|
1320
|
+
Gets the mass unit of each surface species.
|
|
1321
|
+
|
|
1322
|
+
Will be one of the following constants:
|
|
1323
|
+
|
|
1324
|
+
- MASS_UNIT_MG = 4 (milligram)
|
|
1325
|
+
- MASS_UNIT_UG = 5 (microgram)
|
|
1326
|
+
- MASS_UNIT_MOL = 6 (mole)
|
|
1327
|
+
- MASS_UNIT_MMOL = 7 (millimole)
|
|
1328
|
+
|
|
1329
|
+
Returns
|
|
1330
|
+
-------
|
|
1331
|
+
`int`
|
|
1332
|
+
Mass unit ID.
|
|
1333
|
+
"""
|
|
1334
|
+
return self.__surface_species_mass_unit
|
|
1335
|
+
|
|
1336
|
+
@property
|
|
1337
|
+
def surface_species_area_unit(self) -> int:
|
|
1338
|
+
"""
|
|
1339
|
+
Gets the surface species area unit.
|
|
1340
|
+
|
|
1341
|
+
Will be one of the following constants:
|
|
1342
|
+
|
|
1343
|
+
- AREA_UNIT_FT2 = 1 (square feet)
|
|
1344
|
+
- AREA_UNIT_M2 = 2 (square meters)
|
|
1345
|
+
- AREA_UNIT_CM2 = 3 (square centimeters)
|
|
1346
|
+
|
|
1347
|
+
Returns
|
|
1348
|
+
-------
|
|
1349
|
+
`int`
|
|
1350
|
+
Area unit ID.
|
|
1351
|
+
"""
|
|
1352
|
+
return self.__surface_species_area_unit
|
|
1353
|
+
|
|
728
1354
|
@property
|
|
729
1355
|
def pressure_sensors(self) -> list[str]:
|
|
730
1356
|
"""
|
|
@@ -972,7 +1598,8 @@ class SensorConfig(JsonSerializable):
|
|
|
972
1598
|
f"but not of '{type(bulk_species_sensors)}'")
|
|
973
1599
|
if any(species_id not in self.__bulk_species for species_id in bulk_species_sensors.keys()):
|
|
974
1600
|
raise ValueError("Unknown bulk species ID in 'bulk_species_sensors'")
|
|
975
|
-
if any(link_id not in self.__links for link_id in
|
|
1601
|
+
if any(link_id not in self.__links for link_id in list(itertools.chain(
|
|
1602
|
+
*bulk_species_sensors.values()))):
|
|
976
1603
|
raise ValueError("Unknown link/pipe ID in 'bulk_species_sensors'")
|
|
977
1604
|
|
|
978
1605
|
self.__bulk_species_link_sensors = bulk_species_sensors
|
|
@@ -1001,7 +1628,7 @@ class SensorConfig(JsonSerializable):
|
|
|
1001
1628
|
for species_id in surface_species_sensors.keys()):
|
|
1002
1629
|
raise ValueError("Unknown surface species ID in 'surface_species_sensors'")
|
|
1003
1630
|
if any(link_id not in self.__links
|
|
1004
|
-
for link_id in
|
|
1631
|
+
for link_id in list(itertools.chain(*surface_species_sensors.values()))):
|
|
1005
1632
|
raise ValueError("Unknown link/pipe ID in 'surface_species_sensors'")
|
|
1006
1633
|
|
|
1007
1634
|
self.__surface_species_sensors = surface_species_sensors
|
|
@@ -1042,7 +1669,12 @@ class SensorConfig(JsonSerializable):
|
|
|
1042
1669
|
"pump_id_to_idx": self.__pump_id_to_idx,
|
|
1043
1670
|
"tank_id_to_idx": self.__tank_id_to_idx,
|
|
1044
1671
|
"bulkspecies_id_to_idx": self.__bulkspecies_id_to_idx,
|
|
1045
|
-
"surfacespecies_id_to_idx": self.__surfacespecies_id_to_idx
|
|
1672
|
+
"surfacespecies_id_to_idx": self.__surfacespecies_id_to_idx,
|
|
1673
|
+
"flow_unit": self.__flow_unit,
|
|
1674
|
+
"quality_unit": self.__quality_unit,
|
|
1675
|
+
"bulk_species_mass_unit": self.__bulk_species_mass_unit,
|
|
1676
|
+
"surface_species_mass_unit": self.__surface_species_mass_unit,
|
|
1677
|
+
"surface_species_area_unit": self.__surface_species_area_unit}
|
|
1046
1678
|
|
|
1047
1679
|
return super().get_attributes() | attr
|
|
1048
1680
|
|
|
@@ -1065,22 +1697,46 @@ class SensorConfig(JsonSerializable):
|
|
|
1065
1697
|
and self.__tank_volume_sensors == other.tank_volume_sensors \
|
|
1066
1698
|
and self.__bulk_species_node_sensors == other.bulk_species_node_sensors \
|
|
1067
1699
|
and self.__bulk_species_link_sensors == other.bulk_species_link_sensors \
|
|
1068
|
-
and self.__surface_species_sensors == other.surface_species_sensors
|
|
1700
|
+
and self.__surface_species_sensors == other.surface_species_sensors \
|
|
1701
|
+
and self.__flow_unit == other.flow_unit \
|
|
1702
|
+
and self.__quality_unit == other.quality_unit \
|
|
1703
|
+
and self.__bulk_species_mass_unit == other.bulk_species_mass_unit \
|
|
1704
|
+
and self.__surface_species_mass_unit == other.surface_species_mass_unit \
|
|
1705
|
+
and self.__surface_species_area_unit == other.surface_species_area_unit \
|
|
1706
|
+
and self.__node_id_to_idx == other.node_id_to_idx \
|
|
1707
|
+
and self.__link_id_to_idx == other.link_id_to_idx \
|
|
1708
|
+
and self.__valve_id_to_idx == other.valve_id_to_idx \
|
|
1709
|
+
and self.__pump_id_to_idx == other.pump_id_to_idx \
|
|
1710
|
+
and self.__tank_id_to_idx == other.tank_id_to_idx \
|
|
1711
|
+
and self.__bulkspecies_id_to_idx == other.bulkspecies_id_to_idx \
|
|
1712
|
+
and self.__surfacespecies_id_to_idx == other.surfacespecies_id_to_idx
|
|
1069
1713
|
|
|
1070
1714
|
def __str__(self) -> str:
|
|
1071
1715
|
return f"nodes: {self.__nodes} links: {self.__links} valves: {self.__valves} " +\
|
|
1072
1716
|
f"pumps: {self.__pumps} tanks: {self.__tanks} bulk_species: {self.__bulk_species} " +\
|
|
1073
|
-
f"surface_species: {self.__surface_species}" + \
|
|
1717
|
+
f"surface_species: {self.__surface_species} " + \
|
|
1718
|
+
f"node_id_to_idx: {self.__node_id_to_idx} link_id_to_idx: {self.__link_id_to_idx} " +\
|
|
1719
|
+
f"pump_id_to_idx: {self.__pump_id_to_idx} tank_id_to_idx: {self.__tank_id_to_idx} " +\
|
|
1720
|
+
f"valve_id_to_idx: {self.__valve_id_to_idx} " +\
|
|
1721
|
+
f"bulkspecies_id_to_idx: {self.__bulkspecies_id_to_idx} " +\
|
|
1722
|
+
f"surfacespecies_id_to_idx: {self.__surfacespecies_id_to_idx}" +\
|
|
1074
1723
|
f"pressure_sensors: {self.__pressure_sensors} flow_sensors: {self.__flow_sensors} " +\
|
|
1075
1724
|
f"demand_sensors: {self.__demand_sensors} " +\
|
|
1076
1725
|
f"quality_node_sensors: {self.__quality_node_sensors} " +\
|
|
1077
1726
|
f"quality_link_sensors: {self.__quality_link_sensors} " +\
|
|
1078
1727
|
f"valve_state_sensors: {self.__valve_state_sensors} " +\
|
|
1079
1728
|
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}"
|
|
1729
|
+
f"tank_volume_sensors: {self.__tank_volume_sensors} " +\
|
|
1730
|
+
f"bulk_species_node_sensors: {self.__bulk_species_node_sensors} " +\
|
|
1731
|
+
f"bulk_species_link_sensors: {self.__bulk_species_link_sensors} " +\
|
|
1732
|
+
f"surface_species_sensors: {self.__surface_species_sensors} " +\
|
|
1733
|
+
f"flow_unit: {flowunit_to_str(self.__flow_unit)} " +\
|
|
1734
|
+
f"quality_unit: {qualityunit_to_str(self.__quality_unit)} " +\
|
|
1735
|
+
"bulk_species_mass_unit: " +\
|
|
1736
|
+
f"{list(map(massunit_to_str, self.__bulk_species_mass_unit))} " +\
|
|
1737
|
+
"surface_species_mass_unit: " +\
|
|
1738
|
+
f"{list(map(massunit_to_str, self.__surface_species_mass_unit))} " +\
|
|
1739
|
+
f"surface_species_area_unit: {areaunit_to_str(self.__surface_species_area_unit)}"
|
|
1084
1740
|
|
|
1085
1741
|
def compute_readings(self, pressures: np.ndarray, flows: np.ndarray, demands: np.ndarray,
|
|
1086
1742
|
nodes_quality: np.ndarray, links_quality: np.ndarray,
|