epyt-flow 0.8.1__py3-none-any.whl → 0.10.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.
Files changed (50) hide show
  1. epyt_flow/VERSION +1 -1
  2. epyt_flow/__init__.py +1 -0
  3. epyt_flow/data/benchmarks/batadal.py +1 -1
  4. epyt_flow/data/benchmarks/battledim.py +4 -3
  5. epyt_flow/data/benchmarks/gecco_water_quality.py +4 -4
  6. epyt_flow/data/benchmarks/leakdb.py +7 -7
  7. epyt_flow/data/benchmarks/water_usage.py +2 -2
  8. epyt_flow/data/networks.py +1 -1
  9. epyt_flow/gym/control_gyms.py +2 -2
  10. epyt_flow/gym/scenario_control_env.py +9 -1
  11. epyt_flow/metrics.py +28 -28
  12. epyt_flow/models/sensor_interpolation_detector.py +3 -3
  13. epyt_flow/rest_api/base_handler.py +4 -4
  14. epyt_flow/rest_api/scada_data/data_handlers.py +11 -11
  15. epyt_flow/rest_api/scada_data/export_handlers.py +2 -2
  16. epyt_flow/rest_api/scada_data/handlers.py +9 -9
  17. epyt_flow/rest_api/scenario/event_handlers.py +6 -6
  18. epyt_flow/rest_api/scenario/handlers.py +15 -15
  19. epyt_flow/rest_api/scenario/simulation_handlers.py +7 -7
  20. epyt_flow/rest_api/scenario/uncertainty_handlers.py +6 -6
  21. epyt_flow/serialization.py +8 -2
  22. epyt_flow/simulation/events/actuator_events.py +1 -1
  23. epyt_flow/simulation/events/leakages.py +1 -1
  24. epyt_flow/simulation/events/quality_events.py +16 -5
  25. epyt_flow/simulation/events/sensor_reading_attack.py +17 -4
  26. epyt_flow/simulation/events/sensor_reading_event.py +21 -6
  27. epyt_flow/simulation/events/system_event.py +1 -1
  28. epyt_flow/simulation/parallel_simulation.py +1 -1
  29. epyt_flow/simulation/scada/__init__.py +3 -1
  30. epyt_flow/simulation/scada/advanced_control.py +8 -4
  31. epyt_flow/simulation/scada/complex_control.py +625 -0
  32. epyt_flow/simulation/scada/custom_control.py +134 -0
  33. epyt_flow/simulation/scada/scada_data.py +133 -130
  34. epyt_flow/simulation/scada/scada_data_export.py +1 -1
  35. epyt_flow/simulation/scada/simple_control.py +317 -0
  36. epyt_flow/simulation/scenario_config.py +124 -24
  37. epyt_flow/simulation/scenario_simulator.py +514 -49
  38. epyt_flow/simulation/scenario_visualizer.py +9 -9
  39. epyt_flow/simulation/sensor_config.py +38 -28
  40. epyt_flow/topology.py +2 -2
  41. epyt_flow/uncertainty/model_uncertainty.py +624 -147
  42. epyt_flow/uncertainty/sensor_noise.py +94 -19
  43. epyt_flow/uncertainty/uncertainties.py +4 -4
  44. epyt_flow/uncertainty/utils.py +7 -7
  45. epyt_flow/utils.py +9 -8
  46. {epyt_flow-0.8.1.dist-info → epyt_flow-0.10.0.dist-info}/LICENSE +1 -1
  47. {epyt_flow-0.8.1.dist-info → epyt_flow-0.10.0.dist-info}/METADATA +7 -6
  48. {epyt_flow-0.8.1.dist-info → epyt_flow-0.10.0.dist-info}/RECORD +50 -47
  49. {epyt_flow-0.8.1.dist-info → epyt_flow-0.10.0.dist-info}/WHEEL +1 -1
  50. {epyt_flow-0.8.1.dist-info → epyt_flow-0.10.0.dist-info}/top_level.txt +0 -0
@@ -34,75 +34,75 @@ class ScadaData(Serializable):
34
34
  ----------
35
35
  sensor_config : :class:`~epyt_flow.simulation.sensor_config.SensorConfig`
36
36
  Specifications of all sensors.
37
- sensor_readings_time : `numpy.ndarray`
37
+ sensor_readings_time : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
38
38
  Time (seconds since simulation start) for each sensor reading row
39
39
  in `sensor_readings_data_raw`.
40
40
 
41
41
  This parameter is expected to be a 1d array with the same size as
42
42
  the number of rows in `sensor_readings_data_raw`.
43
- pressure_data_raw : `numpy.ndarray`, optional
43
+ pressure_data_raw : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, optional
44
44
  Raw pressure values of all nodes as a two-dimensional array --
45
45
  first dimension encodes time, second dimension pressure at nodes.
46
46
 
47
47
  The default is None,
48
- flow_data_raw : `numpy.ndarray`, optional
48
+ flow_data_raw : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, optional
49
49
  Raw flow values of all links/pipes --
50
50
  first dimension encodes time, second dimension pressure at links/pipes.
51
51
 
52
52
  The default is None.
53
- demand_data_raw : `numpy.ndarray`, optional
53
+ demand_data_raw : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, optional
54
54
  Raw demand values of all nodes --
55
55
  first dimension encodes time, second dimension demand at nodes.
56
56
 
57
57
  The default is None.
58
- node_quality_data_raw : `numpy.ndarray`, optional
58
+ node_quality_data_raw : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, optional
59
59
  Raw quality values of all nodes --
60
60
  first dimension encodes time, second dimension quality at nodes.
61
61
 
62
62
  The default is None.
63
- link_quality_data_raw : `numpy.ndarray`, optional
63
+ link_quality_data_raw : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, optional
64
64
  Raw quality values of all links/pipes --
65
65
  first dimension encodes time, second dimension quality at links/pipes.
66
66
 
67
67
  The default is None.
68
- pumps_state_data_raw : `numpy.ndarray`, optional
68
+ pumps_state_data_raw : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, optional
69
69
  States of all pumps --
70
70
  first dimension encodes time, second dimension states of pumps.
71
71
 
72
72
  The default is None.
73
- valves_state_data_raw : `numpy.ndarray`, optional
73
+ valves_state_data_raw : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, optional
74
74
  States of all valves --
75
75
  first dimension encodes time, second dimension states of valves.
76
76
 
77
77
  The default is None.
78
- tanks_volume_data_raw : `numpy.ndarray`, optional
78
+ tanks_volume_data_raw : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, optional
79
79
  Water volumes in all tanks --
80
80
  first dimension encodes time, second dimension water volume in tanks.
81
81
 
82
82
  The default is None.
83
- surface_species_concentration_raw : `numpy.ndarray`, optional
83
+ surface_species_concentration_raw : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, optional
84
84
  Raw concentrations of surface species as a tree dimensional array --
85
85
  first dimension encodes time, second dimension denotes the different surface species,
86
86
  third dimension denotes species concentrations at links/pipes.
87
87
 
88
88
  The default is None.
89
- bulk_species_node_concentration_raw : `numpy.ndarray`, optional
89
+ bulk_species_node_concentration_raw : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, optional
90
90
  Raw concentrations of bulk species at nodes as a tree dimensional array --
91
91
  first dimension encodes time, second dimension denotes the different bulk species,
92
92
  third dimension denotes species concentrations at nodes.
93
93
 
94
94
  The default is None.
95
- bulk_species_link_concentration_raw : `numpy.ndarray`, optional
95
+ bulk_species_link_concentration_raw : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, optional
96
96
  Raw concentrations of bulk species at links as a tree dimensional array --
97
97
  first dimension encodes time, second dimension denotes the different bulk species,
98
98
  third dimension denotes species concentrations at nodes.
99
99
 
100
100
  The default is None.
101
- pumps_energy_usage_data_raw : `numpy.ndarray`, optional
101
+ pumps_energy_usage_data_raw : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, optional
102
102
  Energy usage data of each pump.
103
103
 
104
104
  The default is None.
105
- pumps_efficiency_data_raw : `numpy.ndarray`, optional
105
+ pumps_efficiency_data_raw : `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, optional
106
106
  Pump efficiency data of each pump.
107
107
 
108
108
  The default is None.
@@ -142,8 +142,6 @@ class ScadaData(Serializable):
142
142
  dict[int, bsr_array]] = None,
143
143
  bulk_species_link_concentration_raw: Union[np.ndarray,
144
144
  dict[int, bsr_array]] = None,
145
- pump_energy_usage_data = None,
146
- pump_efficiency_data = None,
147
145
  pumps_energy_usage_data_raw: np.ndarray = None,
148
146
  pumps_efficiency_data_raw: np.ndarray = None,
149
147
  sensor_faults: list[SensorFault] = [],
@@ -289,11 +287,6 @@ class ScadaData(Serializable):
289
287
  raise TypeError("'frozen_sensor_config' must be an instance of 'bool' " +
290
288
  f"but not of '{type(frozen_sensor_config)}'")
291
289
 
292
- if pump_efficiency_data is not None or pump_energy_usage_data is not None:
293
- warnings.warn("Loading a file that was created with an outdated version of EPyT-Flow" +
294
- " -- support of such old files will be removed in the next release!",
295
- DeprecationWarning)
296
-
297
290
  def __raise_shape_mismatch(var_name: str) -> None:
298
291
  raise ValueError(f"Shape mismatch in '{var_name}' -- " +
299
292
  "i.e number of time steps in 'sensor_readings_time' " +
@@ -1160,7 +1153,7 @@ class ScadaData(Serializable):
1160
1153
 
1161
1154
  Returns
1162
1155
  -------
1163
- `numpy.ndarray`
1156
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
1164
1157
  Raw pressure readings.
1165
1158
  """
1166
1159
  return deepcopy(self.__pressure_data_raw)
@@ -1172,7 +1165,7 @@ class ScadaData(Serializable):
1172
1165
 
1173
1166
  Returns
1174
1167
  -------
1175
- `numpy.ndarray`
1168
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
1176
1169
  Raw flow readings.
1177
1170
  """
1178
1171
  return deepcopy(self.__flow_data_raw)
@@ -1184,7 +1177,7 @@ class ScadaData(Serializable):
1184
1177
 
1185
1178
  Returns
1186
1179
  -------
1187
- `numpy.ndarray`
1180
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
1188
1181
  Raw demand readings.
1189
1182
  """
1190
1183
  return deepcopy(self.__demand_data_raw)
@@ -1196,7 +1189,7 @@ class ScadaData(Serializable):
1196
1189
 
1197
1190
  Returns
1198
1191
  -------
1199
- `numpy.ndarray`
1192
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
1200
1193
  Raw node quality readings.
1201
1194
  """
1202
1195
  return deepcopy(self.__node_quality_data_raw)
@@ -1208,7 +1201,7 @@ class ScadaData(Serializable):
1208
1201
 
1209
1202
  Returns
1210
1203
  -------
1211
- `numpy.ndarray`
1204
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
1212
1205
  Raw link quality readings.
1213
1206
  """
1214
1207
  return deepcopy(self.__link_quality_data_raw)
@@ -1220,7 +1213,7 @@ class ScadaData(Serializable):
1220
1213
 
1221
1214
  Returns
1222
1215
  -------
1223
- `numpy.ndarray`
1216
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
1224
1217
  Sensor readings time stamps.
1225
1218
  """
1226
1219
  return deepcopy(self.__sensor_readings_time)
@@ -1232,7 +1225,7 @@ class ScadaData(Serializable):
1232
1225
 
1233
1226
  Returns
1234
1227
  -------
1235
- `numpy.ndarray`
1228
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
1236
1229
  Raw pump state readings.
1237
1230
  """
1238
1231
  return deepcopy(self.__pumps_state_data_raw)
@@ -1244,7 +1237,7 @@ class ScadaData(Serializable):
1244
1237
 
1245
1238
  Returns
1246
1239
  -------
1247
- `numpy.ndarray`
1240
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
1248
1241
  Raw valve state readings.
1249
1242
  """
1250
1243
  return deepcopy(self.__valves_state_data_raw)
@@ -1256,7 +1249,7 @@ class ScadaData(Serializable):
1256
1249
 
1257
1250
  Returns
1258
1251
  -------
1259
- `numpy.ndarray`
1252
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
1260
1253
  Raw tank volume readings.
1261
1254
  """
1262
1255
  return deepcopy(self.__tanks_volume_data_raw)
@@ -1268,7 +1261,7 @@ class ScadaData(Serializable):
1268
1261
 
1269
1262
  Returns
1270
1263
  -------
1271
- `numpy.ndarray`
1264
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
1272
1265
  Raw species concentrations.
1273
1266
  """
1274
1267
  return deepcopy(self.__surface_species_concentration_raw)
@@ -1280,7 +1273,7 @@ class ScadaData(Serializable):
1280
1273
 
1281
1274
  Returns
1282
1275
  -------
1283
- `numpy.ndarray`
1276
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
1284
1277
  Raw species concentrations.
1285
1278
  """
1286
1279
  return deepcopy(self.__bulk_species_node_concentration_raw)
@@ -1292,7 +1285,7 @@ class ScadaData(Serializable):
1292
1285
 
1293
1286
  Returns
1294
1287
  -------
1295
- `numpy.ndarray`
1288
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
1296
1289
  Raw species concentrations.
1297
1290
  """
1298
1291
  return deepcopy(self.__bulk_species_link_concentration_raw)
@@ -1304,7 +1297,7 @@ class ScadaData(Serializable):
1304
1297
 
1305
1298
  Returns
1306
1299
  -------
1307
- `numpy.ndarray`
1300
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
1308
1301
  Energy consumption of each pump.
1309
1302
  """
1310
1303
  return deepcopy(self.__pumps_energy_usage_data_raw)
@@ -1316,60 +1309,67 @@ class ScadaData(Serializable):
1316
1309
 
1317
1310
  Returns
1318
1311
  -------
1319
- `numpy.ndarray`
1312
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
1320
1313
  Pumps' efficiency.
1321
1314
  """
1322
1315
  return deepcopy(self.__pumps_efficiency_data_raw)
1323
1316
 
1317
+ def __map_sensor_to_idx(self, sensor_type: int, sensor_id: Union[str, tuple[str, str]]) -> int:
1318
+ if sensor_type == SENSOR_TYPE_NODE_PRESSURE:
1319
+ return self.__sensor_config.get_index_of_reading(pressure_sensor=sensor_id)
1320
+ elif sensor_type == SENSOR_TYPE_NODE_QUALITY:
1321
+ return self.__sensor_config.get_index_of_reading(node_quality_sensor=sensor_id)
1322
+ elif sensor_type == SENSOR_TYPE_NODE_DEMAND:
1323
+ return self.__sensor_config.get_index_of_reading(demand_sensor=sensor_id)
1324
+ elif sensor_type == SENSOR_TYPE_LINK_FLOW:
1325
+ return self.__sensor_config.get_index_of_reading(flow_sensor=sensor_id)
1326
+ elif sensor_type == SENSOR_TYPE_LINK_QUALITY:
1327
+ return self.__sensor_config.get_index_of_reading(link_quality_sensor=sensor_id)
1328
+ elif sensor_type == SENSOR_TYPE_VALVE_STATE:
1329
+ return self.__sensor_config.get_index_of_reading(valve_state_sensor=sensor_id)
1330
+ elif sensor_type == SENSOR_TYPE_PUMP_STATE:
1331
+ return self.__sensor_config.get_index_of_reading(pump_state_sensor=sensor_id)
1332
+ elif sensor_type == SENSOR_TYPE_PUMP_EFFICIENCY:
1333
+ return self.__sensor_config.get_index_of_reading(pump_efficiency_sensor=sensor_id)
1334
+ elif sensor_type == SENSOR_TYPE_PUMP_ENERGYCONSUMPTION:
1335
+ return self.__sensor_config.get_index_of_reading(pump_energyconsumption_sensor=sensor_id)
1336
+ elif sensor_type == SENSOR_TYPE_TANK_VOLUME:
1337
+ return self.__sensor_config.get_index_of_reading(tank_volume_sensor=sensor_id)
1338
+ elif sensor_type == SENSOR_TYPE_NODE_BULK_SPECIES:
1339
+ return self.__sensor_config.get_index_of_reading(bulk_species_node_sensor=sensor_id)
1340
+ elif sensor_type == SENSOR_TYPE_LINK_BULK_SPECIES:
1341
+ return self.__sensor_config.get_index_of_reading(bulk_species_link_sensor=sensor_id)
1342
+ elif sensor_type == SENSOR_TYPE_SURFACE_SPECIES:
1343
+ return self.__sensor_config.get_index_of_reading(surface_species_sensor=sensor_id)
1344
+ else:
1345
+ raise ValueError(f"Unknown sensor type '{sensor_type}'")
1346
+
1324
1347
  def __init(self):
1325
- self.__apply_sensor_noise = lambda x: x
1348
+ self.__apply_global_sensor_noise = lambda x: x
1326
1349
  if self.__sensor_noise is not None:
1327
- self.__apply_sensor_noise = self.__sensor_noise.apply
1350
+ self.__apply_global_sensor_noise = self.__sensor_noise.apply_global_uncertainty
1328
1351
 
1329
1352
  self.__apply_sensor_reading_events = []
1330
1353
  for sensor_event in self.__sensor_reading_events:
1331
- idx = None
1332
- if sensor_event.sensor_type == SENSOR_TYPE_NODE_PRESSURE:
1333
- idx = self.__sensor_config.get_index_of_reading(
1334
- pressure_sensor=sensor_event.sensor_id)
1335
- elif sensor_event.sensor_type == SENSOR_TYPE_NODE_QUALITY:
1336
- idx = self.__sensor_config.get_index_of_reading(
1337
- node_quality_sensor=sensor_event.sensor_id)
1338
- elif sensor_event.sensor_type == SENSOR_TYPE_NODE_DEMAND:
1339
- idx = self.__sensor_config.get_index_of_reading(
1340
- demand_sensor=sensor_event.sensor_id)
1341
- elif sensor_event.sensor_type == SENSOR_TYPE_LINK_FLOW:
1342
- idx = self.__sensor_config.get_index_of_reading(
1343
- flow_sensor=sensor_event.sensor_id)
1344
- elif sensor_event.sensor_type == SENSOR_TYPE_LINK_QUALITY:
1345
- idx = self.__sensor_config.get_index_of_reading(
1346
- link_quality_sensor=sensor_event.sensor_id)
1347
- elif sensor_event.sensor_type == SENSOR_TYPE_VALVE_STATE:
1348
- idx = self.__sensor_config.get_index_of_reading(
1349
- valve_state_sensor=sensor_event.sensor_id)
1350
- elif sensor_event.sensor_type == SENSOR_TYPE_PUMP_STATE:
1351
- idx = self.__sensor_config.get_index_of_reading(
1352
- pump_state_sensor=sensor_event.sensor_id)
1353
- elif sensor_event.sensor_type == SENSOR_TYPE_PUMP_EFFICIENCY:
1354
- idx = self.__sensor_config.get_index_of_reading(
1355
- pump_efficiency_sensor=sensor_event.sensor_id)
1356
- elif sensor_event.sensor_type == SENSOR_TYPE_PUMP_ENERGYCONSUMPTION:
1357
- idx = self.__sensor_config.get_index_of_reading(
1358
- pump_energyconsumption_sensor=sensor_event.sensor_id)
1359
- elif sensor_event.sensor_type == SENSOR_TYPE_TANK_VOLUME:
1360
- idx = self.__sensor_config.get_index_of_reading(
1361
- tank_volume_sensor=sensor_event.sensor_id)
1362
- elif sensor_event.sensor_type == SENSOR_TYPE_NODE_BULK_SPECIES:
1363
- idx = self.__sensor_config.get_index_of_reading(
1364
- bulk_species_node_sensor=sensor_event.sensor_id)
1354
+ sensor_id = sensor_event.sensor_id
1355
+ if sensor_event.sensor_type == SENSOR_TYPE_NODE_BULK_SPECIES:
1356
+ for species_id, node_sensors_id in self.__sensor_config.bulk_species_node_sensors.items():
1357
+ if sensor_id in node_sensors_id:
1358
+ idx = self.__map_sensor_to_idx(sensor_event.sensor_type, (species_id, sensor_id))
1359
+ self.__apply_sensor_reading_events.append((idx, sensor_event.apply))
1365
1360
  elif sensor_event.sensor_type == SENSOR_TYPE_LINK_BULK_SPECIES:
1366
- idx = self.__sensor_config.get_index_of_reading(
1367
- bulk_species_link_sensor=sensor_event.sensor_id)
1361
+ for species_id, link_sensors_id in self.__sensor_config.bulk_species_link_sensors.items():
1362
+ if sensor_id in link_sensors_id:
1363
+ idx = self.__map_sensor_to_idx(sensor_event.sensor_type, (species_id, sensor_id))
1364
+ self.__apply_sensor_reading_events.append((idx, sensor_event.apply))
1368
1365
  elif sensor_event.sensor_type == SENSOR_TYPE_SURFACE_SPECIES:
1369
- idx = self.__sensor_config.get_index_of_reading(
1370
- surface_species_sensor=sensor_event.sensor_id)
1371
-
1372
- self.__apply_sensor_reading_events.append((idx, sensor_event.apply))
1366
+ for species_id, link_sensors_id in self.__sensor_config.surface_species_sensors.items():
1367
+ if sensor_id in link_sensors_id:
1368
+ idx = self.__map_sensor_to_idx(sensor_event.sensor_type, (species_id, sensor_id))
1369
+ self.__apply_sensor_reading_events.append((idx, sensor_event.apply))
1370
+ else:
1371
+ idx = self.__map_sensor_to_idx(sensor_event.sensor_type, sensor_event.sensor_id)
1372
+ self.__apply_sensor_reading_events.append((idx, sensor_event.apply))
1373
1373
 
1374
1374
  self.__sensor_readings = None
1375
1375
 
@@ -1959,7 +1959,7 @@ class ScadaData(Serializable):
1959
1959
 
1960
1960
  if self.__pumps_energy_usage_data_raw is not None:
1961
1961
  self.__pumps_energy_usage_data_raw = np.concatenate(
1962
- (self.__pumps_energy_usage_data_raw, other.pumps_energy_usage_data_raw),
1962
+ (self.__pumps_energy_usage_data_raw, other.pumps_energyconsumption_data_raw),
1963
1963
  axis=0)
1964
1964
 
1965
1965
  if self.__pumps_efficiency_data_raw is not None:
@@ -1974,7 +1974,7 @@ class ScadaData(Serializable):
1974
1974
 
1975
1975
  Returns
1976
1976
  -------
1977
- `numpy.ndarray`
1977
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
1978
1978
  Final sensor readings.
1979
1979
  """
1980
1980
  # Comute clean sensor readings
@@ -2026,18 +2026,21 @@ class ScadaData(Serializable):
2026
2026
  sensor_readings = np.concatenate(data, axis=1)
2027
2027
 
2028
2028
  # Apply sensor uncertainties
2029
- state_sensors_idx = [] # Pump states and valve states are NOT affected!
2030
- for link_id in self.sensor_config.pump_state_sensors:
2031
- state_sensors_idx.append(
2032
- self.__sensor_config.get_index_of_reading(pump_state_sensor=link_id))
2033
- for link_id in self.sensor_config.valve_state_sensors:
2034
- state_sensors_idx.append(
2035
- self.__sensor_config.get_index_of_reading(valve_state_sensor=link_id))
2029
+ if self.__sensor_noise is not None:
2030
+ state_sensors_idx = [] # Pump states and valve states are NOT affected!
2031
+ for link_id in self.sensor_config.pump_state_sensors:
2032
+ state_sensors_idx.append(
2033
+ self.__sensor_config.get_index_of_reading(pump_state_sensor=link_id))
2034
+ for link_id in self.sensor_config.valve_state_sensors:
2035
+ state_sensors_idx.append(
2036
+ self.__sensor_config.get_index_of_reading(valve_state_sensor=link_id))
2036
2037
 
2037
- mask = np.ones(sensor_readings.shape[1], dtype=bool)
2038
- mask[state_sensors_idx] = False
2038
+ mask = np.ones(sensor_readings.shape[1], dtype=bool)
2039
+ mask[state_sensors_idx] = False
2040
+ sensor_readings[:, mask] = self.__apply_global_sensor_noise(sensor_readings[:, mask])
2039
2041
 
2040
- sensor_readings[:, mask] = self.__apply_sensor_noise(sensor_readings[:, mask])
2042
+ sensor_readings = self.__sensor_noise.apply_local_uncertainty(self.__map_sensor_to_idx,
2043
+ sensor_readings)
2041
2044
 
2042
2045
  # Apply sensor faults
2043
2046
  for idx, f in self.__apply_sensor_reading_events:
@@ -2075,7 +2078,7 @@ class ScadaData(Serializable):
2075
2078
 
2076
2079
  Returns
2077
2080
  -------
2078
- `numpy.ndarray`
2081
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
2079
2082
  Pressure sensor readings.
2080
2083
  """
2081
2084
  if self.__sensor_config.pressure_sensors == []:
@@ -2125,14 +2128,14 @@ class ScadaData(Serializable):
2125
2128
  i.e. a plot can not be shown and saved to a file at the same time!
2126
2129
 
2127
2130
  The default is None.
2128
- ax : `matplotlib.axes.Axes`, optional
2131
+ ax : `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_, optional
2129
2132
  If not None, 'ax' is used for plotting.
2130
2133
 
2131
2134
  The default is None.
2132
2135
 
2133
2136
  Returns
2134
2137
  -------
2135
- `matplotlib.axes.Axes`
2138
+ `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_
2136
2139
  Plot.
2137
2140
  """
2138
2141
  data = self.get_data_pressures(sensor_locations)
@@ -2162,7 +2165,7 @@ class ScadaData(Serializable):
2162
2165
 
2163
2166
  Returns
2164
2167
  -------
2165
- `numpy.ndarray`
2168
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
2166
2169
  Flow sensor readings.
2167
2170
  """
2168
2171
  if self.__sensor_config.flow_sensors == []:
@@ -2212,14 +2215,14 @@ class ScadaData(Serializable):
2212
2215
  i.e. a plot can not be shown and saved to a file at the same time!
2213
2216
 
2214
2217
  The default is None.
2215
- ax : `matplotlib.axes.Axes`, optional
2218
+ ax : `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_, optional
2216
2219
  If not None, 'ax' is used for plotting.
2217
2220
 
2218
2221
  The default is None.
2219
2222
 
2220
2223
  Returns
2221
2224
  -------
2222
- `matplotlib.axes.Axes`
2225
+ `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_
2223
2226
  Plot.
2224
2227
  """
2225
2228
  data = self.get_data_flows(sensor_locations)
@@ -2248,7 +2251,7 @@ class ScadaData(Serializable):
2248
2251
 
2249
2252
  Returns
2250
2253
  -------
2251
- `numpy.ndarray`
2254
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
2252
2255
  Demand sensor readings.
2253
2256
  """
2254
2257
  if self.__sensor_config.demand_sensors == []:
@@ -2298,14 +2301,14 @@ class ScadaData(Serializable):
2298
2301
  i.e. a plot can not be shown and saved to a file at the same time!
2299
2302
 
2300
2303
  The default is None.
2301
- ax : `matplotlib.axes.Axes`, optional
2304
+ ax : `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_, optional
2302
2305
  If not None, 'ax' is used for plotting.
2303
2306
 
2304
2307
  The default is None.
2305
2308
 
2306
2309
  Returns
2307
2310
  -------
2308
- `matplotlib.axes.Axes`
2311
+ `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_
2309
2312
  Plot.
2310
2313
  """
2311
2314
  data = self.get_data_demands(sensor_locations)
@@ -2334,7 +2337,7 @@ class ScadaData(Serializable):
2334
2337
 
2335
2338
  Returns
2336
2339
  -------
2337
- `numpy.ndarray`
2340
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
2338
2341
  Node quality sensor readings.
2339
2342
  """
2340
2343
  if self.__sensor_config.quality_node_sensors == []:
@@ -2386,14 +2389,14 @@ class ScadaData(Serializable):
2386
2389
  i.e. a plot can not be shown and saved to a file at the same time!
2387
2390
 
2388
2391
  The default is None.
2389
- ax : `matplotlib.axes.Axes`, optional
2392
+ ax : `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_, optional
2390
2393
  If not None, 'ax' is used for plotting.
2391
2394
 
2392
2395
  The default is None.
2393
2396
 
2394
2397
  Returns
2395
2398
  -------
2396
- `matplotlib.axes.Axes`
2399
+ `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_
2397
2400
  Plot.
2398
2401
  """
2399
2402
  data = self.get_data_nodes_quality(sensor_locations)
@@ -2423,7 +2426,7 @@ class ScadaData(Serializable):
2423
2426
 
2424
2427
  Returns
2425
2428
  -------
2426
- `numpy.ndarray`
2429
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
2427
2430
  Link quality sensor readings.
2428
2431
  """
2429
2432
  if self.__sensor_config.quality_link_sensors == []:
@@ -2475,14 +2478,14 @@ class ScadaData(Serializable):
2475
2478
  i.e. a plot can not be shown and saved to a file at the same time!
2476
2479
 
2477
2480
  The default is None.
2478
- ax : `matplotlib.axes.Axes`, optional
2481
+ ax : `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_, optional
2479
2482
  If not None, 'ax' is used for plotting.
2480
2483
 
2481
2484
  The default is None.
2482
2485
 
2483
2486
  Returns
2484
2487
  -------
2485
- `matplotlib.axes.Axes`
2488
+ `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_
2486
2489
  Plot.
2487
2490
  """
2488
2491
  data = self.get_data_links_quality(sensor_locations)
@@ -2512,7 +2515,7 @@ class ScadaData(Serializable):
2512
2515
 
2513
2516
  Returns
2514
2517
  -------
2515
- `numpy.ndarray`
2518
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
2516
2519
  Pump state sensor readings.
2517
2520
  """
2518
2521
  if self.__sensor_config.pump_state_sensors == []:
@@ -2563,14 +2566,14 @@ class ScadaData(Serializable):
2563
2566
  i.e. a plot can not be shown and saved to a file at the same time!
2564
2567
 
2565
2568
  The default is None.
2566
- ax : `matplotlib.axes.Axes`, optional
2569
+ ax : `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_, optional
2567
2570
  If not None, 'ax' is used for plotting.
2568
2571
 
2569
2572
  The default is None.
2570
2573
 
2571
2574
  Returns
2572
2575
  -------
2573
- `matplotlib.axes.Axes`
2576
+ `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_
2574
2577
  Plot.
2575
2578
  """
2576
2579
  data = self.get_data_pumps_state(sensor_locations)
@@ -2599,7 +2602,7 @@ class ScadaData(Serializable):
2599
2602
 
2600
2603
  Returns
2601
2604
  -------
2602
- `numpy.ndarray`
2605
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
2603
2606
  Pump efficiency sensor readings.
2604
2607
  """
2605
2608
  if self.__sensor_config.pump_efficiency_sensors == []:
@@ -2651,14 +2654,14 @@ class ScadaData(Serializable):
2651
2654
  i.e. a plot can not be shown and saved to a file at the same time!
2652
2655
 
2653
2656
  The default is None.
2654
- ax : `matplotlib.axes.Axes`, optional
2657
+ ax : `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_, optional
2655
2658
  If not None, 'ax' is used for plotting.
2656
2659
 
2657
2660
  The default is None.
2658
2661
 
2659
2662
  Returns
2660
2663
  -------
2661
- `matplotlib.axes.Axes`
2664
+ `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_
2662
2665
  Plot.
2663
2666
  """
2664
2667
  data = self.get_data_pumps_efficiency(sensor_locations)
@@ -2668,7 +2671,7 @@ class ScadaData(Serializable):
2668
2671
  return plot_timeseries_data(data.T, labels=[f"Pump {n_id}"
2669
2672
  for n_id in pump_efficiency_sensors],
2670
2673
  x_axis_label=self.__get_x_axis_label(),
2671
- y_axis_label="Efficiency in $\%$",
2674
+ y_axis_label="Efficiency in $%$",
2672
2675
  show=show, save_to_file=save_to_file, ax=ax)
2673
2676
 
2674
2677
  def get_data_pumps_energyconsumption(self, sensor_locations: list[str] = None) -> np.ndarray:
@@ -2687,7 +2690,7 @@ class ScadaData(Serializable):
2687
2690
 
2688
2691
  Returns
2689
2692
  -------
2690
- `numpy.ndarray`
2693
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
2691
2694
  Pump energy consumption sensor readings.
2692
2695
  """
2693
2696
  if self.__sensor_config.pump_energyconsumption_sensors == []:
@@ -2739,14 +2742,14 @@ class ScadaData(Serializable):
2739
2742
  i.e. a plot can not be shown and saved to a file at the same time!
2740
2743
 
2741
2744
  The default is None.
2742
- ax : `matplotlib.axes.Axes`, optional
2745
+ ax : `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_, optional
2743
2746
  If not None, 'ax' is used for plotting.
2744
2747
 
2745
2748
  The default is None.
2746
2749
 
2747
2750
  Returns
2748
2751
  -------
2749
- `matplotlib.axes.Axes`
2752
+ `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_
2750
2753
  Plot.
2751
2754
  """
2752
2755
  data = self.get_data_pumps_energyconsumption(sensor_locations)
@@ -2774,7 +2777,7 @@ class ScadaData(Serializable):
2774
2777
 
2775
2778
  Returns
2776
2779
  -------
2777
- `numpy.ndarray`
2780
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
2778
2781
  Valve state sensor readings.
2779
2782
  """
2780
2783
  if self.__sensor_config.valve_state_sensors == []:
@@ -2825,14 +2828,14 @@ class ScadaData(Serializable):
2825
2828
  i.e. a plot can not be shown and saved to a file at the same time!
2826
2829
 
2827
2830
  The default is None.
2828
- ax : `matplotlib.axes.Axes`, optional
2831
+ ax : `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_, optional
2829
2832
  If not None, 'ax' is used for plotting.
2830
2833
 
2831
2834
  The default is None.
2832
2835
 
2833
2836
  Returns
2834
2837
  -------
2835
- `matplotlib.axes.Axes`
2838
+ `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_
2836
2839
  Plot.
2837
2840
  """
2838
2841
  data = self.get_data_valves_state(sensor_locations)
@@ -2861,7 +2864,7 @@ class ScadaData(Serializable):
2861
2864
 
2862
2865
  Returns
2863
2866
  -------
2864
- `numpy.ndarray`
2867
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
2865
2868
  Water tanks volume sensor readings.
2866
2869
  """
2867
2870
  if self.__sensor_config.tank_volume_sensors == []:
@@ -2912,14 +2915,14 @@ class ScadaData(Serializable):
2912
2915
  i.e. a plot can not be shown and saved to a file at the same time!
2913
2916
 
2914
2917
  The default is None.
2915
- ax : `matplotlib.axes.Axes`, optional
2918
+ ax : `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_, optional
2916
2919
  If not None, 'ax' is used for plotting.
2917
2920
 
2918
2921
  The default is None.
2919
2922
 
2920
2923
  Returns
2921
2924
  -------
2922
- `matplotlib.axes.Axes`
2925
+ `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_
2923
2926
  Plot.
2924
2927
  """
2925
2928
  data = self.get_data_tanks_water_volume(sensor_locations)
@@ -2953,7 +2956,7 @@ class ScadaData(Serializable):
2953
2956
 
2954
2957
  Returns
2955
2958
  -------
2956
- `numpy.ndarray`
2959
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
2957
2960
  Surface species concentration sensor readings.
2958
2961
  """
2959
2962
  if self.__sensor_config.surface_species_sensors == {}:
@@ -3014,14 +3017,14 @@ class ScadaData(Serializable):
3014
3017
  i.e. a plot can not be shown and saved to a file at the same time!
3015
3018
 
3016
3019
  The default is None.
3017
- ax : `matplotlib.axes.Axes`, optional
3020
+ ax : `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_, optional
3018
3021
  If not None, 'ax' is used for plotting.
3019
3022
 
3020
3023
  The default is None.
3021
3024
 
3022
3025
  Returns
3023
3026
  -------
3024
- `matplotlib.axes.Axes`
3027
+ `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_
3025
3028
  Plot.
3026
3029
  """
3027
3030
  data = self.get_data_surface_species_concentration(surface_species_sensor_locations)
@@ -3069,7 +3072,7 @@ class ScadaData(Serializable):
3069
3072
 
3070
3073
  Returns
3071
3074
  -------
3072
- `numpy.ndarray`
3075
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
3073
3076
  Bulk species concentration sensor readings.
3074
3077
  """
3075
3078
  if self.__sensor_config.bulk_species_node_sensors == {}:
@@ -3130,14 +3133,14 @@ class ScadaData(Serializable):
3130
3133
  i.e. a plot can not be shown and saved to a file at the same time!
3131
3134
 
3132
3135
  The default is None.
3133
- ax : `matplotlib.axes.Axes`, optional
3136
+ ax : `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_, optional
3134
3137
  If not None, 'ax' is used for plotting.
3135
3138
 
3136
3139
  The default is None.
3137
3140
 
3138
3141
  Returns
3139
3142
  -------
3140
- `matplotlib.axes.Axes`
3143
+ `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_
3141
3144
  Plot.
3142
3145
  """
3143
3146
  data = self.get_data_bulk_species_node_concentration(bulk_species_node_sensors)
@@ -3184,7 +3187,7 @@ class ScadaData(Serializable):
3184
3187
 
3185
3188
  Returns
3186
3189
  -------
3187
- `numpy.ndarray`
3190
+ `numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_
3188
3191
  Bulk species concentration sensor readings.
3189
3192
  """
3190
3193
  if self.__sensor_config.bulk_species_link_sensors == {}:
@@ -3245,14 +3248,14 @@ class ScadaData(Serializable):
3245
3248
  i.e. a plot can not be shown and saved to a file at the same time!
3246
3249
 
3247
3250
  The default is None.
3248
- ax : `matplotlib.axes.Axes`, optional
3251
+ ax : `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_, optional
3249
3252
  If not None, 'ax' is used for plotting.
3250
3253
 
3251
3254
  The default is None.
3252
3255
 
3253
3256
  Returns
3254
3257
  -------
3255
- `matplotlib.axes.Axes`
3258
+ `matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html>`_
3256
3259
  Plot.
3257
3260
  """
3258
3261
  data = self.get_data_bulk_species_link_concentration(bulk_species_link_sensors)
@@ -3296,7 +3299,7 @@ class ScadaData(Serializable):
3296
3299
 
3297
3300
  Returns
3298
3301
  -------
3299
- `pandas.DataFrame`
3302
+ `pandas.DataFrame <https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html>`_
3300
3303
  Exported data.
3301
3304
  """
3302
3305
  from .scada_data_export import ScadaDataExport