epyt-flow 0.11.0__py3-none-any.whl → 0.13.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-MSX/Src/msxtoolkit.c +1 -1
- epyt_flow/VERSION +1 -1
- epyt_flow/data/benchmarks/gecco_water_quality.py +2 -2
- epyt_flow/data/benchmarks/leakdb.py +40 -5
- epyt_flow/data/benchmarks/water_usage.py +4 -3
- epyt_flow/gym/__init__.py +0 -3
- epyt_flow/gym/scenario_control_env.py +5 -12
- epyt_flow/rest_api/scenario/control_handlers.py +118 -0
- epyt_flow/rest_api/scenario/event_handlers.py +114 -1
- epyt_flow/rest_api/scenario/handlers.py +33 -0
- epyt_flow/rest_api/server.py +14 -2
- epyt_flow/simulation/backend/__init__.py +1 -0
- epyt_flow/simulation/backend/my_epyt.py +1056 -0
- epyt_flow/simulation/events/quality_events.py +3 -1
- epyt_flow/simulation/scada/scada_data.py +201 -12
- epyt_flow/simulation/scenario_simulator.py +179 -87
- epyt_flow/topology.py +8 -7
- epyt_flow/uncertainty/sensor_noise.py +2 -9
- epyt_flow/utils.py +30 -0
- epyt_flow/visualization/scenario_visualizer.py +159 -69
- epyt_flow/visualization/visualization_utils.py +144 -17
- {epyt_flow-0.11.0.dist-info → epyt_flow-0.13.0.dist-info}/METADATA +4 -4
- {epyt_flow-0.11.0.dist-info → epyt_flow-0.13.0.dist-info}/RECORD +26 -29
- {epyt_flow-0.11.0.dist-info → epyt_flow-0.13.0.dist-info}/WHEEL +1 -1
- epyt_flow/gym/control_gyms.py +0 -55
- epyt_flow/metrics.py +0 -471
- epyt_flow/models/__init__.py +0 -2
- epyt_flow/models/event_detector.py +0 -36
- epyt_flow/models/sensor_interpolation_detector.py +0 -123
- epyt_flow/simulation/scada/advanced_control.py +0 -138
- {epyt_flow-0.11.0.dist-info → epyt_flow-0.13.0.dist-info/licenses}/LICENSE +0 -0
- {epyt_flow-0.11.0.dist-info → epyt_flow-0.13.0.dist-info}/top_level.txt +0 -0
|
@@ -27,6 +27,8 @@ class SpeciesInjectionEvent(SystemEvent, JsonSerializable):
|
|
|
27
27
|
profile : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
28
28
|
Injection strength profile -- i.e. every entry corresponds to the strength of the injection
|
|
29
29
|
at a point in time. Pattern will repeat if it is shorter than the total injection time.
|
|
30
|
+
|
|
31
|
+
Note that the pattern time step is equivalent to the EPANET pattern time step.
|
|
30
32
|
source_type : `int`
|
|
31
33
|
Type of the bulk species injection source -- must be one of
|
|
32
34
|
the following EPANET toolkit constants:
|
|
@@ -198,7 +200,7 @@ class SpeciesInjectionEvent(SystemEvent, JsonSerializable):
|
|
|
198
200
|
pattern_id)
|
|
199
201
|
|
|
200
202
|
def cleanup(self) -> None:
|
|
201
|
-
warnings.warn("Can not undo
|
|
203
|
+
warnings.warn("Can not undo SpeciesInjectionEvent -- " +
|
|
202
204
|
"EPANET-MSX does not support removing patterns")
|
|
203
205
|
|
|
204
206
|
def apply(self, cur_time: int) -> None:
|
|
@@ -41,6 +41,10 @@ class ScadaData(Serializable):
|
|
|
41
41
|
|
|
42
42
|
This parameter is expected to be a 1d array with the same size as
|
|
43
43
|
the number of rows in `sensor_readings_data_raw`.
|
|
44
|
+
network_topo : :class:`~epyt_flow.topology.NetworkTopology`
|
|
45
|
+
Topology of the water distribution network.
|
|
46
|
+
warnings_code : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
47
|
+
Codes/IDs of EPANET errors/warnings (if any) for each time step.
|
|
44
48
|
pressure_data_raw : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, optional
|
|
45
49
|
Raw pressure values of all nodes as a two-dimensional array --
|
|
46
50
|
first dimension encodes time, second dimension pressure at nodes.
|
|
@@ -128,10 +132,9 @@ class ScadaData(Serializable):
|
|
|
128
132
|
will be stored -- this usually leads to a significant reduction in memory consumption.
|
|
129
133
|
|
|
130
134
|
The default is False.
|
|
131
|
-
network_topo : :class:`~epyt_flow.topology.NetworkTopology`
|
|
132
|
-
Topology of the water distribution network.
|
|
133
135
|
"""
|
|
134
136
|
def __init__(self, sensor_config: SensorConfig, sensor_readings_time: np.ndarray,
|
|
137
|
+
network_topo: NetworkTopology, warnings_code: np.ndarray = None,
|
|
135
138
|
pressure_data_raw: Union[np.ndarray, bsr_array] = None,
|
|
136
139
|
flow_data_raw: Union[np.ndarray, bsr_array] = None,
|
|
137
140
|
demand_data_raw: Union[np.ndarray, bsr_array] = None,
|
|
@@ -151,17 +154,11 @@ class ScadaData(Serializable):
|
|
|
151
154
|
sensor_reading_attacks: list[SensorReadingAttack] = [],
|
|
152
155
|
sensor_reading_events: list[SensorReadingEvent] = [],
|
|
153
156
|
sensor_noise: SensorNoise = None, frozen_sensor_config: bool = False,
|
|
154
|
-
network_topo: NetworkTopology = None,
|
|
155
157
|
**kwds):
|
|
156
|
-
if network_topo
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
f"of '{type(network_topo)}'")
|
|
161
|
-
else:
|
|
162
|
-
warnings.warn("You are loading a SCADA data instance that was created with an " +
|
|
163
|
-
"outdated version of EPyT-Flow. Future releases will require " +
|
|
164
|
-
"'network_topo' != None. Please upgrade!")
|
|
158
|
+
if not isinstance(network_topo, NetworkTopology):
|
|
159
|
+
raise TypeError("'network_topo' must be an instance of " +
|
|
160
|
+
"'epyt_flow.topology.NetworkTopology' but not " +
|
|
161
|
+
f"of '{type(network_topo)}'")
|
|
165
162
|
if not isinstance(sensor_config, SensorConfig):
|
|
166
163
|
raise TypeError("'sensor_config' must be an instance of " +
|
|
167
164
|
"'epyt_flow.simulation.SensorConfig' but not of " +
|
|
@@ -169,6 +166,14 @@ class ScadaData(Serializable):
|
|
|
169
166
|
if not isinstance(sensor_readings_time, np.ndarray):
|
|
170
167
|
raise TypeError("'sensor_readings_time' must be an instance of 'numpy.ndarray' " +
|
|
171
168
|
f"but not of '{type(sensor_readings_time)}'")
|
|
169
|
+
if warnings_code is None:
|
|
170
|
+
warnings.warn("Loading a file that was created with an outdated version of EPyT-Flow" +
|
|
171
|
+
" -- support of such old files will be removed in the next release!",
|
|
172
|
+
DeprecationWarning)
|
|
173
|
+
else:
|
|
174
|
+
if not isinstance(warnings_code, np.ndarray):
|
|
175
|
+
raise TypeError("'warnings_code' must be an instance of 'numpy.ndarray' " +
|
|
176
|
+
f"but not of '{type(warnings_code)}'")
|
|
172
177
|
if pressure_data_raw is not None:
|
|
173
178
|
if not isinstance(pressure_data_raw, np.ndarray) and \
|
|
174
179
|
not isinstance(pressure_data_raw, bsr_array):
|
|
@@ -306,6 +311,9 @@ class ScadaData(Serializable):
|
|
|
306
311
|
"must match number of raw measurements.")
|
|
307
312
|
|
|
308
313
|
n_time_steps = sensor_readings_time.shape[0]
|
|
314
|
+
if warnings_code is not None:
|
|
315
|
+
if warnings_code.shape[0] != n_time_steps:
|
|
316
|
+
__raise_shape_mismatch("warnings_code")
|
|
309
317
|
if pressure_data_raw is not None:
|
|
310
318
|
if pressure_data_raw.shape[0] != n_time_steps:
|
|
311
319
|
__raise_shape_mismatch("pressure_data_raw")
|
|
@@ -360,6 +368,7 @@ class ScadaData(Serializable):
|
|
|
360
368
|
|
|
361
369
|
self.__network_topo = network_topo
|
|
362
370
|
self.__sensor_config = sensor_config
|
|
371
|
+
self.__warnings_code = warnings_code
|
|
363
372
|
self.__sensor_noise = sensor_noise
|
|
364
373
|
self.__sensor_reading_events = sensor_faults + sensor_reading_attacks + \
|
|
365
374
|
sensor_reading_events
|
|
@@ -1045,6 +1054,7 @@ class ScadaData(Serializable):
|
|
|
1045
1054
|
surface_species_area_unit=new_surface_species_area_unit)
|
|
1046
1055
|
|
|
1047
1056
|
return ScadaData(network_topo=self.network_topo,
|
|
1057
|
+
warnings_code=self.warnings_code,
|
|
1048
1058
|
sensor_config=sensor_config,
|
|
1049
1059
|
sensor_readings_time=self.sensor_readings_time,
|
|
1050
1060
|
sensor_reading_events=self.sensor_reading_events,
|
|
@@ -1076,6 +1086,19 @@ class ScadaData(Serializable):
|
|
|
1076
1086
|
"""
|
|
1077
1087
|
return deepcopy(self.__network_topo)
|
|
1078
1088
|
|
|
1089
|
+
@property
|
|
1090
|
+
def warnings_code(self) -> np.ndarray:
|
|
1091
|
+
"""
|
|
1092
|
+
Returns the codes/IDs of EPANET errors/warnings (if any) for each time step.
|
|
1093
|
+
Note that zero denotes the absence of any error/warning.
|
|
1094
|
+
|
|
1095
|
+
Returns:
|
|
1096
|
+
--------
|
|
1097
|
+
`numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
|
|
1098
|
+
Codes/IDs of EPANET errors/warnings (if any) for each time step.
|
|
1099
|
+
"""
|
|
1100
|
+
return deepcopy(self.__warnings_code)
|
|
1101
|
+
|
|
1079
1102
|
@property
|
|
1080
1103
|
def frozen_sensor_config(self) -> bool:
|
|
1081
1104
|
"""
|
|
@@ -1402,6 +1425,7 @@ class ScadaData(Serializable):
|
|
|
1402
1425
|
|
|
1403
1426
|
def get_attributes(self) -> dict:
|
|
1404
1427
|
attr = {"network_topo": self.__network_topo,
|
|
1428
|
+
"warnings_code": self.__warnings_code,
|
|
1405
1429
|
"sensor_config": self.__sensor_config,
|
|
1406
1430
|
"frozen_sensor_config": self.__frozen_sensor_config,
|
|
1407
1431
|
"sensor_noise": self.__sensor_noise,
|
|
@@ -1554,6 +1578,7 @@ class ScadaData(Serializable):
|
|
|
1554
1578
|
|
|
1555
1579
|
try:
|
|
1556
1580
|
return self.__network_topo == other.network_topo \
|
|
1581
|
+
and np.all(self.__warnings_code == other.warnings_code) \
|
|
1557
1582
|
and self.__sensor_config == other.sensor_config \
|
|
1558
1583
|
and self.__frozen_sensor_config == other.frozen_sensor_config \
|
|
1559
1584
|
and self.__sensor_noise == other.sensor_noise \
|
|
@@ -1583,6 +1608,7 @@ class ScadaData(Serializable):
|
|
|
1583
1608
|
|
|
1584
1609
|
def __str__(self) -> str:
|
|
1585
1610
|
return f"network_topo: {self.__network_topo} sensor_config: {self.__sensor_config} " + \
|
|
1611
|
+
f"warnings_code: {self.__warnings_code} " + \
|
|
1586
1612
|
f"frozen_sensor_config: {self.__frozen_sensor_config} " + \
|
|
1587
1613
|
f"sensor_noise: {self.__sensor_noise} " + \
|
|
1588
1614
|
f"sensor_reading_events: {self.__sensor_reading_events} " + \
|
|
@@ -1782,6 +1808,7 @@ class ScadaData(Serializable):
|
|
|
1782
1808
|
|
|
1783
1809
|
return ScadaData(network_topo=self.network_topo, sensor_config=self.sensor_config,
|
|
1784
1810
|
sensor_readings_time=self.sensor_readings_time[start_idx:end_idx],
|
|
1811
|
+
warnings_code=self.__warnings_code[start_idx:end_idx],
|
|
1785
1812
|
frozen_sensor_config=self.frozen_sensor_config,
|
|
1786
1813
|
sensor_noise=self.sensor_noise,
|
|
1787
1814
|
sensor_reading_events=self.sensor_reading_events,
|
|
@@ -1940,6 +1967,8 @@ class ScadaData(Serializable):
|
|
|
1940
1967
|
self.__sensor_readings_time = np.concatenate(
|
|
1941
1968
|
(self.__sensor_readings_time, other.sensor_readings_time), axis=0)
|
|
1942
1969
|
|
|
1970
|
+
self.__warnings_code = np.concatenate((self.__warnings_code, other.warnings_code), axis=0)
|
|
1971
|
+
|
|
1943
1972
|
if self.__pressure_data_raw is not None:
|
|
1944
1973
|
self.__pressure_data_raw = np.concatenate(
|
|
1945
1974
|
(self.__pressure_data_raw, other.pressure_data_raw), axis=0)
|
|
@@ -2928,6 +2957,38 @@ class ScadaData(Serializable):
|
|
|
2928
2957
|
for s_id in sensor_locations]
|
|
2929
2958
|
return self.__sensor_readings[:, idx]
|
|
2930
2959
|
|
|
2960
|
+
def get_data_pumps_state_as_node_features(self,
|
|
2961
|
+
default_missing_value: float = 0.
|
|
2962
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
2963
|
+
"""
|
|
2964
|
+
Returns the pump state as node features together with a boolean mask indicating the
|
|
2965
|
+
presence of a sensor.
|
|
2966
|
+
|
|
2967
|
+
Parameters
|
|
2968
|
+
----------
|
|
2969
|
+
default_missing_value : `float`, optional
|
|
2970
|
+
Default value (i.e. missing value) for nodes where no pump state sensor is installed.
|
|
2971
|
+
|
|
2972
|
+
The default is 0.
|
|
2973
|
+
|
|
2974
|
+
Returns
|
|
2975
|
+
-------
|
|
2976
|
+
tuple[`numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_]
|
|
2977
|
+
Pump state as node features of shape [num_time_steps, num_nodes], and mask of shape [num_nodes].
|
|
2978
|
+
"""
|
|
2979
|
+
mask = np.zeros(len(self.__sensor_config.pumps))
|
|
2980
|
+
pump_features = np.array([[default_missing_value] * len(self.__sensor_config.pumps)
|
|
2981
|
+
for _ in range(len(self.__sensor_readings_time))])
|
|
2982
|
+
pumps_id = self.__network_topo.get_all_pumps()
|
|
2983
|
+
|
|
2984
|
+
state_readings = self.get_data_pumps_state()
|
|
2985
|
+
for pumps_state_idx, pump_id in enumerate(self.__sensor_config.pump_state_sensors):
|
|
2986
|
+
idx = pumps_id.index(pump_id)
|
|
2987
|
+
pump_features[:, idx] = state_readings[:, pumps_state_idx]
|
|
2988
|
+
mask[idx] = 1
|
|
2989
|
+
|
|
2990
|
+
return pump_features, mask
|
|
2991
|
+
|
|
2931
2992
|
def plot_pumps_state(self, sensor_locations: list[str] = None, show: bool = True,
|
|
2932
2993
|
save_to_file: str = None, ax: matplotlib.axes.Axes = None
|
|
2933
2994
|
) -> matplotlib.axes.Axes:
|
|
@@ -3015,6 +3076,38 @@ class ScadaData(Serializable):
|
|
|
3015
3076
|
for s_id in sensor_locations]
|
|
3016
3077
|
return self.__sensor_readings[:, idx]
|
|
3017
3078
|
|
|
3079
|
+
def get_data_pumps_efficiency_as_node_features(self,
|
|
3080
|
+
default_missing_value: float = 0.
|
|
3081
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
3082
|
+
"""
|
|
3083
|
+
Returns the pump efficiency as node features together with a boolean mask indicating the
|
|
3084
|
+
presence of a sensor.
|
|
3085
|
+
|
|
3086
|
+
Parameters
|
|
3087
|
+
----------
|
|
3088
|
+
default_missing_value : `float`, optional
|
|
3089
|
+
Default value (i.e. missing value) for nodes where no pump efficiency sensor is installed.
|
|
3090
|
+
|
|
3091
|
+
The default is 0.
|
|
3092
|
+
|
|
3093
|
+
Returns
|
|
3094
|
+
-------
|
|
3095
|
+
tuple[`numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_]
|
|
3096
|
+
Pump efficiencies as node features of shape [num_time_steps, num_nodes], and mask of shape [num_nodes].
|
|
3097
|
+
"""
|
|
3098
|
+
mask = np.zeros(len(self.__sensor_config.pumps))
|
|
3099
|
+
pump_features = np.array([[default_missing_value] * len(self.__sensor_config.pumps)
|
|
3100
|
+
for _ in range(len(self.__sensor_readings_time))])
|
|
3101
|
+
pumps_id = self.__network_topo.get_all_pumps()
|
|
3102
|
+
|
|
3103
|
+
efficiency_readings = self.get_data_pumps_efficiency()
|
|
3104
|
+
for pumps_efficiency_idx, pump_id in enumerate(self.__sensor_config.pump_efficiency_sensors):
|
|
3105
|
+
idx = pumps_id.index(pump_id)
|
|
3106
|
+
pump_features[:, idx] = efficiency_readings[:, pumps_efficiency_idx]
|
|
3107
|
+
mask[idx] = 1
|
|
3108
|
+
|
|
3109
|
+
return pump_features, mask
|
|
3110
|
+
|
|
3018
3111
|
def plot_pumps_efficiency(self, sensor_locations: list[str] = None, show: bool = True,
|
|
3019
3112
|
save_to_file: str = None, ax: matplotlib.axes.Axes = None
|
|
3020
3113
|
) -> matplotlib.axes.Axes:
|
|
@@ -3103,6 +3196,38 @@ class ScadaData(Serializable):
|
|
|
3103
3196
|
for s_id in sensor_locations]
|
|
3104
3197
|
return self.__sensor_readings[:, idx]
|
|
3105
3198
|
|
|
3199
|
+
def get_data_pumps_energyconsumption_as_node_features(self,
|
|
3200
|
+
default_missing_value: float = 0.
|
|
3201
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
3202
|
+
"""
|
|
3203
|
+
Returns the pump energy consumption as node features together with a boolean mask indicating the
|
|
3204
|
+
presence of a sensor.
|
|
3205
|
+
|
|
3206
|
+
Parameters
|
|
3207
|
+
----------
|
|
3208
|
+
default_missing_value : `float`, optional
|
|
3209
|
+
Default value (i.e. missing value) for nodes where no pump energy consumption sensor is installed.
|
|
3210
|
+
|
|
3211
|
+
The default is 0.
|
|
3212
|
+
|
|
3213
|
+
Returns
|
|
3214
|
+
-------
|
|
3215
|
+
tuple[`numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_]
|
|
3216
|
+
Pump energy consumptions as node features of shape [num_time_steps, num_nodes], and mask of shape [num_nodes].
|
|
3217
|
+
"""
|
|
3218
|
+
mask = np.zeros(len(self.__sensor_config.pumps))
|
|
3219
|
+
pump_features = np.array([[default_missing_value] * len(self.__sensor_config.pumps)
|
|
3220
|
+
for _ in range(len(self.__sensor_readings_time))])
|
|
3221
|
+
pumps_id = self.__network_topo.get_all_pumps()
|
|
3222
|
+
|
|
3223
|
+
energyconsumption_readings = self.get_data_pumps_energyconsumption()
|
|
3224
|
+
for pumps_energyconsumption_idx, pump_id in enumerate(self.__sensor_config.pump_energyconsumption_sensors):
|
|
3225
|
+
idx = pumps_id.index(pump_id)
|
|
3226
|
+
pump_features[:, idx] = energyconsumption_readings[:, pumps_energyconsumption_idx]
|
|
3227
|
+
mask[idx] = 1
|
|
3228
|
+
|
|
3229
|
+
return pump_features, mask
|
|
3230
|
+
|
|
3106
3231
|
def plot_pumps_energyconsumption(self, sensor_locations: list[str] = None, show: bool = True,
|
|
3107
3232
|
save_to_file: str = None, ax: matplotlib.axes.Axes = None
|
|
3108
3233
|
) -> matplotlib.axes.Axes:
|
|
@@ -3190,6 +3315,38 @@ class ScadaData(Serializable):
|
|
|
3190
3315
|
for s_id in sensor_locations]
|
|
3191
3316
|
return self.__sensor_readings[:, idx]
|
|
3192
3317
|
|
|
3318
|
+
def get_data_valves_state_as_node_features(self,
|
|
3319
|
+
default_missing_value: float = 0.
|
|
3320
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
3321
|
+
"""
|
|
3322
|
+
Returns the valves state as node features together with a boolean mask indicating the
|
|
3323
|
+
presence of a sensor.
|
|
3324
|
+
|
|
3325
|
+
Parameters
|
|
3326
|
+
----------
|
|
3327
|
+
default_missing_value : `float`, optional
|
|
3328
|
+
Default value (i.e. missing value) for nodes where no valves state sensor is installed.
|
|
3329
|
+
|
|
3330
|
+
The default is 0.
|
|
3331
|
+
|
|
3332
|
+
Returns
|
|
3333
|
+
-------
|
|
3334
|
+
tuple[`numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_]
|
|
3335
|
+
Valves state as node features of shape [num_time_steps, num_nodes], and mask of shape [num_nodes].
|
|
3336
|
+
"""
|
|
3337
|
+
mask = np.zeros(len(self.__sensor_config.valves))
|
|
3338
|
+
valve_features = np.array([[default_missing_value] * len(self.__sensor_config.valves)
|
|
3339
|
+
for _ in range(len(self.__sensor_readings_time))])
|
|
3340
|
+
valves_id = self.__network_topo.get_all_valves()
|
|
3341
|
+
|
|
3342
|
+
state_readings = self.get_data_valves_state()
|
|
3343
|
+
for valves_state_idx, valve_id in enumerate(self.__sensor_config.valve_state_sensors):
|
|
3344
|
+
idx = valves_id.index(valve_id)
|
|
3345
|
+
valve_features[:, idx] = state_readings[:, valves_state_idx]
|
|
3346
|
+
mask[idx] = 1
|
|
3347
|
+
|
|
3348
|
+
return valve_features, mask
|
|
3349
|
+
|
|
3193
3350
|
def plot_valves_state(self, sensor_locations: list[str] = None, show: bool = True,
|
|
3194
3351
|
save_to_file: str = None, ax: matplotlib.axes.Axes = None
|
|
3195
3352
|
) -> matplotlib.axes.Axes:
|
|
@@ -3277,6 +3434,38 @@ class ScadaData(Serializable):
|
|
|
3277
3434
|
for s_id in sensor_locations]
|
|
3278
3435
|
return self.__sensor_readings[:, idx]
|
|
3279
3436
|
|
|
3437
|
+
def get_data_tanks_water_volume_as_node_features(self,
|
|
3438
|
+
default_missing_value: float = 0.
|
|
3439
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
3440
|
+
"""
|
|
3441
|
+
Returns the tank water volume as node features together with a boolean mask indicating the
|
|
3442
|
+
presence of a sensor.
|
|
3443
|
+
|
|
3444
|
+
Parameters
|
|
3445
|
+
----------
|
|
3446
|
+
default_missing_value : `float`, optional
|
|
3447
|
+
Default value (i.e. missing value) for nodes where no tank water volume sensor is installed.
|
|
3448
|
+
|
|
3449
|
+
The default is 0.
|
|
3450
|
+
|
|
3451
|
+
Returns
|
|
3452
|
+
-------
|
|
3453
|
+
tuple[`numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_]
|
|
3454
|
+
Tank water volumes as node features of shape [num_time_steps, num_nodes], and mask of shape [num_nodes].
|
|
3455
|
+
"""
|
|
3456
|
+
mask = np.zeros(len(self.__sensor_config.tanks))
|
|
3457
|
+
tank_features = np.array([[default_missing_value] * len(self.__sensor_config.tanks)
|
|
3458
|
+
for _ in range(len(self.__sensor_readings_time))])
|
|
3459
|
+
tanks_id = self.__network_topo.get_all_tanks()
|
|
3460
|
+
|
|
3461
|
+
water_volume_readings = self.get_data_tanks_water_volume()
|
|
3462
|
+
for tanks_water_volume_idx, tank_id in enumerate(self.__sensor_config.tank_volume_sensors):
|
|
3463
|
+
idx = tanks_id.index(tank_id)
|
|
3464
|
+
tank_features[:, idx] = water_volume_readings[:, tanks_water_volume_idx]
|
|
3465
|
+
mask[idx] = 1
|
|
3466
|
+
|
|
3467
|
+
return tank_features, mask
|
|
3468
|
+
|
|
3280
3469
|
def plot_tanks_water_volume(self, sensor_locations: list[str] = None, show: bool = True,
|
|
3281
3470
|
save_to_file: str = None, ax: matplotlib.axes.Axes = None
|
|
3282
3471
|
) -> matplotlib.axes.Axes:
|