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 +1 -1
- epyt_flow/data/benchmarks/battledim.py +6 -2
- epyt_flow/data/benchmarks/leakdb.py +11 -9
- epyt_flow/data/networks.py +1 -2
- epyt_flow/gym/scenario_control_env.py +101 -6
- epyt_flow/serialization.py +13 -3
- epyt_flow/simulation/scada/advanced_control.py +1 -1
- epyt_flow/simulation/scada/scada_data.py +159 -55
- epyt_flow/simulation/scenario_config.py +6 -2
- epyt_flow/simulation/scenario_simulator.py +274 -64
- epyt_flow/simulation/scenario_visualizer.py +1 -1
- epyt_flow/simulation/sensor_config.py +222 -15
- epyt_flow/topology.py +162 -4
- epyt_flow/utils.py +25 -2
- {epyt_flow-0.4.0.dist-info → epyt_flow-0.6.0.dist-info}/METADATA +2 -2
- {epyt_flow-0.4.0.dist-info → epyt_flow-0.6.0.dist-info}/RECORD +19 -19
- {epyt_flow-0.4.0.dist-info → epyt_flow-0.6.0.dist-info}/WHEEL +1 -1
- {epyt_flow-0.4.0.dist-info → epyt_flow-0.6.0.dist-info}/LICENSE +0 -0
- {epyt_flow-0.4.0.dist-info → epyt_flow-0.6.0.dist-info}/top_level.txt +0 -0
epyt_flow/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
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)
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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}",
|
epyt_flow/data/networks.py
CHANGED
|
@@ -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 = {
|
|
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
|
|
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
|
|
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
|
|
98
|
-
Triple of observations (:class:`~epyt_flow.
|
|
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()
|
epyt_flow/serialization.py
CHANGED
|
@@ -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
|
-
|
|
386
|
+
inst = load(f.read())
|
|
382
387
|
else:
|
|
383
388
|
with gzip.open(f_in, "rb") as f:
|
|
384
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
134
|
-
pump_efficiency_data
|
|
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
|
|
199
|
-
if not isinstance(
|
|
200
|
-
raise TypeError("'
|
|
201
|
-
f"but not of '{type(
|
|
202
|
-
if
|
|
203
|
-
if not isinstance(
|
|
204
|
-
raise TypeError("'
|
|
205
|
-
f"but not of '{type(
|
|
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
|
|
275
|
-
if
|
|
276
|
-
__raise_shape_mismatch("
|
|
277
|
-
if
|
|
278
|
-
if
|
|
279
|
-
__raise_shape_mismatch("
|
|
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
|
-
|
|
910
|
-
|
|
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
|
|
1177
|
+
def pumps_energyconsumption_data_raw(self) -> np.ndarray:
|
|
1158
1178
|
"""
|
|
1159
|
-
Gets the energy
|
|
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
|
|
1184
|
+
Energy consumption of each pump.
|
|
1170
1185
|
"""
|
|
1171
|
-
return deepcopy(self.
|
|
1186
|
+
return deepcopy(self.__pumps_energy_usage_data_raw)
|
|
1172
1187
|
|
|
1173
1188
|
@property
|
|
1174
|
-
def
|
|
1189
|
+
def pumps_efficiency_data_raw(self) -> np.ndarray:
|
|
1175
1190
|
"""
|
|
1176
|
-
Gets the
|
|
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.
|
|
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
|
-
"
|
|
1254
|
-
"
|
|
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.
|
|
1284
|
-
|
|
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"
|
|
1306
|
-
f"
|
|
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.
|
|
1489
|
-
|
|
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.
|
|
1492
|
-
|
|
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.
|
|
1579
|
-
self.
|
|
1580
|
-
(self.
|
|
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.
|
|
1584
|
-
self.
|
|
1585
|
-
(self.
|
|
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
|