epyt-flow 0.1.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/EPANET/SRC_engines/AUTHORS +28 -0
- epyt_flow/EPANET/EPANET/SRC_engines/LICENSE +21 -0
- epyt_flow/EPANET/EPANET/SRC_engines/Readme_SRC_Engines.txt +18 -0
- epyt_flow/EPANET/EPANET/SRC_engines/enumstxt.h +134 -0
- epyt_flow/EPANET/EPANET/SRC_engines/epanet.c +5578 -0
- epyt_flow/EPANET/EPANET/SRC_engines/epanet2.c +865 -0
- epyt_flow/EPANET/EPANET/SRC_engines/epanet2.def +131 -0
- epyt_flow/EPANET/EPANET/SRC_engines/errors.dat +73 -0
- epyt_flow/EPANET/EPANET/SRC_engines/funcs.h +193 -0
- epyt_flow/EPANET/EPANET/SRC_engines/genmmd.c +1000 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hash.c +177 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hash.h +28 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hydcoeffs.c +1151 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hydraul.c +1117 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hydsolver.c +720 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hydstatus.c +476 -0
- epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2.h +431 -0
- epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_2.h +1786 -0
- epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_enums.h +468 -0
- epyt_flow/EPANET/EPANET/SRC_engines/inpfile.c +810 -0
- epyt_flow/EPANET/EPANET/SRC_engines/input1.c +707 -0
- epyt_flow/EPANET/EPANET/SRC_engines/input2.c +864 -0
- epyt_flow/EPANET/EPANET/SRC_engines/input3.c +2170 -0
- epyt_flow/EPANET/EPANET/SRC_engines/main.c +93 -0
- epyt_flow/EPANET/EPANET/SRC_engines/mempool.c +142 -0
- epyt_flow/EPANET/EPANET/SRC_engines/mempool.h +24 -0
- epyt_flow/EPANET/EPANET/SRC_engines/output.c +852 -0
- epyt_flow/EPANET/EPANET/SRC_engines/project.c +1359 -0
- epyt_flow/EPANET/EPANET/SRC_engines/quality.c +685 -0
- epyt_flow/EPANET/EPANET/SRC_engines/qualreact.c +743 -0
- epyt_flow/EPANET/EPANET/SRC_engines/qualroute.c +694 -0
- epyt_flow/EPANET/EPANET/SRC_engines/report.c +1489 -0
- epyt_flow/EPANET/EPANET/SRC_engines/rules.c +1362 -0
- epyt_flow/EPANET/EPANET/SRC_engines/smatrix.c +871 -0
- epyt_flow/EPANET/EPANET/SRC_engines/text.h +497 -0
- epyt_flow/EPANET/EPANET/SRC_engines/types.h +874 -0
- epyt_flow/EPANET/EPANET-MSX/MSX_Updates.txt +53 -0
- epyt_flow/EPANET/EPANET-MSX/Src/dispersion.h +27 -0
- epyt_flow/EPANET/EPANET-MSX/Src/hash.c +107 -0
- epyt_flow/EPANET/EPANET-MSX/Src/hash.h +28 -0
- epyt_flow/EPANET/EPANET-MSX/Src/include/epanetmsx.h +102 -0
- epyt_flow/EPANET/EPANET-MSX/Src/include/epanetmsx_export.h +42 -0
- epyt_flow/EPANET/EPANET-MSX/Src/mathexpr.c +937 -0
- epyt_flow/EPANET/EPANET-MSX/Src/mathexpr.h +39 -0
- epyt_flow/EPANET/EPANET-MSX/Src/mempool.c +204 -0
- epyt_flow/EPANET/EPANET-MSX/Src/mempool.h +24 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxchem.c +1285 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxcompiler.c +368 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxdict.h +42 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxdispersion.c +586 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxerr.c +116 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxfile.c +260 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxfuncs.c +175 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxfuncs.h +35 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxinp.c +1504 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxout.c +401 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxproj.c +791 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxqual.c +2010 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxrpt.c +400 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxtank.c +422 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxtoolkit.c +1164 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxtypes.h +551 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxutils.c +524 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxutils.h +56 -0
- epyt_flow/EPANET/EPANET-MSX/Src/newton.c +158 -0
- epyt_flow/EPANET/EPANET-MSX/Src/newton.h +34 -0
- epyt_flow/EPANET/EPANET-MSX/Src/rk5.c +287 -0
- epyt_flow/EPANET/EPANET-MSX/Src/rk5.h +39 -0
- epyt_flow/EPANET/EPANET-MSX/Src/ros2.c +293 -0
- epyt_flow/EPANET/EPANET-MSX/Src/ros2.h +35 -0
- epyt_flow/EPANET/EPANET-MSX/Src/smatrix.c +816 -0
- epyt_flow/EPANET/EPANET-MSX/Src/smatrix.h +29 -0
- epyt_flow/EPANET/EPANET-MSX/readme.txt +14 -0
- epyt_flow/EPANET/compile.sh +4 -0
- epyt_flow/VERSION +1 -0
- epyt_flow/__init__.py +24 -0
- epyt_flow/data/__init__.py +0 -0
- epyt_flow/data/benchmarks/__init__.py +11 -0
- epyt_flow/data/benchmarks/batadal.py +257 -0
- epyt_flow/data/benchmarks/batadal_data.py +28 -0
- epyt_flow/data/benchmarks/battledim.py +473 -0
- epyt_flow/data/benchmarks/battledim_data.py +51 -0
- epyt_flow/data/benchmarks/gecco_water_quality.py +267 -0
- epyt_flow/data/benchmarks/leakdb.py +592 -0
- epyt_flow/data/benchmarks/leakdb_data.py +18923 -0
- epyt_flow/data/benchmarks/water_usage.py +123 -0
- epyt_flow/data/networks.py +650 -0
- epyt_flow/gym/__init__.py +4 -0
- epyt_flow/gym/control_gyms.py +47 -0
- epyt_flow/gym/scenario_control_env.py +101 -0
- epyt_flow/metrics.py +404 -0
- epyt_flow/models/__init__.py +2 -0
- epyt_flow/models/event_detector.py +31 -0
- epyt_flow/models/sensor_interpolation_detector.py +118 -0
- epyt_flow/rest_api/__init__.py +4 -0
- epyt_flow/rest_api/base_handler.py +70 -0
- epyt_flow/rest_api/res_manager.py +95 -0
- epyt_flow/rest_api/scada_data_handler.py +476 -0
- epyt_flow/rest_api/scenario_handler.py +352 -0
- epyt_flow/rest_api/server.py +106 -0
- epyt_flow/serialization.py +438 -0
- epyt_flow/simulation/__init__.py +5 -0
- epyt_flow/simulation/events/__init__.py +6 -0
- epyt_flow/simulation/events/actuator_events.py +259 -0
- epyt_flow/simulation/events/event.py +81 -0
- epyt_flow/simulation/events/leakages.py +404 -0
- epyt_flow/simulation/events/sensor_faults.py +267 -0
- epyt_flow/simulation/events/sensor_reading_attack.py +185 -0
- epyt_flow/simulation/events/sensor_reading_event.py +170 -0
- epyt_flow/simulation/events/system_event.py +88 -0
- epyt_flow/simulation/parallel_simulation.py +147 -0
- epyt_flow/simulation/scada/__init__.py +3 -0
- epyt_flow/simulation/scada/advanced_control.py +134 -0
- epyt_flow/simulation/scada/scada_data.py +1589 -0
- epyt_flow/simulation/scada/scada_data_export.py +255 -0
- epyt_flow/simulation/scenario_config.py +608 -0
- epyt_flow/simulation/scenario_simulator.py +1897 -0
- epyt_flow/simulation/scenario_visualizer.py +61 -0
- epyt_flow/simulation/sensor_config.py +1289 -0
- epyt_flow/topology.py +290 -0
- epyt_flow/uncertainty/__init__.py +3 -0
- epyt_flow/uncertainty/model_uncertainty.py +302 -0
- epyt_flow/uncertainty/sensor_noise.py +73 -0
- epyt_flow/uncertainty/uncertainties.py +555 -0
- epyt_flow/uncertainty/utils.py +206 -0
- epyt_flow/utils.py +306 -0
- epyt_flow-0.1.0.dist-info/LICENSE +21 -0
- epyt_flow-0.1.0.dist-info/METADATA +139 -0
- epyt_flow-0.1.0.dist-info/RECORD +131 -0
- epyt_flow-0.1.0.dist-info/WHEEL +5 -0
- epyt_flow-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1289 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module provides a class for implementing sensor configurations.
|
|
3
|
+
"""
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
import numpy as np
|
|
6
|
+
import epyt
|
|
7
|
+
|
|
8
|
+
from ..serialization import SENSOR_CONFIG_ID, JsonSerializable, serializable
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
SENSOR_TYPE_NODE_PRESSURE = 1
|
|
12
|
+
SENSOR_TYPE_NODE_QUALITY = 2
|
|
13
|
+
SENSOR_TYPE_NODE_DEMAND = 3
|
|
14
|
+
SENSOR_TYPE_LINK_FLOW = 4
|
|
15
|
+
SENSOR_TYPE_LINK_QUALITY = 5
|
|
16
|
+
SENSOR_TYPE_VALVE_STATE = 6
|
|
17
|
+
SENSOR_TYPE_PUMP_STATE = 7
|
|
18
|
+
SENSOR_TYPE_TANK_VOLUME = 8
|
|
19
|
+
SENSOR_TYPE_NODE_BULK_SPECIES = 9
|
|
20
|
+
SENSOR_TYPE_LINK_BULK_SPECIES = 10
|
|
21
|
+
SENSOR_TYPE_SURFACE_SPECIES = 11
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@serializable(SENSOR_CONFIG_ID, ".epytflow_sensor_config")
|
|
25
|
+
class SensorConfig(JsonSerializable):
|
|
26
|
+
"""
|
|
27
|
+
Class for storing a sensor configuration.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
nodes : `list[str]`
|
|
32
|
+
List of all nodes (i.e. IDs) in the network.
|
|
33
|
+
links : `list[str]`
|
|
34
|
+
List of all links/pipes (i.e. IDs) in the network.
|
|
35
|
+
valves : `list[str]`
|
|
36
|
+
List of all valves (i.e. IDs) in the network.
|
|
37
|
+
pumps : `list[str]`
|
|
38
|
+
List of all pumps (i.e. IDs) in the network.
|
|
39
|
+
tanks : `list[str]`
|
|
40
|
+
List of all tanks (i.e. IDs) in the network.
|
|
41
|
+
species : `list[str]`
|
|
42
|
+
List of all (EPANET-MSX) species (i.e. IDs) in the network
|
|
43
|
+
pressure_sensors : `list[str]`, optional
|
|
44
|
+
List of all nodes (i.e. IDs) at which a pressure sensor is placed.
|
|
45
|
+
|
|
46
|
+
The default is an empty list.
|
|
47
|
+
flow_sensors : `list[str]`, optional
|
|
48
|
+
List of all links/pipes (i.e. IDs) at which a flow sensor is placed.
|
|
49
|
+
|
|
50
|
+
The default is an empty list.
|
|
51
|
+
demand_sensors : `list[str]`, optional
|
|
52
|
+
List of all nodes (i.e. IDs) at which a demand sensor is placed.
|
|
53
|
+
|
|
54
|
+
The default is an empty list.
|
|
55
|
+
quality_node_sensors : `list[str]`, optional
|
|
56
|
+
List of all nodes (i.e. IDs) at which a quality sensor is placed.
|
|
57
|
+
|
|
58
|
+
The default is an empty list.
|
|
59
|
+
quality_link_sensors : `list[str]`, optional
|
|
60
|
+
List of all links/pipes (i.e. IDs) at which a flow sensor is placed.
|
|
61
|
+
|
|
62
|
+
The default is an empty list.
|
|
63
|
+
valve_state_sensors : `list[str]`, optional
|
|
64
|
+
List of all valves (i.e. IDs) at which a valve state sensor is placed.
|
|
65
|
+
|
|
66
|
+
The default is an empty list.
|
|
67
|
+
pump_state_sensors : `list[str]`, optional
|
|
68
|
+
List of all pumps (i.e. IDs) at which a pump state sensor is placed.
|
|
69
|
+
|
|
70
|
+
The default is an empty list.
|
|
71
|
+
tank_volume_sensors : `list[str]`, optional
|
|
72
|
+
List of all tanks (i.e. IDs) at which a tank volume sensor is placed.
|
|
73
|
+
|
|
74
|
+
The default is an empty list.
|
|
75
|
+
bulk_species_node_sensors : `dict`, optional
|
|
76
|
+
Bulk species node sensors as a dictionary -- i.e. bulk species ID are the keys,
|
|
77
|
+
and the sensor locations (node IDs) are the values.
|
|
78
|
+
|
|
79
|
+
The default is an empty list.
|
|
80
|
+
bulk_species_link_sensors : `dict`, optional
|
|
81
|
+
Bulk species link/pipe sensors as a dictionary -- i.e. bulk species ID are the keys,
|
|
82
|
+
and the sensor locations (link/pipe IDs) are the values.
|
|
83
|
+
|
|
84
|
+
The default is an empty list.
|
|
85
|
+
surface_species_sensors : `dict`, optional
|
|
86
|
+
Surface species sensors as a dictionary -- i.e. surface species ID are the keys,
|
|
87
|
+
and the sensor locations (link/pipe IDs) are the values.
|
|
88
|
+
|
|
89
|
+
The default is an empty list.
|
|
90
|
+
node_id_to_idx : `dict`, optional
|
|
91
|
+
Mapping of a node ID to the EPANET index (i.e. position in the raw sensor reading data).
|
|
92
|
+
|
|
93
|
+
If None is given, it is assumed that the nodes (in 'nodes') are
|
|
94
|
+
sorted according to their EPANET index.
|
|
95
|
+
|
|
96
|
+
The default is None.
|
|
97
|
+
link_id_to_idx : `dict`, optional
|
|
98
|
+
Mapping of a link/pipe ID to the EPANET index
|
|
99
|
+
(i.e. position in the raw sensor reading data).
|
|
100
|
+
|
|
101
|
+
If None is given, it is assumed that the links/pipes (in 'links') are
|
|
102
|
+
sorted according to their EPANET index..
|
|
103
|
+
|
|
104
|
+
The default is None.
|
|
105
|
+
valve_id_to_idx : `dict`, optional
|
|
106
|
+
Mapping of a valve ID to the EPANET index (i.e. position in the raw sensor reading data).
|
|
107
|
+
|
|
108
|
+
If None is given, it is assumed that the valves (in 'valves') are
|
|
109
|
+
sorted according to their EPANET index.
|
|
110
|
+
|
|
111
|
+
The default is None.
|
|
112
|
+
pump_id_to_idx : `dict`, optional
|
|
113
|
+
Mapping of a pump ID to the EPANET index (i.e. position in the raw sensor reading data).
|
|
114
|
+
|
|
115
|
+
If None is given, it is assumed that the pumps (in 'pumps') are
|
|
116
|
+
sorted according to their EPANET index.
|
|
117
|
+
|
|
118
|
+
The default is None.
|
|
119
|
+
tank_id_to_idx : `dict`, optional
|
|
120
|
+
Mapping of a tank ID to the EPANET index (i.e. position in the raw sensor reading data).
|
|
121
|
+
|
|
122
|
+
If None is given, it is assumed that the tanks (in 'tanks') are
|
|
123
|
+
sorted according to their EPANET index.
|
|
124
|
+
|
|
125
|
+
The default is None.
|
|
126
|
+
bulkspecies_id_to_idx : `dict`, optional
|
|
127
|
+
Mapping of a surface species ID to the EPANET index
|
|
128
|
+
(i.e. position in the raw sensor reading data).
|
|
129
|
+
|
|
130
|
+
If None is given, it is assumed that the surface species (in 'surface_species') are
|
|
131
|
+
sorted according to their EPANET index.
|
|
132
|
+
|
|
133
|
+
The default is None.
|
|
134
|
+
"""
|
|
135
|
+
def __init__(self, nodes: list[str], links: list[str], valves: list[str], pumps: list[str],
|
|
136
|
+
tanks: list[str], bulk_species: list[str], surface_species: list[str],
|
|
137
|
+
pressure_sensors: list[str] = [],
|
|
138
|
+
flow_sensors: list[str] = [],
|
|
139
|
+
demand_sensors: list[str] = [],
|
|
140
|
+
quality_node_sensors: list[str] = [],
|
|
141
|
+
quality_link_sensors: list[str] = [],
|
|
142
|
+
valve_state_sensors: list[str] = [],
|
|
143
|
+
pump_state_sensors: list[str] = [],
|
|
144
|
+
tank_volume_sensors: list[str] = [],
|
|
145
|
+
bulk_species_node_sensors: dict = {},
|
|
146
|
+
bulk_species_link_sensors: dict = {},
|
|
147
|
+
surface_species_sensors: dict = {},
|
|
148
|
+
node_id_to_idx: dict = None, link_id_to_idx: dict = None,
|
|
149
|
+
valve_id_to_idx: dict = None, pump_id_to_idx: dict = None,
|
|
150
|
+
tank_id_to_idx: dict = None, bulkspecies_id_to_idx: dict = None,
|
|
151
|
+
surfacespecies_id_to_idx: dict = None, **kwds):
|
|
152
|
+
if not isinstance(nodes, list):
|
|
153
|
+
raise TypeError("'nodes' must be an instance of 'list[str]' " +
|
|
154
|
+
f"but not of '{type(nodes)}'")
|
|
155
|
+
if len(nodes) == 0:
|
|
156
|
+
raise ValueError("'nodes' must be a list of all nodes (i.e. IDs) in the network.")
|
|
157
|
+
if any(not isinstance(n, str) for n in nodes):
|
|
158
|
+
raise TypeError("Each item in 'nodes' must be an instance of 'str' -- " +
|
|
159
|
+
"ID of a node in the network.")
|
|
160
|
+
|
|
161
|
+
if not isinstance(links, list):
|
|
162
|
+
raise TypeError("'links' must be an instance of 'list[str]' " +
|
|
163
|
+
f"but not of '{type(links)}'")
|
|
164
|
+
if len(links) == 0:
|
|
165
|
+
raise ValueError("'links' must be a list of all links/pipes (i.e. IDs) in the network.")
|
|
166
|
+
if any(not isinstance(link, str) for link in links):
|
|
167
|
+
raise TypeError("Each item in 'links' must be an instance of 'str' -- " +
|
|
168
|
+
"ID of a link/pipe in the network.")
|
|
169
|
+
|
|
170
|
+
if not isinstance(valves, list):
|
|
171
|
+
raise TypeError("'valves' must be an instance of 'list[str]' " +
|
|
172
|
+
f"but not of '{type(valves)}'")
|
|
173
|
+
if any(v not in links for v in valves):
|
|
174
|
+
raise ValueError("Each item in 'valves' must be in 'links'")
|
|
175
|
+
|
|
176
|
+
if not isinstance(pumps, list):
|
|
177
|
+
raise TypeError("'pumps' must be an instance of 'list[str]' " +
|
|
178
|
+
f"but not of '{type(pumps)}'")
|
|
179
|
+
if any(p not in links for p in pumps):
|
|
180
|
+
raise ValueError("Each item in 'pumps' must be in 'links'")
|
|
181
|
+
|
|
182
|
+
if not isinstance(tanks, list):
|
|
183
|
+
raise TypeError("'tanks' must be an instance of 'list[str]' " +
|
|
184
|
+
f"but not of '{type(tanks)}'")
|
|
185
|
+
if any(v not in nodes for v in tanks):
|
|
186
|
+
raise ValueError("Each item in 'tanks' must be in 'nodes'")
|
|
187
|
+
|
|
188
|
+
if not isinstance(bulk_species, list):
|
|
189
|
+
raise TypeError("'bulk_species' must be an instance of 'list[str]' " +
|
|
190
|
+
f"but not of '{type(bulk_species)}'")
|
|
191
|
+
if any(not isinstance(bulk_species_id, str) for bulk_species_id in bulk_species):
|
|
192
|
+
raise TypeError("Each item in 'bulk_species' must be an instance of 'str'")
|
|
193
|
+
|
|
194
|
+
if not isinstance(surface_species, list):
|
|
195
|
+
raise TypeError("'surface_species' must be an instance of 'list[str]' " +
|
|
196
|
+
f"but not of '{type(surface_species)}'")
|
|
197
|
+
if any(not isinstance(surface_species_id, str) for surface_species_id in surface_species):
|
|
198
|
+
raise TypeError("Each item in 'surface_species' must be an instance of 'str'")
|
|
199
|
+
|
|
200
|
+
if not isinstance(pressure_sensors, list):
|
|
201
|
+
raise TypeError("'pressure_sensors' must be an instance of 'list[str]' " +
|
|
202
|
+
f"but not of '{type(pressure_sensors)}'")
|
|
203
|
+
if any(n not in nodes for n in pressure_sensors):
|
|
204
|
+
raise ValueError("Each item in 'pressure_sensors' must be in 'nodes' -- " +
|
|
205
|
+
"cannot place a sensor at a non-existing node.")
|
|
206
|
+
|
|
207
|
+
if not isinstance(flow_sensors, list):
|
|
208
|
+
raise TypeError("'flow_sensors' must be an instance of 'list[str]' " +
|
|
209
|
+
f"but not of '{type(flow_sensors)}'")
|
|
210
|
+
if any(link not in links for link in flow_sensors):
|
|
211
|
+
raise ValueError("Each item in 'flow_sensors' must be in 'links' -- cannot " +
|
|
212
|
+
"place a sensor at a non-existing link/pipe.")
|
|
213
|
+
|
|
214
|
+
if not isinstance(demand_sensors, list):
|
|
215
|
+
raise TypeError("'demand_sensors' must be an instance of 'list[str]' " +
|
|
216
|
+
f"but not of '{type(demand_sensors)}'")
|
|
217
|
+
if any(n not in nodes for n in demand_sensors):
|
|
218
|
+
raise ValueError("Each item in 'demand_sensors' must be in 'nodes' -- cannot " +
|
|
219
|
+
"place a sensor at a non-existing node.")
|
|
220
|
+
|
|
221
|
+
if not isinstance(quality_node_sensors, list):
|
|
222
|
+
raise TypeError("'quality_node_sensors' must be an instance of 'list[str]' " +
|
|
223
|
+
f"but not of '{type(quality_node_sensors)}'")
|
|
224
|
+
if any(n not in nodes for n in quality_node_sensors):
|
|
225
|
+
raise ValueError("Each item in 'quality_node_sensors' must be in 'nodes' -- cannot " +
|
|
226
|
+
"place a sensor at a non-existing node.")
|
|
227
|
+
|
|
228
|
+
if not isinstance(quality_link_sensors, list):
|
|
229
|
+
raise TypeError("'quality_link_sensors' must be an instance of 'list[str]' " +
|
|
230
|
+
f"but not of '{type(quality_link_sensors)}'")
|
|
231
|
+
if any(link not in links for link in quality_link_sensors):
|
|
232
|
+
raise ValueError("Each item in 'quality_link_sensors' must be in 'links' -- cannot " +
|
|
233
|
+
"place a sensor at a non-existing link/pipe.")
|
|
234
|
+
|
|
235
|
+
if not isinstance(valve_state_sensors, list):
|
|
236
|
+
raise TypeError("'valve_state_sensors' must be an instance of 'list[str]' " +
|
|
237
|
+
f"but not of '{type(valve_state_sensors)}'")
|
|
238
|
+
if any(link not in valves for link in valve_state_sensors):
|
|
239
|
+
raise ValueError("Each item in 'valve_state_sensors' must be in 'valves' -- cannot " +
|
|
240
|
+
"place a sensor at a non-existing valve.")
|
|
241
|
+
|
|
242
|
+
if not isinstance(pump_state_sensors, list):
|
|
243
|
+
raise TypeError("'pump_state_sensors' must be an instance of 'list[str]' " +
|
|
244
|
+
f"but not of '{type(pump_state_sensors)}'")
|
|
245
|
+
if any(link not in pumps for link in pump_state_sensors):
|
|
246
|
+
raise ValueError("Each item in 'pump_state_sensors' must be in 'pumps' -- cannot " +
|
|
247
|
+
"place a sensor at a non-existing pump.")
|
|
248
|
+
|
|
249
|
+
if not isinstance(tank_volume_sensors, list):
|
|
250
|
+
raise TypeError("'tank_volume_sensors' must be an instance of 'list[str]' " +
|
|
251
|
+
f"but not of '{type(tank_volume_sensors)}'")
|
|
252
|
+
if any(n not in tanks for n in tank_volume_sensors):
|
|
253
|
+
raise ValueError("Each item in 'tank_volume_sensors' must be in 'tanks' -- cannot " +
|
|
254
|
+
"place a sensor at a non-existing tanks.")
|
|
255
|
+
|
|
256
|
+
if not isinstance(bulk_species_node_sensors, dict):
|
|
257
|
+
raise TypeError("'bulk_species_node_sensors' must be an instance of 'dict' but not " +
|
|
258
|
+
f"of '{type(bulk_species_node_sensors)}'")
|
|
259
|
+
if any(bulk_species_id not in bulk_species
|
|
260
|
+
for bulk_species_id in bulk_species_node_sensors.keys()):
|
|
261
|
+
raise ValueError("Unknown bulk species ID in 'bulk_species_node_sensors'")
|
|
262
|
+
if any(node_id not in nodes for node_id in bulk_species_node_sensors.values()):
|
|
263
|
+
raise ValueError("Unknown node ID in 'bulk_species_node_sensors'")
|
|
264
|
+
|
|
265
|
+
if not isinstance(bulk_species_link_sensors, dict):
|
|
266
|
+
raise TypeError("'bulk_species_link_sensors' must be an instance of 'dict' but not " +
|
|
267
|
+
f"of '{type(bulk_species_link_sensors)}'")
|
|
268
|
+
if any(bulk_species_id not in bulk_species
|
|
269
|
+
for bulk_species_id in bulk_species_link_sensors.keys()):
|
|
270
|
+
raise ValueError("Unknown bulk species ID in 'bulk_species_link_sensors'")
|
|
271
|
+
if any(link_id not in links for link_id in bulk_species_link_sensors.values()):
|
|
272
|
+
raise ValueError("Unknown link/pipe ID in 'bulk_species_link_sensors'")
|
|
273
|
+
|
|
274
|
+
if not isinstance(surface_species_sensors, dict):
|
|
275
|
+
raise TypeError("'surface_species_sensors' must be an instance of 'dict' but not " +
|
|
276
|
+
f"of '{type(surface_species_sensors)}'")
|
|
277
|
+
if any(surface_species_id not in surface_species_sensors
|
|
278
|
+
for surface_species_id in surface_species_sensors.keys()):
|
|
279
|
+
raise ValueError("Unknown surface species ID in 'surface_species_sensors'")
|
|
280
|
+
if any(link_id not in links for link_id in surface_species_sensors.values()):
|
|
281
|
+
raise ValueError("Unknown link ID in 'surface_species_sensors'")
|
|
282
|
+
|
|
283
|
+
if node_id_to_idx is not None:
|
|
284
|
+
if not isinstance(node_id_to_idx, dict):
|
|
285
|
+
raise TypeError("'node_id_to_idx' must be an instance of 'dict' " +
|
|
286
|
+
f"but not of '{type(node_id_to_idx)}'")
|
|
287
|
+
if any(n not in nodes for n in node_id_to_idx.keys()):
|
|
288
|
+
raise ValueError("Unknown node ID in 'node_id_to_idx'")
|
|
289
|
+
|
|
290
|
+
if link_id_to_idx is not None:
|
|
291
|
+
if not isinstance(link_id_to_idx, dict):
|
|
292
|
+
raise TypeError("'link_id_to_idx' must be an instance of 'dict' " +
|
|
293
|
+
f"but not of '{type(link_id_to_idx)}'")
|
|
294
|
+
if any(link_id not in links for link_id in link_id_to_idx.keys()):
|
|
295
|
+
raise ValueError("Unknown link/pipe ID in 'link_id_to_idx'")
|
|
296
|
+
|
|
297
|
+
if valve_id_to_idx is not None:
|
|
298
|
+
if not isinstance(valve_id_to_idx, dict):
|
|
299
|
+
raise TypeError("'valve_id_to_idx' must be an instance of 'dict' " +
|
|
300
|
+
f"but not of '{type(valve_id_to_idx)}'")
|
|
301
|
+
if any(v not in valves for v in valve_id_to_idx.keys()):
|
|
302
|
+
raise ValueError("Unknown valve ID in 'valve_id_to_idx'")
|
|
303
|
+
|
|
304
|
+
if pump_id_to_idx is not None:
|
|
305
|
+
if not isinstance(pump_id_to_idx, dict):
|
|
306
|
+
raise TypeError("'pump_id_to_idx' must be an instance of 'dict' " +
|
|
307
|
+
f"but not of '{type(pump_id_to_idx)}'")
|
|
308
|
+
if any(p not in valves for p in pump_id_to_idx.keys()):
|
|
309
|
+
raise ValueError("Unknown pump ID in 'pump_id_to_idx'")
|
|
310
|
+
|
|
311
|
+
if tank_id_to_idx is not None:
|
|
312
|
+
if not isinstance(tank_id_to_idx, dict):
|
|
313
|
+
raise TypeError("'tank_id_to_idx' must be an instance of 'dict' " +
|
|
314
|
+
f"but not of '{type(tank_id_to_idx)}'")
|
|
315
|
+
if any(t not in tanks for t in tank_id_to_idx.keys()):
|
|
316
|
+
raise ValueError("Unknown tank ID in 'tank_id_to_idx'")
|
|
317
|
+
|
|
318
|
+
if bulkspecies_id_to_idx is not None:
|
|
319
|
+
if not isinstance(bulkspecies_id_to_idx, dict):
|
|
320
|
+
raise TypeError("'bulkspecies_id_to_idx' must be an instance of 'dict' " +
|
|
321
|
+
f"but not of '{type(bulkspecies_id_to_idx)}'")
|
|
322
|
+
if any(s not in bulk_species for s in bulkspecies_id_to_idx.keys()):
|
|
323
|
+
raise ValueError("Unknown bulk species ID in 'bulkspecies_id_to_idx'")
|
|
324
|
+
|
|
325
|
+
if surfacespecies_id_to_idx is not None:
|
|
326
|
+
if not isinstance(surfacespecies_id_to_idx, dict):
|
|
327
|
+
raise TypeError("'surfacespecies_id_to_idx' must be an instance of 'dict' " +
|
|
328
|
+
f"but not of '{type(surfacespecies_id_to_idx)}'")
|
|
329
|
+
if any(s not in surface_species for s in surfacespecies_id_to_idx.keys()):
|
|
330
|
+
raise ValueError("Unknown surface species ID in 'surfacespecies_id_to_idx'")
|
|
331
|
+
|
|
332
|
+
self.__nodes = nodes
|
|
333
|
+
self.__links = links
|
|
334
|
+
self.__valves = valves
|
|
335
|
+
self.__pumps = pumps
|
|
336
|
+
self.__tanks = tanks
|
|
337
|
+
self.__bulk_species = bulk_species
|
|
338
|
+
self.__surface_species = surface_species
|
|
339
|
+
self.__pressure_sensors = pressure_sensors
|
|
340
|
+
self.__flow_sensors = flow_sensors
|
|
341
|
+
self.__demand_sensors = demand_sensors
|
|
342
|
+
self.__quality_node_sensors = quality_node_sensors
|
|
343
|
+
self.__quality_link_sensors = quality_link_sensors
|
|
344
|
+
self.__valve_state_sensors = valve_state_sensors
|
|
345
|
+
self.__pump_state_sensors = pump_state_sensors
|
|
346
|
+
self.__tank_volume_sensors = tank_volume_sensors
|
|
347
|
+
self.__bulk_species_node_sensors = bulk_species_node_sensors
|
|
348
|
+
self.__bulk_species_link_sensors = bulk_species_link_sensors
|
|
349
|
+
self.__surface_species_sensors = surface_species_sensors
|
|
350
|
+
self.__node_id_to_idx = node_id_to_idx
|
|
351
|
+
self.__link_id_to_idx = link_id_to_idx
|
|
352
|
+
self.__valve_id_to_idx = valve_id_to_idx
|
|
353
|
+
self.__pump_id_to_idx = pump_id_to_idx
|
|
354
|
+
self.__tank_id_to_idx = tank_id_to_idx
|
|
355
|
+
self.__bulkspecies_id_to_idx = bulkspecies_id_to_idx
|
|
356
|
+
self.__surfacespecies_id_to_idx = surfacespecies_id_to_idx
|
|
357
|
+
|
|
358
|
+
self.__compute_indices() # Compute indices
|
|
359
|
+
|
|
360
|
+
super().__init__(**kwds)
|
|
361
|
+
|
|
362
|
+
def node_id_to_idx(self, node_id: str) -> int:
|
|
363
|
+
"""
|
|
364
|
+
Gets the index of a given node ID.
|
|
365
|
+
|
|
366
|
+
Parameters
|
|
367
|
+
----------
|
|
368
|
+
node_id : `str`
|
|
369
|
+
Node ID.
|
|
370
|
+
|
|
371
|
+
Returns
|
|
372
|
+
-------
|
|
373
|
+
`int`
|
|
374
|
+
Index of the given node.
|
|
375
|
+
"""
|
|
376
|
+
if self.__node_id_to_idx is not None:
|
|
377
|
+
return self.__node_id_to_idx[node_id]
|
|
378
|
+
else:
|
|
379
|
+
return self.__nodes.index(node_id)
|
|
380
|
+
|
|
381
|
+
def link_id_to_idx(self, link_id: str) -> int:
|
|
382
|
+
"""
|
|
383
|
+
Gets the index of a given link ID.
|
|
384
|
+
|
|
385
|
+
Parameters
|
|
386
|
+
----------
|
|
387
|
+
link_id : `str`
|
|
388
|
+
Link ID.
|
|
389
|
+
|
|
390
|
+
Returns
|
|
391
|
+
-------
|
|
392
|
+
`int`
|
|
393
|
+
Index of the given link.
|
|
394
|
+
"""
|
|
395
|
+
if self.__node_id_to_idx is not None:
|
|
396
|
+
return self.__link_id_to_idx[link_id]
|
|
397
|
+
else:
|
|
398
|
+
return self.__links.index(link_id)
|
|
399
|
+
|
|
400
|
+
def valve_id_to_idx(self, valve_id: str) -> int:
|
|
401
|
+
"""
|
|
402
|
+
Gets the index of a given valve ID.
|
|
403
|
+
|
|
404
|
+
Parameters
|
|
405
|
+
----------
|
|
406
|
+
valve_id : `str`
|
|
407
|
+
Valve ID.
|
|
408
|
+
|
|
409
|
+
Returns
|
|
410
|
+
-------
|
|
411
|
+
`int`
|
|
412
|
+
Index of the given valve.
|
|
413
|
+
"""
|
|
414
|
+
if self.__valve_id_to_idx is not None:
|
|
415
|
+
return self.__valve_id_to_idx[valve_id]
|
|
416
|
+
else:
|
|
417
|
+
return self.__valves.index(valve_id)
|
|
418
|
+
|
|
419
|
+
def pump_id_to_idx(self, pump_id: str) -> int:
|
|
420
|
+
"""
|
|
421
|
+
Gets the index of a given pump ID.
|
|
422
|
+
|
|
423
|
+
Parameters
|
|
424
|
+
----------
|
|
425
|
+
pump_id : `str`
|
|
426
|
+
Pump ID.
|
|
427
|
+
|
|
428
|
+
Returns
|
|
429
|
+
-------
|
|
430
|
+
`int`
|
|
431
|
+
Index of the given pump.
|
|
432
|
+
"""
|
|
433
|
+
if self.__pump_id_to_idx is not None:
|
|
434
|
+
return self.__pump_id_to_idx[pump_id]
|
|
435
|
+
else:
|
|
436
|
+
return self.__pumps.index(pump_id)
|
|
437
|
+
|
|
438
|
+
def tank_id_to_idx(self, tank_id: str) -> int:
|
|
439
|
+
"""
|
|
440
|
+
Gets the index of a given tank ID.
|
|
441
|
+
|
|
442
|
+
Parameters
|
|
443
|
+
----------
|
|
444
|
+
tank_id : `str`
|
|
445
|
+
Tank ID.
|
|
446
|
+
|
|
447
|
+
Returns
|
|
448
|
+
-------
|
|
449
|
+
`int`
|
|
450
|
+
Index of the given tank.
|
|
451
|
+
"""
|
|
452
|
+
if self.__tank_id_to_idx is not None:
|
|
453
|
+
return self.__tank_id_to_idx[tank_id]
|
|
454
|
+
else:
|
|
455
|
+
return self.__tanks.index(tank_id)
|
|
456
|
+
|
|
457
|
+
def bulkspecies_id_to_idx(self, bulk_species_id: str) -> int:
|
|
458
|
+
"""
|
|
459
|
+
Gets the index of a given bulk species ID.
|
|
460
|
+
|
|
461
|
+
Parameters
|
|
462
|
+
----------
|
|
463
|
+
bulk_species_id : `str`
|
|
464
|
+
Bulk species ID.
|
|
465
|
+
|
|
466
|
+
Returns
|
|
467
|
+
-------
|
|
468
|
+
`int`
|
|
469
|
+
Index of the given bulk species.
|
|
470
|
+
"""
|
|
471
|
+
if self.__bulkspecies_id_to_idx is not None:
|
|
472
|
+
return self.__bulkspecies_id_to_idx[bulk_species_id]
|
|
473
|
+
else:
|
|
474
|
+
return self.__bulk_species.index(bulk_species_id)
|
|
475
|
+
|
|
476
|
+
def surfacespecies_id_to_idx(self, surface_species_id: str) -> int:
|
|
477
|
+
"""
|
|
478
|
+
Gets the index of a given surface species ID.
|
|
479
|
+
|
|
480
|
+
Parameters
|
|
481
|
+
----------
|
|
482
|
+
surface_species_id : `str`
|
|
483
|
+
Surface species ID.
|
|
484
|
+
|
|
485
|
+
Returns
|
|
486
|
+
-------
|
|
487
|
+
`int`
|
|
488
|
+
Index of the given surface species.
|
|
489
|
+
"""
|
|
490
|
+
if self.__surfacespecies_id_to_idx is not None:
|
|
491
|
+
return self.__surfacespecies_id_to_idx[surface_species_id]
|
|
492
|
+
else:
|
|
493
|
+
return self.__surface_species.index(surface_species_id)
|
|
494
|
+
|
|
495
|
+
def __compute_indices(self):
|
|
496
|
+
self.__pressure_idx = np.array([self.node_id_to_idx(n)
|
|
497
|
+
for n in self.__pressure_sensors], dtype=np.int32)
|
|
498
|
+
self.__flow_idx = np.array([self.link_id_to_idx(link)
|
|
499
|
+
for link in self.__flow_sensors], dtype=np.int32)
|
|
500
|
+
self.__demand_idx = np.array([self.node_id_to_idx(n)
|
|
501
|
+
for n in self.__demand_sensors], dtype=np.int32)
|
|
502
|
+
self.__quality_node_idx = np.array([self.node_id_to_idx(n)
|
|
503
|
+
for n in self.__quality_node_sensors], dtype=np.int32)
|
|
504
|
+
self.__quality_link_idx = np.array([self.link_id_to_idx(link)
|
|
505
|
+
for link in self.__quality_link_sensors],
|
|
506
|
+
dtype=np.int32)
|
|
507
|
+
self.__valve_state_idx = np.array([self.valve_id_to_idx(v)
|
|
508
|
+
for v in self.__valve_state_sensors], dtype=np.int32)
|
|
509
|
+
self.__pump_state_idx = np.array([self.pump_id_to_idx(p)
|
|
510
|
+
for p in self.__pump_state_sensors], dtype=np.int32)
|
|
511
|
+
self.__tank_volume_idx = np.array([self.tank_id_to_idx(t)
|
|
512
|
+
for t in self.__tank_volume_sensors], dtype=np.int32)
|
|
513
|
+
self.__bulk_species_node_idx = np.array([(self.bulkspecies_id_to_idx(s),
|
|
514
|
+
[self.node_id_to_idx(node_id)
|
|
515
|
+
for node_id in self.__bulk_species_node_sensors[s]])
|
|
516
|
+
for s in self.__bulk_species_node_sensors.keys()],
|
|
517
|
+
dtype=object)
|
|
518
|
+
self.__bulk_species_link_idx = np.array([(self.bulkspecies_id_to_idx(s),
|
|
519
|
+
[self.link_id_to_idx(link_id)
|
|
520
|
+
for link_id in self.__bulk_species_link_sensors[s]])
|
|
521
|
+
for s in self.__bulk_species_link_sensors.keys()],
|
|
522
|
+
dtype=object)
|
|
523
|
+
self.__surface_species_idx = np.array([(self.surfacespecies_id_to_idx(s),
|
|
524
|
+
[self.link_id_to_idx(link_id)
|
|
525
|
+
for link_id in self.__surface_species_sensors[s]])
|
|
526
|
+
for s in self.__surface_species_sensors.keys()],
|
|
527
|
+
dtype=object)
|
|
528
|
+
|
|
529
|
+
n_pressure_sensors = len(self.__pressure_sensors)
|
|
530
|
+
n_flow_sensors = len(self.__flow_sensors)
|
|
531
|
+
n_demand_sensors = len(self.__demand_sensors)
|
|
532
|
+
n_node_quality_sensors = len(self.__quality_node_sensors)
|
|
533
|
+
n_link_quality_sensors = len(self.__quality_link_sensors)
|
|
534
|
+
n_valve_state_sensors = len(self.__valve_state_sensors)
|
|
535
|
+
n_pump_state_sensors = len(self.__pump_state_sensors)
|
|
536
|
+
n_tank_volume_sensors = len(self.__tank_volume_sensors)
|
|
537
|
+
n_bulk_species_node_sensors = len(self.__bulk_species_node_sensors.values())
|
|
538
|
+
n_bulk_species_link_sensors = len(self.__bulk_species_link_sensors.values())
|
|
539
|
+
|
|
540
|
+
pressure_idx_shift = 0
|
|
541
|
+
flow_idx_shift = pressure_idx_shift + n_pressure_sensors
|
|
542
|
+
demand_idx_shift = flow_idx_shift + n_flow_sensors
|
|
543
|
+
node_quality_idx_shift = demand_idx_shift + n_demand_sensors
|
|
544
|
+
link_quality_idx_shift = node_quality_idx_shift + n_node_quality_sensors
|
|
545
|
+
valve_state_idx_shift = link_quality_idx_shift + n_link_quality_sensors
|
|
546
|
+
pump_state_idx_shift = valve_state_idx_shift + n_valve_state_sensors
|
|
547
|
+
tank_volume_idx_shift = pump_state_idx_shift + n_pump_state_sensors
|
|
548
|
+
bulk_species_node_idx_shift = tank_volume_idx_shift + n_tank_volume_sensors
|
|
549
|
+
bulk_species_link_idx_shift = bulk_species_node_idx_shift + n_bulk_species_node_sensors
|
|
550
|
+
surface_species_idx_shift = bulk_species_link_idx_shift + n_bulk_species_link_sensors
|
|
551
|
+
|
|
552
|
+
def __build_sensors_id_to_idx(sensors: list[str], initial_idx_shift: int) -> dict:
|
|
553
|
+
return {sensor_id: i + initial_idx_shift
|
|
554
|
+
for sensor_id, i in zip(sensors, range(len(sensors)))}
|
|
555
|
+
|
|
556
|
+
def __build_species_sensors_id_to_idx(species_sensors: dict, initial_idx_shift) -> dict:
|
|
557
|
+
r = {}
|
|
558
|
+
|
|
559
|
+
cur_idx_shift = initial_idx_shift
|
|
560
|
+
for species_id in species_sensors:
|
|
561
|
+
r[species_id] = {}
|
|
562
|
+
for sensor_id in species_sensors[species_id]:
|
|
563
|
+
r[species_id][sensor_id] = cur_idx_shift
|
|
564
|
+
cur_idx_shift += 1
|
|
565
|
+
|
|
566
|
+
return r
|
|
567
|
+
|
|
568
|
+
mapping = {"pressure": __build_sensors_id_to_idx(self.__pressure_sensors,
|
|
569
|
+
pressure_idx_shift),
|
|
570
|
+
"flow": __build_sensors_id_to_idx(self.__flow_sensors,flow_idx_shift),
|
|
571
|
+
"demand": __build_sensors_id_to_idx(self.__demand_sensors,demand_idx_shift),
|
|
572
|
+
"quality_node": __build_sensors_id_to_idx(self.__quality_node_sensors,
|
|
573
|
+
node_quality_idx_shift),
|
|
574
|
+
"quality_link": __build_sensors_id_to_idx(self.__quality_link_sensors,
|
|
575
|
+
link_quality_idx_shift),
|
|
576
|
+
"valve_state": __build_sensors_id_to_idx(self.__valve_state_sensors,
|
|
577
|
+
valve_state_idx_shift),
|
|
578
|
+
"pump_state": __build_sensors_id_to_idx(self.__pump_state_sensors,
|
|
579
|
+
pump_state_idx_shift),
|
|
580
|
+
"tank_volume": __build_sensors_id_to_idx(self.__tank_volume_sensors,
|
|
581
|
+
tank_volume_idx_shift),
|
|
582
|
+
"bulk_species_node":
|
|
583
|
+
__build_species_sensors_id_to_idx(self.__bulk_species_node_sensors,
|
|
584
|
+
bulk_species_node_idx_shift),
|
|
585
|
+
"bulk_species_link":
|
|
586
|
+
__build_species_sensors_id_to_idx(self.__bulk_species_link_sensors,
|
|
587
|
+
bulk_species_link_idx_shift),
|
|
588
|
+
"surface_species":
|
|
589
|
+
__build_species_sensors_id_to_idx(self.__surface_species_sensors,
|
|
590
|
+
surface_species_idx_shift)}
|
|
591
|
+
self.__sensors_id_to_idx = mapping
|
|
592
|
+
|
|
593
|
+
def validate(self, epanet_api: epyt.epanet) -> None:
|
|
594
|
+
"""
|
|
595
|
+
Validates this sensor configuration --
|
|
596
|
+
i.e. checks whether all nodes, etc. exist in the .inp file.
|
|
597
|
+
|
|
598
|
+
Parameters
|
|
599
|
+
----------
|
|
600
|
+
epanet_api : `epyt.epanet`
|
|
601
|
+
EPANET and EPANET-MSX API.
|
|
602
|
+
"""
|
|
603
|
+
if not isinstance(epanet_api, epyt.epanet):
|
|
604
|
+
raise TypeError("'epanet_api' must be an instance of 'epyt.epanet' " +
|
|
605
|
+
f"but not of '{type(epanet_api)}'")
|
|
606
|
+
|
|
607
|
+
nodes = epanet_api.getNodeNameID()
|
|
608
|
+
links = epanet_api.getLinkNameID()
|
|
609
|
+
valves = epanet_api.getLinkValveNameID()
|
|
610
|
+
pumps = epanet_api.getLinkPumpNameID()
|
|
611
|
+
tanks = epanet_api.getNodeTankNameID()
|
|
612
|
+
|
|
613
|
+
bulk_species = []
|
|
614
|
+
surface_species = []
|
|
615
|
+
if hasattr(epanet_api, "msx"):
|
|
616
|
+
for species_id, species_type in zip(epanet_api.getMSXSpeciesNameID(),
|
|
617
|
+
epanet_api.getMSXSpeciesType()):
|
|
618
|
+
if species_type == "BULK":
|
|
619
|
+
bulk_species.append(species_id)
|
|
620
|
+
elif species_type == "WALL":
|
|
621
|
+
surface_species.append(species_id)
|
|
622
|
+
|
|
623
|
+
if any(node_id not in nodes for node_id in self.__nodes):
|
|
624
|
+
raise ValueError("Invalid node ID detected -- " +
|
|
625
|
+
"all given node IDs must exist in the .inp file")
|
|
626
|
+
if any(link_id not in links for link_id in self.__links):
|
|
627
|
+
raise ValueError("Invalid link/pipe ID detected -- all given link/pipe IDs " +
|
|
628
|
+
"must exist in the .inp file")
|
|
629
|
+
if any(valve_id not in valves for valve_id in self.__valves):
|
|
630
|
+
raise ValueError("Invalid valve ID detected -- all given valve IDs must exist " +
|
|
631
|
+
"in the .inp file")
|
|
632
|
+
if any(pump_id not in pumps for pump_id in self.__pumps):
|
|
633
|
+
raise ValueError("Invalid pump ID detected -- all given pump IDs must exist " +
|
|
634
|
+
"in the .inp file")
|
|
635
|
+
if any(tank_id not in tanks for tank_id in self.__tanks):
|
|
636
|
+
raise ValueError("Invalid tank ID detected -- all given tank IDs must exist " +
|
|
637
|
+
"in the .inp file")
|
|
638
|
+
if any(surface_species_id not in surface_species
|
|
639
|
+
for surface_species_id in self.__surface_species):
|
|
640
|
+
raise ValueError("Invalid surface species ID detected")
|
|
641
|
+
if any(bulk_species_id not in bulk_species for bulk_species_id in self.__bulk_species):
|
|
642
|
+
raise ValueError("Invalid bulk species ID detected")
|
|
643
|
+
|
|
644
|
+
@property
|
|
645
|
+
def nodes(self) -> list[str]:
|
|
646
|
+
"""
|
|
647
|
+
Gets all node IDs.
|
|
648
|
+
|
|
649
|
+
Returns
|
|
650
|
+
-------
|
|
651
|
+
`list[str]`
|
|
652
|
+
All node IDs.
|
|
653
|
+
"""
|
|
654
|
+
return self.__nodes.copy()
|
|
655
|
+
|
|
656
|
+
@property
|
|
657
|
+
def links(self) -> list[str]:
|
|
658
|
+
"""
|
|
659
|
+
Gets all link IDs.
|
|
660
|
+
|
|
661
|
+
Returns
|
|
662
|
+
-------
|
|
663
|
+
`list[str]`
|
|
664
|
+
All link IDs.
|
|
665
|
+
"""
|
|
666
|
+
return self.__links.copy()
|
|
667
|
+
|
|
668
|
+
@property
|
|
669
|
+
def valves(self) -> list[str]:
|
|
670
|
+
"""
|
|
671
|
+
Gets all valve IDs (subset of link IDs).
|
|
672
|
+
|
|
673
|
+
Returns
|
|
674
|
+
-------
|
|
675
|
+
`list[str]`
|
|
676
|
+
All valve IDs.
|
|
677
|
+
"""
|
|
678
|
+
return self.__valves.copy()
|
|
679
|
+
|
|
680
|
+
@property
|
|
681
|
+
def pumps(self) -> list[str]:
|
|
682
|
+
"""
|
|
683
|
+
Gets all pump IDs (subset of link IDs).
|
|
684
|
+
|
|
685
|
+
Returns
|
|
686
|
+
-------
|
|
687
|
+
`list[str]`
|
|
688
|
+
All pump IDs.
|
|
689
|
+
"""
|
|
690
|
+
return self.__pumps.copy()
|
|
691
|
+
|
|
692
|
+
@property
|
|
693
|
+
def tanks(self) -> list[str]:
|
|
694
|
+
"""
|
|
695
|
+
Gets all tank IDs (subset of node IDs).
|
|
696
|
+
|
|
697
|
+
Returns
|
|
698
|
+
-------
|
|
699
|
+
`list[str]`
|
|
700
|
+
All tank IDs.
|
|
701
|
+
"""
|
|
702
|
+
return self.__tanks.copy()
|
|
703
|
+
|
|
704
|
+
@property
|
|
705
|
+
def bulk_species(self) -> list[str]:
|
|
706
|
+
"""
|
|
707
|
+
Gets all bulk species IDs -- i.e. species that live in the water.
|
|
708
|
+
|
|
709
|
+
Returns
|
|
710
|
+
-------
|
|
711
|
+
`list[str]`
|
|
712
|
+
All species IDs.
|
|
713
|
+
"""
|
|
714
|
+
return self.__bulk_species.copy()
|
|
715
|
+
|
|
716
|
+
@property
|
|
717
|
+
def surface_species(self) -> list[str]:
|
|
718
|
+
"""
|
|
719
|
+
Gets all surface species IDs -- i.e. species that live links/pipes.
|
|
720
|
+
|
|
721
|
+
Returns
|
|
722
|
+
-------
|
|
723
|
+
`list[str]`
|
|
724
|
+
All species IDs.
|
|
725
|
+
"""
|
|
726
|
+
return self.__surface_species.copy()
|
|
727
|
+
|
|
728
|
+
@property
|
|
729
|
+
def pressure_sensors(self) -> list[str]:
|
|
730
|
+
"""
|
|
731
|
+
Gets all pressure sensors (i.e. IDs of nodes at which a pressure sensor is placed).
|
|
732
|
+
|
|
733
|
+
Returns
|
|
734
|
+
-------
|
|
735
|
+
`list[str]`
|
|
736
|
+
All node IDs with a pressure sensor.
|
|
737
|
+
"""
|
|
738
|
+
return self.__pressure_sensors.copy()
|
|
739
|
+
|
|
740
|
+
@pressure_sensors.setter
|
|
741
|
+
def pressure_sensors(self, pressure_sensors: list[str]) -> None:
|
|
742
|
+
if not isinstance(pressure_sensors, list):
|
|
743
|
+
raise TypeError("'pressure_sensors' must be an instance of 'list[str]' " +
|
|
744
|
+
f"but not of '{type(pressure_sensors)}'")
|
|
745
|
+
if any(n not in self.__nodes for n in pressure_sensors):
|
|
746
|
+
raise ValueError("Each item in 'pressure_sensors' must be in 'nodes' -- cannot " +
|
|
747
|
+
"place a sensor at a non-existing node.")
|
|
748
|
+
|
|
749
|
+
self.__pressure_sensors = pressure_sensors
|
|
750
|
+
|
|
751
|
+
self.__compute_indices()
|
|
752
|
+
|
|
753
|
+
@property
|
|
754
|
+
def flow_sensors(self) -> list[str]:
|
|
755
|
+
"""
|
|
756
|
+
Gets all flow sensors (i.e. IDs of links at which a flow sensor is placed).
|
|
757
|
+
|
|
758
|
+
Returns
|
|
759
|
+
-------
|
|
760
|
+
`list[str]`
|
|
761
|
+
All link IDs with a flow sensor.
|
|
762
|
+
"""
|
|
763
|
+
return self.__flow_sensors.copy()
|
|
764
|
+
|
|
765
|
+
@flow_sensors.setter
|
|
766
|
+
def flow_sensors(self, flow_sensors: list[str]) -> None:
|
|
767
|
+
if not isinstance(flow_sensors, list):
|
|
768
|
+
raise TypeError("'pressure_sensors' must be an instance of 'list[str]' " +
|
|
769
|
+
f"but not of '{type(flow_sensors)}'")
|
|
770
|
+
if any(link not in self.__links for link in flow_sensors):
|
|
771
|
+
raise ValueError("Each item in 'flow_sensors' must be in 'links' -- cannot " +
|
|
772
|
+
"place a sensor at a non-existing link/pipe.")
|
|
773
|
+
|
|
774
|
+
self.__flow_sensors = flow_sensors
|
|
775
|
+
|
|
776
|
+
self.__compute_indices()
|
|
777
|
+
|
|
778
|
+
@property
|
|
779
|
+
def demand_sensors(self) -> list[str]:
|
|
780
|
+
"""
|
|
781
|
+
Gets all demand sensors (i.e. IDs of nodes at which a demand sensor is placed).
|
|
782
|
+
|
|
783
|
+
Returns
|
|
784
|
+
-------
|
|
785
|
+
`list[str]`
|
|
786
|
+
All node IDs with a demand sensor.
|
|
787
|
+
"""
|
|
788
|
+
return self.__demand_sensors.copy()
|
|
789
|
+
|
|
790
|
+
@demand_sensors.setter
|
|
791
|
+
def demand_sensors(self, demand_sensors: list[str]) -> None:
|
|
792
|
+
if not isinstance(demand_sensors, list):
|
|
793
|
+
raise TypeError("'demand_sensors' must be an instance of 'list[str]' " +
|
|
794
|
+
f"but not of '{type(demand_sensors)}'")
|
|
795
|
+
if any(n not in self.__nodes for n in demand_sensors):
|
|
796
|
+
raise ValueError("Each item in 'demand_sensors' must be in 'nodes' -- cannot " +
|
|
797
|
+
"place a sensor at a non-existing node.")
|
|
798
|
+
|
|
799
|
+
self.__demand_sensors = demand_sensors
|
|
800
|
+
|
|
801
|
+
self.__compute_indices()
|
|
802
|
+
|
|
803
|
+
@property
|
|
804
|
+
def quality_node_sensors(self) -> list[str]:
|
|
805
|
+
"""
|
|
806
|
+
Gets all node quality sensors (i.e. IDs of nodes at which a node quality sensor is placed).
|
|
807
|
+
|
|
808
|
+
Returns
|
|
809
|
+
-------
|
|
810
|
+
`list[str]`
|
|
811
|
+
All node IDs with a node quality sensor.
|
|
812
|
+
"""
|
|
813
|
+
return self.__quality_node_sensors.copy()
|
|
814
|
+
|
|
815
|
+
@quality_node_sensors.setter
|
|
816
|
+
def quality_node_sensors(self, quality_node_sensors: list[str]) -> None:
|
|
817
|
+
if not isinstance(quality_node_sensors, list):
|
|
818
|
+
raise TypeError("'quality_node_sensors' must be an instance of 'list[str]' " +
|
|
819
|
+
f"but not of '{type(quality_node_sensors)}'")
|
|
820
|
+
if any(n not in self.__nodes for n in quality_node_sensors):
|
|
821
|
+
raise ValueError("Each item in 'quality_node_sensors' must be in 'nodes' -- cannot " +
|
|
822
|
+
"place a sensor at a non-existing node.")
|
|
823
|
+
|
|
824
|
+
self.__quality_node_sensors = quality_node_sensors
|
|
825
|
+
|
|
826
|
+
self.__compute_indices()
|
|
827
|
+
|
|
828
|
+
@property
|
|
829
|
+
def quality_link_sensors(self) -> list[str]:
|
|
830
|
+
"""
|
|
831
|
+
Gets all link quality sensors (i.e. IDs of links at which a link quality sensor is placed).
|
|
832
|
+
|
|
833
|
+
Returns
|
|
834
|
+
-------
|
|
835
|
+
`list[str]`
|
|
836
|
+
All link IDs with a link quality sensor.
|
|
837
|
+
"""
|
|
838
|
+
return self.__quality_link_sensors.copy()
|
|
839
|
+
|
|
840
|
+
@quality_link_sensors.setter
|
|
841
|
+
def quality_link_sensors(self, quality_link_sensors: list[str]) -> None:
|
|
842
|
+
if not isinstance(quality_link_sensors, list):
|
|
843
|
+
raise TypeError("'quality_link_sensors' must be an instance of 'list[str]' " +
|
|
844
|
+
f"but not of '{type(quality_link_sensors)}'")
|
|
845
|
+
if any(link not in self.__links for link in quality_link_sensors):
|
|
846
|
+
raise ValueError("Each item in 'quality_link_sensors' must be in 'links' -- cannot " +
|
|
847
|
+
"place a sensor at a non-existing link/pipe.")
|
|
848
|
+
|
|
849
|
+
self.__quality_link_sensors = quality_link_sensors
|
|
850
|
+
|
|
851
|
+
self.__compute_indices()
|
|
852
|
+
|
|
853
|
+
@property
|
|
854
|
+
def valve_state_sensors(self) -> list[str]:
|
|
855
|
+
"""
|
|
856
|
+
Gets all valve state sensors (i.e. IDs of valves at which a valve state sensor is placed).
|
|
857
|
+
|
|
858
|
+
Returns
|
|
859
|
+
-------
|
|
860
|
+
`list[str]`
|
|
861
|
+
All valve IDs with a valve state sensor.
|
|
862
|
+
"""
|
|
863
|
+
return self.__valve_state_sensors.copy()
|
|
864
|
+
|
|
865
|
+
@valve_state_sensors.setter
|
|
866
|
+
def valve_state_sensors(self, valve_state_sensors: list[str]) -> None:
|
|
867
|
+
if not isinstance(valve_state_sensors, list):
|
|
868
|
+
raise TypeError("'valve_state_sensors' must be an instance of 'list[str]' " +
|
|
869
|
+
f"but not of '{type(valve_state_sensors)}'")
|
|
870
|
+
if any(link not in self.__valves for link in valve_state_sensors):
|
|
871
|
+
raise ValueError("Each item in 'valve_state_sensors' must be in 'valves' -- cannot " +
|
|
872
|
+
"place a sensor at a non-existing valves.")
|
|
873
|
+
|
|
874
|
+
self.__valve_state_sensors = valve_state_sensors
|
|
875
|
+
|
|
876
|
+
self.__compute_indices()
|
|
877
|
+
|
|
878
|
+
@property
|
|
879
|
+
def pump_state_sensors(self) -> list[str]:
|
|
880
|
+
"""
|
|
881
|
+
Gets all pump state sensors (i.e. IDs of pumps at which a pump state sensor is placed).
|
|
882
|
+
|
|
883
|
+
Returns
|
|
884
|
+
-------
|
|
885
|
+
`list[str]`
|
|
886
|
+
All link IDs with a pump state sensor.
|
|
887
|
+
"""
|
|
888
|
+
return self.__pump_state_sensors.copy()
|
|
889
|
+
|
|
890
|
+
@pump_state_sensors.setter
|
|
891
|
+
def pump_state_sensors(self, pump_state_sensors: list[str]) -> None:
|
|
892
|
+
if not isinstance(pump_state_sensors, list):
|
|
893
|
+
raise TypeError("'pump_state_sensors' must be an instance of 'list[str]' " +
|
|
894
|
+
f"but not of '{type(pump_state_sensors)}'")
|
|
895
|
+
if any(link not in self.__pumps for link in pump_state_sensors):
|
|
896
|
+
raise ValueError("Each item in 'pump_state_sensors' must be in 'pumps' -- cannot " +
|
|
897
|
+
"place a sensor at a non-existing pump.")
|
|
898
|
+
|
|
899
|
+
self.__pump_state_sensors = pump_state_sensors
|
|
900
|
+
|
|
901
|
+
self.__compute_indices()
|
|
902
|
+
|
|
903
|
+
@property
|
|
904
|
+
def tank_volume_sensors(self) -> list[str]:
|
|
905
|
+
"""
|
|
906
|
+
Gets all tank volume sensors (i.e. IDs of tanks at which a tank volume sensor is placed).
|
|
907
|
+
|
|
908
|
+
Returns
|
|
909
|
+
-------
|
|
910
|
+
`list[str]`
|
|
911
|
+
All tank IDs with a tank volume sensor.
|
|
912
|
+
"""
|
|
913
|
+
return self.__tank_volume_sensors.copy()
|
|
914
|
+
|
|
915
|
+
@tank_volume_sensors.setter
|
|
916
|
+
def tank_volume_sensors(self, tank_volume_sensors: list[str]) -> None:
|
|
917
|
+
if not isinstance(tank_volume_sensors, list):
|
|
918
|
+
raise TypeError("'tank_volume_sensors' must be an instance of 'list[str]' " +
|
|
919
|
+
f"but not of '{type(tank_volume_sensors)}'")
|
|
920
|
+
if any(n not in self.__tanks for n in tank_volume_sensors):
|
|
921
|
+
raise ValueError("Each item in 'tank_volume_sensors' must be in 'tanks' -- cannot " +
|
|
922
|
+
"place a sensor at a non-existing tanks.")
|
|
923
|
+
|
|
924
|
+
self.__tank_volume_sensors = tank_volume_sensors
|
|
925
|
+
|
|
926
|
+
self.__compute_indices()
|
|
927
|
+
|
|
928
|
+
@property
|
|
929
|
+
def bulk_species_node_sensors(self) -> dict:
|
|
930
|
+
"""
|
|
931
|
+
Gets all bulk species node sensors as a dictionary --
|
|
932
|
+
i.e. bulk species IDs as keys and node IDs as values.
|
|
933
|
+
|
|
934
|
+
Returns
|
|
935
|
+
-------
|
|
936
|
+
`dict`
|
|
937
|
+
Bulk species sensors -- keys: bulk species IDs, values: node IDs.
|
|
938
|
+
"""
|
|
939
|
+
return deepcopy(self.__bulk_species_node_sensors)
|
|
940
|
+
|
|
941
|
+
@bulk_species_node_sensors.setter
|
|
942
|
+
def bulk_species_node_sensors(self, bulk_species_sensors: dict) -> None:
|
|
943
|
+
if not isinstance(bulk_species_sensors, dict):
|
|
944
|
+
raise TypeError("'bulk_species_sensors' must be an instance of 'dict' " +
|
|
945
|
+
f"but not of '{type(bulk_species_sensors)}'")
|
|
946
|
+
if any(species_id not in self.__bulk_species for species_id in bulk_species_sensors.keys()):
|
|
947
|
+
raise ValueError("Unknown bulk species ID in 'bulk_species_sensors'")
|
|
948
|
+
if any(node_id not in self.__nodes for node_id in sum(bulk_species_sensors.values(), [])):
|
|
949
|
+
raise ValueError("Unknown node ID in 'bulk_species_sensors'")
|
|
950
|
+
|
|
951
|
+
self.__bulk_species_node_sensors = bulk_species_sensors
|
|
952
|
+
|
|
953
|
+
self.__compute_indices()
|
|
954
|
+
|
|
955
|
+
@property
|
|
956
|
+
def bulk_species_link_sensors(self) -> dict:
|
|
957
|
+
"""
|
|
958
|
+
Gets all bulk species link/pipe sensors as a dictionary --
|
|
959
|
+
i.e. bulk species IDs as keys and link/pipe IDs as values.
|
|
960
|
+
|
|
961
|
+
Returns
|
|
962
|
+
-------
|
|
963
|
+
`dict`
|
|
964
|
+
Bulk species sensors -- keys: bulk species IDs, values: link/pipe IDs.
|
|
965
|
+
"""
|
|
966
|
+
return deepcopy(self.__bulk_species_link_sensors)
|
|
967
|
+
|
|
968
|
+
@bulk_species_link_sensors.setter
|
|
969
|
+
def bulk_species_link_sensors(self, bulk_species_sensors: dict) -> None:
|
|
970
|
+
if not isinstance(bulk_species_sensors, dict):
|
|
971
|
+
raise TypeError("'bulk_species_sensors' must be an instance of 'dict' " +
|
|
972
|
+
f"but not of '{type(bulk_species_sensors)}'")
|
|
973
|
+
if any(species_id not in self.__bulk_species for species_id in bulk_species_sensors.keys()):
|
|
974
|
+
raise ValueError("Unknown bulk species ID in 'bulk_species_sensors'")
|
|
975
|
+
if any(link_id not in self.__links for link_id in sum(bulk_species_sensors.values(), [])):
|
|
976
|
+
raise ValueError("Unknown link/pipe ID in 'bulk_species_sensors'")
|
|
977
|
+
|
|
978
|
+
self.__bulk_species_link_sensors = bulk_species_sensors
|
|
979
|
+
|
|
980
|
+
self.__compute_indices()
|
|
981
|
+
|
|
982
|
+
@property
|
|
983
|
+
def surface_species_sensors(self) -> dict:
|
|
984
|
+
"""
|
|
985
|
+
Gets all surface species sensors as a dictionary --
|
|
986
|
+
i.e. surface species IDs as keys and link/pipe IDs as values.
|
|
987
|
+
|
|
988
|
+
Returns
|
|
989
|
+
-------
|
|
990
|
+
`dict`
|
|
991
|
+
Surface species sensors -- keys: surface species IDs, values: link/pipe IDs.
|
|
992
|
+
"""
|
|
993
|
+
return deepcopy(self.__surface_species_sensors)
|
|
994
|
+
|
|
995
|
+
@surface_species_sensors.setter
|
|
996
|
+
def surface_species_sensors(self, surface_species_sensors: dict) -> None:
|
|
997
|
+
if not isinstance(surface_species_sensors, dict):
|
|
998
|
+
raise TypeError("'surface_species_sensors' must be an instance of 'dict' " +
|
|
999
|
+
f"but not of '{type(surface_species_sensors)}'")
|
|
1000
|
+
if any(species_id not in self.__surface_species
|
|
1001
|
+
for species_id in surface_species_sensors.keys()):
|
|
1002
|
+
raise ValueError("Unknown surface species ID in 'surface_species_sensors'")
|
|
1003
|
+
if any(link_id not in self.__links
|
|
1004
|
+
for link_id in sum(surface_species_sensors.values(), [])):
|
|
1005
|
+
raise ValueError("Unknown link/pipe ID in 'surface_species_sensors'")
|
|
1006
|
+
|
|
1007
|
+
self.__surface_species_sensors = surface_species_sensors
|
|
1008
|
+
|
|
1009
|
+
self.__compute_indices()
|
|
1010
|
+
|
|
1011
|
+
@property
|
|
1012
|
+
def sensors_id_to_idx(self) -> dict:
|
|
1013
|
+
"""
|
|
1014
|
+
Gets a mapping of sensor IDs to indices in the final Numpy array returned by `get_data()`.
|
|
1015
|
+
|
|
1016
|
+
Returns
|
|
1017
|
+
-------
|
|
1018
|
+
`dict`
|
|
1019
|
+
Mapping of sensor IDs to indices in the final Numpy array.
|
|
1020
|
+
"""
|
|
1021
|
+
return deepcopy(self.__sensors_id_to_idx)
|
|
1022
|
+
|
|
1023
|
+
def get_attributes(self) -> dict:
|
|
1024
|
+
attr = {"nodes": self.__nodes, "links": self.__links,
|
|
1025
|
+
"valves": self.__valves, "pumps": self.__pumps,
|
|
1026
|
+
"tanks": self.__tanks, "bulk_species": self.__bulk_species,
|
|
1027
|
+
"surface_species": self.__surface_species,
|
|
1028
|
+
"pressure_sensors": self.__pressure_sensors,
|
|
1029
|
+
"flow_sensors": self.__flow_sensors,
|
|
1030
|
+
"demand_sensors": self.__demand_sensors,
|
|
1031
|
+
"quality_node_sensors": self.__quality_node_sensors,
|
|
1032
|
+
"quality_link_sensors": self.__quality_link_sensors,
|
|
1033
|
+
"valve_state_sensors": self.__valve_state_sensors,
|
|
1034
|
+
"pump_state_sensors": self.__pump_state_sensors,
|
|
1035
|
+
"tank_volume_sensors": self.__tank_volume_sensors,
|
|
1036
|
+
"bulk_species_node_sensors": self.__bulk_species_node_sensors,
|
|
1037
|
+
"bulk_species_link_sensors": self.__bulk_species_link_sensors,
|
|
1038
|
+
"surface_species_sensors": self.__surface_species_sensors,
|
|
1039
|
+
"node_id_to_idx": self.__node_id_to_idx,
|
|
1040
|
+
"link_id_to_idx": self.__link_id_to_idx,
|
|
1041
|
+
"valve_id_to_idx": self.__valve_id_to_idx,
|
|
1042
|
+
"pump_id_to_idx": self.__pump_id_to_idx,
|
|
1043
|
+
"tank_id_to_idx": self.__tank_id_to_idx,
|
|
1044
|
+
"bulkspecies_id_to_idx": self.__bulkspecies_id_to_idx,
|
|
1045
|
+
"surfacespecies_id_to_idx": self.__surfacespecies_id_to_idx}
|
|
1046
|
+
|
|
1047
|
+
return super().get_attributes() | attr
|
|
1048
|
+
|
|
1049
|
+
def __eq__(self, other) -> bool:
|
|
1050
|
+
if not isinstance(other, SensorConfig):
|
|
1051
|
+
raise TypeError("Can not compare 'SensorConfig' instance " +
|
|
1052
|
+
f"with '{type(other)}' instance")
|
|
1053
|
+
|
|
1054
|
+
return self.__nodes == other.nodes and self.__links == other.links \
|
|
1055
|
+
and self.__valves == other.valves and self.__pumps == other.pumps \
|
|
1056
|
+
and self.__tanks == other.tanks and self.__bulk_species == other.bulk_species \
|
|
1057
|
+
and self.__surface_species == other.surface_species \
|
|
1058
|
+
and self.__pressure_sensors == other.pressure_sensors \
|
|
1059
|
+
and self.__flow_sensors == other.flow_sensors \
|
|
1060
|
+
and self.__demand_sensors == other.demand_sensors \
|
|
1061
|
+
and self.__quality_node_sensors == other.quality_node_sensors \
|
|
1062
|
+
and self.__quality_link_sensors == other.quality_link_sensors \
|
|
1063
|
+
and self.__valve_state_sensors == other.valve_state_sensors \
|
|
1064
|
+
and self.__pump_state_sensors == other.pump_state_sensors \
|
|
1065
|
+
and self.__tank_volume_sensors == other.tank_volume_sensors \
|
|
1066
|
+
and self.__bulk_species_node_sensors == other.bulk_species_node_sensors \
|
|
1067
|
+
and self.__bulk_species_link_sensors == other.bulk_species_link_sensors \
|
|
1068
|
+
and self.__surface_species_sensors == other.surface_species_sensors
|
|
1069
|
+
|
|
1070
|
+
def __str__(self) -> str:
|
|
1071
|
+
return f"nodes: {self.__nodes} links: {self.__links} valves: {self.__valves} " +\
|
|
1072
|
+
f"pumps: {self.__pumps} tanks: {self.__tanks} bulk_species: {self.__bulk_species} " +\
|
|
1073
|
+
f"surface_species: {self.__surface_species}" + \
|
|
1074
|
+
f"pressure_sensors: {self.__pressure_sensors} flow_sensors: {self.__flow_sensors} " +\
|
|
1075
|
+
f"demand_sensors: {self.__demand_sensors} " +\
|
|
1076
|
+
f"quality_node_sensors: {self.__quality_node_sensors} " +\
|
|
1077
|
+
f"quality_link_sensors: {self.__quality_link_sensors} " +\
|
|
1078
|
+
f"valve_state_sensors: {self.__valve_state_sensors} " +\
|
|
1079
|
+
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}"
|
|
1084
|
+
|
|
1085
|
+
def compute_readings(self, pressures: np.ndarray, flows: np.ndarray, demands: np.ndarray,
|
|
1086
|
+
nodes_quality: np.ndarray, links_quality: np.ndarray,
|
|
1087
|
+
pumps_state: np.ndarray, valves_state: np.ndarray,
|
|
1088
|
+
tanks_volume: np.ndarray, bulk_species_node_concentrations: np.ndarray,
|
|
1089
|
+
bulk_species_link_concentrations: np.ndarray,
|
|
1090
|
+
surface_species_concentrations: np.ndarray) -> np.ndarray:
|
|
1091
|
+
"""
|
|
1092
|
+
Applies the sensor configuration to a set of raw simulation results --
|
|
1093
|
+
i.e. computes the sensor readings as an array.
|
|
1094
|
+
|
|
1095
|
+
Parameters
|
|
1096
|
+
----------
|
|
1097
|
+
pressures : `numpy.ndarray`
|
|
1098
|
+
Pressure values at all nodes.
|
|
1099
|
+
flows : `numpy.ndarray`
|
|
1100
|
+
Flow values at all links/pipes.
|
|
1101
|
+
demands : `numpy.ndarray`
|
|
1102
|
+
Demand values at all nodes.
|
|
1103
|
+
nodes_quality : `numpy.ndarray`
|
|
1104
|
+
Quality values at all nodes.
|
|
1105
|
+
links_quality : `numpy.ndarray`
|
|
1106
|
+
Quality values at all links/pipes.
|
|
1107
|
+
pumps_state : `numpy.ndarray`
|
|
1108
|
+
States of all pumps.
|
|
1109
|
+
valves_state : `numpy.ndarray`
|
|
1110
|
+
States of all valves.
|
|
1111
|
+
tanks_volume : `numpy.ndarray`
|
|
1112
|
+
Water volume in all tanks.
|
|
1113
|
+
bulk_species_node_concentrations : `numpy.ndarray`
|
|
1114
|
+
Bulk species concentrations at all nodes.
|
|
1115
|
+
|
|
1116
|
+
Expect a three-dimensional array: First dimension denotes time,
|
|
1117
|
+
second dimension corresponds to species ID,
|
|
1118
|
+
and third dimension contains the concentration.
|
|
1119
|
+
bulk_species_link_concentrations : `numpy.ndarray`
|
|
1120
|
+
Bulk species concentrations at all links/pipes.
|
|
1121
|
+
|
|
1122
|
+
Expect a three-dimensional array: First dimension denotes time,
|
|
1123
|
+
second dimension corresponds to species ID,
|
|
1124
|
+
and third dimension contains the concentration.
|
|
1125
|
+
surface_species_concentrations : `numpy.ndarray`
|
|
1126
|
+
Surface species concentrations at all links/pipes.
|
|
1127
|
+
|
|
1128
|
+
Expect a three-dimensional array: First dimension denotes time,
|
|
1129
|
+
second dimension corresponds to species ID,
|
|
1130
|
+
and third dimension contains the concentration.
|
|
1131
|
+
|
|
1132
|
+
Returns
|
|
1133
|
+
-------
|
|
1134
|
+
`numpy.ndarray`
|
|
1135
|
+
Sensor readings.
|
|
1136
|
+
"""
|
|
1137
|
+
data = []
|
|
1138
|
+
|
|
1139
|
+
if pressures is not None:
|
|
1140
|
+
data.append(pressures[:, self.__pressure_idx])
|
|
1141
|
+
else:
|
|
1142
|
+
if len(self.__pressure_sensors) != 0:
|
|
1143
|
+
raise ValueError("Pressure readings requested but no pressure data is given")
|
|
1144
|
+
|
|
1145
|
+
if flows is not None:
|
|
1146
|
+
data.append(flows[:, self.__flow_idx])
|
|
1147
|
+
else:
|
|
1148
|
+
if len(self.__flow_sensors) != 0:
|
|
1149
|
+
raise ValueError("Flow readings requested but no flow data is given")
|
|
1150
|
+
|
|
1151
|
+
if demands is not None:
|
|
1152
|
+
data.append(demands[:, self.__demand_idx])
|
|
1153
|
+
else:
|
|
1154
|
+
if len(self.__demand_sensors) != 0:
|
|
1155
|
+
raise ValueError("Demand readings requested but no demand data is given")
|
|
1156
|
+
|
|
1157
|
+
if nodes_quality is not None:
|
|
1158
|
+
data.append(nodes_quality[:, self.__quality_node_idx])
|
|
1159
|
+
else:
|
|
1160
|
+
if len(self.__quality_node_sensors) != 0:
|
|
1161
|
+
raise ValueError("Node water quality readings requested " +
|
|
1162
|
+
"but no water quality data at nodes is given")
|
|
1163
|
+
|
|
1164
|
+
if links_quality is not None:
|
|
1165
|
+
data.append(links_quality[:, self.__quality_link_idx])
|
|
1166
|
+
else:
|
|
1167
|
+
if len(self.__quality_link_sensors) != 0:
|
|
1168
|
+
raise ValueError("Link/Pipe water quality readings requested " +
|
|
1169
|
+
"but no water quality data at links/pipes is given")
|
|
1170
|
+
|
|
1171
|
+
if valves_state is not None:
|
|
1172
|
+
data.append(valves_state[:, self.__valve_state_idx])
|
|
1173
|
+
else:
|
|
1174
|
+
if len(self.__valve_state_sensors) != 0:
|
|
1175
|
+
raise ValueError("Valve states readings requested " +
|
|
1176
|
+
"but no valve state data is given")
|
|
1177
|
+
|
|
1178
|
+
if pumps_state is not None:
|
|
1179
|
+
data.append(pumps_state[:, self.__pump_state_idx])
|
|
1180
|
+
else:
|
|
1181
|
+
if len(self.__pump_state_sensors) != 0:
|
|
1182
|
+
raise ValueError("Pump states readings requested " +
|
|
1183
|
+
"but no pump state data is given")
|
|
1184
|
+
|
|
1185
|
+
if tanks_volume is not None:
|
|
1186
|
+
data.append(tanks_volume[:, self.__tank_volume_idx])
|
|
1187
|
+
else:
|
|
1188
|
+
if len(self.__tank_volume_sensors) != 0:
|
|
1189
|
+
raise ValueError("Water volumes in tanks is requested but no " +
|
|
1190
|
+
"tank water volume data is given")
|
|
1191
|
+
|
|
1192
|
+
if surface_species_concentrations is not None:
|
|
1193
|
+
for species_idx, links_idx in self.__surface_species_idx:
|
|
1194
|
+
data.append(surface_species_concentrations[:, species_idx, links_idx].
|
|
1195
|
+
reshape(-1, len(links_idx)))
|
|
1196
|
+
else:
|
|
1197
|
+
if len(self.__surface_species_sensors) != 0:
|
|
1198
|
+
raise ValueError("Surface species concentratinons requested but no " +
|
|
1199
|
+
"surface species concentration data is given")
|
|
1200
|
+
|
|
1201
|
+
if bulk_species_node_concentrations is not None:
|
|
1202
|
+
for species_idx, nodes_idx in self.__bulk_species_node_idx:
|
|
1203
|
+
data.append(bulk_species_node_concentrations[:, species_idx, nodes_idx].
|
|
1204
|
+
reshape(-1, len(nodes_idx)))
|
|
1205
|
+
else:
|
|
1206
|
+
if len(self.__bulk_species_node_sensors) != 0:
|
|
1207
|
+
raise ValueError("Bulk species concentratinons requested but no " +
|
|
1208
|
+
"bulk species node concentration data is given")
|
|
1209
|
+
|
|
1210
|
+
if bulk_species_link_concentrations is not None:
|
|
1211
|
+
for species_idx, links_idx in self.__bulk_species_link_idx:
|
|
1212
|
+
data.append(bulk_species_link_concentrations[:, species_idx, links_idx].
|
|
1213
|
+
reshape(-1, len(links_idx)))
|
|
1214
|
+
else:
|
|
1215
|
+
if len(self.__bulk_species_link_sensors) != 0:
|
|
1216
|
+
raise ValueError("Bulk species concentratinons requested but no " +
|
|
1217
|
+
"bulk species link/pipe concentration data is given")
|
|
1218
|
+
|
|
1219
|
+
return np.concatenate(data, axis=1)
|
|
1220
|
+
|
|
1221
|
+
def get_index_of_reading(self, pressure_sensor: str = None, flow_sensor: str = None,
|
|
1222
|
+
demand_sensor: str = None, node_quality_sensor: str = None,
|
|
1223
|
+
link_quality_sensor: str = None, valve_state_sensor: str = None,
|
|
1224
|
+
pump_state_sensor: str = None, tank_volume_sensor: str = None,
|
|
1225
|
+
bulk_species_node_sensor: tuple[str, str] = None,
|
|
1226
|
+
bulk_species_link_sensor: tuple[str, str] = None,
|
|
1227
|
+
surface_species_sensor: tuple[str, str] = None) -> int:
|
|
1228
|
+
"""
|
|
1229
|
+
Gets the index of a particular sensor in the final sensor readings array.
|
|
1230
|
+
|
|
1231
|
+
Note that only one sensor ID is converted to an index. In case of multiple sensor IDs,
|
|
1232
|
+
call this function for each sensor ID separately.
|
|
1233
|
+
|
|
1234
|
+
.. note::
|
|
1235
|
+
|
|
1236
|
+
This function only returns the correct results if the sensor configuraton is NOT frozen!
|
|
1237
|
+
|
|
1238
|
+
Parameters
|
|
1239
|
+
----------
|
|
1240
|
+
pressure_sensor : `str`
|
|
1241
|
+
ID of the pressure sensor.
|
|
1242
|
+
flow_sensor : `str`
|
|
1243
|
+
ID of the flow sensor.
|
|
1244
|
+
demand_sensor : `str`
|
|
1245
|
+
ID of the demand sensor.
|
|
1246
|
+
node_quality_sensor : `str`
|
|
1247
|
+
ID of the quality sensor (at a node).
|
|
1248
|
+
link_quality_sensor : `str`
|
|
1249
|
+
ID of the quality sensor (at a link/pipe).
|
|
1250
|
+
valve_state_sensor : `str`
|
|
1251
|
+
ID of the state sensor (at a valve).
|
|
1252
|
+
pump_state_sensor : `str`
|
|
1253
|
+
ID of the state sensor (at a pump).
|
|
1254
|
+
tank_volume_sensor : `str`
|
|
1255
|
+
ID of the water volume sensor (at a tank)
|
|
1256
|
+
bulk_species_node_sensor : `tuple[str, str]`
|
|
1257
|
+
Tuple of bulk species ID and sensor node ID.
|
|
1258
|
+
bulk_species_link_sensor : `tuple[str, str]`
|
|
1259
|
+
Tuple of bulk species ID and sensor link/pipe ID.
|
|
1260
|
+
surface_species_sensor : `tuple[str, str]`
|
|
1261
|
+
Tuple of surface species ID and sensor link/pipe ID.
|
|
1262
|
+
"""
|
|
1263
|
+
if pressure_sensor is not None:
|
|
1264
|
+
return self.__sensors_id_to_idx["pressure"][pressure_sensor]
|
|
1265
|
+
elif flow_sensor is not None:
|
|
1266
|
+
return self.__sensors_id_to_idx["flow"][flow_sensor]
|
|
1267
|
+
elif demand_sensor is not None:
|
|
1268
|
+
return self.__sensors_id_to_idx["demand"][demand_sensor]
|
|
1269
|
+
elif node_quality_sensor is not None:
|
|
1270
|
+
return self.__sensors_id_to_idx["quality_node"][node_quality_sensor]
|
|
1271
|
+
elif link_quality_sensor is not None:
|
|
1272
|
+
return self.__sensors_id_to_idx["quality_link"][link_quality_sensor]
|
|
1273
|
+
elif valve_state_sensor is not None:
|
|
1274
|
+
return self.__sensors_id_to_idx["valve_state"][valve_state_sensor]
|
|
1275
|
+
elif pump_state_sensor is not None:
|
|
1276
|
+
return self.__sensors_id_to_idx["pump_state"][pump_state_sensor]
|
|
1277
|
+
elif tank_volume_sensor is not None:
|
|
1278
|
+
return self.__sensors_id_to_idx["tank_volume"][tank_volume_sensor]
|
|
1279
|
+
elif surface_species_sensor is not None:
|
|
1280
|
+
species_id, sensor_id = surface_species_sensor
|
|
1281
|
+
return self.__sensors_id_to_idx["surface_species"][species_id][sensor_id]
|
|
1282
|
+
elif bulk_species_node_sensor is not None:
|
|
1283
|
+
species_id, sensor_id = bulk_species_node_sensor
|
|
1284
|
+
return self.__sensors_id_to_idx["bulk_species_node"][species_id][sensor_id]
|
|
1285
|
+
elif bulk_species_link_sensor is not None:
|
|
1286
|
+
species_id, sensor_id = bulk_species_link_sensor
|
|
1287
|
+
return self.__sensors_id_to_idx["bulk_species_link"][species_id][sensor_id]
|
|
1288
|
+
else:
|
|
1289
|
+
raise ValueError("No sensor given")
|