epyt-flow 0.10.0__py3-none-any.whl → 0.12.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/gecco_water_quality.py +2 -2
- epyt_flow/data/benchmarks/leakdb.py +40 -5
- epyt_flow/data/benchmarks/water_usage.py +4 -3
- epyt_flow/data/networks.py +27 -14
- epyt_flow/gym/__init__.py +0 -3
- epyt_flow/gym/scenario_control_env.py +11 -13
- epyt_flow/rest_api/scenario/control_handlers.py +118 -0
- epyt_flow/rest_api/scenario/event_handlers.py +114 -1
- epyt_flow/rest_api/scenario/handlers.py +33 -0
- epyt_flow/rest_api/server.py +14 -2
- epyt_flow/serialization.py +1 -0
- epyt_flow/simulation/__init__.py +0 -1
- epyt_flow/simulation/backend/__init__.py +1 -0
- epyt_flow/simulation/backend/my_epyt.py +1056 -0
- epyt_flow/simulation/events/actuator_events.py +7 -1
- epyt_flow/simulation/events/quality_events.py +3 -1
- epyt_flow/simulation/scada/scada_data.py +716 -5
- epyt_flow/simulation/scenario_config.py +1 -40
- epyt_flow/simulation/scenario_simulator.py +645 -119
- epyt_flow/simulation/sensor_config.py +18 -2
- epyt_flow/topology.py +24 -7
- epyt_flow/uncertainty/model_uncertainty.py +80 -62
- epyt_flow/uncertainty/sensor_noise.py +15 -4
- epyt_flow/uncertainty/uncertainties.py +71 -18
- epyt_flow/uncertainty/utils.py +40 -13
- epyt_flow/utils.py +45 -1
- epyt_flow/visualization/__init__.py +2 -0
- epyt_flow/visualization/scenario_visualizer.py +1240 -0
- epyt_flow/visualization/visualization_utils.py +738 -0
- {epyt_flow-0.10.0.dist-info → epyt_flow-0.12.0.dist-info}/METADATA +15 -4
- {epyt_flow-0.10.0.dist-info → epyt_flow-0.12.0.dist-info}/RECORD +35 -36
- {epyt_flow-0.10.0.dist-info → epyt_flow-0.12.0.dist-info}/WHEEL +1 -1
- epyt_flow/gym/control_gyms.py +0 -47
- epyt_flow/metrics.py +0 -466
- epyt_flow/models/__init__.py +0 -2
- epyt_flow/models/event_detector.py +0 -31
- epyt_flow/models/sensor_interpolation_detector.py +0 -118
- epyt_flow/simulation/scada/advanced_control.py +0 -138
- epyt_flow/simulation/scenario_visualizer.py +0 -1307
- {epyt_flow-0.10.0.dist-info → epyt_flow-0.12.0.dist-info/licenses}/LICENSE +0 -0
- {epyt_flow-0.10.0.dist-info → epyt_flow-0.12.0.dist-info}/top_level.txt +0 -0
|
@@ -1,1307 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Module provides a class for visualizing scenarios.
|
|
3
|
-
"""
|
|
4
|
-
from typing import Optional, Union, List, Tuple, Iterable
|
|
5
|
-
from deprecated import deprecated
|
|
6
|
-
|
|
7
|
-
import matplotlib.pyplot as plt
|
|
8
|
-
from matplotlib.animation import FuncAnimation
|
|
9
|
-
import matplotlib as mpl
|
|
10
|
-
import networkx.drawing.nx_pylab as nxp
|
|
11
|
-
import numpy as np
|
|
12
|
-
from svgpath2mpl import parse_path
|
|
13
|
-
|
|
14
|
-
from .scenario_simulator import ScenarioSimulator
|
|
15
|
-
from .scada.scada_data import ScadaData
|
|
16
|
-
|
|
17
|
-
PUMP_PATH = ('M 202.5 93 A 41.5 42 0 0 0 161 135 A 41.5 42 0 0 0 202.5 177 A '
|
|
18
|
-
'41.5 42 0 0 0 244 135 A 41.5 42 0 0 0 241.94922 122 L 278 122 '
|
|
19
|
-
'L 278 93 L 203 93 L 203 93.011719 A 41.5 42 0 0 0 202.5 93 z')
|
|
20
|
-
RESERVOIR_PATH = ('M 325 41 A 43 24.5 0 0 0 282.05664 65 L 282 65 L 282 65.5 '
|
|
21
|
-
'L 282 163 L 282 168 L 282 216 L 305 216 L 305 168 L 345 '
|
|
22
|
-
'168 L 345 216 L 368 216 L 368 168 L 368 163 L 368 65.5 L '
|
|
23
|
-
'368 65 L 367.98047 65 A 43 24.5 0 0 0 325 41 z')
|
|
24
|
-
TANK_PATH = ('M 325 41 A 43 24.5 0 0 0 282.05664 65 L 282 65 L 282 65.5 L 282 '
|
|
25
|
-
'185 L 368 185 L 368 65.5 L 368 65 L 367.98047 65 A 43 24.5 0 0'
|
|
26
|
-
' 0 325 41 z')
|
|
27
|
-
VALVE_PATH = ('M 9.9999064 9.9999064 L 9.9999064 110 L 69.999862 59.999955 L '
|
|
28
|
-
'9.9999064 9.9999064 z M 69.999862 59.999955 L 129.99982 110 L '
|
|
29
|
-
'129.99982 9.9999064 L 69.999862 59.999955 z')
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class Marker:
|
|
33
|
-
"""
|
|
34
|
-
The Marker class provides svg representations of hydraulic components
|
|
35
|
-
(pump, reservoir, tank and valve), which are loaded from their respective
|
|
36
|
-
svg paths and transformed into :class:`~matplotlib.path.Path` objects in
|
|
37
|
-
order to be used with the matplotlib library.
|
|
38
|
-
|
|
39
|
-
Attributes
|
|
40
|
-
----------
|
|
41
|
-
pump : :class:`~matplotlib.path.Path` object
|
|
42
|
-
Marker for the pump, loaded from PUMP_PATH.
|
|
43
|
-
reservoir : :class:`~matplotlib.path.Path` object
|
|
44
|
-
Marker for the reservoir, loaded from RESERVOIR_PATH.
|
|
45
|
-
tank : :class:`~matplotlib.path.Path` object
|
|
46
|
-
Marker for the tank, loaded from TANK_PATH.
|
|
47
|
-
valve : :class:`~matplotlib.path.Path` object
|
|
48
|
-
Marker for the valve, loaded from VALVE_PATH.
|
|
49
|
-
|
|
50
|
-
Methods
|
|
51
|
-
-------
|
|
52
|
-
__marker_from_path(path, scale_p=1)
|
|
53
|
-
Loads and applies transformations to the marker shape from the given
|
|
54
|
-
path.
|
|
55
|
-
"""
|
|
56
|
-
def __init__(self):
|
|
57
|
-
"""
|
|
58
|
-
Initializes the Marker class and assigns :class:`~matplotlib.path.Path`
|
|
59
|
-
markers for pump, reservoir, tank, and valve components.
|
|
60
|
-
"""
|
|
61
|
-
self.pump = self.__marker_from_path(PUMP_PATH, 2)
|
|
62
|
-
self.reservoir = self.__marker_from_path(RESERVOIR_PATH)
|
|
63
|
-
self.tank = self.__marker_from_path(TANK_PATH)
|
|
64
|
-
self.valve = self.__marker_from_path(VALVE_PATH)
|
|
65
|
-
|
|
66
|
-
@staticmethod
|
|
67
|
-
def __marker_from_path(path: str, scale_p: int = 1) -> mpl.path.Path:
|
|
68
|
-
"""
|
|
69
|
-
Loads the marker from the specified path and adjusts it representation
|
|
70
|
-
by aligning, rotating and scaling it.
|
|
71
|
-
|
|
72
|
-
Parameters
|
|
73
|
-
----------
|
|
74
|
-
path : `str`
|
|
75
|
-
The svg path describing the marker shape.
|
|
76
|
-
scale_p : `float`, optional
|
|
77
|
-
Scaling factor for the marker (default is 1).
|
|
78
|
-
|
|
79
|
-
Returns
|
|
80
|
-
-------
|
|
81
|
-
marker_tmp : :class:`~matplotlib.path.Path` object
|
|
82
|
-
The transformed marker object after loading and adjusting it.
|
|
83
|
-
"""
|
|
84
|
-
marker_tmp = parse_path(path)
|
|
85
|
-
marker_tmp.vertices -= marker_tmp.vertices.mean(axis=0)
|
|
86
|
-
marker_tmp = marker_tmp.transformed(
|
|
87
|
-
mpl.transforms.Affine2D().rotate_deg(180))
|
|
88
|
-
marker_tmp = marker_tmp.transformed(
|
|
89
|
-
mpl.transforms.Affine2D().scale(-scale_p, scale_p))
|
|
90
|
-
return marker_tmp
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
class ScenarioVisualizer:
|
|
94
|
-
"""
|
|
95
|
-
This class provides the necessary function to generate visualizations in
|
|
96
|
-
the form of plots or animations from water network data.
|
|
97
|
-
|
|
98
|
-
Given a :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator` object, this class
|
|
99
|
-
provides the necessary functions to plot the network topology and to color
|
|
100
|
-
hydraulic elements according to simulation data. The resulting plot can
|
|
101
|
-
then either be displayed or saved.
|
|
102
|
-
|
|
103
|
-
Attributes
|
|
104
|
-
----------
|
|
105
|
-
__scenario : :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`
|
|
106
|
-
ScenarioSimulator object containing the network topology and
|
|
107
|
-
configurations to obtain the simulation data which should be displayed.
|
|
108
|
-
fig : :class:`~matplotlib.pyplot.Figure` or None
|
|
109
|
-
Figure object used for plotting, created and customized by calling the
|
|
110
|
-
methods of this class, initialized as None.
|
|
111
|
-
ax : :class:`~matplotlib.axes.Axes` or None
|
|
112
|
-
The axes for plotting, initialized as None.
|
|
113
|
-
scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` or None
|
|
114
|
-
SCADA data created by the ScenarioSimulator object, initialized as
|
|
115
|
-
None.
|
|
116
|
-
topology : :class:`~epyt_flow.topology.NetworkTopology`
|
|
117
|
-
Topology object retrieved from the scenario, containing the structure
|
|
118
|
-
of the water distribution network.
|
|
119
|
-
pos_dict : `dict`
|
|
120
|
-
A dictionary mapping nodes to their coordinates in the correct format
|
|
121
|
-
for drawing.
|
|
122
|
-
pipe_parameters : `dict`
|
|
123
|
-
Parameters for visualizing pipes in the correct format for drawing.
|
|
124
|
-
junction_parameters : `dict`
|
|
125
|
-
Parameters for visualizing junctions in the correct format for drawing.
|
|
126
|
-
tank_parameters : `dict`
|
|
127
|
-
Parameters for visualizing tanks in the correct format for drawing.
|
|
128
|
-
reservoir_parameters : `dict`
|
|
129
|
-
Parameters for visualizing reservoirs in the correct format for
|
|
130
|
-
drawing.
|
|
131
|
-
valve_parameters : `dict`
|
|
132
|
-
Parameters for visualizing valves in the correct format for drawing.
|
|
133
|
-
pump_parameters : `dict`
|
|
134
|
-
Parameters for visualizing pumps in the correct format for drawing.
|
|
135
|
-
animation_dict : `dict`
|
|
136
|
-
A dictionary containing frame by frame data for the animated
|
|
137
|
-
components.
|
|
138
|
-
colorbars : `dict`
|
|
139
|
-
A dictionary containing the necessary data for drawing the required
|
|
140
|
-
colorbars.
|
|
141
|
-
"""
|
|
142
|
-
def __init__(self, scenario: ScenarioSimulator) -> None:
|
|
143
|
-
"""
|
|
144
|
-
Initializes the class with a given scenario, sets up the topology,
|
|
145
|
-
SCADA data, and parameters for visualizing various hydraulic components
|
|
146
|
-
(pipes, junctions, tanks, reservoirs, valves, and pumps).
|
|
147
|
-
|
|
148
|
-
Parameters
|
|
149
|
-
----------
|
|
150
|
-
scenario : :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`
|
|
151
|
-
An instance of the `ScenarioSimulator` class, used to simulate and
|
|
152
|
-
retrieve the system topology.
|
|
153
|
-
|
|
154
|
-
Raises
|
|
155
|
-
------
|
|
156
|
-
TypeError
|
|
157
|
-
If `scenario` is not an instance of
|
|
158
|
-
:class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`.
|
|
159
|
-
|
|
160
|
-
"""
|
|
161
|
-
if not isinstance(scenario, ScenarioSimulator):
|
|
162
|
-
raise TypeError("'scenario' must be an instance of " +
|
|
163
|
-
"'epyt_flow.simulation.ScenarioSimulator' " +
|
|
164
|
-
f"but not of '{type(scenario)}'")
|
|
165
|
-
|
|
166
|
-
self.__scenario = scenario
|
|
167
|
-
self.fig = None
|
|
168
|
-
self.ax = None
|
|
169
|
-
self.scada_data = None
|
|
170
|
-
markers = Marker()
|
|
171
|
-
self.topology = self.__scenario.get_topology()
|
|
172
|
-
self.pos_dict = {x: self.topology.get_node_info(x)['coord'] for x in
|
|
173
|
-
self.topology.get_all_nodes()}
|
|
174
|
-
self.pipe_parameters = {
|
|
175
|
-
'edgelist': [x[1] for x in self.topology.get_all_links()],
|
|
176
|
-
'edge_color': 'k'}
|
|
177
|
-
self.junction_parameters = {
|
|
178
|
-
'nodelist': self.topology.get_all_junctions(), 'node_size': 10,
|
|
179
|
-
'node_color': 'k'}
|
|
180
|
-
self.tank_parameters = {'nodelist': self.topology.get_all_tanks(),
|
|
181
|
-
'node_size': 100, 'node_color': 'k',
|
|
182
|
-
'node_shape': markers.tank}
|
|
183
|
-
self.reservoir_parameters = {
|
|
184
|
-
'nodelist': self.topology.get_all_reservoirs(), 'node_size': 100,
|
|
185
|
-
'node_color': 'k', 'node_shape': markers.reservoir}
|
|
186
|
-
self.valve_parameters = {'nodelist': self.topology.get_all_valves(),
|
|
187
|
-
'node_size': 75, 'node_color': 'k',
|
|
188
|
-
'node_shape': markers.valve}
|
|
189
|
-
self.pump_parameters = {'nodelist': self.topology.get_all_pumps(),
|
|
190
|
-
'node_size': 100, 'node_color': 'k',
|
|
191
|
-
'node_shape': markers.pump}
|
|
192
|
-
self.animation_dict = {}
|
|
193
|
-
self.colorbars = {}
|
|
194
|
-
|
|
195
|
-
def __get_midpoints(self, elements: List[str]) -> dict[str, tuple[float, float]]:
|
|
196
|
-
"""
|
|
197
|
-
Computes and returns the midpoints for drawing either valves or pumps
|
|
198
|
-
in a water distribution network.
|
|
199
|
-
|
|
200
|
-
For each element ID in the provided list, the method calculates the
|
|
201
|
-
midpoint between its start and end nodes' coordinates.
|
|
202
|
-
|
|
203
|
-
Parameters
|
|
204
|
-
----------
|
|
205
|
-
elements : `list[str]`
|
|
206
|
-
A list of element IDs (e.g., pump IDs, valve IDs) for which to
|
|
207
|
-
compute the midpoints.
|
|
208
|
-
|
|
209
|
-
Returns
|
|
210
|
-
-------
|
|
211
|
-
elements_dict : `dict`
|
|
212
|
-
A dictionary where the keys are element IDs and the values are the
|
|
213
|
-
corresponding midpoints, represented as 2D coordinates [x, y].
|
|
214
|
-
"""
|
|
215
|
-
elements_pos_dict = {}
|
|
216
|
-
for element in elements:
|
|
217
|
-
if element in self.topology.pumps:
|
|
218
|
-
start_node, end_node = self.topology.get_pump_info(element)[
|
|
219
|
-
'end_points']
|
|
220
|
-
elif element in self.topology.valves:
|
|
221
|
-
start_node, end_node = self.topology.get_valve_info(element)[
|
|
222
|
-
'end_points']
|
|
223
|
-
else:
|
|
224
|
-
raise ValueError(f"Unknown element '{element}'")
|
|
225
|
-
start_pos = self.topology.get_node_info(start_node)['coord']
|
|
226
|
-
end_pos = self.topology.get_node_info(end_node)['coord']
|
|
227
|
-
pos = [(start_pos[0] + end_pos[0]) / 2,
|
|
228
|
-
(start_pos[1] + end_pos[1]) / 2]
|
|
229
|
-
elements_pos_dict[element] = pos
|
|
230
|
-
return elements_pos_dict
|
|
231
|
-
|
|
232
|
-
def __get_next_frame(self, frame_number: int) -> None:
|
|
233
|
-
"""
|
|
234
|
-
Draws the next frame of a water distribution network animation.
|
|
235
|
-
|
|
236
|
-
This method updates a visualization animation with the hydraulic
|
|
237
|
-
components colored according to the scada data corresponding to the
|
|
238
|
-
current frame.
|
|
239
|
-
|
|
240
|
-
Parameters
|
|
241
|
-
----------
|
|
242
|
-
frame_number : `int`
|
|
243
|
-
The current frame number used to retrieve the data corresponding to
|
|
244
|
-
that frame
|
|
245
|
-
"""
|
|
246
|
-
self.ax = self.fig.add_subplot(111)
|
|
247
|
-
self.ax.axis('off')
|
|
248
|
-
|
|
249
|
-
nxp.draw_networkx_edges(self.topology, self.pos_dict, ax=self.ax,
|
|
250
|
-
label='Pipes', **self.pipe_parameters)
|
|
251
|
-
|
|
252
|
-
if 'junctions' in self.animation_dict:
|
|
253
|
-
self.junction_parameters['node_color'] = \
|
|
254
|
-
self.animation_dict['junctions'][frame_number]
|
|
255
|
-
if 'pipes' in self.animation_dict:
|
|
256
|
-
self.pipe_parameters['edge_color'] = self.animation_dict['pipes'][
|
|
257
|
-
frame_number]
|
|
258
|
-
if 'pipe_sizes' in self.animation_dict:
|
|
259
|
-
self.pipe_parameters['width'] = self.animation_dict['pipe_sizes'][
|
|
260
|
-
frame_number]
|
|
261
|
-
if 'pumps' in self.animation_dict:
|
|
262
|
-
self.pump_parameters['node_color'] = self.animation_dict['pumps'][
|
|
263
|
-
frame_number]
|
|
264
|
-
if 'tanks' in self.animation_dict:
|
|
265
|
-
self.tank_parameters['node_color'] = self.animation_dict['tanks'][
|
|
266
|
-
frame_number]
|
|
267
|
-
if 'valves' in self.animation_dict:
|
|
268
|
-
self.valve_parameters['node_color'] = \
|
|
269
|
-
self.animation_dict['valves'][frame_number]
|
|
270
|
-
|
|
271
|
-
nxp.draw_networkx_nodes(self.topology, self.pos_dict, ax=self.ax,
|
|
272
|
-
label='Junctions', **self.junction_parameters)
|
|
273
|
-
nxp.draw_networkx_nodes(self.topology, self.pos_dict, ax=self.ax,
|
|
274
|
-
label='Tanks', **self.tank_parameters)
|
|
275
|
-
nxp.draw_networkx_nodes(self.topology, self.pos_dict, ax=self.ax,
|
|
276
|
-
label='Reservoirs',
|
|
277
|
-
**self.reservoir_parameters)
|
|
278
|
-
nxp.draw_networkx_nodes(
|
|
279
|
-
self.topology,
|
|
280
|
-
self.__get_midpoints(self.topology.get_all_valves()), ax=self.ax,
|
|
281
|
-
label='Valves', **self.valve_parameters)
|
|
282
|
-
nxp.draw_networkx_nodes(
|
|
283
|
-
self.topology, self.__get_midpoints(self.topology.get_all_pumps()),
|
|
284
|
-
ax=self.ax, label='Pumps', **self.pump_parameters)
|
|
285
|
-
|
|
286
|
-
self.ax.legend(fontsize=6)
|
|
287
|
-
|
|
288
|
-
def __get_link_data(
|
|
289
|
-
self, scada_data: Optional[ScadaData] = None,
|
|
290
|
-
parameter: str = 'flow_rate', statistic: str = 'mean',
|
|
291
|
-
pit: Optional[Union[int, Tuple[int]]] = None,
|
|
292
|
-
intervals: Optional[Union[int, List[Union[int, float]]]] = None,
|
|
293
|
-
conversion: Optional[dict] = None)\
|
|
294
|
-
-> Tuple[Union[List, Iterable], int]:
|
|
295
|
-
|
|
296
|
-
"""
|
|
297
|
-
Retrieves or generates SCADA data and processes it according to the
|
|
298
|
-
parameters.
|
|
299
|
-
|
|
300
|
-
The method extracts SCADA data corresponding to links. The given
|
|
301
|
-
statistic is then applied and the data returned in a format suitable
|
|
302
|
-
for plotting.
|
|
303
|
-
|
|
304
|
-
Parameters
|
|
305
|
-
----------
|
|
306
|
-
scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`, optional
|
|
307
|
-
The SCADA data object to retrieve link data from. If `None`, a
|
|
308
|
-
simulation is run to generate the SCADA data. Default is `None`.
|
|
309
|
-
parameter : `str`, optional
|
|
310
|
-
The type of link data to retrieve. Must be either 'flow_rate',
|
|
311
|
-
'link_quality', or 'diameter'. Default is 'flow_rate'.
|
|
312
|
-
statistic : `str`, optional
|
|
313
|
-
The statistic to calculate for the link data. Can be 'mean', 'min',
|
|
314
|
-
'max', or 'time_step'. Default is 'mean'.
|
|
315
|
-
pit : `int` or `tuple(int, int)`, optional
|
|
316
|
-
Point in time for the 'time_step' statistic. Can be either one
|
|
317
|
-
point or a tuple setting a range. Required if 'time_step' is
|
|
318
|
-
selected as the statistic. Default is `None`.
|
|
319
|
-
intervals : `int`, `float`, or `list` of `int` or `float`, optional
|
|
320
|
-
If specified, the link data will be grouped into intervals. This
|
|
321
|
-
can either be an integer specifying the number of groups or a
|
|
322
|
-
`list` of boundary points defining the intervals. Default is
|
|
323
|
-
`None`.
|
|
324
|
-
conversion : `dict`, optional
|
|
325
|
-
A dictionary of conversion parameters to convert SCADA data units.
|
|
326
|
-
Default is `None`.
|
|
327
|
-
|
|
328
|
-
Returns
|
|
329
|
-
-------
|
|
330
|
-
sorted_values : `list`
|
|
331
|
-
A list of processed and sorted values for each link in the water
|
|
332
|
-
distribution network.
|
|
333
|
-
sim_length : `int`
|
|
334
|
-
The length of the simulation or SCADA data used.
|
|
335
|
-
|
|
336
|
-
Raises
|
|
337
|
-
------
|
|
338
|
-
ValueError
|
|
339
|
-
If an invalid `parameter`, `statistic`, or `intervals` argument is
|
|
340
|
-
provided, or if `pit` is not provided when using the 'time_step'
|
|
341
|
-
statistic.
|
|
342
|
-
|
|
343
|
-
"""
|
|
344
|
-
|
|
345
|
-
if scada_data:
|
|
346
|
-
self.scada_data = scada_data
|
|
347
|
-
elif not self.scada_data:
|
|
348
|
-
self.scada_data = self.__scenario.run_simulation()
|
|
349
|
-
|
|
350
|
-
if conversion:
|
|
351
|
-
self.scada_data = self.scada_data.convert_units(**conversion)
|
|
352
|
-
|
|
353
|
-
if parameter == 'flow_rate':
|
|
354
|
-
values = self.scada_data.flow_data_raw
|
|
355
|
-
elif parameter == 'link_quality':
|
|
356
|
-
values = self.scada_data.link_quality_data_raw
|
|
357
|
-
elif parameter == 'diameter':
|
|
358
|
-
value_dict = {
|
|
359
|
-
link[0]: self.topology.get_link_info(link[0])['diameter'] for
|
|
360
|
-
link in self.topology.get_all_links()}
|
|
361
|
-
sorted_values = [value_dict[x[0]] for x in
|
|
362
|
-
self.topology.get_all_links()]
|
|
363
|
-
return (self.__rescale(sorted_values, (1, 2)),
|
|
364
|
-
self.scada_data.flow_data_raw.shape[0])
|
|
365
|
-
else:
|
|
366
|
-
raise ValueError('Parameter must be flow_rate or link_quality')
|
|
367
|
-
|
|
368
|
-
sim_length = values.shape[0]
|
|
369
|
-
|
|
370
|
-
if statistic == 'mean':
|
|
371
|
-
stat_values = np.mean(values, axis=0)
|
|
372
|
-
elif statistic == 'min':
|
|
373
|
-
stat_values = np.min(values, axis=0)
|
|
374
|
-
elif statistic == 'max':
|
|
375
|
-
stat_values = np.max(values, axis=0)
|
|
376
|
-
elif statistic == 'time_step':
|
|
377
|
-
if not pit and pit != 0:
|
|
378
|
-
raise ValueError(
|
|
379
|
-
'Please input point in time (pit) parameter when selecting'
|
|
380
|
-
' time_step statistic')
|
|
381
|
-
stat_values = np.take(values, pit, axis=0)
|
|
382
|
-
else:
|
|
383
|
-
raise ValueError(
|
|
384
|
-
'Statistic parameter must be mean, min, max or time_step')
|
|
385
|
-
|
|
386
|
-
if intervals is None:
|
|
387
|
-
pass
|
|
388
|
-
elif isinstance(intervals, (int, float)):
|
|
389
|
-
interv = np.linspace(stat_values.min(), stat_values.max(),
|
|
390
|
-
intervals + 1)
|
|
391
|
-
stat_values = np.digitize(stat_values, interv) - 1
|
|
392
|
-
elif isinstance(intervals, list):
|
|
393
|
-
stat_values = np.digitize(stat_values, intervals) - 1
|
|
394
|
-
else:
|
|
395
|
-
raise ValueError(
|
|
396
|
-
'Intervals must be either number of groups or list of interval'
|
|
397
|
-
' boundary points')
|
|
398
|
-
|
|
399
|
-
value_dict = dict(zip(self.scada_data.sensor_config.links,
|
|
400
|
-
stat_values))
|
|
401
|
-
sorted_values = [value_dict[x[0]] for x in
|
|
402
|
-
self.topology.get_all_links()]
|
|
403
|
-
|
|
404
|
-
return sorted_values, sim_length
|
|
405
|
-
|
|
406
|
-
@staticmethod
|
|
407
|
-
def __get_parameters_update(statistic: str, values: np.ndarray,
|
|
408
|
-
pit: Union[int, Tuple[int]],
|
|
409
|
-
intervals: Union[int, List[Union[int, float]]],
|
|
410
|
-
all_junctions: List[str],
|
|
411
|
-
junction_sorting: List[str]) -> List:
|
|
412
|
-
"""
|
|
413
|
-
Computes and returns statistical values for junctions in a water
|
|
414
|
-
network.
|
|
415
|
-
|
|
416
|
-
This method processes a 2D array of data (e.g., flow rates or quality)
|
|
417
|
-
by calculating specified statistics (mean, min, max, or time step) and
|
|
418
|
-
optionally grouping the data into intervals. It returns the data sorted
|
|
419
|
-
according to the provided junction sorting order.
|
|
420
|
-
|
|
421
|
-
Parameters
|
|
422
|
-
----------
|
|
423
|
-
statistic : `str`
|
|
424
|
-
The statistical operation to apply to the data. Must be one of
|
|
425
|
-
'mean', 'min', 'max', or 'time_step'.
|
|
426
|
-
values : :class:`~np.ndarray`
|
|
427
|
-
A 2D NumPy array of shape (timesteps, junctions) containing the
|
|
428
|
-
data for all junctions over time.
|
|
429
|
-
pit : `int` or `tuple` of `int`
|
|
430
|
-
The point in time or range of points in time for which to retrieve
|
|
431
|
-
data, required if 'time_step' is selected as the statistic. If an
|
|
432
|
-
integer is provided, it selects a single point in time.
|
|
433
|
-
intervals : `int`, `float`, or `list[int]` or `list[float]`
|
|
434
|
-
If specified, divides the data into intervals. Can be an integer
|
|
435
|
-
representing the number of groups, or a list of boundary points
|
|
436
|
-
defining the intervals.
|
|
437
|
-
all_junctions : `list` of `str`
|
|
438
|
-
A list of all junction IDs in the network, corresponding to the
|
|
439
|
-
data in the `values` array.
|
|
440
|
-
junction_sorting : `list` of `str`
|
|
441
|
-
The order in which to sort the junctions for the return value.
|
|
442
|
-
|
|
443
|
-
Returns
|
|
444
|
-
-------
|
|
445
|
-
sorted_values : `list`
|
|
446
|
-
A list of statistical values for the junctions, sorted according to
|
|
447
|
-
`junction_sorting`.
|
|
448
|
-
|
|
449
|
-
Raises
|
|
450
|
-
------
|
|
451
|
-
ValueError
|
|
452
|
-
If the `statistic` is not 'mean', 'min', 'max', or 'time_step', or
|
|
453
|
-
if `pit` is not provided for the 'time_step' statistic, or if
|
|
454
|
-
`intervals` is not in a valid format.
|
|
455
|
-
|
|
456
|
-
"""
|
|
457
|
-
|
|
458
|
-
if statistic == 'mean':
|
|
459
|
-
stat_values = np.mean(values, axis=0)
|
|
460
|
-
elif statistic == 'min':
|
|
461
|
-
stat_values = np.min(values, axis=0)
|
|
462
|
-
elif statistic == 'max':
|
|
463
|
-
stat_values = np.max(values, axis=0)
|
|
464
|
-
elif statistic == 'time_step':
|
|
465
|
-
if not pit and pit != 0:
|
|
466
|
-
raise ValueError(
|
|
467
|
-
'Please input point in time (pit) parameter when selecting'
|
|
468
|
-
' time_step statistic')
|
|
469
|
-
stat_values = np.take(values, pit, axis=0)
|
|
470
|
-
else:
|
|
471
|
-
raise ValueError(
|
|
472
|
-
'Statistic parameter must be mean, min, max or time_step')
|
|
473
|
-
|
|
474
|
-
if intervals is None:
|
|
475
|
-
pass
|
|
476
|
-
elif isinstance(intervals, (int, float)):
|
|
477
|
-
interv = np.linspace(stat_values.min(), stat_values.max(),
|
|
478
|
-
intervals + 1)
|
|
479
|
-
stat_values = np.digitize(stat_values, interv) - 1
|
|
480
|
-
elif isinstance(intervals, list):
|
|
481
|
-
stat_values = np.digitize(stat_values, intervals) - 1
|
|
482
|
-
else:
|
|
483
|
-
raise ValueError(
|
|
484
|
-
'Intervals must be either number of groups or list of interval'
|
|
485
|
-
' boundary points')
|
|
486
|
-
|
|
487
|
-
value_dict = dict(zip(all_junctions, stat_values))
|
|
488
|
-
sorted_values = [value_dict[x] for x in junction_sorting]
|
|
489
|
-
|
|
490
|
-
return sorted_values
|
|
491
|
-
|
|
492
|
-
@staticmethod
|
|
493
|
-
def __rescale(values: np.ndarray, scale_min_max: List,
|
|
494
|
-
values_min_max: List = None) -> List:
|
|
495
|
-
"""
|
|
496
|
-
Rescales the given values to a new range.
|
|
497
|
-
|
|
498
|
-
This method rescales an array of values to fit within a specified
|
|
499
|
-
minimum and maximum scale range. Optionally, the minimum and maximum
|
|
500
|
-
of the input values can be manually provided; otherwise, they are
|
|
501
|
-
automatically determined from the data.
|
|
502
|
-
|
|
503
|
-
Parameters
|
|
504
|
-
----------
|
|
505
|
-
values : :class:`~np.ndarray`
|
|
506
|
-
The array of numerical values to be rescaled.
|
|
507
|
-
scale_min_max : `list`
|
|
508
|
-
A list containing two elements: the minimum and maximum values
|
|
509
|
-
of the desired output range.
|
|
510
|
-
values_min_max : `list`, optional
|
|
511
|
-
A list containing two elements: the minimum and maximum values
|
|
512
|
-
of the input data. If not provided, they are computed from the
|
|
513
|
-
input `values`. Default is `None`.
|
|
514
|
-
|
|
515
|
-
Returns
|
|
516
|
-
-------
|
|
517
|
-
rescaled_values : `list`
|
|
518
|
-
A list of values rescaled to the range specified by
|
|
519
|
-
`scale_min_max`.
|
|
520
|
-
|
|
521
|
-
"""
|
|
522
|
-
|
|
523
|
-
if not values_min_max:
|
|
524
|
-
min_val, max_val = min(values), max(values)
|
|
525
|
-
else:
|
|
526
|
-
min_val, max_val = values_min_max
|
|
527
|
-
scale = scale_min_max[1] - scale_min_max[0]
|
|
528
|
-
|
|
529
|
-
def range_map(x):
|
|
530
|
-
return scale_min_max[0] + (x - min_val) / (
|
|
531
|
-
max_val - min_val) * scale
|
|
532
|
-
|
|
533
|
-
return [range_map(x) for x in values]
|
|
534
|
-
|
|
535
|
-
def show_animation(self, export_to_file: str = None,
|
|
536
|
-
return_animation: bool = False)\
|
|
537
|
-
-> Optional[FuncAnimation]:
|
|
538
|
-
"""
|
|
539
|
-
Displays, exports, or returns an animation of a water distribution
|
|
540
|
-
network over time.
|
|
541
|
-
|
|
542
|
-
This method generates an animation of a network and either shows it or
|
|
543
|
-
returns the :class:`~FuncAnimation` object. Optionally, the animation
|
|
544
|
-
is saved to a file.
|
|
545
|
-
|
|
546
|
-
Parameters
|
|
547
|
-
----------
|
|
548
|
-
export_to_file : `str`, optional
|
|
549
|
-
The file path where the animation should be saved, if provided.
|
|
550
|
-
Default is `None`.
|
|
551
|
-
return_animation : `bool`, optional
|
|
552
|
-
If `True`, the animation object is returned. If `False`, the
|
|
553
|
-
animation will be shown, but not returned. Default is `False`.
|
|
554
|
-
|
|
555
|
-
Returns
|
|
556
|
-
-------
|
|
557
|
-
anim : :class:`~FuncAnimation` or None
|
|
558
|
-
Returns the animation object if `return_animation` is `True`.
|
|
559
|
-
Otherwise, returns `None`.
|
|
560
|
-
|
|
561
|
-
"""
|
|
562
|
-
self.fig = plt.figure(figsize=(6.4, 4.8), dpi=200)
|
|
563
|
-
|
|
564
|
-
total_frames = float('inf')
|
|
565
|
-
for ll in self.animation_dict.values():
|
|
566
|
-
total_frames = min(total_frames, len(ll))
|
|
567
|
-
|
|
568
|
-
if not self.animation_dict or total_frames == 0:
|
|
569
|
-
raise RuntimeError("The color or resize functions must be called "
|
|
570
|
-
"with a time_step range (pit) to enable "
|
|
571
|
-
"animations")
|
|
572
|
-
|
|
573
|
-
anim = FuncAnimation(self.fig, self.__get_next_frame,
|
|
574
|
-
frames=total_frames, interval=25)
|
|
575
|
-
|
|
576
|
-
if export_to_file is not None:
|
|
577
|
-
anim.save(export_to_file, writer='ffmpeg', fps=4)
|
|
578
|
-
if return_animation:
|
|
579
|
-
plt.close(self.fig)
|
|
580
|
-
return anim
|
|
581
|
-
plt.show()
|
|
582
|
-
return None
|
|
583
|
-
|
|
584
|
-
def show_plot(self, export_to_file: str = None) -> None:
|
|
585
|
-
"""
|
|
586
|
-
Displays a static plot of the water distribution network.
|
|
587
|
-
|
|
588
|
-
This method generates a static plot of the water distribution network,
|
|
589
|
-
visualizing pipes, junctions, tanks, reservoirs, valves, and pumps.
|
|
590
|
-
The plot can be displayed and saved to a file.
|
|
591
|
-
|
|
592
|
-
Parameters
|
|
593
|
-
----------
|
|
594
|
-
export_to_file : `str`, optional
|
|
595
|
-
The file path where the plot should be saved, if provided.
|
|
596
|
-
Default is `None`.
|
|
597
|
-
|
|
598
|
-
"""
|
|
599
|
-
self.fig = plt.figure(figsize=(6.4, 4.8), dpi=200)
|
|
600
|
-
self.ax = self.fig.add_subplot(111)
|
|
601
|
-
self.ax.axis('off')
|
|
602
|
-
|
|
603
|
-
nxp.draw_networkx_edges(self.topology, self.pos_dict, ax=self.ax,
|
|
604
|
-
label='Pipes', **self.pipe_parameters)
|
|
605
|
-
nxp.draw_networkx_nodes(self.topology, self.pos_dict, ax=self.ax,
|
|
606
|
-
label='Junctions', **self.junction_parameters)
|
|
607
|
-
nxp.draw_networkx_nodes(self.topology, self.pos_dict, ax=self.ax,
|
|
608
|
-
label='Tanks', **self.tank_parameters)
|
|
609
|
-
nxp.draw_networkx_nodes(self.topology, self.pos_dict, ax=self.ax,
|
|
610
|
-
label='Reservoirs',
|
|
611
|
-
**self.reservoir_parameters)
|
|
612
|
-
nxp.draw_networkx_nodes(
|
|
613
|
-
self.topology,
|
|
614
|
-
self.__get_midpoints(self.topology.get_all_valves()), ax=self.ax,
|
|
615
|
-
label='Valves', **self.valve_parameters)
|
|
616
|
-
nxp.draw_networkx_nodes(
|
|
617
|
-
self.topology, self.__get_midpoints(self.topology.get_all_pumps()),
|
|
618
|
-
ax=self.ax, label='Pumps', **self.pump_parameters)
|
|
619
|
-
self.ax.legend(fontsize=6)
|
|
620
|
-
|
|
621
|
-
for colorbar_stats in self.colorbars.values():
|
|
622
|
-
self.fig.colorbar(ax=self.ax, **colorbar_stats)
|
|
623
|
-
|
|
624
|
-
if export_to_file is not None:
|
|
625
|
-
plt.savefig(export_to_file, transparent=True, bbox_inches='tight',
|
|
626
|
-
dpi=200)
|
|
627
|
-
plt.show()
|
|
628
|
-
|
|
629
|
-
def color_nodes(
|
|
630
|
-
self, scada_data: Optional[ScadaData] = None,
|
|
631
|
-
parameter: str = 'pressure', statistic: str = 'mean',
|
|
632
|
-
pit: Optional[Union[int, Tuple[int]]] = None,
|
|
633
|
-
colormap: str = 'viridis',
|
|
634
|
-
intervals: Optional[Union[int, List[Union[int, float]]]] = None,
|
|
635
|
-
conversion: Optional[dict] = None, show_colorbar: bool = False) ->\
|
|
636
|
-
None:
|
|
637
|
-
"""
|
|
638
|
-
Colors the nodes (junctions) in the water distribution network based on
|
|
639
|
-
the SCADA data and the specified parameters.
|
|
640
|
-
|
|
641
|
-
This method either takes or generates SCADA data, applies a statistic
|
|
642
|
-
to the chosen parameter, optionally groups the results and prepares the
|
|
643
|
-
results to be either displayed statically ot animated.
|
|
644
|
-
|
|
645
|
-
Parameters
|
|
646
|
-
----------
|
|
647
|
-
scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`, optional
|
|
648
|
-
The SCADA data object containing node data. If `None`, a simulation
|
|
649
|
-
will be run to generate SCADA data. Default is `None`.
|
|
650
|
-
parameter : `str`, optional
|
|
651
|
-
The node data to visualize. Must be 'pressure', 'demand', or
|
|
652
|
-
'node_quality'. Default is 'pressure'.
|
|
653
|
-
statistic : `str`, optional
|
|
654
|
-
The statistic to calculate for the data. Can be 'mean', 'min',
|
|
655
|
-
'max', or 'time_step'. Default is 'mean'.
|
|
656
|
-
pit : `int`, `tuple(int, int)`, optional
|
|
657
|
-
The point in time or range of time steps for the 'time_step'
|
|
658
|
-
statistic. If a tuple is provided, it should contain two integers
|
|
659
|
-
representing the start and end time steps. A tuple is necessary to
|
|
660
|
-
process the data for the :meth:`~ScenarioVisualizer.show_animation`
|
|
661
|
-
method. Default is `None`.
|
|
662
|
-
colormap : `str`, optional
|
|
663
|
-
The colormap to use for visualizing node values. Default is
|
|
664
|
-
'viridis'.
|
|
665
|
-
intervals : `int`, `list[int]` or `list[float]`, optional
|
|
666
|
-
If provided, the data will be grouped into intervals. It can be an
|
|
667
|
-
integer specifying the number of groups or a list of boundary
|
|
668
|
-
points. Default is `None`.
|
|
669
|
-
conversion : `dict`, optional
|
|
670
|
-
A dictionary of conversion parameters to convert SCADA data units.
|
|
671
|
-
Default is `None`.
|
|
672
|
-
show_colorbar : `bool`, optional
|
|
673
|
-
If `True`, a colorbar will be displayed on the plot to indicate the
|
|
674
|
-
range of node values. Default is `False`.
|
|
675
|
-
|
|
676
|
-
Raises
|
|
677
|
-
------
|
|
678
|
-
ValueError
|
|
679
|
-
If the `parameter` is not one of 'pressure', 'demand', or
|
|
680
|
-
'node_quality', or if `pit` is not correctly provided for the
|
|
681
|
-
'time_step' statistic.
|
|
682
|
-
|
|
683
|
-
"""
|
|
684
|
-
|
|
685
|
-
self.junction_parameters.update({'cmap': colormap})
|
|
686
|
-
|
|
687
|
-
if scada_data:
|
|
688
|
-
self.scada_data = scada_data
|
|
689
|
-
elif not self.scada_data:
|
|
690
|
-
self.scada_data = self.__scenario.run_simulation()
|
|
691
|
-
|
|
692
|
-
if conversion:
|
|
693
|
-
self.scada_data = self.scada_data.convert_units(**conversion)
|
|
694
|
-
|
|
695
|
-
if parameter == 'pressure':
|
|
696
|
-
values = self.scada_data.pressure_data_raw
|
|
697
|
-
elif parameter == 'demand':
|
|
698
|
-
values = self.scada_data.demand_data_raw
|
|
699
|
-
elif parameter == 'node_quality':
|
|
700
|
-
values = self.scada_data.node_quality_data_raw
|
|
701
|
-
else:
|
|
702
|
-
raise ValueError(
|
|
703
|
-
'Parameter must be pressure, demand or node_quality')
|
|
704
|
-
|
|
705
|
-
if statistic == 'time_step' and isinstance(pit, tuple) and len(
|
|
706
|
-
pit) == 2 and all(isinstance(i, int) for i in pit):
|
|
707
|
-
sorted_values = self.__get_parameters_update(
|
|
708
|
-
statistic, values, pit[0], intervals,
|
|
709
|
-
self.scada_data.sensor_config.nodes,
|
|
710
|
-
self.topology.get_all_junctions())
|
|
711
|
-
self.animation_dict['junctions'] = []
|
|
712
|
-
vmin, vmax = min(sorted_values), max(sorted_values)
|
|
713
|
-
for frame in range(*pit):
|
|
714
|
-
if frame > values.shape[0] - 1:
|
|
715
|
-
break
|
|
716
|
-
sorted_values = self.__get_parameters_update(
|
|
717
|
-
statistic, values, frame, intervals,
|
|
718
|
-
self.scada_data.sensor_config.nodes,
|
|
719
|
-
self.topology.get_all_junctions())
|
|
720
|
-
vmin, vmax = (min(*sorted_values, vmin),
|
|
721
|
-
max(*sorted_values, vmax))
|
|
722
|
-
self.animation_dict['junctions'].append(sorted_values)
|
|
723
|
-
self.junction_parameters['vmin'] = vmin
|
|
724
|
-
self.junction_parameters['vmax'] = vmax
|
|
725
|
-
else:
|
|
726
|
-
sorted_values = self.__get_parameters_update(
|
|
727
|
-
statistic, values, pit, intervals,
|
|
728
|
-
self.scada_data.sensor_config.nodes,
|
|
729
|
-
self.topology.get_all_junctions())
|
|
730
|
-
self.junction_parameters.update(
|
|
731
|
-
{'node_color': sorted_values, 'vmin': min(sorted_values),
|
|
732
|
-
'vmax': max(sorted_values)})
|
|
733
|
-
|
|
734
|
-
if show_colorbar:
|
|
735
|
-
if statistic == 'time_step':
|
|
736
|
-
label = str(parameter).capitalize() + ' at timestep ' + str(
|
|
737
|
-
pit)
|
|
738
|
-
else:
|
|
739
|
-
label = str(statistic).capitalize() + ' ' + str(parameter)
|
|
740
|
-
self.colorbars['junctions'] = {'mappable': plt.cm.ScalarMappable(
|
|
741
|
-
norm=mpl.colors.Normalize(
|
|
742
|
-
vmin=self.junction_parameters['vmin'],
|
|
743
|
-
vmax=self.junction_parameters['vmax']), cmap=colormap),
|
|
744
|
-
'label': label}
|
|
745
|
-
|
|
746
|
-
def color_links(
|
|
747
|
-
self, scada_data: Optional[ScadaData] = None,
|
|
748
|
-
parameter: str = 'flow_rate', statistic: str = 'mean',
|
|
749
|
-
pit: Optional[Union[int, Tuple[int]]] = None,
|
|
750
|
-
colormap: str = 'coolwarm',
|
|
751
|
-
intervals: Optional[Union[int, List[Union[int, float]]]] = None,
|
|
752
|
-
conversion: Optional[dict] = None, show_colorbar: bool = False) ->\
|
|
753
|
-
None:
|
|
754
|
-
"""
|
|
755
|
-
Colors the links (pipes) in the water distribution network based on the
|
|
756
|
-
SCADA data and the specified parameters.
|
|
757
|
-
|
|
758
|
-
This method either takes or generates SCADA data, applies a statistic
|
|
759
|
-
to the chosen parameter, optionally groups the results and prepares the
|
|
760
|
-
results to be either displayed statically ot animated.
|
|
761
|
-
|
|
762
|
-
Parameters
|
|
763
|
-
----------
|
|
764
|
-
scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`, optional
|
|
765
|
-
The SCADA data object. If `None`, the method will run a simulation.
|
|
766
|
-
Default is `None`.
|
|
767
|
-
parameter : `str`, optional
|
|
768
|
-
The link data to visualize. Options are 'flow_rate', 'velocity', or
|
|
769
|
-
'status'. Default is 'flow_rate'.
|
|
770
|
-
statistic : `str`, optional
|
|
771
|
-
The statistic to calculate for the data. Can be 'mean', 'min',
|
|
772
|
-
'max', or 'time_step'. Default is 'mean'.
|
|
773
|
-
pit : `int` or `tuple(int, int)`, optional
|
|
774
|
-
The point in time or range of time steps for the 'time_step'
|
|
775
|
-
statistic. If a tuple is provided, it should contain two integers
|
|
776
|
-
representing the start and end time steps. A tuple is necessary to
|
|
777
|
-
process the data for the :meth:`~ScenarioVisualizer.show_animation`
|
|
778
|
-
method. Default is `None`.
|
|
779
|
-
colormap : `str`, optional
|
|
780
|
-
The colormap to use for visualizing link values. Default is
|
|
781
|
-
'coolwarm'.
|
|
782
|
-
intervals : `int`, `list[int]`, `list[float]`, optional
|
|
783
|
-
If provided, the data will be grouped into intervals. It can be an
|
|
784
|
-
integer specifying the number of groups or a list of boundary
|
|
785
|
-
points. Default is `None`.
|
|
786
|
-
conversion : `dict`, optional
|
|
787
|
-
A dictionary of conversion parameters to convert SCADA data units.
|
|
788
|
-
Default is `None`.
|
|
789
|
-
show_colorbar : `bool`, optional
|
|
790
|
-
If `True`, a colorbar will be displayed on the plot to indicate the
|
|
791
|
-
range of values. Default is `False`.
|
|
792
|
-
|
|
793
|
-
Raises
|
|
794
|
-
------
|
|
795
|
-
ValueError
|
|
796
|
-
If `parameter` is not a valid link data parameter or if `pit` is
|
|
797
|
-
incorrectly provided for the 'time_step' statistic.
|
|
798
|
-
|
|
799
|
-
"""
|
|
800
|
-
|
|
801
|
-
if statistic == 'time_step' and isinstance(pit, tuple) and len(
|
|
802
|
-
pit) == 2 and all(isinstance(i, int) for i in pit):
|
|
803
|
-
sorted_values, sim_length = self.__get_link_data(scada_data,
|
|
804
|
-
parameter,
|
|
805
|
-
statistic, pit[0],
|
|
806
|
-
intervals,
|
|
807
|
-
conversion)
|
|
808
|
-
self.pipe_parameters.update({'edge_color': sorted_values,
|
|
809
|
-
'edge_cmap': mpl.colormaps[colormap],
|
|
810
|
-
'edge_vmin': min(sorted_values),
|
|
811
|
-
'edge_vmax': max(sorted_values)})
|
|
812
|
-
self.animation_dict['pipes'] = []
|
|
813
|
-
vmin = min(sorted_values)
|
|
814
|
-
vmax = max(sorted_values)
|
|
815
|
-
for frame in range(*pit):
|
|
816
|
-
if frame > sim_length - 1:
|
|
817
|
-
break
|
|
818
|
-
sorted_values, _ = self.__get_link_data(scada_data, parameter,
|
|
819
|
-
statistic, frame,
|
|
820
|
-
intervals, conversion)
|
|
821
|
-
vmin = min(*sorted_values, vmin)
|
|
822
|
-
vmax = max(*sorted_values, vmax)
|
|
823
|
-
self.animation_dict['pipes'].append(sorted_values)
|
|
824
|
-
self.pipe_parameters['edge_vmin'] = vmin
|
|
825
|
-
self.pipe_parameters['edge_vmax'] = vmax
|
|
826
|
-
else:
|
|
827
|
-
sorted_values, _ = self.__get_link_data(scada_data, parameter,
|
|
828
|
-
statistic, pit, intervals,
|
|
829
|
-
conversion)
|
|
830
|
-
self.pipe_parameters.update({'edge_color': sorted_values,
|
|
831
|
-
'edge_cmap': mpl.colormaps[colormap],
|
|
832
|
-
'edge_vmin': min(sorted_values),
|
|
833
|
-
'edge_vmax': max(sorted_values)})
|
|
834
|
-
|
|
835
|
-
if show_colorbar:
|
|
836
|
-
if statistic == 'time_step':
|
|
837
|
-
label = (str(parameter).capitalize().replace('_', ' ')
|
|
838
|
-
+ ' at timestep ' + str(pit))
|
|
839
|
-
else:
|
|
840
|
-
label = str(statistic).capitalize() + ' ' + str(
|
|
841
|
-
parameter).replace('_', ' ')
|
|
842
|
-
self.colorbars['pipes'] = {'mappable': plt.cm.ScalarMappable(
|
|
843
|
-
norm=mpl.colors.Normalize(
|
|
844
|
-
vmin=self.pipe_parameters['edge_vmin'],
|
|
845
|
-
vmax=self.pipe_parameters['edge_vmax']), cmap=colormap),
|
|
846
|
-
'label': label}
|
|
847
|
-
|
|
848
|
-
def color_pumps(
|
|
849
|
-
self, scada_data: Optional[ScadaData] = None,
|
|
850
|
-
parameter: str = 'efficiency', statistic: str = 'mean',
|
|
851
|
-
pit: Optional[Union[int, Tuple[int]]] = None,
|
|
852
|
-
intervals: Optional[Union[int, List[Union[int, float]]]] = None,
|
|
853
|
-
colormap: str = 'viridis', show_colorbar: bool = False) -> None:
|
|
854
|
-
"""
|
|
855
|
-
Colors the pumps in the water distribution network based on SCADA data
|
|
856
|
-
and the specified parameters.
|
|
857
|
-
|
|
858
|
-
This method either takes or generates SCADA data, applies a statistic
|
|
859
|
-
to the chosen parameter, optionally groups the results and prepares the
|
|
860
|
-
results to be either displayed statically ot animated.
|
|
861
|
-
|
|
862
|
-
Parameters
|
|
863
|
-
----------
|
|
864
|
-
scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`, optional
|
|
865
|
-
The SCADA data object containing the pump data. If `None`, a
|
|
866
|
-
simulation will be run to generate SCADA data. Default is `None`.
|
|
867
|
-
parameter : `str`, optional
|
|
868
|
-
The pump data to visualize. Must be 'efficiency',
|
|
869
|
-
'energy_consumption', or 'state'. Default is 'efficiency'.
|
|
870
|
-
statistic : `str`, optional
|
|
871
|
-
The statistic to calculate for the data. Can be 'mean', 'min',
|
|
872
|
-
'max', or 'time_step'. Default is 'mean'.
|
|
873
|
-
pit : `int`, `tuple(int, int)`, optional
|
|
874
|
-
The point in time or range of time steps for the 'time_step'
|
|
875
|
-
statistic. If a tuple is provided, it should contain two integers
|
|
876
|
-
representing the start and end time steps. A tuple is necessary to
|
|
877
|
-
process the data for the :meth:`~ScenarioVisualizer.show_animation`
|
|
878
|
-
method. Default is `None`.
|
|
879
|
-
intervals : `int`, `list[int]`, `list[float]`, optional
|
|
880
|
-
If provided, the data will be grouped into intervals. It can be an
|
|
881
|
-
integer specifying the number of groups or a list of boundary
|
|
882
|
-
points. Default is `None`.
|
|
883
|
-
colormap : `str`, optional
|
|
884
|
-
The colormap to use for visualizing pump values. Default is
|
|
885
|
-
'viridis'.
|
|
886
|
-
show_colorbar : `bool`, optional
|
|
887
|
-
If `True`, a colorbar will be displayed on the plot to indicate the
|
|
888
|
-
range of pump values. Default is `False`.
|
|
889
|
-
|
|
890
|
-
Raises
|
|
891
|
-
------
|
|
892
|
-
ValueError
|
|
893
|
-
If the `parameter` is not one of 'efficiency',
|
|
894
|
-
'energy_consumption', or 'state', or if `pit` is not correctly
|
|
895
|
-
provided for the 'time_step' statistic.
|
|
896
|
-
|
|
897
|
-
"""
|
|
898
|
-
|
|
899
|
-
self.pump_parameters.update({'cmap': colormap})
|
|
900
|
-
|
|
901
|
-
if scada_data:
|
|
902
|
-
self.scada_data = scada_data
|
|
903
|
-
elif not self.scada_data:
|
|
904
|
-
self.scada_data = self.__scenario.run_simulation()
|
|
905
|
-
|
|
906
|
-
if parameter == 'efficiency':
|
|
907
|
-
values = self.scada_data.pumps_efficiency_data_raw
|
|
908
|
-
elif parameter == 'energy_consumption':
|
|
909
|
-
values = self.scada_data.pumps_energyconsumption_data_raw
|
|
910
|
-
elif parameter == 'state':
|
|
911
|
-
values = self.scada_data.pumps_state_data_raw
|
|
912
|
-
else:
|
|
913
|
-
raise ValueError(
|
|
914
|
-
'Parameter must be efficiency, energy_consumption or state')
|
|
915
|
-
|
|
916
|
-
if statistic == 'time_step' and isinstance(pit, tuple) and len(
|
|
917
|
-
pit) == 2 and all(isinstance(i, int) for i in pit):
|
|
918
|
-
sorted_values = self.__get_parameters_update(
|
|
919
|
-
statistic, values, pit[0], intervals,
|
|
920
|
-
self.scada_data.sensor_config.pumps,
|
|
921
|
-
self.topology.get_all_pumps())
|
|
922
|
-
self.animation_dict['pumps'] = []
|
|
923
|
-
vmin = min(sorted_values)
|
|
924
|
-
vmax = max(sorted_values)
|
|
925
|
-
for frame in range(*pit):
|
|
926
|
-
if frame > values.shape[0] - 1:
|
|
927
|
-
break
|
|
928
|
-
sorted_values = self.__get_parameters_update(
|
|
929
|
-
statistic, values, frame, intervals,
|
|
930
|
-
self.scada_data.sensor_config.pumps,
|
|
931
|
-
self.topology.get_all_pumps())
|
|
932
|
-
vmin = min(*sorted_values, vmin)
|
|
933
|
-
vmax = max(*sorted_values, vmax)
|
|
934
|
-
self.animation_dict['pumps'].append(sorted_values)
|
|
935
|
-
self.pump_parameters['vmin'] = vmin
|
|
936
|
-
self.pump_parameters['vmax'] = vmax
|
|
937
|
-
else:
|
|
938
|
-
sorted_values = self.__get_parameters_update(
|
|
939
|
-
statistic, values, pit, intervals,
|
|
940
|
-
self.scada_data.sensor_config.pumps,
|
|
941
|
-
self.topology.get_all_pumps())
|
|
942
|
-
self.pump_parameters.update(
|
|
943
|
-
{'node_color': sorted_values, 'vmin': min(sorted_values),
|
|
944
|
-
'vmax': max(sorted_values)})
|
|
945
|
-
|
|
946
|
-
if show_colorbar:
|
|
947
|
-
if statistic == 'time_step':
|
|
948
|
-
label = str(parameter).capitalize().replace(
|
|
949
|
-
'_', ' ') + ' at timestep ' + str(pit)
|
|
950
|
-
else:
|
|
951
|
-
label = str(statistic).capitalize() + ' ' + str(
|
|
952
|
-
parameter).replace('_', ' ')
|
|
953
|
-
self.colorbars['pumps'] = {'mappable': plt.cm.ScalarMappable(
|
|
954
|
-
norm=mpl.colors.Normalize(vmin=self.pump_parameters['vmin'],
|
|
955
|
-
vmax=self.pump_parameters['vmax']),
|
|
956
|
-
cmap=colormap), 'label': label}
|
|
957
|
-
|
|
958
|
-
def color_tanks(
|
|
959
|
-
self, scada_data: Optional[ScadaData] = None,
|
|
960
|
-
statistic: str = 'mean',
|
|
961
|
-
pit: Optional[Union[int, Tuple[int]]] = None,
|
|
962
|
-
intervals: Optional[Union[int, List[Union[int, float]]]] = None,
|
|
963
|
-
colormap: str = 'viridis', show_colorbar: bool = False) -> None:
|
|
964
|
-
"""
|
|
965
|
-
Colors the tanks in the water distribution network based on the SCADA
|
|
966
|
-
tank volume data and the specified statistic.
|
|
967
|
-
|
|
968
|
-
This method either takes or generates SCADA data, applies a statistic
|
|
969
|
-
to the tank volume data, optionally groups the results and prepares
|
|
970
|
-
them to be either displayed statically ot animated.
|
|
971
|
-
|
|
972
|
-
Parameters
|
|
973
|
-
----------
|
|
974
|
-
scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`, optional
|
|
975
|
-
The SCADA data object containing tank volume data.
|
|
976
|
-
If `None`, a simulation will be run to generate it.
|
|
977
|
-
Default is `None`.
|
|
978
|
-
statistic : `str`, optional
|
|
979
|
-
The statistic to calculate for the data. Can be 'mean', 'min',
|
|
980
|
-
'max', or 'time_step'. Default is 'mean'.
|
|
981
|
-
pit : `int`, `tuple(int, int)`, optional
|
|
982
|
-
The point in time or range of time steps for the 'time_step'
|
|
983
|
-
statistic. If a tuple is provided, it should contain two integers
|
|
984
|
-
representing the start and end time steps. A tuple is necessary to
|
|
985
|
-
process the data for the :meth:`~ScenarioVisualizer.show_animation`
|
|
986
|
-
method. Default is `None`.
|
|
987
|
-
intervals : `int`, `list[int]`, `list[float]`, optional
|
|
988
|
-
If provided, the data will be grouped into intervals. It can be an
|
|
989
|
-
integer specifying the number of groups or a list of boundary
|
|
990
|
-
points. Default is `None`.
|
|
991
|
-
colormap : `str`, optional
|
|
992
|
-
The colormap to use for visualizing tank values. Default is
|
|
993
|
-
'viridis'.
|
|
994
|
-
show_colorbar : `bool`, optional
|
|
995
|
-
If `True`, a colorbar will be displayed on the plot to indicate the
|
|
996
|
-
range of tank volume values. Default is `False`.
|
|
997
|
-
|
|
998
|
-
Raises
|
|
999
|
-
------
|
|
1000
|
-
ValueError
|
|
1001
|
-
If `pit` is not correctly provided for the 'time_step' statistic.
|
|
1002
|
-
|
|
1003
|
-
"""
|
|
1004
|
-
self.pump_parameters.update({'node_size': 10, 'cmap': colormap})
|
|
1005
|
-
|
|
1006
|
-
if scada_data:
|
|
1007
|
-
self.scada_data = scada_data
|
|
1008
|
-
elif not self.scada_data:
|
|
1009
|
-
self.scada_data = self.__scenario.run_simulation()
|
|
1010
|
-
|
|
1011
|
-
values = self.scada_data.tanks_volume_data_raw
|
|
1012
|
-
|
|
1013
|
-
if statistic == 'time_step' and isinstance(pit, tuple) and len(
|
|
1014
|
-
pit) == 2 and all(isinstance(i, int) for i in pit):
|
|
1015
|
-
sorted_values = self.__get_parameters_update(
|
|
1016
|
-
statistic, values, pit[0], intervals,
|
|
1017
|
-
self.scada_data.sensor_config.tanks,
|
|
1018
|
-
self.topology.get_all_tanks())
|
|
1019
|
-
self.animation_dict['tanks'] = []
|
|
1020
|
-
vmin = min(sorted_values)
|
|
1021
|
-
vmax = max(sorted_values)
|
|
1022
|
-
for frame in range(*pit):
|
|
1023
|
-
if frame > values.shape[0] - 1:
|
|
1024
|
-
break
|
|
1025
|
-
sorted_values = self.__get_parameters_update(
|
|
1026
|
-
statistic, values, frame, intervals,
|
|
1027
|
-
self.scada_data.sensor_config.tanks,
|
|
1028
|
-
self.topology.get_all_tanks())
|
|
1029
|
-
vmin = min(*sorted_values, vmin)
|
|
1030
|
-
vmax = max(*sorted_values, vmax)
|
|
1031
|
-
self.animation_dict['tanks'].append(sorted_values)
|
|
1032
|
-
self.tank_parameters['vmin'] = vmin
|
|
1033
|
-
self.tank_parameters['vmax'] = vmax
|
|
1034
|
-
else:
|
|
1035
|
-
sorted_values = self.__get_parameters_update(
|
|
1036
|
-
statistic, values, pit, intervals,
|
|
1037
|
-
self.scada_data.sensor_config.tanks,
|
|
1038
|
-
self.topology.get_all_tanks())
|
|
1039
|
-
self.tank_parameters.update(
|
|
1040
|
-
{'node_color': sorted_values, 'vmin': min(sorted_values),
|
|
1041
|
-
'vmax': max(sorted_values)})
|
|
1042
|
-
|
|
1043
|
-
if show_colorbar:
|
|
1044
|
-
if statistic == 'time_step':
|
|
1045
|
-
label = 'tank volume'.capitalize() + ' at timestep ' + str(pit)
|
|
1046
|
-
else:
|
|
1047
|
-
label = str(statistic).capitalize() + ' ' + 'tank volume'
|
|
1048
|
-
self.colorbars['tanks'] = {'mappable': plt.cm.ScalarMappable(
|
|
1049
|
-
norm=mpl.colors.Normalize(vmin=self.tank_parameters['vmin'],
|
|
1050
|
-
vmax=self.tank_parameters['vmax']),
|
|
1051
|
-
cmap=colormap), 'label': label}
|
|
1052
|
-
|
|
1053
|
-
def color_valves(
|
|
1054
|
-
self, scada_data: Optional[ScadaData] = None,
|
|
1055
|
-
statistic: str = 'mean',
|
|
1056
|
-
pit: Optional[Union[int, Tuple[int]]] = None,
|
|
1057
|
-
intervals: Optional[Union[int, List[Union[int, float]]]] = None,
|
|
1058
|
-
colormap: str = 'viridis', show_colorbar: bool = False) -> None:
|
|
1059
|
-
"""
|
|
1060
|
-
Colors the valves in the water distribution network based on SCADA
|
|
1061
|
-
valve state data and the specified statistic.
|
|
1062
|
-
|
|
1063
|
-
This method either takes or generates SCADA data, applies a statistic
|
|
1064
|
-
to the valve state data, optionally groups the results and prepares
|
|
1065
|
-
them to be either displayed statically ot animated.
|
|
1066
|
-
|
|
1067
|
-
Parameters
|
|
1068
|
-
----------
|
|
1069
|
-
scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`, optional
|
|
1070
|
-
The SCADA data object containing valve state data. If `None`, a
|
|
1071
|
-
simulation is run to generate SCADA data. Default is `None`.
|
|
1072
|
-
statistic : `str`, optional
|
|
1073
|
-
The statistic to calculate for the data. Can be 'mean', 'min',
|
|
1074
|
-
'max', or 'time_step'. Default is 'mean'.
|
|
1075
|
-
pit : `int`, `tuple(int)`, optional
|
|
1076
|
-
The point in time or range of time steps for the 'time_step'
|
|
1077
|
-
statistic. If a tuple is provided, it should contain two integers
|
|
1078
|
-
representing the start and end time steps. A tuple is necessary to
|
|
1079
|
-
process the data for the :meth:`~ScenarioVisualizer.show_animation`
|
|
1080
|
-
method. Default is `None`.
|
|
1081
|
-
intervals : `int`, `list[int]`, `list[float]`, optional
|
|
1082
|
-
If provided, the data will be grouped into intervals. It can be an
|
|
1083
|
-
integer specifying the number of groups or a list of
|
|
1084
|
-
boundary points. Default is `None`.
|
|
1085
|
-
colormap : `str`, optional
|
|
1086
|
-
The colormap to use for visualizing valve state values. Default is
|
|
1087
|
-
'viridis'.
|
|
1088
|
-
show_colorbar : `bool`, optional
|
|
1089
|
-
If `True`, a colorbar will be displayed on the plot to indicate the
|
|
1090
|
-
range of valve state values. Default is `False`.
|
|
1091
|
-
|
|
1092
|
-
Raises
|
|
1093
|
-
------
|
|
1094
|
-
ValueError
|
|
1095
|
-
If `pit` is not correctly provided for the 'time_step' statistic.
|
|
1096
|
-
|
|
1097
|
-
"""
|
|
1098
|
-
|
|
1099
|
-
self.valve_parameters.update({'node_size': 15, 'cmap': colormap})
|
|
1100
|
-
|
|
1101
|
-
if scada_data:
|
|
1102
|
-
self.scada_data = scada_data
|
|
1103
|
-
elif not self.scada_data:
|
|
1104
|
-
self.scada_data = self.__scenario.run_simulation()
|
|
1105
|
-
|
|
1106
|
-
values = self.scada_data.valves_state_data_raw
|
|
1107
|
-
|
|
1108
|
-
if statistic == 'time_step' and isinstance(pit, tuple) and len(
|
|
1109
|
-
pit) == 2 and all(isinstance(i, int) for i in pit):
|
|
1110
|
-
sorted_values = self.__get_parameters_update(
|
|
1111
|
-
statistic, values, pit[0], intervals,
|
|
1112
|
-
self.scada_data.sensor_config.valves,
|
|
1113
|
-
self.topology.get_all_valves())
|
|
1114
|
-
self.animation_dict['valves'] = []
|
|
1115
|
-
vmin = min(sorted_values)
|
|
1116
|
-
vmax = max(sorted_values)
|
|
1117
|
-
for frame in range(*pit):
|
|
1118
|
-
if frame > values.shape[0] - 1:
|
|
1119
|
-
break
|
|
1120
|
-
sorted_values = self.__get_parameters_update(
|
|
1121
|
-
statistic, values, frame, intervals,
|
|
1122
|
-
self.scada_data.sensor_config.valves,
|
|
1123
|
-
self.topology.get_all_valves())
|
|
1124
|
-
vmin = min(*sorted_values, vmin)
|
|
1125
|
-
vmax = max(*sorted_values, vmax)
|
|
1126
|
-
self.animation_dict['valves'].append(sorted_values)
|
|
1127
|
-
self.valve_parameters['vmin'] = vmin
|
|
1128
|
-
self.valve_parameters['vmax'] = vmax
|
|
1129
|
-
else:
|
|
1130
|
-
sorted_values = self.__get_parameters_update(
|
|
1131
|
-
statistic, values, pit, intervals,
|
|
1132
|
-
self.scada_data.sensor_config.valves,
|
|
1133
|
-
self.topology.get_all_valves())
|
|
1134
|
-
self.valve_parameters.update(
|
|
1135
|
-
{'node_color': sorted_values, 'vmin': min(sorted_values),
|
|
1136
|
-
'vmax': max(sorted_values)})
|
|
1137
|
-
|
|
1138
|
-
if show_colorbar:
|
|
1139
|
-
if statistic == 'time_step':
|
|
1140
|
-
label = 'valve state'.capitalize() + ' at timestep ' + str(pit)
|
|
1141
|
-
else:
|
|
1142
|
-
label = str(statistic).capitalize() + ' ' + 'valve state'
|
|
1143
|
-
self.colorbars['valves'] = {'mappable': plt.cm.ScalarMappable(
|
|
1144
|
-
norm=mpl.colors.Normalize(vmin=self.valve_parameters['vmin'],
|
|
1145
|
-
vmax=self.valve_parameters['vmax']),
|
|
1146
|
-
cmap=colormap), 'label': label}
|
|
1147
|
-
|
|
1148
|
-
def resize_links(
|
|
1149
|
-
self, scada_data: Optional[ScadaData] = None,
|
|
1150
|
-
parameter: str = 'flow_rate', statistic: str = 'mean',
|
|
1151
|
-
line_widths: Tuple[int] = (1, 2),
|
|
1152
|
-
pit: Optional[Union[int, Tuple[int]]] = None,
|
|
1153
|
-
intervals: Optional[Union[int, List[Union[int, float]]]] = None,
|
|
1154
|
-
conversion: Optional[dict] = None) -> None:
|
|
1155
|
-
"""
|
|
1156
|
-
Resizes the width of the links (pipes) in the water distribution
|
|
1157
|
-
network based on SCADA data and the specified parameters.
|
|
1158
|
-
|
|
1159
|
-
This method either takes or generates SCADA data, applies a statistic,
|
|
1160
|
-
optionally groups the results and prepares them to be either displayed
|
|
1161
|
-
statically ot animated as link width.
|
|
1162
|
-
|
|
1163
|
-
Parameters
|
|
1164
|
-
----------
|
|
1165
|
-
scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`, optional
|
|
1166
|
-
The SCADA data object. If `None`, a simulation will be run to
|
|
1167
|
-
generate it. Default is `None`.
|
|
1168
|
-
parameter : `str`, optional
|
|
1169
|
-
The data used to resize to. Default is 'flow_rate'.
|
|
1170
|
-
statistic : `str`, optional
|
|
1171
|
-
The statistic to calculate for the data. Can be 'mean', 'min',
|
|
1172
|
-
'max', or 'time_step'. Default is 'mean'.
|
|
1173
|
-
line_widths : `tuple(int, int)`, optional
|
|
1174
|
-
A tuple specifying the range of line widths to use when resizing
|
|
1175
|
-
links based on the data. Default is (1, 2).
|
|
1176
|
-
pit : `int` or `tuple(int, int)`, optional
|
|
1177
|
-
The point in time or range of time steps for the 'time_step'
|
|
1178
|
-
statistic. If a tuple is provided, it should contain two integers
|
|
1179
|
-
representing the start and end time steps. A tuple is necessary to
|
|
1180
|
-
process the data for the :meth:`~ScenarioVisualizer.show_animation`
|
|
1181
|
-
method. Default is `None`.
|
|
1182
|
-
intervals : `int` or `list[int]` or `list[float]`, optional
|
|
1183
|
-
If provided, the data will be grouped into intervals. It can be an
|
|
1184
|
-
integer specifying the number of groups or a list of boundary
|
|
1185
|
-
points. Default is `None`.
|
|
1186
|
-
conversion : `dict`, optional
|
|
1187
|
-
A dictionary of conversion parameters to convert SCADA data units.
|
|
1188
|
-
Default is `None`.
|
|
1189
|
-
"""
|
|
1190
|
-
|
|
1191
|
-
if statistic == 'time_step' and isinstance(pit, tuple) and len(
|
|
1192
|
-
pit) == 2 and all(isinstance(i, int) for i in pit):
|
|
1193
|
-
sorted_values, sim_length = self.__get_link_data(scada_data,
|
|
1194
|
-
parameter,
|
|
1195
|
-
statistic, pit[0],
|
|
1196
|
-
intervals,
|
|
1197
|
-
conversion)
|
|
1198
|
-
pipe_size_list = []
|
|
1199
|
-
vmin = min(sorted_values)
|
|
1200
|
-
vmax = max(sorted_values)
|
|
1201
|
-
for frame in range(*pit):
|
|
1202
|
-
if frame > sim_length - 1:
|
|
1203
|
-
break
|
|
1204
|
-
sorted_values, _ = self.__get_link_data(scada_data, parameter,
|
|
1205
|
-
statistic, frame,
|
|
1206
|
-
intervals, conversion)
|
|
1207
|
-
vmin = min(*sorted_values, vmin)
|
|
1208
|
-
vmax = max(*sorted_values, vmax)
|
|
1209
|
-
pipe_size_list.append(sorted_values)
|
|
1210
|
-
self.animation_dict['pipe_sizes'] = []
|
|
1211
|
-
for vals in pipe_size_list:
|
|
1212
|
-
self.animation_dict['pipe_sizes'].append(
|
|
1213
|
-
self.__rescale(vals, line_widths,
|
|
1214
|
-
values_min_max=(vmin, vmax)))
|
|
1215
|
-
else:
|
|
1216
|
-
sorted_values, _ = self.__get_link_data(scada_data, parameter,
|
|
1217
|
-
statistic, pit, intervals,
|
|
1218
|
-
conversion)
|
|
1219
|
-
self.pipe_parameters.update(
|
|
1220
|
-
{'width': self.__rescale(sorted_values, line_widths)})
|
|
1221
|
-
|
|
1222
|
-
def hide_nodes(self) -> None:
|
|
1223
|
-
"""
|
|
1224
|
-
Hides all nodes (junctions) in the water distribution network
|
|
1225
|
-
visualization.
|
|
1226
|
-
|
|
1227
|
-
This method clears the node list from the `junction_parameters`
|
|
1228
|
-
dictionary, effectively removing all nodes from view in the current
|
|
1229
|
-
visualization.
|
|
1230
|
-
"""
|
|
1231
|
-
self.junction_parameters['nodelist'] = []
|
|
1232
|
-
|
|
1233
|
-
def highlight_sensor_config(self) -> None:
|
|
1234
|
-
"""
|
|
1235
|
-
Highlights nodes and links that have sensors in the sensor_config in
|
|
1236
|
-
the water distribution network visualization.
|
|
1237
|
-
|
|
1238
|
-
This method identifies nodes and links equipped with different types of
|
|
1239
|
-
sensors from the :class:`~epyt_flow.simulation.sensor_config.SensorConfig` and
|
|
1240
|
-
updates their visual appearance. Nodes with sensors are highlighted
|
|
1241
|
-
with an orange border, while links with sensors are displayed with a
|
|
1242
|
-
dashed line style.
|
|
1243
|
-
"""
|
|
1244
|
-
highlighted_nodes = []
|
|
1245
|
-
highlighted_links = []
|
|
1246
|
-
|
|
1247
|
-
sensor_config = self.__scenario.sensor_config
|
|
1248
|
-
highlighted_nodes += (sensor_config.pressure_sensors
|
|
1249
|
-
+ sensor_config.demand_sensors
|
|
1250
|
-
+ sensor_config.quality_node_sensors)
|
|
1251
|
-
highlighted_links += (sensor_config.flow_sensors
|
|
1252
|
-
+ sensor_config.quality_link_sensors)
|
|
1253
|
-
|
|
1254
|
-
node_edges = [
|
|
1255
|
-
(17, 163, 252) if node in highlighted_nodes else (0, 0, 0) for node
|
|
1256
|
-
in self.topology]
|
|
1257
|
-
pipe_style = ['dashed' if link in highlighted_links else 'solid' for
|
|
1258
|
-
link in self.topology]
|
|
1259
|
-
|
|
1260
|
-
self.junction_parameters.update(
|
|
1261
|
-
{'linewidths': 1, 'edgecolors': node_edges})
|
|
1262
|
-
self.pipe_parameters.update({'style': pipe_style})
|
|
1263
|
-
|
|
1264
|
-
@deprecated(reason="This function will be removed in feature versions, "
|
|
1265
|
-
"please use show_plot() instead.")
|
|
1266
|
-
def plot_topology(self, show_sensor_config: bool = False,
|
|
1267
|
-
export_to_file: str = None) -> None:
|
|
1268
|
-
"""
|
|
1269
|
-
Plots the topology of the water distribution network in the given
|
|
1270
|
-
scenario.
|
|
1271
|
-
|
|
1272
|
-
Parameters
|
|
1273
|
-
----------
|
|
1274
|
-
show_sensor_config : `bool`, optional
|
|
1275
|
-
Indicates whether the sensor configuration should be shown as well.
|
|
1276
|
-
|
|
1277
|
-
The default is False.
|
|
1278
|
-
export_to_file : `str`, optional
|
|
1279
|
-
Path to the file where the visualization will be stored.
|
|
1280
|
-
If None, visualization will be just shown but NOT be stored
|
|
1281
|
-
anywhere.
|
|
1282
|
-
|
|
1283
|
-
The default is None.
|
|
1284
|
-
"""
|
|
1285
|
-
_ = plt.figure()
|
|
1286
|
-
|
|
1287
|
-
highlighted_links = None
|
|
1288
|
-
highlighted_nodes = None
|
|
1289
|
-
if show_sensor_config is True:
|
|
1290
|
-
highlighted_nodes = []
|
|
1291
|
-
highlighted_links = []
|
|
1292
|
-
|
|
1293
|
-
sensor_config = self.__scenario.sensor_config
|
|
1294
|
-
highlighted_nodes += (sensor_config.pressure_sensors
|
|
1295
|
-
+ sensor_config.demand_sensors
|
|
1296
|
-
+ sensor_config.quality_node_sensors)
|
|
1297
|
-
highlighted_links += (sensor_config.flow_sensors
|
|
1298
|
-
+ sensor_config.quality_link_sensors)
|
|
1299
|
-
|
|
1300
|
-
self.__scenario.epanet_api.plot(highlightlink=highlighted_links,
|
|
1301
|
-
highlightnode=highlighted_nodes,
|
|
1302
|
-
figure=False)
|
|
1303
|
-
|
|
1304
|
-
if export_to_file is not None:
|
|
1305
|
-
plt.savefig(export_to_file, transparent=True, bbox_inches='tight')
|
|
1306
|
-
else:
|
|
1307
|
-
plt.show()
|