epyt-flow 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- epyt_flow/EPANET/EPANET/SRC_engines/AUTHORS +28 -0
- epyt_flow/EPANET/EPANET/SRC_engines/LICENSE +21 -0
- epyt_flow/EPANET/EPANET/SRC_engines/Readme_SRC_Engines.txt +18 -0
- epyt_flow/EPANET/EPANET/SRC_engines/enumstxt.h +134 -0
- epyt_flow/EPANET/EPANET/SRC_engines/epanet.c +5578 -0
- epyt_flow/EPANET/EPANET/SRC_engines/epanet2.c +865 -0
- epyt_flow/EPANET/EPANET/SRC_engines/epanet2.def +131 -0
- epyt_flow/EPANET/EPANET/SRC_engines/errors.dat +73 -0
- epyt_flow/EPANET/EPANET/SRC_engines/funcs.h +193 -0
- epyt_flow/EPANET/EPANET/SRC_engines/genmmd.c +1000 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hash.c +177 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hash.h +28 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hydcoeffs.c +1151 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hydraul.c +1117 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hydsolver.c +720 -0
- epyt_flow/EPANET/EPANET/SRC_engines/hydstatus.c +476 -0
- epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2.h +431 -0
- epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_2.h +1786 -0
- epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_enums.h +468 -0
- epyt_flow/EPANET/EPANET/SRC_engines/inpfile.c +810 -0
- epyt_flow/EPANET/EPANET/SRC_engines/input1.c +707 -0
- epyt_flow/EPANET/EPANET/SRC_engines/input2.c +864 -0
- epyt_flow/EPANET/EPANET/SRC_engines/input3.c +2170 -0
- epyt_flow/EPANET/EPANET/SRC_engines/main.c +93 -0
- epyt_flow/EPANET/EPANET/SRC_engines/mempool.c +142 -0
- epyt_flow/EPANET/EPANET/SRC_engines/mempool.h +24 -0
- epyt_flow/EPANET/EPANET/SRC_engines/output.c +852 -0
- epyt_flow/EPANET/EPANET/SRC_engines/project.c +1359 -0
- epyt_flow/EPANET/EPANET/SRC_engines/quality.c +685 -0
- epyt_flow/EPANET/EPANET/SRC_engines/qualreact.c +743 -0
- epyt_flow/EPANET/EPANET/SRC_engines/qualroute.c +694 -0
- epyt_flow/EPANET/EPANET/SRC_engines/report.c +1489 -0
- epyt_flow/EPANET/EPANET/SRC_engines/rules.c +1362 -0
- epyt_flow/EPANET/EPANET/SRC_engines/smatrix.c +871 -0
- epyt_flow/EPANET/EPANET/SRC_engines/text.h +497 -0
- epyt_flow/EPANET/EPANET/SRC_engines/types.h +874 -0
- epyt_flow/EPANET/EPANET-MSX/MSX_Updates.txt +53 -0
- epyt_flow/EPANET/EPANET-MSX/Src/dispersion.h +27 -0
- epyt_flow/EPANET/EPANET-MSX/Src/hash.c +107 -0
- epyt_flow/EPANET/EPANET-MSX/Src/hash.h +28 -0
- epyt_flow/EPANET/EPANET-MSX/Src/include/epanetmsx.h +102 -0
- epyt_flow/EPANET/EPANET-MSX/Src/include/epanetmsx_export.h +42 -0
- epyt_flow/EPANET/EPANET-MSX/Src/mathexpr.c +937 -0
- epyt_flow/EPANET/EPANET-MSX/Src/mathexpr.h +39 -0
- epyt_flow/EPANET/EPANET-MSX/Src/mempool.c +204 -0
- epyt_flow/EPANET/EPANET-MSX/Src/mempool.h +24 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxchem.c +1285 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxcompiler.c +368 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxdict.h +42 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxdispersion.c +586 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxerr.c +116 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxfile.c +260 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxfuncs.c +175 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxfuncs.h +35 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxinp.c +1504 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxout.c +401 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxproj.c +791 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxqual.c +2010 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxrpt.c +400 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxtank.c +422 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxtoolkit.c +1164 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxtypes.h +551 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxutils.c +524 -0
- epyt_flow/EPANET/EPANET-MSX/Src/msxutils.h +56 -0
- epyt_flow/EPANET/EPANET-MSX/Src/newton.c +158 -0
- epyt_flow/EPANET/EPANET-MSX/Src/newton.h +34 -0
- epyt_flow/EPANET/EPANET-MSX/Src/rk5.c +287 -0
- epyt_flow/EPANET/EPANET-MSX/Src/rk5.h +39 -0
- epyt_flow/EPANET/EPANET-MSX/Src/ros2.c +293 -0
- epyt_flow/EPANET/EPANET-MSX/Src/ros2.h +35 -0
- epyt_flow/EPANET/EPANET-MSX/Src/smatrix.c +816 -0
- epyt_flow/EPANET/EPANET-MSX/Src/smatrix.h +29 -0
- epyt_flow/EPANET/EPANET-MSX/readme.txt +14 -0
- epyt_flow/EPANET/compile.sh +4 -0
- epyt_flow/VERSION +1 -0
- epyt_flow/__init__.py +24 -0
- epyt_flow/data/__init__.py +0 -0
- epyt_flow/data/benchmarks/__init__.py +11 -0
- epyt_flow/data/benchmarks/batadal.py +257 -0
- epyt_flow/data/benchmarks/batadal_data.py +28 -0
- epyt_flow/data/benchmarks/battledim.py +473 -0
- epyt_flow/data/benchmarks/battledim_data.py +51 -0
- epyt_flow/data/benchmarks/gecco_water_quality.py +267 -0
- epyt_flow/data/benchmarks/leakdb.py +592 -0
- epyt_flow/data/benchmarks/leakdb_data.py +18923 -0
- epyt_flow/data/benchmarks/water_usage.py +123 -0
- epyt_flow/data/networks.py +650 -0
- epyt_flow/gym/__init__.py +4 -0
- epyt_flow/gym/control_gyms.py +47 -0
- epyt_flow/gym/scenario_control_env.py +101 -0
- epyt_flow/metrics.py +404 -0
- epyt_flow/models/__init__.py +2 -0
- epyt_flow/models/event_detector.py +31 -0
- epyt_flow/models/sensor_interpolation_detector.py +118 -0
- epyt_flow/rest_api/__init__.py +4 -0
- epyt_flow/rest_api/base_handler.py +70 -0
- epyt_flow/rest_api/res_manager.py +95 -0
- epyt_flow/rest_api/scada_data_handler.py +476 -0
- epyt_flow/rest_api/scenario_handler.py +352 -0
- epyt_flow/rest_api/server.py +106 -0
- epyt_flow/serialization.py +438 -0
- epyt_flow/simulation/__init__.py +5 -0
- epyt_flow/simulation/events/__init__.py +6 -0
- epyt_flow/simulation/events/actuator_events.py +259 -0
- epyt_flow/simulation/events/event.py +81 -0
- epyt_flow/simulation/events/leakages.py +404 -0
- epyt_flow/simulation/events/sensor_faults.py +267 -0
- epyt_flow/simulation/events/sensor_reading_attack.py +185 -0
- epyt_flow/simulation/events/sensor_reading_event.py +170 -0
- epyt_flow/simulation/events/system_event.py +88 -0
- epyt_flow/simulation/parallel_simulation.py +147 -0
- epyt_flow/simulation/scada/__init__.py +3 -0
- epyt_flow/simulation/scada/advanced_control.py +134 -0
- epyt_flow/simulation/scada/scada_data.py +1589 -0
- epyt_flow/simulation/scada/scada_data_export.py +255 -0
- epyt_flow/simulation/scenario_config.py +608 -0
- epyt_flow/simulation/scenario_simulator.py +1897 -0
- epyt_flow/simulation/scenario_visualizer.py +61 -0
- epyt_flow/simulation/sensor_config.py +1289 -0
- epyt_flow/topology.py +290 -0
- epyt_flow/uncertainty/__init__.py +3 -0
- epyt_flow/uncertainty/model_uncertainty.py +302 -0
- epyt_flow/uncertainty/sensor_noise.py +73 -0
- epyt_flow/uncertainty/uncertainties.py +555 -0
- epyt_flow/uncertainty/utils.py +206 -0
- epyt_flow/utils.py +306 -0
- epyt_flow-0.1.0.dist-info/LICENSE +21 -0
- epyt_flow-0.1.0.dist-info/METADATA +139 -0
- epyt_flow-0.1.0.dist-info/RECORD +131 -0
- epyt_flow-0.1.0.dist-info/WHEEL +5 -0
- epyt_flow-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1589 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module provides a class for storing and processing SCADA data.
|
|
3
|
+
"""
|
|
4
|
+
import warnings
|
|
5
|
+
from typing import Callable
|
|
6
|
+
from copy import deepcopy
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from ..sensor_config import SensorConfig, SENSOR_TYPE_LINK_FLOW, SENSOR_TYPE_LINK_QUALITY, \
|
|
10
|
+
SENSOR_TYPE_NODE_DEMAND, SENSOR_TYPE_NODE_PRESSURE, SENSOR_TYPE_NODE_QUALITY, \
|
|
11
|
+
SENSOR_TYPE_PUMP_STATE, SENSOR_TYPE_TANK_VOLUME, SENSOR_TYPE_VALVE_STATE, \
|
|
12
|
+
SENSOR_TYPE_NODE_BULK_SPECIES, SENSOR_TYPE_LINK_BULK_SPECIES, SENSOR_TYPE_SURFACE_SPECIES
|
|
13
|
+
from ..events import SensorFault, SensorReadingAttack, SensorReadingEvent
|
|
14
|
+
from ...uncertainty import SensorNoise
|
|
15
|
+
from ...serialization import serializable, Serializable, SCADA_DATA_ID
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@serializable(SCADA_DATA_ID, ".epytflow_scada_data")
|
|
19
|
+
class ScadaData(Serializable):
|
|
20
|
+
"""
|
|
21
|
+
Class for storing and processing SCADA data.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
sensor_config : :class:`~epyt_flow.simulation.sensor_config.SensorConfig`
|
|
26
|
+
Specifications of all sensors.
|
|
27
|
+
sensor_readings_time : `numpy.ndarray`
|
|
28
|
+
Time (seconds since simulation start) for each sensor reading row
|
|
29
|
+
in `sensor_readings_data_raw`.
|
|
30
|
+
|
|
31
|
+
This parameter is expected to be a 1d array with the same size as
|
|
32
|
+
the number of rows in `sensor_readings_data_raw`.
|
|
33
|
+
pressure_data_raw : `numpy.ndarray`, optional
|
|
34
|
+
Raw pressure values of all nodes as a two-dimensional array --
|
|
35
|
+
first dimension encodes time, second dimension pressure at nodes.
|
|
36
|
+
|
|
37
|
+
The default is None,
|
|
38
|
+
flow_data_raw : `numpy.ndarray`, optional
|
|
39
|
+
Raw flow values of all links/pipes --
|
|
40
|
+
first dimension encodes time, second dimension pressure at links/pipes.
|
|
41
|
+
|
|
42
|
+
The default is None.
|
|
43
|
+
demand_data_raw : `numpy.ndarray`, optional
|
|
44
|
+
Raw demand values of all nodes --
|
|
45
|
+
first dimension encodes time, second dimension demand at nodes.
|
|
46
|
+
|
|
47
|
+
The default is None.
|
|
48
|
+
node_quality_data_raw : `numpy.ndarray`, optional
|
|
49
|
+
Raw quality values of all nodes --
|
|
50
|
+
first dimension encodes time, second dimension quality at nodes.
|
|
51
|
+
|
|
52
|
+
The default is None.
|
|
53
|
+
link_quality_data_raw : `numpy.ndarray`, optional
|
|
54
|
+
Raw quality values of all links/pipes --
|
|
55
|
+
first dimension encodes time, second dimension quality at links/pipes.
|
|
56
|
+
|
|
57
|
+
The default is None.
|
|
58
|
+
pumps_state_data_raw : `numpy.ndarray`, optional
|
|
59
|
+
States of all pumps --
|
|
60
|
+
first dimension encodes time, second dimension states of pumps.
|
|
61
|
+
|
|
62
|
+
The default is None.
|
|
63
|
+
valves_state_data_raw : `numpy.ndarray`, optional
|
|
64
|
+
States of all valves --
|
|
65
|
+
first dimension encodes time, second dimension states of valves.
|
|
66
|
+
|
|
67
|
+
The default is None.
|
|
68
|
+
tanks_volume_data_raw : `numpy.ndarray`, optional
|
|
69
|
+
Water volumes in all tanks --
|
|
70
|
+
first dimension encodes time, second dimension water volume in tanks.
|
|
71
|
+
|
|
72
|
+
The default is None.
|
|
73
|
+
surface_species_concentration_raw : `numpy.ndarray`, optional
|
|
74
|
+
Raw concentrations of surface species as a tree dimensional array --
|
|
75
|
+
first dimension encodes time, second dimension denotes the different surface species,
|
|
76
|
+
third dimension denotes species concentrations at links/pipes.
|
|
77
|
+
|
|
78
|
+
The default is None.
|
|
79
|
+
bulk_species_node_concentration_raw : `numpy.ndarray`, optional
|
|
80
|
+
Raw concentrations of bulk species at nodes as a tree dimensional array --
|
|
81
|
+
first dimension encodes time, second dimension denotes the different bulk species,
|
|
82
|
+
third dimension denotes species concentrations at nodes.
|
|
83
|
+
|
|
84
|
+
The default is None.
|
|
85
|
+
bulk_species_link_concentration_raw : `numpy.ndarray`, optional
|
|
86
|
+
Raw concentrations of bulk species at links as a tree dimensional array --
|
|
87
|
+
first dimension encodes time, second dimension denotes the different bulk species,
|
|
88
|
+
third dimension denotes species concentrations at nodes.
|
|
89
|
+
|
|
90
|
+
The default is None.
|
|
91
|
+
pump_energy_usage_data : `numpy.ndarray`, optional
|
|
92
|
+
Energy usage data of each pump.
|
|
93
|
+
|
|
94
|
+
The default is None.
|
|
95
|
+
pump_efficiency_data : `numpy.ndarray`, optional
|
|
96
|
+
Pump efficiency data of each pump.
|
|
97
|
+
|
|
98
|
+
The default is None.
|
|
99
|
+
sensor_faults : list[:class:`~epyt_flow.simulation.events.sensor_faults.SensorFault`], optional
|
|
100
|
+
List of sensor faults to be applied to the sensor readings.
|
|
101
|
+
|
|
102
|
+
The default is an empty list.
|
|
103
|
+
sensor_reading_attacks : list[:class:`~epyt_flow.simulation.events.sensor_reading_attack.SensorReadingAttack`], optional
|
|
104
|
+
List of sensor reading attacks to be applied to the sensor readings.
|
|
105
|
+
|
|
106
|
+
The default is an empty list.
|
|
107
|
+
sensor_reading_events : list[`:class:`~epyt_flow.simulation.events.sensor_reading_event.SensorReadingEvent`], optional
|
|
108
|
+
List of additional sensor reading events that are to be applied to the sensor readings.
|
|
109
|
+
|
|
110
|
+
The default is an empty list.
|
|
111
|
+
sensor_noise : :class:`~epyt_flow.uncertainty.sensor_noise.SensorNoise`, optional
|
|
112
|
+
Specification of the sensor noise/uncertainty to be added to the sensor readings.
|
|
113
|
+
|
|
114
|
+
The default is None.
|
|
115
|
+
frozen_sensor_config : `bool`, optional
|
|
116
|
+
If True, the sensor config can not be changed and only the required sensor nodes/links
|
|
117
|
+
will be stored -- this usually leads to a significant reduction in memory consumption.
|
|
118
|
+
|
|
119
|
+
The default is False.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def __init__(self, sensor_config: SensorConfig, sensor_readings_time: np.ndarray,
|
|
123
|
+
pressure_data_raw: np.ndarray = None, flow_data_raw: np.ndarray = None,
|
|
124
|
+
demand_data_raw: np.ndarray = None, node_quality_data_raw: np.ndarray = None,
|
|
125
|
+
link_quality_data_raw: np.ndarray = None, pumps_state_data_raw: np.ndarray = None,
|
|
126
|
+
valves_state_data_raw: np.ndarray = None, tanks_volume_data_raw: np.ndarray = None,
|
|
127
|
+
surface_species_concentration_raw: np.ndarray = None,
|
|
128
|
+
bulk_species_node_concentration_raw: np.ndarray = None,
|
|
129
|
+
bulk_species_link_concentration_raw: np.ndarray = None,
|
|
130
|
+
pump_energy_usage_data: np.ndarray = None,
|
|
131
|
+
pump_efficiency_data: np.ndarray = None,
|
|
132
|
+
sensor_faults: list[SensorFault] = [],
|
|
133
|
+
sensor_reading_attacks: list[SensorReadingAttack] = [],
|
|
134
|
+
sensor_reading_events: list[SensorReadingEvent] = [],
|
|
135
|
+
sensor_noise: SensorNoise = None, frozen_sensor_config: bool = False, **kwds):
|
|
136
|
+
if not isinstance(sensor_config, SensorConfig):
|
|
137
|
+
raise TypeError("'sensor_config' must be an instance of " +
|
|
138
|
+
"'epyt_flow.simulation.SensorConfig' but not of " +
|
|
139
|
+
f"'{type(sensor_config)}'")
|
|
140
|
+
if not isinstance(sensor_readings_time, np.ndarray):
|
|
141
|
+
raise TypeError("'sensor_readings_time' must be an instance of 'numpy.ndarray' " +
|
|
142
|
+
f"but not of '{type(sensor_readings_time)}'")
|
|
143
|
+
if pressure_data_raw is not None:
|
|
144
|
+
if not isinstance(pressure_data_raw, np.ndarray):
|
|
145
|
+
raise TypeError("'pressure_data_raw' must be an instance of 'numpy.ndarray'" +
|
|
146
|
+
f" but not of '{type(pressure_data_raw)}'")
|
|
147
|
+
if flow_data_raw is not None:
|
|
148
|
+
if not isinstance(flow_data_raw, np.ndarray):
|
|
149
|
+
raise TypeError("'flow_data_raw' must be an instance of 'numpy.ndarray' " +
|
|
150
|
+
f"but not of '{type(flow_data_raw)}'")
|
|
151
|
+
if demand_data_raw is not None:
|
|
152
|
+
if not isinstance(demand_data_raw, np.ndarray):
|
|
153
|
+
raise TypeError("'demand_data_raw' must be an instance of 'numpy.ndarray' " +
|
|
154
|
+
f"but not of '{type(demand_data_raw)}'")
|
|
155
|
+
if node_quality_data_raw is not None:
|
|
156
|
+
if not isinstance(node_quality_data_raw, np.ndarray):
|
|
157
|
+
raise TypeError("'node_quality_data_raw' must be an instance of 'numpy.ndarray'" +
|
|
158
|
+
f" but not of '{type(node_quality_data_raw)}'")
|
|
159
|
+
if link_quality_data_raw is not None:
|
|
160
|
+
if not isinstance(link_quality_data_raw, np.ndarray):
|
|
161
|
+
raise TypeError("'link_quality_data_raw' must be an instance of 'numpy.ndarray'" +
|
|
162
|
+
f" but not of '{type(link_quality_data_raw)}'")
|
|
163
|
+
if pumps_state_data_raw is not None:
|
|
164
|
+
if not isinstance(pumps_state_data_raw, np.ndarray):
|
|
165
|
+
raise TypeError("'pumps_state_data_raw' must be an instance of 'numpy.ndarray' " +
|
|
166
|
+
f"but no of '{type(pumps_state_data_raw)}'")
|
|
167
|
+
if valves_state_data_raw is not None:
|
|
168
|
+
if not isinstance(valves_state_data_raw, np.ndarray):
|
|
169
|
+
raise TypeError("'valves_state_data_raw' must be an instance of 'numpy.ndarray' " +
|
|
170
|
+
f"but no of '{type(valves_state_data_raw)}'")
|
|
171
|
+
if tanks_volume_data_raw is not None:
|
|
172
|
+
if not isinstance(tanks_volume_data_raw, np.ndarray):
|
|
173
|
+
raise TypeError("'tanks_volume_data_raw' must be an instance of 'numpy.ndarray'" +
|
|
174
|
+
f" but not of '{type(tanks_volume_data_raw)}'")
|
|
175
|
+
if sensor_faults is None or not isinstance(sensor_faults, list):
|
|
176
|
+
raise TypeError("'sensor_faults' must be a list of " +
|
|
177
|
+
"'epyt_flow.simulation.events.SensorFault' instances but " +
|
|
178
|
+
f"'{type(sensor_faults)}'")
|
|
179
|
+
if surface_species_concentration_raw is not None:
|
|
180
|
+
if not isinstance(surface_species_concentration_raw, np.ndarray):
|
|
181
|
+
raise TypeError("'surface_species_concentration_raw' must be an instance of " +
|
|
182
|
+
"'numpy.ndarray' but not of " +
|
|
183
|
+
f"'{type(surface_species_concentration_raw)}'")
|
|
184
|
+
if bulk_species_node_concentration_raw is not None:
|
|
185
|
+
if not isinstance(bulk_species_node_concentration_raw, np.ndarray):
|
|
186
|
+
raise TypeError("'bulk_species_node_concentration_raw' must be an instance of " +
|
|
187
|
+
"'numpy.ndarray' but not of " +
|
|
188
|
+
f"'{type(bulk_species_node_concentration_raw)}'")
|
|
189
|
+
if bulk_species_link_concentration_raw is not None:
|
|
190
|
+
if not isinstance(bulk_species_link_concentration_raw, np.ndarray):
|
|
191
|
+
raise TypeError("'bulk_species_link_concentration_raw' must be an instance of " +
|
|
192
|
+
"'numpy.ndarray' but not of " +
|
|
193
|
+
f"'{type(bulk_species_link_concentration_raw)}'")
|
|
194
|
+
if pump_energy_usage_data is not None:
|
|
195
|
+
if not isinstance(pump_energy_usage_data, np.ndarray):
|
|
196
|
+
raise TypeError("'pump_energy_usage_data' must be an instance of 'numpy.ndarray' " +
|
|
197
|
+
f"but not of '{type(pump_energy_usage_data)}'")
|
|
198
|
+
if pump_efficiency_data is not None:
|
|
199
|
+
if not isinstance(pump_efficiency_data, np.ndarray):
|
|
200
|
+
raise TypeError("'pump_efficiency_data' must be an instance of 'numpy.ndarray' " +
|
|
201
|
+
f"but not of '{type(pump_efficiency_data)}'")
|
|
202
|
+
if len(sensor_faults) != 0:
|
|
203
|
+
if any(not isinstance(f, SensorFault) for f in sensor_faults):
|
|
204
|
+
raise TypeError("'sensor_faults' must be a list of " +
|
|
205
|
+
"'epyt_flow.simulation.event.SensorFault' instances")
|
|
206
|
+
if len(sensor_reading_attacks) != 0:
|
|
207
|
+
if any(not isinstance(f, SensorReadingAttack) for f in sensor_reading_attacks):
|
|
208
|
+
raise TypeError("'sensor_reading_attacks' must be a list of " +
|
|
209
|
+
"'epyt_flow.simulation.event.SensorReadingAttack' instances")
|
|
210
|
+
if len(sensor_reading_events) != 0:
|
|
211
|
+
if any(not isinstance(f, SensorReadingEvent) for f in sensor_reading_events):
|
|
212
|
+
raise TypeError("'sensor_reading_events' must be a list of " +
|
|
213
|
+
"'epyt_flow.simulation.event.SensorReadingEvent' instances")
|
|
214
|
+
if sensor_noise is not None and not isinstance(sensor_noise, SensorNoise):
|
|
215
|
+
raise TypeError("'sensor_noise' must be an instance of " +
|
|
216
|
+
"'epyt_flow.uncertainty.SensorNoise' but not of " +
|
|
217
|
+
f"'{type(sensor_noise)}'")
|
|
218
|
+
if not isinstance(frozen_sensor_config, bool):
|
|
219
|
+
raise TypeError("'frozen_sensor_config' must be an instance of 'bool' " +
|
|
220
|
+
f"but not of '{type(frozen_sensor_config)}'")
|
|
221
|
+
|
|
222
|
+
def __raise_shape_mismatch(var_name: str) -> None:
|
|
223
|
+
raise ValueError(f"Shape mismatch in '{var_name}' -- " +
|
|
224
|
+
"i.e number of time steps in 'sensor_readings_time' " +
|
|
225
|
+
"must match number of raw measurements.")
|
|
226
|
+
|
|
227
|
+
n_time_steps = sensor_readings_time.shape[0]
|
|
228
|
+
if pressure_data_raw is not None:
|
|
229
|
+
if pressure_data_raw.shape[0] != n_time_steps:
|
|
230
|
+
__raise_shape_mismatch("pressure_data_raw")
|
|
231
|
+
if flow_data_raw is not None:
|
|
232
|
+
if flow_data_raw.shape[0] != n_time_steps:
|
|
233
|
+
__raise_shape_mismatch("flow_data_raw")
|
|
234
|
+
if demand_data_raw is not None:
|
|
235
|
+
if demand_data_raw.shape[0] != n_time_steps:
|
|
236
|
+
__raise_shape_mismatch("demand_data_raw")
|
|
237
|
+
if node_quality_data_raw is not None:
|
|
238
|
+
if node_quality_data_raw.shape[0] != n_time_steps:
|
|
239
|
+
__raise_shape_mismatch("node_quality_data_raw")
|
|
240
|
+
if link_quality_data_raw is not None:
|
|
241
|
+
if link_quality_data_raw.shape[0] != n_time_steps:
|
|
242
|
+
__raise_shape_mismatch("link_quality_data_raw")
|
|
243
|
+
if valves_state_data_raw is not None:
|
|
244
|
+
if valves_state_data_raw.shape[0] != n_time_steps:
|
|
245
|
+
__raise_shape_mismatch("valves_state_data_raw")
|
|
246
|
+
if pumps_state_data_raw is not None:
|
|
247
|
+
if pumps_state_data_raw.shape[0] != n_time_steps:
|
|
248
|
+
__raise_shape_mismatch("pumps_state_data_raw")
|
|
249
|
+
if tanks_volume_data_raw is not None:
|
|
250
|
+
if tanks_volume_data_raw.shape[0] != n_time_steps:
|
|
251
|
+
__raise_shape_mismatch("tanks_volume_data_raw")
|
|
252
|
+
if valves_state_data_raw is not None:
|
|
253
|
+
if not valves_state_data_raw.shape[0] == n_time_steps:
|
|
254
|
+
__raise_shape_mismatch("valves_state_data_raw")
|
|
255
|
+
if pumps_state_data_raw is not None:
|
|
256
|
+
if not pumps_state_data_raw.shape[0] == n_time_steps:
|
|
257
|
+
__raise_shape_mismatch("pumps_state_data_raw")
|
|
258
|
+
if tanks_volume_data_raw is not None:
|
|
259
|
+
if not tanks_volume_data_raw.shape[0] == n_time_steps:
|
|
260
|
+
__raise_shape_mismatch("tanks_volume_data_raw")
|
|
261
|
+
if bulk_species_node_concentration_raw is not None:
|
|
262
|
+
if bulk_species_node_concentration_raw.shape[0] != n_time_steps:
|
|
263
|
+
__raise_shape_mismatch("bulk_species_node_concentration_raw")
|
|
264
|
+
if bulk_species_link_concentration_raw is not None:
|
|
265
|
+
if bulk_species_link_concentration_raw.shape[0] != n_time_steps:
|
|
266
|
+
__raise_shape_mismatch("bulk_species_link_concentration_raw")
|
|
267
|
+
if surface_species_concentration_raw is not None:
|
|
268
|
+
if surface_species_concentration_raw.shape[0] != n_time_steps:
|
|
269
|
+
__raise_shape_mismatch("surface_species_concentration_raw")
|
|
270
|
+
if pump_energy_usage_data is not None:
|
|
271
|
+
if pump_energy_usage_data.shape[0] != n_time_steps:
|
|
272
|
+
__raise_shape_mismatch("pump_energy_usage_data")
|
|
273
|
+
if pump_efficiency_data is not None:
|
|
274
|
+
if pump_efficiency_data.shape[0] != n_time_steps:
|
|
275
|
+
__raise_shape_mismatch("pump_efficiency_data")
|
|
276
|
+
|
|
277
|
+
self.__sensor_config = sensor_config
|
|
278
|
+
self.__sensor_noise = sensor_noise
|
|
279
|
+
self.__sensor_reading_events = sensor_faults + sensor_reading_attacks + \
|
|
280
|
+
sensor_reading_events
|
|
281
|
+
|
|
282
|
+
self.__sensor_readings = None
|
|
283
|
+
self.__frozen_sensor_config = frozen_sensor_config
|
|
284
|
+
self.__sensor_readings_time = sensor_readings_time
|
|
285
|
+
self.__pump_energy_usage_data = pump_energy_usage_data
|
|
286
|
+
self.__pump_efficiency_data = pump_efficiency_data
|
|
287
|
+
|
|
288
|
+
if self.__frozen_sensor_config is False:
|
|
289
|
+
self.__pressure_data_raw = pressure_data_raw
|
|
290
|
+
self.__flow_data_raw = flow_data_raw
|
|
291
|
+
self.__demand_data_raw = demand_data_raw
|
|
292
|
+
self.__node_quality_data_raw = node_quality_data_raw
|
|
293
|
+
self.__link_quality_data_raw = link_quality_data_raw
|
|
294
|
+
self.__pumps_state_data_raw = pumps_state_data_raw
|
|
295
|
+
self.__valves_state_data_raw = valves_state_data_raw
|
|
296
|
+
self.__tanks_volume_data_raw = tanks_volume_data_raw
|
|
297
|
+
self.__surface_species_concentration_raw = surface_species_concentration_raw
|
|
298
|
+
self.__bulk_species_node_concentration_raw = bulk_species_node_concentration_raw
|
|
299
|
+
self.__bulk_species_link_concentration_raw = bulk_species_link_concentration_raw
|
|
300
|
+
else:
|
|
301
|
+
sensor_config = self.__sensor_config
|
|
302
|
+
|
|
303
|
+
node_to_idx = sensor_config.node_id_to_idx
|
|
304
|
+
link_to_idx = sensor_config.link_id_to_idx
|
|
305
|
+
pump_to_idx = sensor_config.pump_id_to_idx
|
|
306
|
+
valve_to_idx = sensor_config.valve_id_to_idx
|
|
307
|
+
tank_to_idx = sensor_config.tank_id_to_idx
|
|
308
|
+
|
|
309
|
+
# EPANET quantities
|
|
310
|
+
def __reduce_data(data: np.ndarray, sensors: list[str],
|
|
311
|
+
item_to_idx: Callable[[str], int]) -> np.ndarray:
|
|
312
|
+
idx = [item_to_idx(item_id) for item_id in sensors]
|
|
313
|
+
|
|
314
|
+
if data is None or len(idx) == 0:
|
|
315
|
+
return None
|
|
316
|
+
else:
|
|
317
|
+
return data[:, idx]
|
|
318
|
+
|
|
319
|
+
self.__pressure_data_raw = __reduce_data(data=pressure_data_raw,
|
|
320
|
+
item_to_idx=node_to_idx,
|
|
321
|
+
sensors=sensor_config.pressure_sensors)
|
|
322
|
+
self.__flow_data_raw = __reduce_data(data=flow_data_raw,
|
|
323
|
+
item_to_idx=link_to_idx,
|
|
324
|
+
sensors=sensor_config.flow_sensors)
|
|
325
|
+
self.__demand_data_raw = __reduce_data(data=demand_data_raw,
|
|
326
|
+
item_to_idx=node_to_idx,
|
|
327
|
+
sensors=sensor_config.demand_sensors)
|
|
328
|
+
self.__node_quality_data_raw = __reduce_data(data=node_quality_data_raw,
|
|
329
|
+
item_to_idx=node_to_idx,
|
|
330
|
+
sensors=sensor_config.quality_node_sensors)
|
|
331
|
+
self.__link_quality_data_raw = __reduce_data(data=link_quality_data_raw,
|
|
332
|
+
item_to_idx=link_to_idx,
|
|
333
|
+
sensors=sensor_config.quality_link_sensors)
|
|
334
|
+
self.__pumps_state_data_raw = __reduce_data(data=pumps_state_data_raw,
|
|
335
|
+
item_to_idx=pump_to_idx,
|
|
336
|
+
sensors=sensor_config.pump_state_sensors)
|
|
337
|
+
self.__valves_state_data_raw = __reduce_data(data=valves_state_data_raw,
|
|
338
|
+
item_to_idx=valve_to_idx,
|
|
339
|
+
sensors=sensor_config.valve_state_sensors)
|
|
340
|
+
self.__tanks_volume_data_raw = __reduce_data(data=tanks_volume_data_raw,
|
|
341
|
+
item_to_idx=tank_to_idx,
|
|
342
|
+
sensors=sensor_config.tank_volume_sensors)
|
|
343
|
+
|
|
344
|
+
# EPANET-MSX quantities
|
|
345
|
+
def __reduce_msx_data(data: np.ndarray, sensors: list[tuple[list[int], list[int]]]
|
|
346
|
+
) -> np.ndarray:
|
|
347
|
+
if data is None or len(sensors) == 0:
|
|
348
|
+
return None
|
|
349
|
+
else:
|
|
350
|
+
r = []
|
|
351
|
+
for species_idx, item_idx in sensors:
|
|
352
|
+
r.append(data[:, species_idx, item_idx].reshape(-1, len(item_idx)))
|
|
353
|
+
|
|
354
|
+
return np.concatenate(r, axis=1)
|
|
355
|
+
|
|
356
|
+
node_bulk_species_idx = [(sensor_config.bulkspecies_id_to_idx(s),
|
|
357
|
+
[sensor_config.node_id_to_idx(node_id)
|
|
358
|
+
for node_id in sensor_config.bulk_species_node_sensors[s]
|
|
359
|
+
]) for s in sensor_config.bulk_species_node_sensors.keys()]
|
|
360
|
+
self.__bulk_species_node_concentration_raw = \
|
|
361
|
+
__reduce_msx_data(data=bulk_species_node_concentration_raw,
|
|
362
|
+
sensors=node_bulk_species_idx)
|
|
363
|
+
|
|
364
|
+
bulk_species_link_idx = [(sensor_config.bulkspecies_id_to_idx(s),
|
|
365
|
+
[sensor_config.link_id_to_idx(link_id)
|
|
366
|
+
for link_id in sensor_config.bulk_species_link_sensors[s]
|
|
367
|
+
]) for s in sensor_config.bulk_species_link_sensors.keys()]
|
|
368
|
+
self.__bulk_species_link_concentration_raw = \
|
|
369
|
+
__reduce_msx_data(data=bulk_species_link_concentration_raw,
|
|
370
|
+
sensors=bulk_species_link_idx)
|
|
371
|
+
|
|
372
|
+
surface_species_idx = [(sensor_config.surfacespecies_id_to_idx(s),
|
|
373
|
+
[sensor_config.link_id_to_idx(link_id)
|
|
374
|
+
for link_id in sensor_config.surface_species_sensors[s]
|
|
375
|
+
]) for s in sensor_config.surface_species_sensors.keys()]
|
|
376
|
+
self.__surface_species_concentration_raw = \
|
|
377
|
+
__reduce_msx_data(data=surface_species_concentration_raw,
|
|
378
|
+
sensors=surface_species_idx)
|
|
379
|
+
|
|
380
|
+
self.__init()
|
|
381
|
+
|
|
382
|
+
super().__init__(**kwds)
|
|
383
|
+
|
|
384
|
+
@property
|
|
385
|
+
def frozen_sensor_config(self) -> bool:
|
|
386
|
+
"""
|
|
387
|
+
Checks if the sensor configuration is frozen or not.
|
|
388
|
+
|
|
389
|
+
Returns
|
|
390
|
+
-------
|
|
391
|
+
`bool`
|
|
392
|
+
True if the sensor configuration is frozen, False otherwise.
|
|
393
|
+
"""
|
|
394
|
+
return self.__frozen_sensor_config
|
|
395
|
+
|
|
396
|
+
@property
|
|
397
|
+
def sensor_config(self) -> SensorConfig:
|
|
398
|
+
"""
|
|
399
|
+
Gets the sensor configuration.
|
|
400
|
+
|
|
401
|
+
Returns
|
|
402
|
+
-------
|
|
403
|
+
:class:`~epyt_flow.simulation.sensor_config.SensorConfig`
|
|
404
|
+
Sensor configuration.
|
|
405
|
+
"""
|
|
406
|
+
return deepcopy(self.__sensor_config)
|
|
407
|
+
|
|
408
|
+
@sensor_config.setter
|
|
409
|
+
def sensor_config(self, sensor_config: SensorConfig) -> None:
|
|
410
|
+
if self.__frozen_sensor_config is True:
|
|
411
|
+
raise RuntimeError("Sensor config can not be changed because it is frozen")
|
|
412
|
+
|
|
413
|
+
self.change_sensor_config(sensor_config)
|
|
414
|
+
|
|
415
|
+
@property
|
|
416
|
+
def sensor_noise(self) -> SensorNoise:
|
|
417
|
+
"""
|
|
418
|
+
Gets the sensor noise.
|
|
419
|
+
|
|
420
|
+
Returns
|
|
421
|
+
-------
|
|
422
|
+
:class:`~epyt_flow.uncertainty.sensor_noise.SensorNoise`
|
|
423
|
+
Sensor noise.
|
|
424
|
+
"""
|
|
425
|
+
return deepcopy(self.__sensor_noise)
|
|
426
|
+
|
|
427
|
+
@sensor_noise.setter
|
|
428
|
+
def sensor_noise(self, sensor_noise: SensorNoise) -> None:
|
|
429
|
+
self.change_sensor_noise(sensor_noise)
|
|
430
|
+
|
|
431
|
+
@property
|
|
432
|
+
def sensor_faults(self) -> list[SensorFault]:
|
|
433
|
+
"""
|
|
434
|
+
Gets all sensor faults.
|
|
435
|
+
|
|
436
|
+
Returns
|
|
437
|
+
-------
|
|
438
|
+
list[:class:`~epyt_flow.simulation.events.sensor_faults.SensorFault`]
|
|
439
|
+
All sensor faults.
|
|
440
|
+
"""
|
|
441
|
+
return deepcopy(list(filter(lambda e: isinstance(e, SensorFault),
|
|
442
|
+
self.__sensor_reading_events)))
|
|
443
|
+
|
|
444
|
+
@sensor_faults.setter
|
|
445
|
+
def sensor_faults(self, sensor_faults: list[SensorFault]) -> None:
|
|
446
|
+
self.change_sensor_faults(sensor_faults)
|
|
447
|
+
|
|
448
|
+
@property
|
|
449
|
+
def sensor_reading_attacks(self) -> list[SensorReadingAttack]:
|
|
450
|
+
"""
|
|
451
|
+
Gets all sensor reading attacks.
|
|
452
|
+
|
|
453
|
+
Returns
|
|
454
|
+
-------
|
|
455
|
+
list[:class:`~epyt_flow.simulation.events.sensor_reading_attack.SensorReadingAttack`]
|
|
456
|
+
All sensor reading attacks.
|
|
457
|
+
"""
|
|
458
|
+
return deepcopy(list(filter(lambda e: isinstance(e, SensorReadingAttack),
|
|
459
|
+
self.__sensor_reading_events)))
|
|
460
|
+
|
|
461
|
+
@sensor_reading_attacks.setter
|
|
462
|
+
def sensor_reading_attacks(self, sensor_reading_attacks: list[SensorReadingAttack]) -> None:
|
|
463
|
+
self.change_sensor_reading_attacks(sensor_reading_attacks)
|
|
464
|
+
|
|
465
|
+
@property
|
|
466
|
+
def sensor_reading_events(self) -> list[SensorReadingEvent]:
|
|
467
|
+
"""
|
|
468
|
+
Gets all sensor reading events.
|
|
469
|
+
|
|
470
|
+
Returns
|
|
471
|
+
-------
|
|
472
|
+
list[:class:`~epyt_flow.simulation.events.sensor_reading_event.SensorReadingEvent`]
|
|
473
|
+
All sensor faults.
|
|
474
|
+
"""
|
|
475
|
+
return deepcopy(self.__sensor_reading_events)
|
|
476
|
+
|
|
477
|
+
@sensor_reading_events.setter
|
|
478
|
+
def sensor_reading_events(self, sensor_reading_events: list[SensorReadingEvent]) -> None:
|
|
479
|
+
self.change_sensor_reading_events(sensor_reading_events)
|
|
480
|
+
|
|
481
|
+
@property
|
|
482
|
+
def pressure_data_raw(self) -> np.ndarray:
|
|
483
|
+
"""
|
|
484
|
+
Gets the raw pressure readings.
|
|
485
|
+
|
|
486
|
+
Returns
|
|
487
|
+
-------
|
|
488
|
+
`numpy.ndarray`
|
|
489
|
+
Raw pressure readings.
|
|
490
|
+
"""
|
|
491
|
+
return deepcopy(self.__pressure_data_raw)
|
|
492
|
+
|
|
493
|
+
@property
|
|
494
|
+
def flow_data_raw(self) -> np.ndarray:
|
|
495
|
+
"""
|
|
496
|
+
Gets the raw flow readings.
|
|
497
|
+
|
|
498
|
+
Returns
|
|
499
|
+
-------
|
|
500
|
+
`numpy.ndarray`
|
|
501
|
+
Raw flow readings.
|
|
502
|
+
"""
|
|
503
|
+
return deepcopy(self.__flow_data_raw)
|
|
504
|
+
|
|
505
|
+
@property
|
|
506
|
+
def demand_data_raw(self) -> np.ndarray:
|
|
507
|
+
"""
|
|
508
|
+
Gets the raw demand readings.
|
|
509
|
+
|
|
510
|
+
Returns
|
|
511
|
+
-------
|
|
512
|
+
`numpy.ndarray`
|
|
513
|
+
Raw demand readings.
|
|
514
|
+
"""
|
|
515
|
+
return deepcopy(self.__demand_data_raw)
|
|
516
|
+
|
|
517
|
+
@property
|
|
518
|
+
def node_quality_data_raw(self) -> np.ndarray:
|
|
519
|
+
"""
|
|
520
|
+
Gets the raw node quality readings.
|
|
521
|
+
|
|
522
|
+
Returns
|
|
523
|
+
-------
|
|
524
|
+
`numpy.ndarray`
|
|
525
|
+
Raw node quality readings.
|
|
526
|
+
"""
|
|
527
|
+
return deepcopy(self.__node_quality_data_raw)
|
|
528
|
+
|
|
529
|
+
@property
|
|
530
|
+
def link_quality_data_raw(self) -> np.ndarray:
|
|
531
|
+
"""
|
|
532
|
+
Gets the raw link quality readings.
|
|
533
|
+
|
|
534
|
+
Returns
|
|
535
|
+
-------
|
|
536
|
+
`numpy.ndarray`
|
|
537
|
+
Raw link quality readings.
|
|
538
|
+
"""
|
|
539
|
+
return deepcopy(self.__link_quality_data_raw)
|
|
540
|
+
|
|
541
|
+
@property
|
|
542
|
+
def sensor_readings_time(self) -> np.ndarray:
|
|
543
|
+
"""
|
|
544
|
+
Gets the sensor readings time stamps.
|
|
545
|
+
|
|
546
|
+
Returns
|
|
547
|
+
-------
|
|
548
|
+
`numpy.ndarray`
|
|
549
|
+
Sensor readings time stamps.
|
|
550
|
+
"""
|
|
551
|
+
return deepcopy(self.__sensor_readings_time)
|
|
552
|
+
|
|
553
|
+
@property
|
|
554
|
+
def pumps_state_data_raw(self) -> np.ndarray:
|
|
555
|
+
"""
|
|
556
|
+
Gets the raw pump state readings.
|
|
557
|
+
|
|
558
|
+
Returns
|
|
559
|
+
-------
|
|
560
|
+
`numpy.ndarray`
|
|
561
|
+
Raw pump state readings.
|
|
562
|
+
"""
|
|
563
|
+
return deepcopy(self.__pumps_state_data_raw)
|
|
564
|
+
|
|
565
|
+
@property
|
|
566
|
+
def valves_state_data_raw(self) -> np.ndarray:
|
|
567
|
+
"""
|
|
568
|
+
Gets the raw valve state readings.
|
|
569
|
+
|
|
570
|
+
Returns
|
|
571
|
+
-------
|
|
572
|
+
`numpy.ndarray`
|
|
573
|
+
Raw valve state readings.
|
|
574
|
+
"""
|
|
575
|
+
return deepcopy(self.__valves_state_data_raw)
|
|
576
|
+
|
|
577
|
+
@property
|
|
578
|
+
def tanks_volume_data_raw(self) -> np.ndarray:
|
|
579
|
+
"""
|
|
580
|
+
Gets the raw tank volume readings.
|
|
581
|
+
|
|
582
|
+
Returns
|
|
583
|
+
-------
|
|
584
|
+
`numpy.ndarray`
|
|
585
|
+
Raw tank volume readings.
|
|
586
|
+
"""
|
|
587
|
+
return deepcopy(self.__tanks_volume_data_raw)
|
|
588
|
+
|
|
589
|
+
@property
|
|
590
|
+
def surface_species_concentration_raw(self) -> np.ndarray:
|
|
591
|
+
"""
|
|
592
|
+
Gets the raw surface species concentrations at links/pipes.
|
|
593
|
+
|
|
594
|
+
Returns
|
|
595
|
+
-------
|
|
596
|
+
`numpy.ndarray`
|
|
597
|
+
Raw species concentrations.
|
|
598
|
+
"""
|
|
599
|
+
return deepcopy(self.__surface_species_concentration_raw)
|
|
600
|
+
|
|
601
|
+
@property
|
|
602
|
+
def bulk_species_node_concentration_raw(self) -> np.ndarray:
|
|
603
|
+
"""
|
|
604
|
+
Gets the raw bulk species concentrations at nodes.
|
|
605
|
+
|
|
606
|
+
Returns
|
|
607
|
+
-------
|
|
608
|
+
`numpy.ndarray`
|
|
609
|
+
Raw species concentrations.
|
|
610
|
+
"""
|
|
611
|
+
return deepcopy(self.__bulk_species_node_concentration_raw)
|
|
612
|
+
|
|
613
|
+
@property
|
|
614
|
+
def bulk_species_link_concentration_raw(self) -> np.ndarray:
|
|
615
|
+
"""
|
|
616
|
+
Gets the raw bulk species concentrations at links/pipes.
|
|
617
|
+
|
|
618
|
+
Returns
|
|
619
|
+
-------
|
|
620
|
+
`numpy.ndarray`
|
|
621
|
+
Raw species concentrations.
|
|
622
|
+
"""
|
|
623
|
+
return deepcopy(self.__bulk_species_link_concentration_raw)
|
|
624
|
+
|
|
625
|
+
@property
|
|
626
|
+
def pump_energy_usage_data(self) -> np.ndarray:
|
|
627
|
+
"""
|
|
628
|
+
Gets the energy usage of each pump.
|
|
629
|
+
|
|
630
|
+
.. note::
|
|
631
|
+
This attribute is NOT included in
|
|
632
|
+
:func:`~epyt_flow.simulation.scada.scada_data.ScadaData.get_data` --
|
|
633
|
+
calling this function is the only way of accessing the energy usage of each pump.
|
|
634
|
+
|
|
635
|
+
Returns
|
|
636
|
+
-------
|
|
637
|
+
`numpy.ndarray`
|
|
638
|
+
Energy usage of each pump.
|
|
639
|
+
"""
|
|
640
|
+
return deepcopy(self.__pump_energy_usage_data)
|
|
641
|
+
|
|
642
|
+
@property
|
|
643
|
+
def pump_efficiency_data(self) -> np.ndarray:
|
|
644
|
+
"""
|
|
645
|
+
Gets the pumps' efficiency.
|
|
646
|
+
|
|
647
|
+
.. note::
|
|
648
|
+
This attribute is NOT included in
|
|
649
|
+
:func:`~epyt_flow.simulation.scada.scada_data.ScadaData.get_data` --
|
|
650
|
+
calling this function is the only way of accessing the pumps' efficiency.
|
|
651
|
+
|
|
652
|
+
Returns
|
|
653
|
+
-------
|
|
654
|
+
`numpy.ndarray`
|
|
655
|
+
Pumps' efficiency.
|
|
656
|
+
"""
|
|
657
|
+
return deepcopy(self.__pump_efficiency_data)
|
|
658
|
+
|
|
659
|
+
def __init(self):
|
|
660
|
+
self.__apply_sensor_noise = lambda x: x
|
|
661
|
+
if self.__sensor_noise is not None:
|
|
662
|
+
self.__apply_sensor_noise = self.__sensor_noise.apply
|
|
663
|
+
|
|
664
|
+
self.__apply_sensor_reading_events = []
|
|
665
|
+
for sensor_event in self.__sensor_reading_events:
|
|
666
|
+
idx = None
|
|
667
|
+
if sensor_event.sensor_type == SENSOR_TYPE_NODE_PRESSURE:
|
|
668
|
+
idx = self.__sensor_config.get_index_of_reading(
|
|
669
|
+
pressure_sensor=sensor_event.sensor_id)
|
|
670
|
+
elif sensor_event.sensor_type == SENSOR_TYPE_NODE_QUALITY:
|
|
671
|
+
idx = self.__sensor_config.get_index_of_reading(
|
|
672
|
+
node_quality_sensor=sensor_event.sensor_id)
|
|
673
|
+
elif sensor_event.sensor_type == SENSOR_TYPE_NODE_DEMAND:
|
|
674
|
+
idx = self.__sensor_config.get_index_of_reading(
|
|
675
|
+
demand_sensor=sensor_event.sensor_id)
|
|
676
|
+
elif sensor_event.sensor_type == SENSOR_TYPE_LINK_FLOW:
|
|
677
|
+
idx = self.__sensor_config.get_index_of_reading(
|
|
678
|
+
flow_sensor=sensor_event.sensor_id)
|
|
679
|
+
elif sensor_event.sensor_type == SENSOR_TYPE_LINK_QUALITY:
|
|
680
|
+
idx = self.__sensor_config.get_index_of_reading(
|
|
681
|
+
link_quality_sensor=sensor_event.sensor_id)
|
|
682
|
+
elif sensor_event.sensor_type == SENSOR_TYPE_VALVE_STATE:
|
|
683
|
+
idx = self.__sensor_config.get_index_of_reading(
|
|
684
|
+
valve_state_sensor=sensor_event.sensor_id)
|
|
685
|
+
elif sensor_event.sensor_type == SENSOR_TYPE_PUMP_STATE:
|
|
686
|
+
idx = self.__sensor_config.get_index_of_reading(
|
|
687
|
+
pump_state_sensor=sensor_event.sensor_id)
|
|
688
|
+
elif sensor_event.sensor_type == SENSOR_TYPE_TANK_VOLUME:
|
|
689
|
+
idx = self.__sensor_config.get_index_of_reading(
|
|
690
|
+
tank_volume_sensor=sensor_event.sensor_id)
|
|
691
|
+
elif sensor_event.sensor_type == SENSOR_TYPE_NODE_BULK_SPECIES:
|
|
692
|
+
idx = self.__sensor_config.get_index_of_reading(
|
|
693
|
+
bulk_species_node_sensor=sensor_event.sensor_id)
|
|
694
|
+
elif sensor_event.sensor_type == SENSOR_TYPE_LINK_BULK_SPECIES:
|
|
695
|
+
idx = self.__sensor_config.get_index_of_reading(
|
|
696
|
+
bulk_species_link_sensor=sensor_event.sensor_id)
|
|
697
|
+
elif sensor_event.sensor_type == SENSOR_TYPE_SURFACE_SPECIES:
|
|
698
|
+
idx = self.__sensor_config.get_index_of_reading(
|
|
699
|
+
surface_species_sensor=sensor_event.sensor_id)
|
|
700
|
+
|
|
701
|
+
self.__apply_sensor_reading_events.append((idx, sensor_event.apply))
|
|
702
|
+
|
|
703
|
+
self.__sensor_readings = None
|
|
704
|
+
|
|
705
|
+
def get_attributes(self) -> dict:
|
|
706
|
+
attr = {"sensor_config": self.__sensor_config,
|
|
707
|
+
"frozen_sensor_config": self.__frozen_sensor_config,
|
|
708
|
+
"sensor_noise": self.__sensor_noise,
|
|
709
|
+
"sensor_reading_events": self.__sensor_reading_events,
|
|
710
|
+
"pressure_data_raw": self.__pressure_data_raw,
|
|
711
|
+
"flow_data_raw": self.__flow_data_raw,
|
|
712
|
+
"demand_data_raw": self.__demand_data_raw,
|
|
713
|
+
"node_quality_data_raw": self.__node_quality_data_raw,
|
|
714
|
+
"link_quality_data_raw": self.__link_quality_data_raw,
|
|
715
|
+
"sensor_readings_time": self.__sensor_readings_time,
|
|
716
|
+
"pumps_state_data_raw": self.__pumps_state_data_raw,
|
|
717
|
+
"valves_state_data_raw": self.__valves_state_data_raw,
|
|
718
|
+
"tanks_volume_data_raw": self.__tanks_volume_data_raw,
|
|
719
|
+
"surface_species_concentration_raw": self.__surface_species_concentration_raw,
|
|
720
|
+
"bulk_species_node_concentration_raw": self.__bulk_species_node_concentration_raw,
|
|
721
|
+
"bulk_species_link_concentration_raw": self.__bulk_species_link_concentration_raw,
|
|
722
|
+
"pump_energy_usage_data": self.__pump_energy_usage_data,
|
|
723
|
+
"pump_efficiency_data": self.__pump_efficiency_data}
|
|
724
|
+
|
|
725
|
+
return super().get_attributes() | attr
|
|
726
|
+
|
|
727
|
+
def __eq__(self, other) -> bool:
|
|
728
|
+
if not isinstance(other, ScadaData):
|
|
729
|
+
raise TypeError(f"Can not compare 'ScadaData' instance to '{type(other)}' instance")
|
|
730
|
+
|
|
731
|
+
try:
|
|
732
|
+
return self.__sensor_config == other.sensor_config \
|
|
733
|
+
and self.__frozen_sensor_config == other.frozen_sensor_config \
|
|
734
|
+
and self.__sensor_noise == other.sensor_noise \
|
|
735
|
+
and all(a == b for a, b in
|
|
736
|
+
zip(self.__sensor_reading_events, other.sensor_reading_events)) \
|
|
737
|
+
and np.all(self.__pressure_data_raw == other.pressure_data_raw) \
|
|
738
|
+
and np.all(self.__flow_data_raw == other.flow_data_raw) \
|
|
739
|
+
and np.all(self.__demand_data_raw == self.demand_data_raw) \
|
|
740
|
+
and np.all(self.__node_quality_data_raw == other.node_quality_data_raw) \
|
|
741
|
+
and np.all(self.__link_quality_data_raw == other.link_quality_data_raw) \
|
|
742
|
+
and np.all(self.__sensor_readings_time == other.sensor_readings_time) \
|
|
743
|
+
and np.all(self.__pumps_state_data_raw == other.pumps_state_data_raw) \
|
|
744
|
+
and np.all(self.__valves_state_data_raw == other.valves_state_data_raw) \
|
|
745
|
+
and np.all(self.__tanks_volume_data_raw == other.tanks_volume_data_raw) \
|
|
746
|
+
and np.all(self.__surface_species_concentration_raw ==
|
|
747
|
+
other.surface_species_concentration_raw) \
|
|
748
|
+
and np.all(self.__bulk_species_node_concentration_raw ==
|
|
749
|
+
other.bulk_species_node_concentration_raw) \
|
|
750
|
+
and np.all(self.__bulk_species_link_concentration_raw ==
|
|
751
|
+
other.bulk_species_link_concentration_raw) \
|
|
752
|
+
and np.all(self.__pump_energy_usage_data == other.pump_energy_usage_data) \
|
|
753
|
+
and np.all(self.__pump_efficiency_data == other.pump_efficiency_data)
|
|
754
|
+
except Exception as ex:
|
|
755
|
+
warnings.warn(ex.__str__())
|
|
756
|
+
return False
|
|
757
|
+
|
|
758
|
+
def __str__(self) -> str:
|
|
759
|
+
return f"sensor_config: {self.__sensor_config} " + \
|
|
760
|
+
f"frozen_sensor_config: {self.__frozen_sensor_config} " + \
|
|
761
|
+
f"sensor_noise: {self.__sensor_noise} " + \
|
|
762
|
+
f"sensor_reading_events: {self.__sensor_reading_events} " + \
|
|
763
|
+
f"pressure_data_raw: {self.__pressure_data_raw} " + \
|
|
764
|
+
f"flow_data_raw: {self.__flow_data_raw} demand_data_raw: {self.__demand_data_raw} " + \
|
|
765
|
+
f"node_quality_data_raw: {self.__node_quality_data_raw} " + \
|
|
766
|
+
f"link_quality_data_raw: {self.__link_quality_data_raw} " + \
|
|
767
|
+
f"sensor_readings_time: {self.__sensor_readings_time} " + \
|
|
768
|
+
f"pumps_state_data_raw: {self.__pumps_state_data_raw} " + \
|
|
769
|
+
f"valves_state_data_raw: {self.__valves_state_data_raw} " + \
|
|
770
|
+
f"tanks_volume_data_raw: {self.__tanks_volume_data_raw} " + \
|
|
771
|
+
f"surface_species_concentration_raw: {self.__surface_species_concentration_raw} " + \
|
|
772
|
+
f"bulk_species_node_concentration_raw: {self.__bulk_species_node_concentration_raw}" +\
|
|
773
|
+
f" bulk_species_link_concentration_raw: {self.__bulk_species_link_concentration_raw}" +\
|
|
774
|
+
f" pump_efficiency_data: {self.__pump_efficiency_data} " + \
|
|
775
|
+
f"pump_energy_usage_data: {self.__pump_energy_usage_data}"
|
|
776
|
+
|
|
777
|
+
def change_sensor_config(self, sensor_config: SensorConfig) -> None:
|
|
778
|
+
"""
|
|
779
|
+
Changes the sensor configuration.
|
|
780
|
+
|
|
781
|
+
Parameters
|
|
782
|
+
----------
|
|
783
|
+
sensor_config : :class:`~epyt_flow.simulation.sensor_config.SensorConfig`
|
|
784
|
+
New sensor configuration.
|
|
785
|
+
"""
|
|
786
|
+
if self.__frozen_sensor_config is True:
|
|
787
|
+
raise RuntimeError("Sensor configuration can not be changed because it is frozen")
|
|
788
|
+
if not isinstance(sensor_config, SensorConfig):
|
|
789
|
+
raise TypeError("'sensor_config' must be an instance of " +
|
|
790
|
+
"'epyt_flow.simulation.SensorConfig' but not of " +
|
|
791
|
+
f"'{type(sensor_config)}'")
|
|
792
|
+
|
|
793
|
+
self.__sensor_config = sensor_config
|
|
794
|
+
self.__init()
|
|
795
|
+
|
|
796
|
+
def change_sensor_noise(self, sensor_noise: SensorNoise) -> None:
|
|
797
|
+
"""
|
|
798
|
+
Changes the sensor noise/uncertainty.
|
|
799
|
+
|
|
800
|
+
Parameters
|
|
801
|
+
----------
|
|
802
|
+
sensor_noise : :class:`~epyt_flow.uncertainty.sensor_noise.SensorNoise`
|
|
803
|
+
New sensor noise/uncertainty specification.
|
|
804
|
+
"""
|
|
805
|
+
if not isinstance(sensor_noise, SensorNoise):
|
|
806
|
+
raise TypeError("'sensor_noise' must be an instance of " +
|
|
807
|
+
"'epyt_flow.uncertainty.SensorNoise' but not of " +
|
|
808
|
+
f"'{type(sensor_noise)}'")
|
|
809
|
+
|
|
810
|
+
self.__sensor_noise = sensor_noise
|
|
811
|
+
self.__init()
|
|
812
|
+
|
|
813
|
+
def change_sensor_faults(self, sensor_faults: list[SensorFault]) -> None:
|
|
814
|
+
"""
|
|
815
|
+
Changes the sensor faults -- overrides all previous sensor faults!
|
|
816
|
+
|
|
817
|
+
sensor_faults : list[:class:`~epyt_flow.simulation.events.sensor_faults.SensorFault`]
|
|
818
|
+
List of new sensor faults.
|
|
819
|
+
"""
|
|
820
|
+
if len(sensor_faults) != 0:
|
|
821
|
+
if any(not isinstance(e, SensorFault) for e in sensor_faults):
|
|
822
|
+
raise TypeError("'sensor_faults' must be a list of " +
|
|
823
|
+
"'epyt_flow.simulation.events.SensorFault' instances")
|
|
824
|
+
|
|
825
|
+
self.__sensor_reading_events = list(filter(lambda e: not isinstance(e, SensorFault),
|
|
826
|
+
self.__sensor_reading_events))
|
|
827
|
+
self.__sensor_reading_events += sensor_faults
|
|
828
|
+
self.__init()
|
|
829
|
+
|
|
830
|
+
def change_sensor_reading_attacks(self,
|
|
831
|
+
sensor_reading_attacks: list[SensorReadingAttack]) -> None:
|
|
832
|
+
"""
|
|
833
|
+
Changes the sensor reading attacks -- overrides all previous sensor reading attacks!
|
|
834
|
+
|
|
835
|
+
sensor_reading_attacks : list[:class:`~epyt_flow.simulation.events.sensor_reading_attack.SensorReadingAttack`]
|
|
836
|
+
List of new sensor reading attacks.
|
|
837
|
+
"""
|
|
838
|
+
if len(sensor_reading_attacks) != 0:
|
|
839
|
+
if any(not isinstance(e, SensorReadingAttack) for e in sensor_reading_attacks):
|
|
840
|
+
raise TypeError("'sensor_reading_attacks' must be a list of " +
|
|
841
|
+
"'epyt_flow.simulation.events.SensorReadingAttack' instances")
|
|
842
|
+
|
|
843
|
+
self.__sensor_reading_events = list(filter(lambda e: not isinstance(e, SensorReadingAttack),
|
|
844
|
+
self.__sensor_reading_events))
|
|
845
|
+
self.__sensor_reading_events += sensor_reading_attacks
|
|
846
|
+
self.__init()
|
|
847
|
+
|
|
848
|
+
def change_sensor_reading_events(self, sensor_reading_events: list[SensorReadingEvent]) -> None:
|
|
849
|
+
"""
|
|
850
|
+
Changes the sensor reading events -- overrides all previous sensor reading events
|
|
851
|
+
(incl. sensor faults)!
|
|
852
|
+
|
|
853
|
+
sensor_reading_events : list[:class:`~epyt_flow.simulation.events.sensor_reading_event.SensorReadingEvent`]
|
|
854
|
+
List of new sensor reading events.
|
|
855
|
+
"""
|
|
856
|
+
if len(sensor_reading_events) != 0:
|
|
857
|
+
if any(not isinstance(e, SensorReadingEvent) for e in sensor_reading_events):
|
|
858
|
+
raise TypeError("'sensor_reading_events' must be a list of " +
|
|
859
|
+
"'epyt_flow.simulation.events.SensorReadingEvent' instances")
|
|
860
|
+
|
|
861
|
+
self.__sensor_reading_events = sensor_reading_events
|
|
862
|
+
self.__init()
|
|
863
|
+
|
|
864
|
+
def join(self, other) -> None:
|
|
865
|
+
"""
|
|
866
|
+
Joins two :class:`~epyt_flow.simulation.scada_data.scada_data.ScadaData` instances based
|
|
867
|
+
on the sensor reading times. Consequently, **both instances must be equal in their
|
|
868
|
+
sensor reading times**.
|
|
869
|
+
Attributes (i.e. types of sensor readings) that are NOT present in THIS instance
|
|
870
|
+
but in `others` will be added to this instance -- all other attributes are ignored.
|
|
871
|
+
The sensor configuration is updated according to the sensor readings in `other`.
|
|
872
|
+
|
|
873
|
+
Parameters
|
|
874
|
+
----------
|
|
875
|
+
other : :class:`~epyt_flow.simulation.scada_data.scada_data.ScadaData`
|
|
876
|
+
Other scada data to be concatenated to this data.
|
|
877
|
+
"""
|
|
878
|
+
if not isinstance(other, ScadaData):
|
|
879
|
+
raise TypeError("'other' must be an instance of 'ScadaData' " +
|
|
880
|
+
f"but not of '{type(other)}'")
|
|
881
|
+
if self.__frozen_sensor_config != other.frozen_sensor_config:
|
|
882
|
+
raise ValueError("Sensor configurations of both instances must be " +
|
|
883
|
+
"either frozen or not frozen")
|
|
884
|
+
if not np.all(self.__sensor_readings_time == other.sensor_readings_time):
|
|
885
|
+
raise ValueError("Both 'ScadaData' instances must be equal in their " +
|
|
886
|
+
"sensor readings times")
|
|
887
|
+
if any(e1 != e2 for e1, e2 in zip(self.__sensor_reading_events,
|
|
888
|
+
other.sensor_reading_events)):
|
|
889
|
+
raise ValueError("'other' must have the same sensor reading events as this instance!")
|
|
890
|
+
if self.__sensor_config.nodes != other.sensor_config.nodes:
|
|
891
|
+
raise ValueError("Inconsistency in nodes found")
|
|
892
|
+
if self.__sensor_config.links != other.sensor_config.links:
|
|
893
|
+
raise ValueError("Inconsistency in links/pipes found")
|
|
894
|
+
if self.__sensor_config.valves != other.sensor_config.valves:
|
|
895
|
+
raise ValueError("Inconsistency in valves found")
|
|
896
|
+
if self.__sensor_config.pumps != other.sensor_config.pumps:
|
|
897
|
+
raise ValueError("Inconsistency in pumps found")
|
|
898
|
+
if self.__sensor_config.tanks != other.sensor_config.tanks:
|
|
899
|
+
raise ValueError("Inconsistency in tanks found")
|
|
900
|
+
if self.__sensor_config.bulk_species != other.sensor_config.bulk_species:
|
|
901
|
+
raise ValueError("Inconsistency in bulk species found")
|
|
902
|
+
if self.__sensor_config.surface_species != other.sensor_config.surface_species:
|
|
903
|
+
raise ValueError("Inconsistency in surface species found")
|
|
904
|
+
|
|
905
|
+
self.__sensor_readings = None
|
|
906
|
+
|
|
907
|
+
if self.__pressure_data_raw is None and other.pressure_data_raw is not None:
|
|
908
|
+
self.__pressure_data_raw = other.pressure_data_raw
|
|
909
|
+
self.__sensor_config.pressure_sensors = other.sensor_config.pressure_sensors
|
|
910
|
+
|
|
911
|
+
if self.__flow_data_raw is None and other.flow_data_raw is not None:
|
|
912
|
+
self.__flow_data_raw = other.flow_data_raw
|
|
913
|
+
self.__sensor_config.flow_sensors = other.sensor_config.flow_sensors
|
|
914
|
+
|
|
915
|
+
if self.__demand_data_raw is None and other.demand_data_raw is not None:
|
|
916
|
+
self.__demand_data_raw = other.demand_data_raw
|
|
917
|
+
self.__sensor_config.demand_sensors = other.sensor_config.demand_sensors
|
|
918
|
+
|
|
919
|
+
if self.__node_quality_data_raw is None and other.node_quality_data_raw is not None:
|
|
920
|
+
self.__node_quality_data_raw = other.node_quality_data_raw
|
|
921
|
+
self.__sensor_config.quality_node_sensors = other.sensor_config.quality_node_sensors
|
|
922
|
+
|
|
923
|
+
if self.__link_quality_data_raw is None and other.link_quality_data_raw is not None:
|
|
924
|
+
self.__link_quality_data_raw = other.link_quality_data_raw
|
|
925
|
+
self.__sensor_config.quality_node_sensors = other.sensor_config.quality_node_sensors
|
|
926
|
+
|
|
927
|
+
if self.__valves_state_data_raw is None and other.valves_state_data_raw is not None:
|
|
928
|
+
self.__valves_state_data_raw = other.valves_state_data_raw
|
|
929
|
+
self.__sensor_config.valve_state_sensors = other.sensor_config.valve_state_sensors
|
|
930
|
+
|
|
931
|
+
if self.__pumps_state_data_raw is None and other.pumps_state_data_raw is not None:
|
|
932
|
+
self.__pumps_state_data_raw = other.pumps_state_data_raw
|
|
933
|
+
self.__sensor_config.pump_state_sensors = other.sensor_config.pump_state_sensors
|
|
934
|
+
|
|
935
|
+
if self.__tanks_volume_data_raw is None and other.tanks_volume_data_raw is not None:
|
|
936
|
+
self.__tanks_volume_data_raw = other.tanks_volume_data_raw
|
|
937
|
+
self.__sensor_config.tank_volume_sensors = other.sensor_config.tank_volume_sensors
|
|
938
|
+
|
|
939
|
+
if self.__bulk_species_node_concentration_raw is None and \
|
|
940
|
+
other.bulk_species_node_concentration_raw is not None:
|
|
941
|
+
self.__bulk_species_node_concentration_raw = other.bulk_species_node_concentration_raw
|
|
942
|
+
self.__sensor_config.bulk_species_node_sensors = \
|
|
943
|
+
other.sensor_config.bulk_species_node_sensors
|
|
944
|
+
|
|
945
|
+
if self.__bulk_species_link_concentration_raw is None and \
|
|
946
|
+
other.bulk_species_link_concentration_raw is not None:
|
|
947
|
+
self.__bulk_species_link_concentration_raw = other.bulk_species_link_concentration_raw
|
|
948
|
+
self.__sensor_config.bulk_species_link_sensors = \
|
|
949
|
+
other.sensor_config.bulk_species_link_sensors
|
|
950
|
+
|
|
951
|
+
if self.__surface_species_concentration_raw is None and \
|
|
952
|
+
other.surface_species_concentration_raw is not None:
|
|
953
|
+
self.__surface_species_concentration_raw = other.surface_species_concentration_raw
|
|
954
|
+
self.__sensor_config.surface_species_sensors = \
|
|
955
|
+
other.sensor_config.surface_species_sensors
|
|
956
|
+
|
|
957
|
+
if self.__pump_energy_usage_data is None and other.pump_energy_usage_data is not None:
|
|
958
|
+
self.__pump_energy_usage_data = other.pump_energy_usage_data
|
|
959
|
+
|
|
960
|
+
if self.__pump_efficiency_data is None and other.pump_efficiency_data is not None:
|
|
961
|
+
self.__pump_efficiency_data = other.pump_efficiency_data
|
|
962
|
+
|
|
963
|
+
self.__init()
|
|
964
|
+
|
|
965
|
+
def concatenate(self, other) -> None:
|
|
966
|
+
"""
|
|
967
|
+
Concatenates two :class:`~epyt_flow.simulation.scada_data.scada_data.ScadaData` instances
|
|
968
|
+
-- i.e. add SCADA data from another given
|
|
969
|
+
:class:`~epyt_flow.simulation.scada_data.scada_data.ScadaData` instance to this one.
|
|
970
|
+
|
|
971
|
+
Note that the two :class:`~epyt_flow.simulation.scada_data.scada_data.ScadaData` instances
|
|
972
|
+
must be the same in all other attributs (e.g. sensor configuration, etc.).
|
|
973
|
+
|
|
974
|
+
Parameters
|
|
975
|
+
----------
|
|
976
|
+
other : :class:`~epyt_flow.simulation.scada_data.scada_data.ScadaData`
|
|
977
|
+
Other scada data to be concatenated to this data.
|
|
978
|
+
"""
|
|
979
|
+
if not isinstance(other, ScadaData):
|
|
980
|
+
raise TypeError(f"'other' must be an instance of 'ScadaData' but not of {type(other)}")
|
|
981
|
+
if self.__sensor_config != other.sensor_config:
|
|
982
|
+
raise ValueError("Sensor configurations must be the same!")
|
|
983
|
+
if self.__frozen_sensor_config != other.frozen_sensor_config:
|
|
984
|
+
raise ValueError("Sensor configurations of both instances must be " +
|
|
985
|
+
"either frozen or not frozen")
|
|
986
|
+
if len(self.__sensor_reading_events) != len(other.sensor_reading_events):
|
|
987
|
+
raise ValueError("'other' must have the same sensor reading events as this instance!")
|
|
988
|
+
if any(e1 != e2 for e1, e2 in zip(self.__sensor_reading_events,
|
|
989
|
+
other.sensor_reading_events)):
|
|
990
|
+
raise ValueError("'other' must have the same sensor reading events as this instance!")
|
|
991
|
+
|
|
992
|
+
self.__sensor_readings = None
|
|
993
|
+
|
|
994
|
+
self.__sensor_readings_time = np.concatenate(
|
|
995
|
+
(self.__sensor_readings_time, other.sensor_readings_time), axis=0)
|
|
996
|
+
|
|
997
|
+
if self.__pressure_data_raw is not None:
|
|
998
|
+
self.__pressure_data_raw = np.concatenate(
|
|
999
|
+
(self.__pressure_data_raw, other.pressure_data_raw), axis=0)
|
|
1000
|
+
|
|
1001
|
+
if self.__flow_data_raw is not None:
|
|
1002
|
+
self.__flow_data_raw = np.concatenate(
|
|
1003
|
+
(self.__flow_data_raw, other.flow_data_raw), axis=0)
|
|
1004
|
+
|
|
1005
|
+
if self.__demand_data_raw is not None:
|
|
1006
|
+
self.__demand_data_raw = np.concatenate(
|
|
1007
|
+
(self.__demand_data_raw, other.demand_data_raw), axis=0)
|
|
1008
|
+
|
|
1009
|
+
if self.__node_quality_data_raw is not None:
|
|
1010
|
+
self.__node_quality_data_raw = np.concatenate(
|
|
1011
|
+
(self.__node_quality_data_raw, other.node_quality_data_raw), axis=0)
|
|
1012
|
+
|
|
1013
|
+
if self.__link_quality_data_raw is not None:
|
|
1014
|
+
self.__link_quality_data_raw = np.concatenate(
|
|
1015
|
+
(self.__link_quality_data_raw, other.link_quality_data_raw), axis=0)
|
|
1016
|
+
|
|
1017
|
+
if self.__pumps_state_data_raw is not None:
|
|
1018
|
+
self.__pumps_state_data_raw = np.concatenate(
|
|
1019
|
+
(self.__pumps_state_data_raw, other.pumps_state_data_raw), axis=0)
|
|
1020
|
+
|
|
1021
|
+
if self.__valves_state_data_raw is not None:
|
|
1022
|
+
self.__valves_state_data_raw = np.concatenate(
|
|
1023
|
+
(self.__valves_state_data_raw, other.valves_state_data_raw), axis=0)
|
|
1024
|
+
|
|
1025
|
+
if self.__tanks_volume_data_raw is not None:
|
|
1026
|
+
self.__tanks_volume_data_raw = np.concatenate(
|
|
1027
|
+
(self.__tanks_volume_data_raw, other.tanks_volume_data_raw), axis=0)
|
|
1028
|
+
|
|
1029
|
+
if self.__surface_species_concentration_raw is not None:
|
|
1030
|
+
self.__surface_species_concentration_raw = np.concatenate(
|
|
1031
|
+
(self.__surface_species_concentration_raw,
|
|
1032
|
+
other.surface_species_concentration_raw),
|
|
1033
|
+
axis=0)
|
|
1034
|
+
|
|
1035
|
+
if self.__bulk_species_node_concentration_raw is not None:
|
|
1036
|
+
self.__bulk_species_node_concentration_raw = np.concatenate(
|
|
1037
|
+
(self.__bulk_species_node_concentration_raw,
|
|
1038
|
+
other.bulk_species_node_concentration_raw),
|
|
1039
|
+
axis=0)
|
|
1040
|
+
|
|
1041
|
+
if self.__bulk_species_link_concentration_raw is not None:
|
|
1042
|
+
self.__bulk_species_link_concentration_raw = np.concatenate(
|
|
1043
|
+
(self.__bulk_species_link_concentration_raw,
|
|
1044
|
+
other.bulk_species_link_concentration_raw),
|
|
1045
|
+
axis=0)
|
|
1046
|
+
|
|
1047
|
+
if self.__pump_energy_usage_data is not None:
|
|
1048
|
+
self.__pump_energy_usage_data = np.concatenate(
|
|
1049
|
+
(self.__pump_energy_usage_data, other.pump_energy_usage_data),
|
|
1050
|
+
axis=0)
|
|
1051
|
+
|
|
1052
|
+
if self.__pump_efficiency_data is not None:
|
|
1053
|
+
self.__pump_efficiency_data = np.concatenate(
|
|
1054
|
+
(self.__pump_efficiency_data, other.pump_efficiency_data),
|
|
1055
|
+
axis=0)
|
|
1056
|
+
|
|
1057
|
+
def get_data(self) -> np.ndarray:
|
|
1058
|
+
"""
|
|
1059
|
+
Computes the final sensor readings -- note that those might be subject to
|
|
1060
|
+
given sensor faults and sensor noise/uncertainty.
|
|
1061
|
+
|
|
1062
|
+
Returns
|
|
1063
|
+
-------
|
|
1064
|
+
`numpy.ndarray`
|
|
1065
|
+
Final sensor readings.
|
|
1066
|
+
"""
|
|
1067
|
+
# Comute clean sensor readings
|
|
1068
|
+
if self.__frozen_sensor_config is False:
|
|
1069
|
+
args = {"pressures": self.__pressure_data_raw,
|
|
1070
|
+
"flows": self.__flow_data_raw,
|
|
1071
|
+
"demands": self.__demand_data_raw,
|
|
1072
|
+
"nodes_quality": self.__node_quality_data_raw,
|
|
1073
|
+
"links_quality": self.__link_quality_data_raw,
|
|
1074
|
+
"pumps_state": self.__pumps_state_data_raw,
|
|
1075
|
+
"valves_state": self.__valves_state_data_raw,
|
|
1076
|
+
"tanks_volume": self.__tanks_volume_data_raw,
|
|
1077
|
+
"bulk_species_node_concentrations": self.__bulk_species_node_concentration_raw,
|
|
1078
|
+
"bulk_species_link_concentrations": self.__bulk_species_link_concentration_raw,
|
|
1079
|
+
"surface_species_concentrations": self.__surface_species_concentration_raw}
|
|
1080
|
+
sensor_readings = self.__sensor_config.compute_readings(**args)
|
|
1081
|
+
else:
|
|
1082
|
+
data = []
|
|
1083
|
+
|
|
1084
|
+
if self.__pressure_data_raw is not None:
|
|
1085
|
+
data.append(self.__pressure_data_raw)
|
|
1086
|
+
if self.__flow_data_raw is not None:
|
|
1087
|
+
data.append(self.__flow_data_raw)
|
|
1088
|
+
if self.__demand_data_raw is not None:
|
|
1089
|
+
data.append(self.__demand_data_raw)
|
|
1090
|
+
if self.__node_quality_data_raw is not None:
|
|
1091
|
+
data.append(self.__node_quality_data_raw)
|
|
1092
|
+
if self.__link_quality_data_raw is not None:
|
|
1093
|
+
data.append(self.__link_quality_data_raw)
|
|
1094
|
+
if self.__valves_state_data_raw is not None:
|
|
1095
|
+
data.append(self.__valves_state_data_raw)
|
|
1096
|
+
if self.__pumps_state_data_raw is not None:
|
|
1097
|
+
data.append(self.__pumps_state_data_raw)
|
|
1098
|
+
if self.__tanks_volume_data_raw is not None:
|
|
1099
|
+
data.append(self.__tanks_volume_data_raw)
|
|
1100
|
+
if self.__surface_species_concentration_raw is not None:
|
|
1101
|
+
data.append(self.__surface_species_concentration_raw)
|
|
1102
|
+
if self.__bulk_species_node_concentration_raw is not None:
|
|
1103
|
+
data.append(self.__bulk_species_node_concentration_raw)
|
|
1104
|
+
if self.__bulk_species_link_concentration_raw is not None:
|
|
1105
|
+
data.append(self.__bulk_species_link_concentration_raw)
|
|
1106
|
+
|
|
1107
|
+
sensor_readings = np.concatenate(data, axis=1)
|
|
1108
|
+
|
|
1109
|
+
# Apply sensor uncertainties
|
|
1110
|
+
state_sensors_idx = [] # Pump states and valve states are NOT affected!
|
|
1111
|
+
for link_id in self.sensor_config.pump_state_sensors:
|
|
1112
|
+
state_sensors_idx.append(
|
|
1113
|
+
self.__sensor_config.get_index_of_reading(pump_state_sensor=link_id))
|
|
1114
|
+
for link_id in self.sensor_config.valve_state_sensors:
|
|
1115
|
+
state_sensors_idx.append(
|
|
1116
|
+
self.__sensor_config.get_index_of_reading(valve_state_sensor=link_id))
|
|
1117
|
+
|
|
1118
|
+
mask = np.ones(sensor_readings.shape[1], dtype=bool)
|
|
1119
|
+
mask[state_sensors_idx] = False
|
|
1120
|
+
|
|
1121
|
+
sensor_readings[:, mask] = self.__apply_sensor_noise(sensor_readings[:, mask])
|
|
1122
|
+
|
|
1123
|
+
# Apply sensor faults
|
|
1124
|
+
for idx, f in self.__apply_sensor_reading_events:
|
|
1125
|
+
sensor_readings[:, idx] = f(sensor_readings[:, idx], self.__sensor_readings_time)
|
|
1126
|
+
|
|
1127
|
+
self.__sensor_readings = deepcopy(sensor_readings)
|
|
1128
|
+
|
|
1129
|
+
return sensor_readings
|
|
1130
|
+
|
|
1131
|
+
def get_data_pressures(self, sensor_locations: list[str] = None) -> np.ndarray:
|
|
1132
|
+
"""
|
|
1133
|
+
Gets the final pressure sensor readings -- note that those might be subject to
|
|
1134
|
+
given sensor faults and sensor noise/uncertainty.
|
|
1135
|
+
|
|
1136
|
+
Parameters
|
|
1137
|
+
----------
|
|
1138
|
+
sensor_locations : `list[str]`, optional
|
|
1139
|
+
Existing pressure sensor locations for which the sensor readings are requested.
|
|
1140
|
+
If None, the readings from all pressure sensors are returned.
|
|
1141
|
+
|
|
1142
|
+
The default is None.
|
|
1143
|
+
|
|
1144
|
+
Returns
|
|
1145
|
+
-------
|
|
1146
|
+
`numpy.ndarray`
|
|
1147
|
+
Pressure sensor readings.
|
|
1148
|
+
"""
|
|
1149
|
+
if self.__sensor_config.pressure_sensors == []:
|
|
1150
|
+
raise ValueError("No pressure sensors set")
|
|
1151
|
+
if sensor_locations is not None:
|
|
1152
|
+
if not isinstance(sensor_locations, list):
|
|
1153
|
+
raise TypeError("'sensor_locations' must be an instance of 'list[str]' " +
|
|
1154
|
+
f"but not of '{type(sensor_locations)}'")
|
|
1155
|
+
if any(s_id not in self.__sensor_config.pressure_sensors for s_id in sensor_locations):
|
|
1156
|
+
raise ValueError("Invalid sensor ID in 'sensor_locations' -- note that all " +
|
|
1157
|
+
"sensors in 'sensor_locations' must be set in the current " +
|
|
1158
|
+
"pressure sensor configuration")
|
|
1159
|
+
else:
|
|
1160
|
+
sensor_locations = self.__sensor_config.pressure_sensors
|
|
1161
|
+
|
|
1162
|
+
if self.__sensor_readings is None:
|
|
1163
|
+
self.get_data()
|
|
1164
|
+
|
|
1165
|
+
idx = [self.__sensor_config.get_index_of_reading(pressure_sensor=s_id)
|
|
1166
|
+
for s_id in sensor_locations]
|
|
1167
|
+
return self.__sensor_readings[:, idx]
|
|
1168
|
+
|
|
1169
|
+
def get_data_flows(self, sensor_locations: list[str] = None) -> np.ndarray:
|
|
1170
|
+
"""
|
|
1171
|
+
Gets the final flow sensor readings -- note that those might be subject to
|
|
1172
|
+
given sensor faults and sensor noise/uncertainty.
|
|
1173
|
+
|
|
1174
|
+
Parameters
|
|
1175
|
+
----------
|
|
1176
|
+
sensor_locations : `list[str]`, optional
|
|
1177
|
+
Existing flow sensor locations for which the sensor readings are requested.
|
|
1178
|
+
If None, the readings from all flow sensors are returned.
|
|
1179
|
+
|
|
1180
|
+
The default is None.
|
|
1181
|
+
|
|
1182
|
+
Returns
|
|
1183
|
+
-------
|
|
1184
|
+
`numpy.ndarray`
|
|
1185
|
+
Flow sensor readings.
|
|
1186
|
+
"""
|
|
1187
|
+
if self.__sensor_config.flow_sensors == []:
|
|
1188
|
+
raise ValueError("No flow sensors set")
|
|
1189
|
+
if sensor_locations is not None:
|
|
1190
|
+
if not isinstance(sensor_locations, list):
|
|
1191
|
+
raise TypeError("'sensor_locations' must be an instance of 'list[str]' " +
|
|
1192
|
+
f"but not of '{type(sensor_locations)}'")
|
|
1193
|
+
if any(s_id not in self.__sensor_config.flow_sensors for s_id in sensor_locations):
|
|
1194
|
+
raise ValueError("Invalid sensor ID in 'sensor_locations' -- note that all " +
|
|
1195
|
+
"sensors in 'sensor_locations' must be set in the current " +
|
|
1196
|
+
"flow sensor configuration")
|
|
1197
|
+
else:
|
|
1198
|
+
sensor_locations = self.__sensor_config.flow_sensors
|
|
1199
|
+
|
|
1200
|
+
if self.__sensor_readings is None:
|
|
1201
|
+
self.get_data()
|
|
1202
|
+
|
|
1203
|
+
idx = [self.__sensor_config.get_index_of_reading(flow_sensor=s_id)
|
|
1204
|
+
for s_id in sensor_locations]
|
|
1205
|
+
return self.__sensor_readings[:, idx]
|
|
1206
|
+
|
|
1207
|
+
def get_data_demands(self, sensor_locations: list[str] = None) -> np.ndarray:
|
|
1208
|
+
"""
|
|
1209
|
+
Gets the final demand sensor readings -- note that those might be subject to
|
|
1210
|
+
given sensor faults and sensor noise/uncertainty.
|
|
1211
|
+
|
|
1212
|
+
Parameters
|
|
1213
|
+
----------
|
|
1214
|
+
sensor_locations : `list[str]`, optional
|
|
1215
|
+
Existing demand sensor locations for which the sensor readings are requested.
|
|
1216
|
+
If None, the readings from all demand sensors are returned.
|
|
1217
|
+
|
|
1218
|
+
The default is None.
|
|
1219
|
+
|
|
1220
|
+
Returns
|
|
1221
|
+
-------
|
|
1222
|
+
`numpy.ndarray`
|
|
1223
|
+
Demand sensor readings.
|
|
1224
|
+
"""
|
|
1225
|
+
if self.__sensor_config.demand_sensors == []:
|
|
1226
|
+
raise ValueError("No demand sensors set")
|
|
1227
|
+
if sensor_locations is not None:
|
|
1228
|
+
if not isinstance(sensor_locations, list):
|
|
1229
|
+
raise TypeError("'sensor_locations' must be an instance of 'list[str]' " +
|
|
1230
|
+
f"but not of '{type(sensor_locations)}'")
|
|
1231
|
+
if any(s_id not in self.__sensor_config.demand_sensors for s_id in sensor_locations):
|
|
1232
|
+
raise ValueError("Invalid sensor ID in 'sensor_locations' -- note that all " +
|
|
1233
|
+
"sensors in 'sensor_locations' must be set in the current " +
|
|
1234
|
+
"demand sensor configuration")
|
|
1235
|
+
else:
|
|
1236
|
+
sensor_locations = self.__sensor_config.demand_sensors
|
|
1237
|
+
|
|
1238
|
+
if self.__sensor_readings is None:
|
|
1239
|
+
self.get_data()
|
|
1240
|
+
|
|
1241
|
+
idx = [self.__sensor_config.get_index_of_reading(demand_sensor=s_id)
|
|
1242
|
+
for s_id in sensor_locations]
|
|
1243
|
+
return self.__sensor_readings[:, idx]
|
|
1244
|
+
|
|
1245
|
+
def get_data_nodes_quality(self, sensor_locations: list[str] = None) -> np.ndarray:
|
|
1246
|
+
"""
|
|
1247
|
+
Gets the final node quality sensor readings -- note that those might be subject to
|
|
1248
|
+
given sensor faults and sensor noise/uncertainty.
|
|
1249
|
+
|
|
1250
|
+
Parameters
|
|
1251
|
+
----------
|
|
1252
|
+
sensor_locations : `list[str]`, optional
|
|
1253
|
+
Existing node quality sensor locations for which the sensor readings are requested.
|
|
1254
|
+
If None, the readings from all node quality sensors are returned.
|
|
1255
|
+
|
|
1256
|
+
The default is None.
|
|
1257
|
+
|
|
1258
|
+
Returns
|
|
1259
|
+
-------
|
|
1260
|
+
`numpy.ndarray`
|
|
1261
|
+
Node quality sensor readings.
|
|
1262
|
+
"""
|
|
1263
|
+
if self.__sensor_config.quality_node_sensors == []:
|
|
1264
|
+
raise ValueError("No node quality sensors set")
|
|
1265
|
+
if sensor_locations is not None:
|
|
1266
|
+
if not isinstance(sensor_locations, list):
|
|
1267
|
+
raise TypeError("'sensor_locations' must be an instance of 'list[str]' " +
|
|
1268
|
+
f"but not of '{type(sensor_locations)}'")
|
|
1269
|
+
if any(s_id not in self.__sensor_config.quality_node_sensors
|
|
1270
|
+
for s_id in sensor_locations):
|
|
1271
|
+
raise ValueError("Invalid sensor ID in 'sensor_locations' -- note that all " +
|
|
1272
|
+
"sensors in 'sensor_locations' must be set in the current " +
|
|
1273
|
+
"node quality sensor configuration")
|
|
1274
|
+
else:
|
|
1275
|
+
sensor_locations = self.__sensor_config.quality_node_sensors
|
|
1276
|
+
|
|
1277
|
+
if self.__sensor_readings is None:
|
|
1278
|
+
self.get_data()
|
|
1279
|
+
|
|
1280
|
+
idx = [self.__sensor_config.get_index_of_reading(node_quality_sensor=s_id)
|
|
1281
|
+
for s_id in sensor_locations]
|
|
1282
|
+
return self.__sensor_readings[:, idx]
|
|
1283
|
+
|
|
1284
|
+
def get_data_links_quality(self, sensor_locations: list[str] = None) -> np.ndarray:
|
|
1285
|
+
"""
|
|
1286
|
+
Gets the final link quality sensor readings -- note that those might be subject to
|
|
1287
|
+
given sensor faults and sensor noise/uncertainty.
|
|
1288
|
+
|
|
1289
|
+
Parameters
|
|
1290
|
+
----------
|
|
1291
|
+
sensor_locations : `list[str]`, optional
|
|
1292
|
+
Existing link quality sensor locations for which the sensor readings are requested.
|
|
1293
|
+
If None, the readings from all link quality sensors are returned.
|
|
1294
|
+
|
|
1295
|
+
The default is None.
|
|
1296
|
+
|
|
1297
|
+
Returns
|
|
1298
|
+
-------
|
|
1299
|
+
`numpy.ndarray`
|
|
1300
|
+
Link quality sensor readings.
|
|
1301
|
+
"""
|
|
1302
|
+
if self.__sensor_config.quality_link_sensors == []:
|
|
1303
|
+
raise ValueError("No link quality sensors set")
|
|
1304
|
+
if sensor_locations is not None:
|
|
1305
|
+
if not isinstance(sensor_locations, list):
|
|
1306
|
+
raise TypeError("'sensor_locations' must be an instance of 'list[str]' " +
|
|
1307
|
+
f"but not of '{type(sensor_locations)}'")
|
|
1308
|
+
if any(s_id not in self.__sensor_config.quality_link_sensors
|
|
1309
|
+
for s_id in sensor_locations):
|
|
1310
|
+
raise ValueError("Invalid sensor ID in 'sensor_locations' -- note that all " +
|
|
1311
|
+
"sensors in 'sensor_locations' must be set in the current " +
|
|
1312
|
+
"link quality sensor configuration")
|
|
1313
|
+
else:
|
|
1314
|
+
sensor_locations = self.__sensor_config.quality_link_sensors
|
|
1315
|
+
|
|
1316
|
+
if self.__sensor_readings is None:
|
|
1317
|
+
self.get_data()
|
|
1318
|
+
|
|
1319
|
+
idx = [self.__sensor_config.get_index_of_reading(link_quality_sensor=s_id)
|
|
1320
|
+
for s_id in sensor_locations]
|
|
1321
|
+
return self.__sensor_readings[:, idx]
|
|
1322
|
+
|
|
1323
|
+
def get_data_pumps_state(self, sensor_locations: list[str] = None) -> np.ndarray:
|
|
1324
|
+
"""
|
|
1325
|
+
Gets the final pump state sensor readings -- note that those might be subject to
|
|
1326
|
+
given sensor faults and sensor noise/uncertainty.
|
|
1327
|
+
|
|
1328
|
+
Parameters
|
|
1329
|
+
----------
|
|
1330
|
+
sensor_locations : `list[str]`, optional
|
|
1331
|
+
Existing pump state sensor locations for which the sensor readings are requested.
|
|
1332
|
+
If None, the readings from all pump state sensors are returned.
|
|
1333
|
+
|
|
1334
|
+
The default is None.
|
|
1335
|
+
|
|
1336
|
+
Returns
|
|
1337
|
+
-------
|
|
1338
|
+
`numpy.ndarray`
|
|
1339
|
+
Pump state sensor readings.
|
|
1340
|
+
"""
|
|
1341
|
+
if self.__sensor_config.pump_state_sensors == []:
|
|
1342
|
+
raise ValueError("No pump state sensors set")
|
|
1343
|
+
if sensor_locations is not None:
|
|
1344
|
+
if not isinstance(sensor_locations, list):
|
|
1345
|
+
raise TypeError("'sensor_locations' must be an instance of 'list[str]' " +
|
|
1346
|
+
f"but not of '{type(sensor_locations)}'")
|
|
1347
|
+
if any(s_id not in self.__sensor_config.pump_state_sensors
|
|
1348
|
+
for s_id in sensor_locations):
|
|
1349
|
+
raise ValueError("Invalid sensor ID in 'sensor_locations' -- note that all " +
|
|
1350
|
+
"sensors in 'sensor_locations' must be set in the current " +
|
|
1351
|
+
"pump state sensor configuration")
|
|
1352
|
+
else:
|
|
1353
|
+
sensor_locations = self.__sensor_config.pump_state_sensors
|
|
1354
|
+
|
|
1355
|
+
if self.__sensor_readings is None:
|
|
1356
|
+
self.get_data()
|
|
1357
|
+
|
|
1358
|
+
idx = [self.__sensor_config.get_index_of_reading(pump_state_sensor=s_id)
|
|
1359
|
+
for s_id in sensor_locations]
|
|
1360
|
+
return self.__sensor_readings[:, idx]
|
|
1361
|
+
|
|
1362
|
+
def get_data_valves_state(self, sensor_locations: list[str] = None) -> np.ndarray:
|
|
1363
|
+
"""
|
|
1364
|
+
Gets the final valve state sensor readings -- note that those might be subject to
|
|
1365
|
+
given sensor faults and sensor noise/uncertainty.
|
|
1366
|
+
|
|
1367
|
+
Parameters
|
|
1368
|
+
----------
|
|
1369
|
+
sensor_locations : `list[str]`, optional
|
|
1370
|
+
Existing valve state sensor locations for which the sensor readings are requested.
|
|
1371
|
+
If None, the readings from all valve state sensors are returned.
|
|
1372
|
+
|
|
1373
|
+
The default is None.
|
|
1374
|
+
|
|
1375
|
+
Returns
|
|
1376
|
+
-------
|
|
1377
|
+
`numpy.ndarray`
|
|
1378
|
+
Valve state sensor readings.
|
|
1379
|
+
"""
|
|
1380
|
+
if self.__sensor_config.valve_state_sensors == []:
|
|
1381
|
+
raise ValueError("No valve state sensors set")
|
|
1382
|
+
if sensor_locations is not None:
|
|
1383
|
+
if not isinstance(sensor_locations, list):
|
|
1384
|
+
raise TypeError("'sensor_locations' must be an instance of 'list[str]' " +
|
|
1385
|
+
f"but not of '{type(sensor_locations)}'")
|
|
1386
|
+
if any(s_id not in self.__sensor_config.valve_state_sensors
|
|
1387
|
+
for s_id in sensor_locations):
|
|
1388
|
+
raise ValueError("Invalid sensor ID in 'sensor_locations' -- note that all " +
|
|
1389
|
+
"sensors in 'sensor_locations' must be set in the current " +
|
|
1390
|
+
"valve state sensor configuration")
|
|
1391
|
+
else:
|
|
1392
|
+
sensor_locations = self.__sensor_config.valve_state_sensors
|
|
1393
|
+
|
|
1394
|
+
if self.__sensor_readings is None:
|
|
1395
|
+
self.get_data()
|
|
1396
|
+
|
|
1397
|
+
idx = [self.__sensor_config.get_index_of_reading(valve_state_sensor=s_id)
|
|
1398
|
+
for s_id in sensor_locations]
|
|
1399
|
+
return self.__sensor_readings[:, idx]
|
|
1400
|
+
|
|
1401
|
+
def get_data_tanks_water_volume(self, sensor_locations: list[str] = None) -> np.ndarray:
|
|
1402
|
+
"""
|
|
1403
|
+
Gets the final water tanks volume sensor readings -- note that those might be subject to
|
|
1404
|
+
given sensor faults and sensor noise/uncertainty.
|
|
1405
|
+
|
|
1406
|
+
Parameters
|
|
1407
|
+
----------
|
|
1408
|
+
sensor_locations : `list[str]`, optional
|
|
1409
|
+
Existing flow sensor locations for which the sensor readings are requested.
|
|
1410
|
+
If None, the readings from all water tanks volume sensors are returned.
|
|
1411
|
+
|
|
1412
|
+
The default is None.
|
|
1413
|
+
|
|
1414
|
+
Returns
|
|
1415
|
+
-------
|
|
1416
|
+
`numpy.ndarray`
|
|
1417
|
+
Water tanks volume sensor readings.
|
|
1418
|
+
"""
|
|
1419
|
+
if self.__sensor_config.tank_volume_sensors == []:
|
|
1420
|
+
raise ValueError("No tank volume sensors set")
|
|
1421
|
+
if sensor_locations is not None:
|
|
1422
|
+
if not isinstance(sensor_locations, list):
|
|
1423
|
+
raise TypeError("'sensor_locations' must be an instance of 'list[str]' " +
|
|
1424
|
+
f"but not of '{type(sensor_locations)}'")
|
|
1425
|
+
if any(s_id not in self.__sensor_config.tank_volume_sensors
|
|
1426
|
+
for s_id in sensor_locations):
|
|
1427
|
+
raise ValueError("Invalid sensor ID in 'sensor_locations' -- note that all " +
|
|
1428
|
+
"sensors in 'sensor_locations' must be set in the current " +
|
|
1429
|
+
"water tanks volume sensor configuration")
|
|
1430
|
+
else:
|
|
1431
|
+
sensor_locations = self.__sensor_config.tank_volume_sensors
|
|
1432
|
+
|
|
1433
|
+
if self.__sensor_readings is None:
|
|
1434
|
+
self.get_data()
|
|
1435
|
+
|
|
1436
|
+
idx = [self.__sensor_config.get_index_of_reading(tank_volume_sensor=s_id)
|
|
1437
|
+
for s_id in sensor_locations]
|
|
1438
|
+
return self.__sensor_readings[:, idx]
|
|
1439
|
+
|
|
1440
|
+
def get_data_surface_species_concentration(self,
|
|
1441
|
+
surface_species_sensor_locations: dict = None
|
|
1442
|
+
) -> np.ndarray:
|
|
1443
|
+
"""
|
|
1444
|
+
Gets the final surface species concentration sensor readings --
|
|
1445
|
+
note that those might be subject to given sensor faults and sensor noise/uncertainty.
|
|
1446
|
+
|
|
1447
|
+
Parameters
|
|
1448
|
+
----------
|
|
1449
|
+
surface_species_sensor_locations : `dict`, optional
|
|
1450
|
+
Existing surface species concentration sensors (species ID and link/pipe IDs) for which
|
|
1451
|
+
the sensor readings are requested.
|
|
1452
|
+
If None, the readings from all surface species concentration sensors are returned.
|
|
1453
|
+
|
|
1454
|
+
The default is None.
|
|
1455
|
+
|
|
1456
|
+
Returns
|
|
1457
|
+
-------
|
|
1458
|
+
`numpy.ndarray`
|
|
1459
|
+
Surface species concentration sensor readings.
|
|
1460
|
+
"""
|
|
1461
|
+
if self.__sensor_config.surface_species_sensors == {}:
|
|
1462
|
+
raise ValueError("No surface species sensors set")
|
|
1463
|
+
if surface_species_sensor_locations is not None:
|
|
1464
|
+
if not isinstance(surface_species_sensor_locations, dict):
|
|
1465
|
+
raise TypeError("'surface_species_sensor_locations' must be an instance of 'dict'" +
|
|
1466
|
+
f" but not of '{type(surface_species_sensor_locations)}'")
|
|
1467
|
+
for species_id in surface_species_sensor_locations:
|
|
1468
|
+
if species_id not in self.__sensor_config.surface_species_sensors:
|
|
1469
|
+
raise ValueError(f"Species '{species_id}' is not included in the " +
|
|
1470
|
+
"sensor configuration")
|
|
1471
|
+
|
|
1472
|
+
my_surface_species_sensor_locations = \
|
|
1473
|
+
self.__sensor_config.surface_species_sensors[species_id]
|
|
1474
|
+
for sensor_id in surface_species_sensor_locations[species_id]:
|
|
1475
|
+
if sensor_id not in my_surface_species_sensor_locations:
|
|
1476
|
+
raise ValueError(f"Link '{sensor_id}' is not included in the " +
|
|
1477
|
+
f"sensor configuration for species '{species_id}'")
|
|
1478
|
+
else:
|
|
1479
|
+
surface_species_sensor_locations = self.__sensor_config.surface_species_sensors
|
|
1480
|
+
|
|
1481
|
+
if self.__sensor_readings is None:
|
|
1482
|
+
self.get_data()
|
|
1483
|
+
|
|
1484
|
+
idx = [self.__sensor_config.get_index_of_reading(
|
|
1485
|
+
surface_species_sensor=(species_id, link_id))
|
|
1486
|
+
for species_id in surface_species_sensor_locations
|
|
1487
|
+
for link_id in surface_species_sensor_locations[species_id]]
|
|
1488
|
+
return self.__sensor_readings[:, idx]
|
|
1489
|
+
|
|
1490
|
+
def get_data_bulk_species_node_concentration(self,
|
|
1491
|
+
bulk_species_sensor_locations: dict = None
|
|
1492
|
+
) -> np.ndarray:
|
|
1493
|
+
"""
|
|
1494
|
+
Gets the final bulk species node concentration sensor readings --
|
|
1495
|
+
note that those might be subject to given sensor faults and sensor noise/uncertainty.
|
|
1496
|
+
|
|
1497
|
+
Parameters
|
|
1498
|
+
----------
|
|
1499
|
+
bulk_species_sensor_locations : `dict`, optional
|
|
1500
|
+
Existing bulk species concentration sensors (species ID and node IDs) for which
|
|
1501
|
+
the sensor readings are requested.
|
|
1502
|
+
If None, the readings from all bulk species node concentration sensors are returned.
|
|
1503
|
+
|
|
1504
|
+
The default is None.
|
|
1505
|
+
|
|
1506
|
+
Returns
|
|
1507
|
+
-------
|
|
1508
|
+
`numpy.ndarray`
|
|
1509
|
+
Bulk species concentration sensor readings.
|
|
1510
|
+
"""
|
|
1511
|
+
if self.__sensor_config.bulk_species_node_sensors == {}:
|
|
1512
|
+
raise ValueError("No bulk species node sensors set")
|
|
1513
|
+
if bulk_species_sensor_locations is not None:
|
|
1514
|
+
if not isinstance(bulk_species_sensor_locations, dict):
|
|
1515
|
+
raise TypeError("'bulk_species_sensor_locations' must be an instance of 'dict'" +
|
|
1516
|
+
f" but not of '{type(bulk_species_sensor_locations)}'")
|
|
1517
|
+
for species_id in bulk_species_sensor_locations:
|
|
1518
|
+
if species_id not in self.__sensor_config.bulk_species_sensors:
|
|
1519
|
+
raise ValueError(f"Species '{species_id}' is not included in the " +
|
|
1520
|
+
"sensor configuration")
|
|
1521
|
+
|
|
1522
|
+
my_bulk_species_sensor_locations = \
|
|
1523
|
+
self.__sensor_config.bulk_species_node_sensors[species_id]
|
|
1524
|
+
for sensor_id in bulk_species_sensor_locations[species_id]:
|
|
1525
|
+
if sensor_id not in my_bulk_species_sensor_locations:
|
|
1526
|
+
raise ValueError(f"Link '{sensor_id}' is not included in the " +
|
|
1527
|
+
f"sensor configuration for species '{species_id}'")
|
|
1528
|
+
else:
|
|
1529
|
+
bulk_species_sensor_locations = self.__sensor_config.bulk_species_node_sensors
|
|
1530
|
+
|
|
1531
|
+
if self.__sensor_readings is None:
|
|
1532
|
+
self.get_data()
|
|
1533
|
+
|
|
1534
|
+
idx = [self.__sensor_config.get_index_of_reading(
|
|
1535
|
+
bulk_species_node_sensor=(species_id, node_id))
|
|
1536
|
+
for species_id in bulk_species_sensor_locations
|
|
1537
|
+
for node_id in bulk_species_sensor_locations[species_id]]
|
|
1538
|
+
return self.__sensor_readings[:, idx]
|
|
1539
|
+
|
|
1540
|
+
def get_data_bulk_species_link_concentration(self,
|
|
1541
|
+
bulk_species_sensor_locations: dict = None
|
|
1542
|
+
) -> np.ndarray:
|
|
1543
|
+
"""
|
|
1544
|
+
Gets the final bulk species link/pipe concentration sensor readings --
|
|
1545
|
+
note that those might be subject to given sensor faults and sensor noise/uncertainty.
|
|
1546
|
+
|
|
1547
|
+
Parameters
|
|
1548
|
+
----------
|
|
1549
|
+
bulk_species_sensor_locations : `dict`, optional
|
|
1550
|
+
Existing bulk species concentration sensors (species ID and link/pipe IDs) for which
|
|
1551
|
+
the sensor readings are requested.
|
|
1552
|
+
If None, the readings from all bulk species concentration link/pipe sensors
|
|
1553
|
+
are returned.
|
|
1554
|
+
|
|
1555
|
+
The default is None.
|
|
1556
|
+
|
|
1557
|
+
Returns
|
|
1558
|
+
-------
|
|
1559
|
+
`numpy.ndarray`
|
|
1560
|
+
Bulk species concentration sensor readings.
|
|
1561
|
+
"""
|
|
1562
|
+
if self.__sensor_config.bulk_species_link_sensors == {}:
|
|
1563
|
+
raise ValueError("No bulk species link/pipe sensors set")
|
|
1564
|
+
if bulk_species_sensor_locations is not None:
|
|
1565
|
+
if not isinstance(bulk_species_sensor_locations, dict):
|
|
1566
|
+
raise TypeError("'bulk_species_sensor_locations' must be an instance of 'dict'" +
|
|
1567
|
+
f" but not of '{type(bulk_species_sensor_locations)}'")
|
|
1568
|
+
for species_id in bulk_species_sensor_locations:
|
|
1569
|
+
if species_id not in self.__sensor_config.bulk_species_link_sensors:
|
|
1570
|
+
raise ValueError(f"Species '{species_id}' is not included in the " +
|
|
1571
|
+
"sensor configuration")
|
|
1572
|
+
|
|
1573
|
+
my_bulk_species_sensor_locations = \
|
|
1574
|
+
self.__sensor_config.bulk_species_link_sensors[species_id]
|
|
1575
|
+
for sensor_id in bulk_species_sensor_locations[species_id]:
|
|
1576
|
+
if sensor_id not in my_bulk_species_sensor_locations:
|
|
1577
|
+
raise ValueError(f"Link '{sensor_id}' is not included in the " +
|
|
1578
|
+
f"sensor configuration for species '{species_id}'")
|
|
1579
|
+
else:
|
|
1580
|
+
bulk_species_sensor_locations = self.__sensor_config.bulk_species_link_sensors
|
|
1581
|
+
|
|
1582
|
+
if self.__sensor_readings is None:
|
|
1583
|
+
self.get_data()
|
|
1584
|
+
|
|
1585
|
+
idx = [self.__sensor_config.get_index_of_reading(
|
|
1586
|
+
bulk_species_link_sensor=(species_id, node_id))
|
|
1587
|
+
for species_id in bulk_species_sensor_locations
|
|
1588
|
+
for node_id in bulk_species_sensor_locations[species_id]]
|
|
1589
|
+
return self.__sensor_readings[:, idx]
|