epyt-flow 0.4.0__py3-none-any.whl → 0.6.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/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.0
1
+ 0.6.0
@@ -458,10 +458,14 @@ def load_scenario(return_test_scenario: bool, download_dir: str = None,
458
458
  ltown_config = load_ltown(use_realistic_demands=True, include_default_sensor_placement=True,
459
459
  verbose=verbose)
460
460
 
461
- # Set simulation duration
461
+ # Set simulation duration and other general parameters such as the demand model
462
462
  general_params = {"simulation_duration": to_seconds(days=365), # One year
463
463
  "hydraulic_time_step": to_seconds(minutes=5), # 5min time steps
464
- "reporting_time_step": to_seconds(minutes=5)} | ltown_config.general_params
464
+ "reporting_time_step": to_seconds(minutes=5),
465
+ "demand_model": {"type": "PDA", "pressure_min": 0,
466
+ "pressure_required": 0.1,
467
+ "pressure_exponent": 0.5}
468
+ } | ltown_config.general_params
465
469
 
466
470
  # Add events
467
471
  start_time = START_TIME_TEST if return_test_scenario is True else START_TIME_TRAIN
@@ -30,7 +30,7 @@ from .leakdb_data import NET1_LEAKAGES, HANOI_LEAKAGES
30
30
  from ...utils import get_temp_folder, to_seconds, unpack_zip_archive, create_path_if_not_exist, \
31
31
  download_if_necessary
32
32
  from ...metrics import f1_score, true_positive_rate, true_negative_rate
33
- from ...simulation import ScenarioSimulator
33
+ from ...simulation import ScenarioSimulator, ToolkitConstants
34
34
  from ...simulation.events import AbruptLeakage, IncipientLeakage
35
35
  from ...simulation import ScenarioConfig
36
36
  from ...simulation.scada import ScadaData
@@ -424,14 +424,19 @@ def load_scenarios(scenarios_id: list[int], use_net1: bool = True,
424
424
  download_dir = download_dir if download_dir is not None else get_temp_folder()
425
425
  network_config = load_network(download_dir)
426
426
 
427
- # Set simulation duration
427
+ # Set simulation duration and other general parameters such as the demand model and flow units
428
428
  hydraulic_time_step = to_seconds(minutes=30) # 30min time steps
429
429
  general_params = {"simulation_duration": to_seconds(days=365), # One year
430
430
  "hydraulic_time_step": hydraulic_time_step,
431
- "reporting_time_step": hydraulic_time_step} | network_config.general_params
431
+ "reporting_time_step": hydraulic_time_step,
432
+ "flow_units_id": ToolkitConstants.EN_CMH,
433
+ "demand_model": {"type": "PDA", "pressure_min": 0,
434
+ "pressure_required": 0.1,
435
+ "pressure_exponent": 0.5}
436
+ } | network_config.general_params
432
437
 
433
438
  # Add demand patterns
434
- def gen_dem(download_dir, use_net1):
439
+ def gen_dem(download_dir):
435
440
  # Taken from https://github.com/KIOS-Research/LeakDB/blob/master/CCWI-WDSA2018/Dataset_Generator_Py3/demandGenerator.py
436
441
  week_pat = scipy.io.loadmat(os.path.join(download_dir, "weekPat_30min.mat"))
437
442
  a_w = week_pat['Aw']
@@ -503,10 +508,7 @@ def load_scenarios(scenarios_id: list[int], use_net1: bool = True,
503
508
 
504
509
  if not os.path.exists(f_inp_in):
505
510
  with ScenarioSimulator(f_inp_in=network_config.f_inp_in) as wdn:
506
- wdn.epanet_api.setTimeHydraulicStep(general_params["hydraulic_time_step"])
507
- wdn.epanet_api.setTimeSimulationDuration(general_params["simulation_duration"])
508
- wdn.epanet_api.setTimePatternStep(general_params["hydraulic_time_step"])
509
- wdn.epanet_api.setFlowUnitsCMH()
511
+ wdn.set_general_parameters(**general_params)
510
512
 
511
513
  wdn.epanet_api.deletePatternsAll()
512
514
 
@@ -519,7 +521,7 @@ def load_scenarios(scenarios_id: list[int], use_net1: bool = True,
519
521
  node_idx = wdn.epanet_api.getNodeIndex(node_id)
520
522
  base_demand = wdn.epanet_api.getNodeBaseDemands(node_idx)[1][0]
521
523
 
522
- my_demand_pattern = np.array(gen_dem(download_dir, use_net1))
524
+ my_demand_pattern = np.array(gen_dem(download_dir))
523
525
 
524
526
  wdn.set_node_demand_pattern(node_id=node_id, base_demand=base_demand,
525
527
  demand_pattern_id=f"demand_{node_id}",
@@ -56,8 +56,7 @@ def get_default_hydraulic_options(flow_units_id: int = None) -> dict:
56
56
  Dictionary with default hydraulics options that can be passed to
57
57
  :func:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator.set_general_parameters`.
58
58
  """
59
- params = {"demand_model": {"type": "PDA", "pressure_min": 0, "pressure_required": 0.1,
60
- "pressure_exponent": 0.5}}
59
+ params = {}
61
60
  if flow_units_id is not None:
62
61
  params |= {"flow_units_id": flow_units_id}
63
62
 
@@ -2,7 +2,9 @@
2
2
  Module provides a base class for control environments.
3
3
  """
4
4
  from abc import abstractmethod, ABC
5
- from copy import deepcopy
5
+ from typing import Union
6
+ import warnings
7
+ import numpy as np
6
8
 
7
9
  from ..simulation import ScenarioSimulator, ScenarioConfig, ScadaData
8
10
 
@@ -32,8 +34,13 @@ class ScenarioControlEnv(ABC):
32
34
  def autoreset(self) -> bool:
33
35
  """
34
36
  True, if environment automatically resets after it terminated.
37
+
38
+ Returns
39
+ -------
40
+ `bool`
41
+ True, if environment automatically resets after it terminated.
35
42
  """
36
- return deepcopy(self.__autoreset)
43
+ return self.__autoreset
37
44
 
38
45
  def __enter__(self):
39
46
  return self
@@ -58,6 +65,11 @@ class ScenarioControlEnv(ABC):
58
65
  def reset(self) -> ScadaData:
59
66
  """
60
67
  Resets the environment (i.e. simulation).
68
+
69
+ Returns
70
+ -------
71
+ :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`
72
+ Current SCADA data (i.e. sensor readings).
61
73
  """
62
74
  if self._scenario_sim is not None:
63
75
  self._scenario_sim.close()
@@ -83,19 +95,102 @@ class ScenarioControlEnv(ABC):
83
95
  else:
84
96
  return None, True
85
97
 
98
+ def set_pump_status(self, pump_id: str, status: int) -> None:
99
+ """
100
+ Sets the status of a pump.
101
+
102
+ Parameters
103
+ ----------
104
+ pump_id : `str`
105
+ ID of the pump for which the status is set.
106
+ status : `int`
107
+ New status of the pump -- either active (i.e. open) or inactive (i.e. closed).
108
+
109
+ Must be one of the following constants defined in
110
+ :class:`~epyt_flow.simulation.events.actuator_events.ActuatorConstants`:
111
+
112
+ - EN_CLOSED = 0
113
+ - EN_OPEN = 1
114
+ """
115
+ pump_idx = self._scenario_sim.epanet_api.getLinkPumpNameID().index(pump_id)
116
+ pump_link_idx = self._scenario_sim.epanet_api.getLinkPumpIndex(pump_idx + 1)
117
+ self._scenario_sim.epanet_api.setLinkStatus(pump_link_idx, status)
118
+
119
+ def set_pump_speed(self, pump_id: str, speed: float) -> None:
120
+ """
121
+ Sets the speed of a pump.
122
+
123
+ Parameters
124
+ ----------
125
+ pump_id : `str`
126
+ ID of the pump for which the pump speed is set.
127
+ speed : `float`
128
+ New pump speed.
129
+ """
130
+ pump_idx = self._scenario_sim.epanet_api.getLinkPumpNameID().index(pump_id)
131
+ pattern_idx = self._scenario_sim.epanet_api.getLinkPumpPatternIndex(pump_idx + 1)
132
+
133
+ if pattern_idx == 0:
134
+ warnings.warn(f"No pattern for pump '{pump_id}' found -- a new pattern is created")
135
+ pattern_idx = self._scenario_sim.epanet_api.addPattern(f"pump_speed_{pump_id}")
136
+ self._scenario_sim.epanet_api.setLinkPumpPatternIndex(pattern_idx)
137
+
138
+ self._scenario_sim.epanet_api.setPattern(pattern_idx, np.array([speed]))
139
+
140
+ def set_valve_status(self, valve_id: str, status: int) -> None:
141
+ """
142
+ Sets the status of a valve.
143
+
144
+ Parameters
145
+ ----------
146
+ valve_id : `str`
147
+ ID of the valve for which the status is set.
148
+ status : `int`
149
+ New status of the valve -- either open or closed.
150
+
151
+ Must be one of the following constants defined in
152
+ :class:`~epyt_flow.simulation.events.actuator_events.ActuatorConstants`:
153
+
154
+ - EN_CLOSED = 0
155
+ - EN_OPEN = 1
156
+ """
157
+ valve_idx = self._scenario_sim.epanet_api.getLinkValveNameID().index(valve_id)
158
+ valve_link_idx = self._scenario_sim.epanet_api.getLinkValveIndex()[valve_idx]
159
+ self._scenario_sim.epanet_api.setLinkStatus(valve_link_idx, status)
160
+
161
+ def set_node_quality_source_value(self, node_id: str, pattern_id: str,
162
+ qual_value: float) -> None:
163
+ """
164
+ Sets the quality source at a particular node to a specific value -- e.g.
165
+ setting the chlorine concentration injection to a specified value.
166
+
167
+ Parameters
168
+ ----------
169
+ node_id : `str`
170
+ ID of the node.
171
+ pattern_id : `str`
172
+ ID of the quality pattern at the specific node.
173
+ qual_value : `float`
174
+ New quality source value.
175
+ """
176
+ node_idx = self._scenario_sim.epanet_api.getNodeIndex(node_id)
177
+ pattern_idx = self._scenario_sim.epanet_api.getPatternIndex(pattern_id)
178
+ self._scenario_sim.epanet_api.setNodeSourceQuality(node_idx, 1)
179
+ self._scenario_sim.epanet_api.setPattern(pattern_idx, np.array([qual_value]))
180
+
86
181
  @abstractmethod
87
- def step(self) -> tuple[ScadaData, float, bool]:
182
+ def step(self, *actions) -> Union[tuple[ScadaData, float, bool], tuple[ScadaData, float]]:
88
183
  """
89
184
  Performs the next step by applying an action and observing
90
185
  the consequences (SCADA data, reward, terminated).
91
186
 
92
187
  Note that `terminated` is only returned if `autoreset=False` otherwise
93
- only SCADA data and reward are returned.
188
+ only the current SCADA data and reward are returned.
94
189
 
95
190
  Returns
96
191
  -------
97
- `(ScadaData, float, bool)`
98
- Triple of observations (:class:`~epyt_flow.simuation.scada.scada_data.ScadaData`),
192
+ `(` :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` `, float, bool)` or `(` :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` `, float)`
193
+ Triple or tuple of observations (:class:`~epyt_flow.simulation.scada.scada_data.ScadaData`),
99
194
  reward (`float`), and terminated (`bool`).
100
195
  """
101
196
  raise NotImplementedError()
@@ -4,6 +4,7 @@ Module provides functions and classes for serialization.
4
4
  from typing import Any, Union
5
5
  from abc import abstractmethod, ABC
6
6
  from io import BufferedIOBase
7
+ import pathlib
7
8
  import importlib
8
9
  import json
9
10
  import gzip
@@ -97,7 +98,9 @@ class Serializable(ABC):
97
98
  Base class for a serializable class -- must be used in conjunction with the
98
99
  :func:`~epyt_flow.serialization.serializable` decorator.
99
100
  """
100
- def __init__(self, **kwds):
101
+ def __init__(self, _parent_path: str = "", **kwds):
102
+ self._parent_path = _parent_path
103
+
101
104
  super().__init__(**kwds)
102
105
 
103
106
  @abstractmethod
@@ -376,12 +379,19 @@ def load_from_file(f_in: str, use_compression: bool = True) -> Any:
376
379
  `Any`
377
380
  Deserialized data.
378
381
  """
382
+ inst = None
383
+
379
384
  if use_compression is False:
380
385
  with open(f_in, "rb") as f:
381
- return umsgpack.unpack(f, ext_handlers=ext_handler_unpack)
386
+ inst = load(f.read())
382
387
  else:
383
388
  with gzip.open(f_in, "rb") as f:
384
- return load(f.read())
389
+ inst = load(f.read())
390
+
391
+ if isinstance(inst, Serializable):
392
+ inst._parent_path = pathlib.Path(f_in).parent.resolve()
393
+
394
+ return inst
385
395
 
386
396
 
387
397
  def save_to_file(f_out: str, data: Any, use_compression: bool = True) -> None:
@@ -98,7 +98,7 @@ class AdvancedControlModule(ABC):
98
98
  - EN_OPEN = 1
99
99
  """
100
100
  valve_idx = self._epanet_api.getLinkValveNameID().index(valve_id)
101
- valve_link_idx = self._epanet_api.getLinkValveIndex(valve_idx + 1)
101
+ valve_link_idx = self._epanet_api.getLinkValveIndex()[valve_idx]
102
102
  self._epanet_api.setLinkStatus(valve_link_idx, status)
103
103
 
104
104
  def set_node_quality_source_value(self, node_id: str, pattern_id: str,
@@ -12,6 +12,7 @@ from ..sensor_config import SensorConfig, is_flowunit_simetric, massunit_to_str,
12
12
  AREA_UNIT_CM2, AREA_UNIT_FT2, AREA_UNIT_M2, \
13
13
  SENSOR_TYPE_LINK_FLOW, SENSOR_TYPE_LINK_QUALITY, SENSOR_TYPE_NODE_DEMAND, \
14
14
  SENSOR_TYPE_NODE_PRESSURE, SENSOR_TYPE_NODE_QUALITY, SENSOR_TYPE_PUMP_STATE, \
15
+ SENSOR_TYPE_PUMP_EFFICIENCY, SENSOR_TYPE_PUMP_ENERGYCONSUMPTION, \
15
16
  SENSOR_TYPE_TANK_VOLUME, SENSOR_TYPE_VALVE_STATE, SENSOR_TYPE_NODE_BULK_SPECIES, \
16
17
  SENSOR_TYPE_LINK_BULK_SPECIES, SENSOR_TYPE_SURFACE_SPECIES
17
18
  from ..events import SensorFault, SensorReadingAttack, SensorReadingEvent
@@ -92,11 +93,11 @@ class ScadaData(Serializable):
92
93
  third dimension denotes species concentrations at nodes.
93
94
 
94
95
  The default is None.
95
- pump_energy_usage_data : `numpy.ndarray`, optional
96
+ pumps_energy_usage_data_raw : `numpy.ndarray`, optional
96
97
  Energy usage data of each pump.
97
98
 
98
99
  The default is None.
99
- pump_efficiency_data : `numpy.ndarray`, optional
100
+ pumps_efficiency_data_raw : `numpy.ndarray`, optional
100
101
  Pump efficiency data of each pump.
101
102
 
102
103
  The default is None.
@@ -130,8 +131,10 @@ class ScadaData(Serializable):
130
131
  surface_species_concentration_raw: np.ndarray = None,
131
132
  bulk_species_node_concentration_raw: np.ndarray = None,
132
133
  bulk_species_link_concentration_raw: np.ndarray = None,
133
- pump_energy_usage_data: np.ndarray = None,
134
- pump_efficiency_data: np.ndarray = None,
134
+ pump_energy_usage_data = None,
135
+ pump_efficiency_data = None,
136
+ pumps_energy_usage_data_raw: np.ndarray = None,
137
+ pumps_efficiency_data_raw: np.ndarray = None,
135
138
  sensor_faults: list[SensorFault] = [],
136
139
  sensor_reading_attacks: list[SensorReadingAttack] = [],
137
140
  sensor_reading_events: list[SensorReadingEvent] = [],
@@ -195,14 +198,14 @@ class ScadaData(Serializable):
195
198
  raise TypeError("'bulk_species_link_concentration_raw' must be an instance of " +
196
199
  "'numpy.ndarray' but not of " +
197
200
  f"'{type(bulk_species_link_concentration_raw)}'")
198
- if pump_energy_usage_data is not None:
199
- if not isinstance(pump_energy_usage_data, np.ndarray):
200
- raise TypeError("'pump_energy_usage_data' must be an instance of 'numpy.ndarray' " +
201
- f"but not of '{type(pump_energy_usage_data)}'")
202
- if pump_efficiency_data is not None:
203
- if not isinstance(pump_efficiency_data, np.ndarray):
204
- raise TypeError("'pump_efficiency_data' must be an instance of 'numpy.ndarray' " +
205
- f"but not of '{type(pump_efficiency_data)}'")
201
+ if pumps_energy_usage_data_raw is not None:
202
+ if not isinstance(pumps_energy_usage_data_raw, np.ndarray):
203
+ raise TypeError("'pumps_energy_usage_data_raw' must be an instance of 'numpy.ndarray' " +
204
+ f"but not of '{type(pumps_energy_usage_data_raw)}'")
205
+ if pumps_efficiency_data_raw is not None:
206
+ if not isinstance(pumps_efficiency_data_raw, np.ndarray):
207
+ raise TypeError("'pumps_efficiency_data_raw' must be an instance of 'numpy.ndarray' " +
208
+ f"but not of '{type(pumps_efficiency_data_raw)}'")
206
209
  if len(sensor_faults) != 0:
207
210
  if any(not isinstance(f, SensorFault) for f in sensor_faults):
208
211
  raise TypeError("'sensor_faults' must be a list of " +
@@ -223,6 +226,11 @@ class ScadaData(Serializable):
223
226
  raise TypeError("'frozen_sensor_config' must be an instance of 'bool' " +
224
227
  f"but not of '{type(frozen_sensor_config)}'")
225
228
 
229
+ if pump_efficiency_data is not None or pump_energy_usage_data is not None:
230
+ warnings.warn("Loading a file that was created with an outdated version of EPyT-Flow" +
231
+ " -- support of such old files will be removed in the next release!",
232
+ DeprecationWarning)
233
+
226
234
  def __raise_shape_mismatch(var_name: str) -> None:
227
235
  raise ValueError(f"Shape mismatch in '{var_name}' -- " +
228
236
  "i.e number of time steps in 'sensor_readings_time' " +
@@ -271,12 +279,12 @@ class ScadaData(Serializable):
271
279
  if surface_species_concentration_raw is not None:
272
280
  if surface_species_concentration_raw.shape[0] != n_time_steps:
273
281
  __raise_shape_mismatch("surface_species_concentration_raw")
274
- if pump_energy_usage_data is not None:
275
- if pump_energy_usage_data.shape[0] != n_time_steps:
276
- __raise_shape_mismatch("pump_energy_usage_data")
277
- if pump_efficiency_data is not None:
278
- if pump_efficiency_data.shape[0] != n_time_steps:
279
- __raise_shape_mismatch("pump_efficiency_data")
282
+ if pumps_energy_usage_data_raw is not None:
283
+ if pumps_energy_usage_data_raw.shape[0] != n_time_steps:
284
+ __raise_shape_mismatch("pumps_energy_usage_data_raw")
285
+ if pumps_efficiency_data_raw is not None:
286
+ if pumps_efficiency_data_raw.shape[0] != n_time_steps:
287
+ __raise_shape_mismatch("pumps_efficiency_data_raw")
280
288
 
281
289
  self.__sensor_config = sensor_config
282
290
  self.__sensor_noise = sensor_noise
@@ -286,8 +294,6 @@ class ScadaData(Serializable):
286
294
  self.__sensor_readings = None
287
295
  self.__frozen_sensor_config = frozen_sensor_config
288
296
  self.__sensor_readings_time = sensor_readings_time
289
- self.__pump_energy_usage_data = pump_energy_usage_data
290
- self.__pump_efficiency_data = pump_efficiency_data
291
297
 
292
298
  if self.__frozen_sensor_config is False:
293
299
  self.__pressure_data_raw = pressure_data_raw
@@ -301,6 +307,8 @@ class ScadaData(Serializable):
301
307
  self.__surface_species_concentration_raw = surface_species_concentration_raw
302
308
  self.__bulk_species_node_concentration_raw = bulk_species_node_concentration_raw
303
309
  self.__bulk_species_link_concentration_raw = bulk_species_link_concentration_raw
310
+ self.__pumps_energy_usage_data_raw = pumps_energy_usage_data_raw
311
+ self.__pumps_efficiency_data_raw = pumps_efficiency_data_raw
304
312
  else:
305
313
  sensor_config = self.__sensor_config
306
314
 
@@ -338,6 +346,14 @@ class ScadaData(Serializable):
338
346
  self.__pumps_state_data_raw = __reduce_data(data=pumps_state_data_raw,
339
347
  item_to_idx=pump_to_idx,
340
348
  sensors=sensor_config.pump_state_sensors)
349
+ self.__pumps_energy_usage_data_raw = \
350
+ __reduce_data(data=pumps_energy_usage_data_raw,
351
+ item_to_idx=pump_to_idx,
352
+ sensors=sensor_config.pump_enegeryconsumption_sensors)
353
+ self.__pumps_efficiency_data_raw = \
354
+ __reduce_data(data=pumps_efficiency_data_raw,
355
+ item_to_idx=pump_to_idx,
356
+ sensors=sensor_config.pump_efficiency_sensors)
341
357
  self.__valves_state_data_raw = __reduce_data(data=valves_state_data_raw,
342
358
  item_to_idx=valve_to_idx,
343
359
  sensors=sensor_config.valve_state_sensors)
@@ -881,6 +897,10 @@ class ScadaData(Serializable):
881
897
  quality_link_sensors=self.__sensor_config.quality_link_sensors,
882
898
  valve_state_sensors=self.__sensor_config.valve_state_sensors,
883
899
  pump_state_sensors=self.__sensor_config.pump_state_sensors,
900
+ pump_efficiency_sensors=
901
+ self.__sensor_config.pump_efficiency_sensors,
902
+ pump_energyconsumption_sensors=
903
+ self.__sensor_config.pump_energyconsumption_sensors,
884
904
  tank_volume_sensors=self.__sensor_config.tank_volume_sensors,
885
905
  bulk_species_node_sensors=
886
906
  self.__sensor_config.bulk_species_node_sensors,
@@ -906,8 +926,8 @@ class ScadaData(Serializable):
906
926
  pumps_state_data_raw=self.pumps_state_data_raw,
907
927
  valves_state_data_raw=self.valves_state_data_raw,
908
928
  tanks_volume_data_raw=tanks_volume_data,
909
- pump_energy_usage_data=self.pump_energy_usage_data,
910
- pump_efficiency_data=self.pump_efficiency_data,
929
+ pumps_energy_usage_data_raw=self.pumps_energyconsumption_data_raw,
930
+ pumps_efficiency_data_raw=self.pumps_efficiency_data_raw,
911
931
  bulk_species_node_concentration_raw=bulk_species_node_concentrations,
912
932
  bulk_species_link_concentration_raw=bulk_species_link_concentrations,
913
933
  surface_species_concentration_raw=surface_species_concentrations)
@@ -1154,38 +1174,28 @@ class ScadaData(Serializable):
1154
1174
  return deepcopy(self.__bulk_species_link_concentration_raw)
1155
1175
 
1156
1176
  @property
1157
- def pump_energy_usage_data(self) -> np.ndarray:
1177
+ def pumps_energyconsumption_data_raw(self) -> np.ndarray:
1158
1178
  """
1159
- Gets the energy usage of each pump.
1160
-
1161
- .. note::
1162
- This attribute is NOT included in
1163
- :func:`~epyt_flow.simulation.scada.scada_data.ScadaData.get_data` --
1164
- calling this function is the only way of accessing the energy usage of each pump.
1179
+ Gets the raw energy consumption of each pump.
1165
1180
 
1166
1181
  Returns
1167
1182
  -------
1168
1183
  `numpy.ndarray`
1169
- Energy usage of each pump.
1184
+ Energy consumption of each pump.
1170
1185
  """
1171
- return deepcopy(self.__pump_energy_usage_data)
1186
+ return deepcopy(self.__pumps_energy_usage_data_raw)
1172
1187
 
1173
1188
  @property
1174
- def pump_efficiency_data(self) -> np.ndarray:
1189
+ def pumps_efficiency_data_raw(self) -> np.ndarray:
1175
1190
  """
1176
- Gets the pumps' efficiency.
1177
-
1178
- .. note::
1179
- This attribute is NOT included in
1180
- :func:`~epyt_flow.simulation.scada.scada_data.ScadaData.get_data` --
1181
- calling this function is the only way of accessing the pumps' efficiency.
1191
+ Gets the raw efficiency of each pump.
1182
1192
 
1183
1193
  Returns
1184
1194
  -------
1185
1195
  `numpy.ndarray`
1186
1196
  Pumps' efficiency.
1187
1197
  """
1188
- return deepcopy(self.__pump_efficiency_data)
1198
+ return deepcopy(self.__pumps_efficiency_data_raw)
1189
1199
 
1190
1200
  def __init(self):
1191
1201
  self.__apply_sensor_noise = lambda x: x
@@ -1216,6 +1226,12 @@ class ScadaData(Serializable):
1216
1226
  elif sensor_event.sensor_type == SENSOR_TYPE_PUMP_STATE:
1217
1227
  idx = self.__sensor_config.get_index_of_reading(
1218
1228
  pump_state_sensor=sensor_event.sensor_id)
1229
+ elif sensor_event.sensor_type == SENSOR_TYPE_PUMP_EFFICIENCY:
1230
+ idx = self.__sensor_config.get_index_of_reading(
1231
+ pump_efficiency_sensor=sensor_event.sensor_id)
1232
+ elif sensor_event.sensor_type == SENSOR_TYPE_PUMP_ENERGYCONSUMPTION:
1233
+ idx = self.__sensor_config.get_index_of_reading(
1234
+ pump_energyconsumption_sensor=sensor_event.sensor_id)
1219
1235
  elif sensor_event.sensor_type == SENSOR_TYPE_TANK_VOLUME:
1220
1236
  idx = self.__sensor_config.get_index_of_reading(
1221
1237
  tank_volume_sensor=sensor_event.sensor_id)
@@ -1250,8 +1266,8 @@ class ScadaData(Serializable):
1250
1266
  "surface_species_concentration_raw": self.__surface_species_concentration_raw,
1251
1267
  "bulk_species_node_concentration_raw": self.__bulk_species_node_concentration_raw,
1252
1268
  "bulk_species_link_concentration_raw": self.__bulk_species_link_concentration_raw,
1253
- "pump_energy_usage_data": self.__pump_energy_usage_data,
1254
- "pump_efficiency_data": self.__pump_efficiency_data}
1269
+ "pumps_energy_usage_data_raw": self.__pumps_energy_usage_data_raw,
1270
+ "pumps_efficiency_data_raw": self.__pumps_efficiency_data_raw}
1255
1271
 
1256
1272
  return super().get_attributes() | attr
1257
1273
 
@@ -1280,8 +1296,9 @@ class ScadaData(Serializable):
1280
1296
  other.bulk_species_node_concentration_raw) \
1281
1297
  and np.all(self.__bulk_species_link_concentration_raw ==
1282
1298
  other.bulk_species_link_concentration_raw) \
1283
- and np.all(self.__pump_energy_usage_data == other.pump_energy_usage_data) \
1284
- and np.all(self.__pump_efficiency_data == other.pump_efficiency_data)
1299
+ and np.all(self.__pumps_energy_usage_data_raw ==
1300
+ other.pumps_energyconsumption_data_raw) \
1301
+ and np.all(self.__pumps_efficiency_data_raw == other.pumps_efficiency_data_raw)
1285
1302
  except Exception as ex:
1286
1303
  warnings.warn(ex.__str__())
1287
1304
  return False
@@ -1302,8 +1319,8 @@ class ScadaData(Serializable):
1302
1319
  f"surface_species_concentration_raw: {self.__surface_species_concentration_raw} " + \
1303
1320
  f"bulk_species_node_concentration_raw: {self.__bulk_species_node_concentration_raw}" +\
1304
1321
  f" bulk_species_link_concentration_raw: {self.__bulk_species_link_concentration_raw}" +\
1305
- f" pump_efficiency_data: {self.__pump_efficiency_data} " + \
1306
- f"pump_energy_usage_data: {self.__pump_energy_usage_data}"
1322
+ f" pumps_efficiency_data_raw: {self.__pumps_efficiency_data_raw} " + \
1323
+ f"pumps_energy_usage_data_raw: {self.__pumps_energy_usage_data_raw}"
1307
1324
 
1308
1325
  def change_sensor_config(self, sensor_config: SensorConfig) -> None:
1309
1326
  """
@@ -1485,11 +1502,13 @@ class ScadaData(Serializable):
1485
1502
  self.__sensor_config.surface_species_sensors = \
1486
1503
  other.sensor_config.surface_species_sensors
1487
1504
 
1488
- if self.__pump_energy_usage_data is None and other.pump_energy_usage_data is not None:
1489
- self.__pump_energy_usage_data = other.pump_energy_usage_data
1505
+ if self.__pumps_energy_usage_data_raw is None and \
1506
+ other.pumps_energy_usage_data_raw is not None:
1507
+ self.__pumps_energy_usage_data_raw = other.pumps_energy_usage_data_raw
1490
1508
 
1491
- if self.__pump_efficiency_data is None and other.pump_efficiency_data is not None:
1492
- self.__pump_efficiency_data = other.pump_efficiency_data
1509
+ if self.__pumps_efficiency_data_raw is None and \
1510
+ other.pumps_efficiency_data_raw is not None:
1511
+ self.__pumps_efficiency_data_raw = other.pumps_efficiency_data_raw
1493
1512
 
1494
1513
  self.__init()
1495
1514
 
@@ -1575,14 +1594,14 @@ class ScadaData(Serializable):
1575
1594
  other.bulk_species_link_concentration_raw),
1576
1595
  axis=0)
1577
1596
 
1578
- if self.__pump_energy_usage_data is not None:
1579
- self.__pump_energy_usage_data = np.concatenate(
1580
- (self.__pump_energy_usage_data, other.pump_energy_usage_data),
1597
+ if self.__pumps_energy_usage_data_raw is not None:
1598
+ self.__pumps_energy_usage_data_raw = np.concatenate(
1599
+ (self.__pumps_energy_usage_data_raw, other.pumps_energy_usage_data_raw),
1581
1600
  axis=0)
1582
1601
 
1583
- if self.__pump_efficiency_data is not None:
1584
- self.__pump_efficiency_data = np.concatenate(
1585
- (self.__pump_efficiency_data, other.pump_efficiency_data),
1602
+ if self.__pumps_efficiency_data_raw is not None:
1603
+ self.__pumps_efficiency_data_raw = np.concatenate(
1604
+ (self.__pumps_efficiency_data_raw, other.pumps_efficiency_data_raw),
1586
1605
  axis=0)
1587
1606
 
1588
1607
  def get_data(self) -> np.ndarray:
@@ -1603,6 +1622,8 @@ class ScadaData(Serializable):
1603
1622
  "nodes_quality": self.__node_quality_data_raw,
1604
1623
  "links_quality": self.__link_quality_data_raw,
1605
1624
  "pumps_state": self.__pumps_state_data_raw,
1625
+ "pumps_efficiency": self.__pumps_efficiency_data_raw,
1626
+ "pumps_energyconsumption": self.__pumps_energy_usage_data_raw,
1606
1627
  "valves_state": self.__valves_state_data_raw,
1607
1628
  "tanks_volume": self.__tanks_volume_data_raw,
1608
1629
  "bulk_species_node_concentrations": self.__bulk_species_node_concentration_raw,
@@ -1626,6 +1647,10 @@ class ScadaData(Serializable):
1626
1647
  data.append(self.__valves_state_data_raw)
1627
1648
  if self.__pumps_state_data_raw is not None:
1628
1649
  data.append(self.__pumps_state_data_raw)
1650
+ if self.__pumps_efficiency_data_raw is not None:
1651
+ data.append(self.__pumps_efficiency_data_raw)
1652
+ if self.__pumps_energy_usage_data_raw is not None:
1653
+ data.append(self.__pumps_energy_usage_data_raw)
1629
1654
  if self.__tanks_volume_data_raw is not None:
1630
1655
  data.append(self.__tanks_volume_data_raw)
1631
1656
  if self.__surface_species_concentration_raw is not None:
@@ -1890,6 +1915,85 @@ class ScadaData(Serializable):
1890
1915
  for s_id in sensor_locations]
1891
1916
  return self.__sensor_readings[:, idx]
1892
1917
 
1918
+ def get_data_pumps_efficiency(self, sensor_locations: list[str] = None) -> np.ndarray:
1919
+ """
1920
+ Gets the final pump efficiency sensor readings -- note that those might be subject to
1921
+ given sensor faults and sensor noise/uncertainty.
1922
+
1923
+ Parameters
1924
+ ----------
1925
+ sensor_locations : `list[str]`, optional
1926
+ Existing pump efficiency sensor locations for which the sensor readings are requested.
1927
+ If None, the readings from all pump efficiency sensors are returned.
1928
+
1929
+ The default is None.
1930
+
1931
+ Returns
1932
+ -------
1933
+ `numpy.ndarray`
1934
+ Pump efficiency sensor readings.
1935
+ """
1936
+ if self.__sensor_config.pump_efficiency_sensors == []:
1937
+ raise ValueError("No pump efficiency sensors set")
1938
+ if sensor_locations is not None:
1939
+ if not isinstance(sensor_locations, list):
1940
+ raise TypeError("'sensor_locations' must be an instance of 'list[str]' " +
1941
+ f"but not of '{type(sensor_locations)}'")
1942
+ if any(s_id not in self.__sensor_config.pump_efficiency_sensors
1943
+ for s_id in sensor_locations):
1944
+ raise ValueError("Invalid sensor ID in 'sensor_locations' -- note that all " +
1945
+ "sensors in 'sensor_locations' must be set in the current " +
1946
+ "pump efficiency sensor configuration")
1947
+ else:
1948
+ sensor_locations = self.__sensor_config.pump_efficiency_sensors
1949
+
1950
+ if self.__sensor_readings is None:
1951
+ self.get_data()
1952
+
1953
+ idx = [self.__sensor_config.get_index_of_reading(pump_efficiency_sensor=s_id)
1954
+ for s_id in sensor_locations]
1955
+ return self.__sensor_readings[:, idx]
1956
+
1957
+ def get_data_pumps_energyconsumption(self, sensor_locations: list[str] = None) -> np.ndarray:
1958
+ """
1959
+ Gets the final pump energy consumption sensor readings -- note that those might be subject
1960
+ to given sensor faults and sensor noise/uncertainty.
1961
+
1962
+ Parameters
1963
+ ----------
1964
+ sensor_locations : `list[str]`, optional
1965
+ Existing pump energy consumption sensor locations for which
1966
+ the sensor readings are requested.
1967
+ If None, the readings from all pump energy consumption sensors are returned.
1968
+
1969
+ The default is None.
1970
+
1971
+ Returns
1972
+ -------
1973
+ `numpy.ndarray`
1974
+ Pump energy consumption sensor readings.
1975
+ """
1976
+ if self.__sensor_config.pump_energyconsumption_sensors == []:
1977
+ raise ValueError("No pump energy consumption sensors set")
1978
+ if sensor_locations is not None:
1979
+ if not isinstance(sensor_locations, list):
1980
+ raise TypeError("'sensor_locations' must be an instance of 'list[str]' " +
1981
+ f"but not of '{type(sensor_locations)}'")
1982
+ if any(s_id not in self.__sensor_config.pump_energyconsumption_sensors
1983
+ for s_id in sensor_locations):
1984
+ raise ValueError("Invalid sensor ID in 'sensor_locations' -- note that all " +
1985
+ "sensors in 'sensor_locations' must be set in the current " +
1986
+ "pump efficiency sensor configuration")
1987
+ else:
1988
+ sensor_locations = self.__sensor_config.pump_energyconsumption_sensors
1989
+
1990
+ if self.__sensor_readings is None:
1991
+ self.get_data()
1992
+
1993
+ idx = [self.__sensor_config.get_index_of_reading(pump_energyconsumption_sensor=s_id)
1994
+ for s_id in sensor_locations]
1995
+ return self.__sensor_readings[:, idx]
1996
+
1893
1997
  def get_data_valves_state(self, sensor_locations: list[str] = None) -> np.ndarray:
1894
1998
  """
1895
1999
  Gets the final valve state sensor readings -- note that those might be subject to