epyt-flow 0.10.0__py3-none-any.whl → 0.11.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/networks.py +27 -14
- epyt_flow/gym/control_gyms.py +8 -0
- epyt_flow/gym/scenario_control_env.py +8 -3
- epyt_flow/metrics.py +5 -0
- epyt_flow/models/event_detector.py +5 -0
- epyt_flow/models/sensor_interpolation_detector.py +5 -0
- epyt_flow/serialization.py +1 -0
- epyt_flow/simulation/__init__.py +0 -1
- epyt_flow/simulation/events/actuator_events.py +7 -1
- epyt_flow/simulation/scada/scada_data.py +527 -5
- epyt_flow/simulation/scenario_config.py +1 -40
- epyt_flow/simulation/scenario_simulator.py +511 -68
- epyt_flow/simulation/sensor_config.py +18 -2
- epyt_flow/topology.py +16 -0
- 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 +15 -1
- epyt_flow/visualization/__init__.py +2 -0
- epyt_flow/{simulation → visualization}/scenario_visualizer.py +429 -586
- epyt_flow/visualization/visualization_utils.py +611 -0
- {epyt_flow-0.10.0.dist-info → epyt_flow-0.11.0.dist-info}/METADATA +12 -1
- {epyt_flow-0.10.0.dist-info → epyt_flow-0.11.0.dist-info}/RECORD +28 -26
- {epyt_flow-0.10.0.dist-info → epyt_flow-0.11.0.dist-info}/WHEEL +1 -1
- {epyt_flow-0.10.0.dist-info → epyt_flow-0.11.0.dist-info}/LICENSE +0 -0
- {epyt_flow-0.10.0.dist-info → epyt_flow-0.11.0.dist-info}/top_level.txt +0 -0
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Module provides a class for visualizing scenarios.
|
|
3
3
|
"""
|
|
4
|
-
from typing import Optional, Union, List, Tuple
|
|
4
|
+
from typing import Optional, Union, List, Tuple
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
5
7
|
from deprecated import deprecated
|
|
6
8
|
|
|
7
9
|
import matplotlib.pyplot as plt
|
|
8
10
|
from matplotlib.animation import FuncAnimation
|
|
9
11
|
import matplotlib as mpl
|
|
10
12
|
import networkx.drawing.nx_pylab as nxp
|
|
11
|
-
import numpy as np
|
|
12
13
|
from svgpath2mpl import parse_path
|
|
13
14
|
|
|
14
|
-
from .scenario_simulator import ScenarioSimulator
|
|
15
|
-
from .scada.scada_data import ScadaData
|
|
15
|
+
from ..simulation.scenario_simulator import ScenarioSimulator
|
|
16
|
+
from ..simulation.scada.scada_data import ScadaData
|
|
17
|
+
from ..visualization import JunctionObject, EdgeObject, ColorScheme, epyt_flow_colors
|
|
16
18
|
|
|
17
19
|
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
20
|
'41.5 42 0 0 0 244 135 A 41.5 42 0 0 0 241.94922 122 L 278 122 '
|
|
@@ -33,18 +35,19 @@ class Marker:
|
|
|
33
35
|
"""
|
|
34
36
|
The Marker class provides svg representations of hydraulic components
|
|
35
37
|
(pump, reservoir, tank and valve), which are loaded from their respective
|
|
36
|
-
svg paths and transformed into
|
|
37
|
-
|
|
38
|
+
svg paths and transformed into
|
|
39
|
+
`~matplotlib.path.Path <https://matplotlib.org/stable/api/path_api.html#matplotlib.path.Path>`_
|
|
40
|
+
objects in order to be used with the matplotlib library.
|
|
38
41
|
|
|
39
42
|
Attributes
|
|
40
43
|
----------
|
|
41
|
-
pump :
|
|
44
|
+
pump : `matplotlib.path.Path <https://matplotlib.org/stable/api/path_api.html#matplotlib.path.Path>`_
|
|
42
45
|
Marker for the pump, loaded from PUMP_PATH.
|
|
43
|
-
reservoir :
|
|
46
|
+
reservoir : `matplotlib.path.Path <https://matplotlib.org/stable/api/path_api.html#matplotlib.path.Path>`_
|
|
44
47
|
Marker for the reservoir, loaded from RESERVOIR_PATH.
|
|
45
|
-
tank :
|
|
48
|
+
tank : `matplotlib.path.Path <https://matplotlib.org/stable/api/path_api.html#matplotlib.path.Path>`_
|
|
46
49
|
Marker for the tank, loaded from TANK_PATH.
|
|
47
|
-
valve :
|
|
50
|
+
valve : `matplotlib.path.Path <https://matplotlib.org/stable/api/path_api.html#matplotlib.path.Path>`_
|
|
48
51
|
Marker for the valve, loaded from VALVE_PATH.
|
|
49
52
|
|
|
50
53
|
Methods
|
|
@@ -53,9 +56,11 @@ class Marker:
|
|
|
53
56
|
Loads and applies transformations to the marker shape from the given
|
|
54
57
|
path.
|
|
55
58
|
"""
|
|
59
|
+
|
|
56
60
|
def __init__(self):
|
|
57
61
|
"""
|
|
58
|
-
Initializes the Marker class and assigns
|
|
62
|
+
Initializes the Marker class and assigns
|
|
63
|
+
`matplotlib.path.Path <https://matplotlib.org/stable/api/path_api.html#matplotlib.path.Path>`_
|
|
59
64
|
markers for pump, reservoir, tank, and valve components.
|
|
60
65
|
"""
|
|
61
66
|
self.pump = self.__marker_from_path(PUMP_PATH, 2)
|
|
@@ -78,7 +83,7 @@ class Marker:
|
|
|
78
83
|
|
|
79
84
|
Returns
|
|
80
85
|
-------
|
|
81
|
-
|
|
86
|
+
`matplotlib.path.Path <https://matplotlib.org/stable/api/path_api.html#matplotlib.path.Path>`_
|
|
82
87
|
The transformed marker object after loading and adjusting it.
|
|
83
88
|
"""
|
|
84
89
|
marker_tmp = parse_path(path)
|
|
@@ -95,20 +100,20 @@ class ScenarioVisualizer:
|
|
|
95
100
|
This class provides the necessary function to generate visualizations in
|
|
96
101
|
the form of plots or animations from water network data.
|
|
97
102
|
|
|
98
|
-
Given a :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`
|
|
99
|
-
provides the necessary functions to plot the network
|
|
100
|
-
hydraulic elements according to simulation data. The
|
|
101
|
-
then
|
|
103
|
+
Given a :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`
|
|
104
|
+
object, this class provides the necessary functions to plot the network
|
|
105
|
+
topology and to color hydraulic elements according to simulation data. The
|
|
106
|
+
resulting plot can then be displayed or saved.
|
|
102
107
|
|
|
103
108
|
Attributes
|
|
104
109
|
----------
|
|
105
110
|
__scenario : :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`
|
|
106
111
|
ScenarioSimulator object containing the network topology and
|
|
107
112
|
configurations to obtain the simulation data which should be displayed.
|
|
108
|
-
fig :
|
|
113
|
+
fig : `matplotlib.pyplot.Figure <https://matplotlib.org/stable/api/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure>`_ or None
|
|
109
114
|
Figure object used for plotting, created and customized by calling the
|
|
110
115
|
methods of this class, initialized as None.
|
|
111
|
-
ax :
|
|
116
|
+
ax : `~matplotlib.axes.Axes <https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.html#matplotlib-axes-axes>`_ or None
|
|
112
117
|
The axes for plotting, initialized as None.
|
|
113
118
|
scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` or None
|
|
114
119
|
SCADA data created by the ScenarioSimulator object, initialized as
|
|
@@ -116,47 +121,58 @@ class ScenarioVisualizer:
|
|
|
116
121
|
topology : :class:`~epyt_flow.topology.NetworkTopology`
|
|
117
122
|
Topology object retrieved from the scenario, containing the structure
|
|
118
123
|
of the water distribution network.
|
|
119
|
-
|
|
120
|
-
|
|
124
|
+
color_scheme : :class:`~epyt_flow.visualization.visualization_utils.ColorScheme`
|
|
125
|
+
Contains the selected ColorScheme for visualization.
|
|
126
|
+
pipe_parameters : :class:`~epyt_flow.visualization.visualization_utils.EdgeObject`
|
|
127
|
+
Class contains parameters for visualizing pipes in the correct format
|
|
128
|
+
for drawing.
|
|
129
|
+
junction_parameters : :class:`~epyt_flow.visualization.visualization_utils.JunctionObject`
|
|
130
|
+
Class contains parameters for visualizing junctions in the correct
|
|
131
|
+
format for drawing.
|
|
132
|
+
tank_parameters : :class:`~epyt_flow.visualization.visualization_utils.JunctionObject`
|
|
133
|
+
Class contains parameters for visualizing tanks in the correct format
|
|
121
134
|
for drawing.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
|
135
|
+
reservoir_parameters : :class:`~epyt_flow.visualization.visualization_utils.JunctionObject`
|
|
136
|
+
Class contains parameters for visualizing reservoirs in the correct
|
|
137
|
+
format for
|
|
130
138
|
drawing.
|
|
131
|
-
valve_parameters : `
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
components.
|
|
139
|
+
valve_parameters : :class:`~epyt_flow.visualization.visualization_utils.JunctionObject`
|
|
140
|
+
Class contains parameters for visualizing valves in the correct format
|
|
141
|
+
for drawing.
|
|
142
|
+
pump_parameters : :class:`~epyt_flow.visualization.visualization_utils.JunctionObject`
|
|
143
|
+
Class contains parameters for visualizing pumps in the correct format
|
|
144
|
+
for drawing.
|
|
138
145
|
colorbars : `dict`
|
|
139
146
|
A dictionary containing the necessary data for drawing the required
|
|
140
147
|
colorbars.
|
|
148
|
+
labels : `dict`
|
|
149
|
+
A dictionary containing components as keys and drawing information for
|
|
150
|
+
the labels as values.
|
|
151
|
+
|
|
141
152
|
"""
|
|
142
|
-
|
|
153
|
+
|
|
154
|
+
def __init__(self, scenario: ScenarioSimulator,
|
|
155
|
+
color_scheme: ColorScheme = epyt_flow_colors) -> None:
|
|
143
156
|
"""
|
|
144
157
|
Initializes the class with a given scenario, sets up the topology,
|
|
145
|
-
SCADA data, and parameters for visualizing
|
|
146
|
-
(pipes, junctions, tanks, reservoirs,
|
|
158
|
+
SCADA data, and the classes containing parameters for visualizing
|
|
159
|
+
various hydraulic components (pipes, junctions, tanks, reservoirs,
|
|
160
|
+
valves, and pumps).
|
|
147
161
|
|
|
148
162
|
Parameters
|
|
149
163
|
----------
|
|
150
164
|
scenario : :class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`
|
|
151
165
|
An instance of the `ScenarioSimulator` class, used to simulate and
|
|
152
166
|
retrieve the system topology.
|
|
167
|
+
color_scheme : :class:`~epyt_flow.visualization.visualization_utils.ColorScheme`
|
|
168
|
+
Contains the selected ColorScheme for visualization. Default is
|
|
169
|
+
EPYT_FLOW.
|
|
153
170
|
|
|
154
171
|
Raises
|
|
155
172
|
------
|
|
156
173
|
TypeError
|
|
157
174
|
If `scenario` is not an instance of
|
|
158
175
|
:class:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator`.
|
|
159
|
-
|
|
160
176
|
"""
|
|
161
177
|
if not isinstance(scenario, ScenarioSimulator):
|
|
162
178
|
raise TypeError("'scenario' must be an instance of " +
|
|
@@ -169,30 +185,43 @@ class ScenarioVisualizer:
|
|
|
169
185
|
self.scada_data = None
|
|
170
186
|
markers = Marker()
|
|
171
187
|
self.topology = self.__scenario.get_topology()
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
self.
|
|
188
|
+
|
|
189
|
+
pos_dict = {x: self.topology.get_node_info(x)['coord'] for x in
|
|
190
|
+
self.topology.get_all_nodes()}
|
|
191
|
+
|
|
192
|
+
self.color_scheme = color_scheme
|
|
193
|
+
|
|
194
|
+
self.pipe_parameters = EdgeObject(
|
|
195
|
+
[x[1] for x in self.topology.get_all_links()], pos_dict,
|
|
196
|
+
self.color_scheme.pipe_color)
|
|
197
|
+
self.junction_parameters = JunctionObject(
|
|
198
|
+
self.topology.get_all_junctions(), pos_dict,
|
|
199
|
+
node_color=self.color_scheme.node_color)
|
|
200
|
+
self.tank_parameters = JunctionObject(self.topology.get_all_tanks(),
|
|
201
|
+
pos_dict, node_size=100,
|
|
202
|
+
node_shape=markers.tank,
|
|
203
|
+
node_color=self.color_scheme.tank_color)
|
|
204
|
+
self.reservoir_parameters = JunctionObject(
|
|
205
|
+
self.topology.get_all_reservoirs(), pos_dict, node_size=100,
|
|
206
|
+
node_shape=markers.reservoir,
|
|
207
|
+
node_color=self.color_scheme.reservoir_color)
|
|
208
|
+
self.valve_parameters = JunctionObject(self.topology.get_all_valves(),
|
|
209
|
+
self._get_midpoints(
|
|
210
|
+
self.topology.get_all_valves()),
|
|
211
|
+
node_size=50,
|
|
212
|
+
node_shape=markers.valve,
|
|
213
|
+
node_color=self.color_scheme.valve_color)
|
|
214
|
+
self.pump_parameters = JunctionObject(self.topology.get_all_pumps(),
|
|
215
|
+
self._get_midpoints(
|
|
216
|
+
self.topology.get_all_pumps()),
|
|
217
|
+
node_size=50,
|
|
218
|
+
node_shape=markers.pump,
|
|
219
|
+
node_color=self.color_scheme.pump_color)
|
|
220
|
+
|
|
193
221
|
self.colorbars = {}
|
|
222
|
+
self.labels = {}
|
|
194
223
|
|
|
195
|
-
def
|
|
224
|
+
def _get_midpoints(self, elements: List[str]) -> dict[str, tuple[float, float]]:
|
|
196
225
|
"""
|
|
197
226
|
Computes and returns the midpoints for drawing either valves or pumps
|
|
198
227
|
in a water distribution network.
|
|
@@ -229,7 +258,7 @@ class ScenarioVisualizer:
|
|
|
229
258
|
elements_pos_dict[element] = pos
|
|
230
259
|
return elements_pos_dict
|
|
231
260
|
|
|
232
|
-
def
|
|
261
|
+
def _get_next_frame(self, frame_number: int) -> None:
|
|
233
262
|
"""
|
|
234
263
|
Draws the next frame of a water distribution network animation.
|
|
235
264
|
|
|
@@ -243,297 +272,150 @@ class ScenarioVisualizer:
|
|
|
243
272
|
The current frame number used to retrieve the data corresponding to
|
|
244
273
|
that frame
|
|
245
274
|
"""
|
|
275
|
+
plt.clf()
|
|
246
276
|
self.ax = self.fig.add_subplot(111)
|
|
247
277
|
self.ax.axis('off')
|
|
248
278
|
|
|
249
|
-
nxp.draw_networkx_edges(self.topology,
|
|
250
|
-
label='Pipes',
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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,
|
|
279
|
+
nxp.draw_networkx_edges(self.topology, ax=self.ax,
|
|
280
|
+
label='Pipes',
|
|
281
|
+
**self.pipe_parameters.get_frame(frame_number))
|
|
282
|
+
nxp.draw_networkx_nodes(self.topology, ax=self.ax,
|
|
283
|
+
label='Junctions',
|
|
284
|
+
**self.junction_parameters.get_frame(
|
|
285
|
+
frame_number))
|
|
286
|
+
nxp.draw_networkx_nodes(self.topology, ax=self.ax,
|
|
287
|
+
label='Tanks',
|
|
288
|
+
**self.tank_parameters.get_frame(frame_number))
|
|
289
|
+
nxp.draw_networkx_nodes(self.topology, ax=self.ax,
|
|
276
290
|
label='Reservoirs',
|
|
277
|
-
**self.reservoir_parameters
|
|
291
|
+
**self.reservoir_parameters.get_frame(
|
|
292
|
+
frame_number))
|
|
278
293
|
nxp.draw_networkx_nodes(
|
|
279
|
-
self.topology,
|
|
280
|
-
self.
|
|
281
|
-
label='Valves', **self.valve_parameters)
|
|
294
|
+
self.topology, ax=self.ax,
|
|
295
|
+
label='Valves', **self.valve_parameters.get_frame(frame_number))
|
|
282
296
|
nxp.draw_networkx_nodes(
|
|
283
|
-
self.topology,
|
|
284
|
-
ax=self.ax, label='Pumps',
|
|
297
|
+
self.topology,
|
|
298
|
+
ax=self.ax, label='Pumps',
|
|
299
|
+
**self.pump_parameters.get_frame(frame_number))
|
|
285
300
|
|
|
301
|
+
self._draw_labels()
|
|
286
302
|
self.ax.legend(fontsize=6)
|
|
287
303
|
|
|
288
|
-
|
|
289
|
-
self,
|
|
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]:
|
|
304
|
+
for colorbar_stats in self.colorbars.values():
|
|
305
|
+
self.fig.colorbar(ax=self.ax, **colorbar_stats)
|
|
295
306
|
|
|
307
|
+
def _interpolate_frames(self, num_inter_frames: int):
|
|
296
308
|
"""
|
|
297
|
-
|
|
298
|
-
|
|
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.
|
|
309
|
+
Interpolates intermediate values between frames using cubic spline
|
|
310
|
+
interpolation for smoother animation.
|
|
303
311
|
|
|
304
312
|
Parameters
|
|
305
313
|
----------
|
|
306
|
-
|
|
307
|
-
|
|
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`.
|
|
314
|
+
num_inter_frames : `int`
|
|
315
|
+
Number of total frames after interpolation.
|
|
327
316
|
|
|
328
317
|
Returns
|
|
329
318
|
-------
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
|
|
319
|
+
num_inter_frames : `int`
|
|
320
|
+
Number of total frames after interpolation.
|
|
343
321
|
"""
|
|
322
|
+
for node_source in [self.junction_parameters, self.tank_parameters,
|
|
323
|
+
self.reservoir_parameters, self.valve_parameters,
|
|
324
|
+
self.pump_parameters]:
|
|
325
|
+
node_source.interpolate(num_inter_frames)
|
|
326
|
+
self.pipe_parameters.interpolate(num_inter_frames)
|
|
344
327
|
|
|
345
|
-
|
|
346
|
-
self.scada_data = scada_data
|
|
347
|
-
elif not self.scada_data:
|
|
348
|
-
self.scada_data = self.__scenario.run_simulation()
|
|
328
|
+
return num_inter_frames
|
|
349
329
|
|
|
350
|
-
|
|
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:
|
|
330
|
+
def _draw_labels(self):
|
|
412
331
|
"""
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
332
|
+
Method accesses the dict `self.labels` and draws all generated labels
|
|
333
|
+
within.
|
|
334
|
+
"""
|
|
335
|
+
for k, v in self.labels.items():
|
|
336
|
+
if k in ['pipes']:
|
|
337
|
+
nxp.draw_networkx_edge_labels(self.topology, ax=self.ax, **v)
|
|
338
|
+
continue
|
|
339
|
+
nxp.draw_networkx_labels(self.topology, ax=self.ax, **v)
|
|
420
340
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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.
|
|
341
|
+
def _get_sensor_config_nodes_and_links(self):
|
|
342
|
+
"""
|
|
343
|
+
Iterates through the sensor config and collects all nodes and links
|
|
344
|
+
within, that have a sensor attached.
|
|
442
345
|
|
|
443
346
|
Returns
|
|
444
347
|
-------
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
-
|
|
348
|
+
highlighted_links : `list`
|
|
349
|
+
List of all links with sensors.
|
|
350
|
+
highlighted_nodes : `list`
|
|
351
|
+
List of all nodes with sensors.
|
|
456
352
|
"""
|
|
353
|
+
highlighted_nodes = []
|
|
354
|
+
highlighted_links = []
|
|
457
355
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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
|
|
356
|
+
sensor_config = self.__scenario.sensor_config
|
|
357
|
+
highlighted_nodes += (sensor_config.pressure_sensors
|
|
358
|
+
+ sensor_config.demand_sensors
|
|
359
|
+
+ sensor_config.quality_node_sensors)
|
|
360
|
+
highlighted_links += (sensor_config.flow_sensors
|
|
361
|
+
+ sensor_config.quality_link_sensors)
|
|
362
|
+
return highlighted_nodes, highlighted_links
|
|
491
363
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
values_min_max: List = None) -> List:
|
|
364
|
+
def add_labels(self, components: list or tuple = () or str,
|
|
365
|
+
font_size: int = 8):
|
|
495
366
|
"""
|
|
496
|
-
|
|
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.
|
|
367
|
+
Adds labels to hydraulic components according to the specified
|
|
368
|
+
components.
|
|
502
369
|
|
|
503
370
|
Parameters
|
|
504
371
|
----------
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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
|
-
|
|
372
|
+
components : `str` or `list` or `tuple`, default is ()
|
|
373
|
+
Can either be 'all': all components, 'sensor_config': all nodes and
|
|
374
|
+
pipes which have a sensor attached, or a list of the component
|
|
375
|
+
names that are to be labeled: nodes, tanks, reservoirs, pipes,
|
|
376
|
+
valves, pumps. If the list is empty, all nodes are labeled.
|
|
377
|
+
font_size : `int`, default is 8
|
|
378
|
+
Font size of the labels.
|
|
521
379
|
"""
|
|
522
|
-
|
|
523
|
-
if
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
380
|
+
sc_nodes, sc_links = None, None
|
|
381
|
+
if components == 'all':
|
|
382
|
+
components = ['nodes', 'tanks', 'reservoirs', 'pipes', 'valves',
|
|
383
|
+
'pumps']
|
|
384
|
+
elif components == 'sensor_config':
|
|
385
|
+
components = ['nodes', 'tanks', 'reservoirs', 'pipes', 'valves',
|
|
386
|
+
'pumps']
|
|
387
|
+
sc_nodes, sc_links = self._get_sensor_config_nodes_and_links()
|
|
388
|
+
|
|
389
|
+
elif len(components) == 0:
|
|
390
|
+
components = ['nodes']
|
|
391
|
+
|
|
392
|
+
component_mapping = {
|
|
393
|
+
'nodes': self.junction_parameters,
|
|
394
|
+
'tanks': self.tank_parameters,
|
|
395
|
+
'reservoirs': self.reservoir_parameters,
|
|
396
|
+
'valves': self.valve_parameters,
|
|
397
|
+
'pumps': self.pump_parameters
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
for component, parameters in component_mapping.items():
|
|
401
|
+
if component in components:
|
|
402
|
+
labels = {n: str(n) for n in parameters.nodelist if
|
|
403
|
+
sc_nodes is None or n in sc_nodes}
|
|
404
|
+
self.labels[component] = {'pos': parameters.pos,
|
|
405
|
+
'labels': labels,
|
|
406
|
+
'font_size': font_size}
|
|
407
|
+
if component in ['pumps', 'valves']:
|
|
408
|
+
self.labels[component]['verticalalignment'] = 'bottom'
|
|
409
|
+
if 'pipes' in components:
|
|
410
|
+
labels = {tuple(n[1]): n[0] for n in self.topology.get_all_links()
|
|
411
|
+
if sc_links is None or n[0] in sc_links}
|
|
412
|
+
self.labels['pipes'] = {'pos': self.pipe_parameters.pos,
|
|
413
|
+
'edge_labels': labels,
|
|
414
|
+
'font_size': font_size}
|
|
534
415
|
|
|
535
416
|
def show_animation(self, export_to_file: str = None,
|
|
536
|
-
return_animation: bool = False
|
|
417
|
+
return_animation: bool = False, duration: int = 5,
|
|
418
|
+
fps: int = 15, interpolate: bool = True) \
|
|
537
419
|
-> Optional[FuncAnimation]:
|
|
538
420
|
"""
|
|
539
421
|
Displays, exports, or returns an animation of a water distribution
|
|
@@ -551,37 +433,62 @@ class ScenarioVisualizer:
|
|
|
551
433
|
return_animation : `bool`, optional
|
|
552
434
|
If `True`, the animation object is returned. If `False`, the
|
|
553
435
|
animation will be shown, but not returned. Default is `False`.
|
|
436
|
+
duration : `int`, default is 5
|
|
437
|
+
Duration of the animation in seconds.
|
|
438
|
+
fps : `int`, default is 15
|
|
439
|
+
Frames per seconds, is achieved through interpolation.
|
|
440
|
+
interpolate : `bool`, default is True
|
|
441
|
+
Whether to allow interpolating the sensor values or not. Necessary
|
|
442
|
+
for fixed fps.
|
|
554
443
|
|
|
555
444
|
Returns
|
|
556
445
|
-------
|
|
557
446
|
anim : :class:`~FuncAnimation` or None
|
|
558
447
|
Returns the animation object if `return_animation` is `True`.
|
|
559
448
|
Otherwise, returns `None`.
|
|
560
|
-
|
|
561
449
|
"""
|
|
562
450
|
self.fig = plt.figure(figsize=(6.4, 4.8), dpi=200)
|
|
563
451
|
|
|
564
452
|
total_frames = float('inf')
|
|
565
|
-
for
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
453
|
+
for node_source in [self.junction_parameters, self.tank_parameters,
|
|
454
|
+
self.reservoir_parameters, self.valve_parameters,
|
|
455
|
+
self.pump_parameters]:
|
|
456
|
+
if not isinstance(node_source.node_color, str) and len(
|
|
457
|
+
node_source.node_color) > 1:
|
|
458
|
+
total_frames = min(total_frames, len(node_source.node_color))
|
|
459
|
+
if hasattr(self.pipe_parameters, 'edge_color'):
|
|
460
|
+
if not isinstance(self.pipe_parameters.edge_color, str) and len(
|
|
461
|
+
self.pipe_parameters.edge_color) > 1:
|
|
462
|
+
total_frames = min(total_frames,
|
|
463
|
+
len(self.pipe_parameters.edge_color))
|
|
464
|
+
if hasattr(self.pipe_parameters, 'width'):
|
|
465
|
+
if not isinstance(self.pipe_parameters.width, str) and len(
|
|
466
|
+
self.pipe_parameters.width) > 1:
|
|
467
|
+
total_frames = min(total_frames,
|
|
468
|
+
len(self.pipe_parameters.width))
|
|
469
|
+
|
|
470
|
+
if total_frames == 0 or total_frames == float('inf'):
|
|
569
471
|
raise RuntimeError("The color or resize functions must be called "
|
|
570
|
-
"with a time_step range (pit) to enable "
|
|
472
|
+
"with a time_step range (pit) > 1 to enable "
|
|
571
473
|
"animations")
|
|
572
474
|
|
|
573
|
-
|
|
574
|
-
|
|
475
|
+
if interpolate:
|
|
476
|
+
total_frames = self._interpolate_frames(fps * duration)
|
|
477
|
+
|
|
478
|
+
anim = FuncAnimation(self.fig, self._get_next_frame,
|
|
479
|
+
frames=total_frames,
|
|
480
|
+
interval=round(duration * 100 / total_frames))
|
|
575
481
|
|
|
576
482
|
if export_to_file is not None:
|
|
577
|
-
anim.save(export_to_file, writer='ffmpeg', fps=
|
|
483
|
+
anim.save(export_to_file, writer='ffmpeg', fps=fps)
|
|
578
484
|
if return_animation:
|
|
579
485
|
plt.close(self.fig)
|
|
580
486
|
return anim
|
|
581
487
|
plt.show()
|
|
582
488
|
return None
|
|
583
489
|
|
|
584
|
-
def show_plot(self, export_to_file: str = None
|
|
490
|
+
def show_plot(self, export_to_file: str = None,
|
|
491
|
+
suppress_plot: bool = False) -> None:
|
|
585
492
|
"""
|
|
586
493
|
Displays a static plot of the water distribution network.
|
|
587
494
|
|
|
@@ -594,46 +501,29 @@ class ScenarioVisualizer:
|
|
|
594
501
|
export_to_file : `str`, optional
|
|
595
502
|
The file path where the plot should be saved, if provided.
|
|
596
503
|
Default is `None`.
|
|
597
|
-
|
|
504
|
+
suppress_plot : `bool`, default is False
|
|
505
|
+
If true, no plot is displayed after running this method.
|
|
598
506
|
"""
|
|
599
507
|
self.fig = plt.figure(figsize=(6.4, 4.8), dpi=200)
|
|
600
|
-
self.
|
|
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)
|
|
508
|
+
self._get_next_frame(0)
|
|
623
509
|
|
|
624
510
|
if export_to_file is not None:
|
|
625
511
|
plt.savefig(export_to_file, transparent=True, bbox_inches='tight',
|
|
626
512
|
dpi=200)
|
|
627
|
-
|
|
513
|
+
if not suppress_plot:
|
|
514
|
+
plt.show()
|
|
515
|
+
else:
|
|
516
|
+
plt.close(self.fig)
|
|
517
|
+
plt.clf()
|
|
628
518
|
|
|
629
519
|
def color_nodes(
|
|
630
|
-
self,
|
|
520
|
+
self, data: Optional[Union[ScadaData, np.ndarray]] = None,
|
|
631
521
|
parameter: str = 'pressure', statistic: str = 'mean',
|
|
632
|
-
pit: Optional[Union[int, Tuple[int]]] = None,
|
|
522
|
+
pit: Optional[Union[int, Tuple[int, int]]] = None,
|
|
633
523
|
colormap: str = 'viridis',
|
|
634
524
|
intervals: Optional[Union[int, List[Union[int, float]]]] = None,
|
|
635
|
-
conversion: Optional[dict] = None,
|
|
636
|
-
None:
|
|
525
|
+
conversion: Optional[dict] = None,
|
|
526
|
+
show_colorbar: bool = False) -> None:
|
|
637
527
|
"""
|
|
638
528
|
Colors the nodes (junctions) in the water distribution network based on
|
|
639
529
|
the SCADA data and the specified parameters.
|
|
@@ -644,9 +534,11 @@ class ScenarioVisualizer:
|
|
|
644
534
|
|
|
645
535
|
Parameters
|
|
646
536
|
----------
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
537
|
+
data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` or
|
|
538
|
+
`numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, optional
|
|
539
|
+
The SCADA data object containing node data or a numpy array of the
|
|
540
|
+
shape nodes*timesteps. If `None`, a simulation is run to generate
|
|
541
|
+
SCADA data. Default is `None`.
|
|
650
542
|
parameter : `str`, optional
|
|
651
543
|
The node data to visualize. Must be 'pressure', 'demand', or
|
|
652
544
|
'node_quality'. Default is 'pressure'.
|
|
@@ -681,11 +573,10 @@ class ScenarioVisualizer:
|
|
|
681
573
|
'time_step' statistic.
|
|
682
574
|
|
|
683
575
|
"""
|
|
576
|
+
self.junction_parameters.cmap = colormap
|
|
684
577
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
if scada_data:
|
|
688
|
-
self.scada_data = scada_data
|
|
578
|
+
if data is not None:
|
|
579
|
+
self.scada_data = data
|
|
689
580
|
elif not self.scada_data:
|
|
690
581
|
self.scada_data = self.__scenario.run_simulation()
|
|
691
582
|
|
|
@@ -698,59 +589,49 @@ class ScenarioVisualizer:
|
|
|
698
589
|
values = self.scada_data.demand_data_raw
|
|
699
590
|
elif parameter == 'node_quality':
|
|
700
591
|
values = self.scada_data.node_quality_data_raw
|
|
592
|
+
elif parameter == 'custom_data':
|
|
593
|
+
# Custom should have the dimensions (timesteps, nodes)
|
|
594
|
+
values = self.scada_data
|
|
701
595
|
else:
|
|
702
596
|
raise ValueError(
|
|
703
|
-
'Parameter must be pressure, demand or
|
|
597
|
+
'Parameter must be pressure, demand, node_quality or custom_'
|
|
598
|
+
'data.')
|
|
704
599
|
|
|
705
600
|
if statistic == 'time_step' and isinstance(pit, tuple) and len(
|
|
706
601
|
pit) == 2 and all(isinstance(i, int) for i in pit):
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
self.animation_dict['junctions'] = []
|
|
712
|
-
vmin, vmax = min(sorted_values), max(sorted_values)
|
|
713
|
-
for frame in range(*pit):
|
|
602
|
+
rng = pit
|
|
603
|
+
if pit[1] == -1:
|
|
604
|
+
rng = (pit[0], values.shape[0])
|
|
605
|
+
for frame in range(*rng):
|
|
714
606
|
if frame > values.shape[0] - 1:
|
|
715
607
|
break
|
|
716
|
-
|
|
717
|
-
|
|
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
|
|
608
|
+
self.junction_parameters.add_frame(statistic, values, frame,
|
|
609
|
+
intervals)
|
|
725
610
|
else:
|
|
726
|
-
|
|
727
|
-
|
|
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)})
|
|
611
|
+
self.junction_parameters.add_frame(statistic, values, pit,
|
|
612
|
+
intervals)
|
|
733
613
|
|
|
734
614
|
if show_colorbar:
|
|
735
615
|
if statistic == 'time_step':
|
|
736
616
|
label = str(parameter).capitalize() + ' at timestep ' + str(
|
|
737
617
|
pit)
|
|
738
618
|
else:
|
|
739
|
-
label = str(statistic).capitalize() + ' ' + str(
|
|
619
|
+
label = str(statistic).capitalize() + ' ' + str(
|
|
620
|
+
parameter).replace('_', ' ')
|
|
740
621
|
self.colorbars['junctions'] = {'mappable': plt.cm.ScalarMappable(
|
|
741
622
|
norm=mpl.colors.Normalize(
|
|
742
|
-
vmin=self.junction_parameters
|
|
743
|
-
vmax=self.junction_parameters
|
|
623
|
+
vmin=self.junction_parameters.vmin,
|
|
624
|
+
vmax=self.junction_parameters.vmin), cmap=colormap),
|
|
744
625
|
'label': label}
|
|
745
626
|
|
|
746
627
|
def color_links(
|
|
747
|
-
self,
|
|
628
|
+
self, data: Optional[Union[ScadaData, np.ndarray]] = None,
|
|
748
629
|
parameter: str = 'flow_rate', statistic: str = 'mean',
|
|
749
|
-
pit: Optional[Union[int, Tuple[int]]] = None,
|
|
630
|
+
pit: Optional[Union[int, Tuple[int, int]]] = None,
|
|
750
631
|
colormap: str = 'coolwarm',
|
|
751
632
|
intervals: Optional[Union[int, List[Union[int, float]]]] = None,
|
|
752
|
-
conversion: Optional[dict] = None,
|
|
753
|
-
None:
|
|
633
|
+
conversion: Optional[dict] = None,
|
|
634
|
+
show_colorbar: bool = False) -> None:
|
|
754
635
|
"""
|
|
755
636
|
Colors the links (pipes) in the water distribution network based on the
|
|
756
637
|
SCADA data and the specified parameters.
|
|
@@ -761,9 +642,11 @@ class ScenarioVisualizer:
|
|
|
761
642
|
|
|
762
643
|
Parameters
|
|
763
644
|
----------
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
645
|
+
data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` or
|
|
646
|
+
`numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, optional
|
|
647
|
+
The SCADA data object containing link data or a numpy array of the
|
|
648
|
+
shape links*timesteps. If `None`, a simulation is run to generate
|
|
649
|
+
SCADA data. Default is `None`.
|
|
767
650
|
parameter : `str`, optional
|
|
768
651
|
The link data to visualize. Options are 'flow_rate', 'velocity', or
|
|
769
652
|
'status'. Default is 'flow_rate'.
|
|
@@ -774,7 +657,7 @@ class ScenarioVisualizer:
|
|
|
774
657
|
The point in time or range of time steps for the 'time_step'
|
|
775
658
|
statistic. If a tuple is provided, it should contain two integers
|
|
776
659
|
representing the start and end time steps. A tuple is necessary to
|
|
777
|
-
process the data for the :
|
|
660
|
+
process the data for the :func:`~ScenarioVisualizer.show_animation`
|
|
778
661
|
method. Default is `None`.
|
|
779
662
|
colormap : `str`, optional
|
|
780
663
|
The colormap to use for visualizing link values. Default is
|
|
@@ -797,40 +680,38 @@ class ScenarioVisualizer:
|
|
|
797
680
|
incorrectly provided for the 'time_step' statistic.
|
|
798
681
|
|
|
799
682
|
"""
|
|
683
|
+
sim_length = None
|
|
684
|
+
|
|
685
|
+
if data is not None:
|
|
686
|
+
self.scada_data = data
|
|
687
|
+
if not isinstance(self.scada_data, ScadaData):
|
|
688
|
+
sim_length = self.scada_data.shape[0]
|
|
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
|
+
self.pipe_parameters.edge_cmap = mpl.colormaps[colormap]
|
|
696
|
+
|
|
697
|
+
if sim_length is None:
|
|
698
|
+
sim_length = self.scada_data.sensor_readings_time.shape[0]
|
|
800
699
|
|
|
801
700
|
if statistic == 'time_step' and isinstance(pit, tuple) and len(
|
|
802
701
|
pit) == 2 and all(isinstance(i, int) for i in pit):
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
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:
|
|
702
|
+
rng = pit
|
|
703
|
+
if pit[1] == -1:
|
|
704
|
+
rng = (pit[0], sim_length)
|
|
705
|
+
for frame in range(*rng):
|
|
706
|
+
if frame >= sim_length:
|
|
817
707
|
break
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
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
|
|
708
|
+
self.pipe_parameters.add_frame(self.topology, 'edge_color',
|
|
709
|
+
self.scada_data, parameter,
|
|
710
|
+
statistic, frame, intervals)
|
|
826
711
|
else:
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
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)})
|
|
712
|
+
self.pipe_parameters.add_frame(self.topology, 'edge_color',
|
|
713
|
+
self.scada_data, parameter,
|
|
714
|
+
statistic, pit, intervals)
|
|
834
715
|
|
|
835
716
|
if show_colorbar:
|
|
836
717
|
if statistic == 'time_step':
|
|
@@ -841,12 +722,12 @@ class ScenarioVisualizer:
|
|
|
841
722
|
parameter).replace('_', ' ')
|
|
842
723
|
self.colorbars['pipes'] = {'mappable': plt.cm.ScalarMappable(
|
|
843
724
|
norm=mpl.colors.Normalize(
|
|
844
|
-
vmin=self.pipe_parameters
|
|
845
|
-
vmax=self.pipe_parameters
|
|
725
|
+
vmin=self.pipe_parameters.edge_vmin,
|
|
726
|
+
vmax=self.pipe_parameters.edge_vmax), cmap=colormap),
|
|
846
727
|
'label': label}
|
|
847
728
|
|
|
848
729
|
def color_pumps(
|
|
849
|
-
self,
|
|
730
|
+
self, data: Optional[Union[ScadaData, np.ndarray]] = None,
|
|
850
731
|
parameter: str = 'efficiency', statistic: str = 'mean',
|
|
851
732
|
pit: Optional[Union[int, Tuple[int]]] = None,
|
|
852
733
|
intervals: Optional[Union[int, List[Union[int, float]]]] = None,
|
|
@@ -861,9 +742,11 @@ class ScenarioVisualizer:
|
|
|
861
742
|
|
|
862
743
|
Parameters
|
|
863
744
|
----------
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
745
|
+
data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` or
|
|
746
|
+
`numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, optional
|
|
747
|
+
The SCADA data object containing pump data or a numpy array of the
|
|
748
|
+
shape pumps*timesteps. If `None`, a simulation is run to generate
|
|
749
|
+
SCADA data. Default is `None`.
|
|
867
750
|
parameter : `str`, optional
|
|
868
751
|
The pump data to visualize. Must be 'efficiency',
|
|
869
752
|
'energy_consumption', or 'state'. Default is 'efficiency'.
|
|
@@ -896,10 +779,10 @@ class ScenarioVisualizer:
|
|
|
896
779
|
|
|
897
780
|
"""
|
|
898
781
|
|
|
899
|
-
self.pump_parameters.
|
|
782
|
+
self.pump_parameters.cmap = colormap
|
|
900
783
|
|
|
901
|
-
if
|
|
902
|
-
self.scada_data =
|
|
784
|
+
if data is not None:
|
|
785
|
+
self.scada_data = data
|
|
903
786
|
elif not self.scada_data:
|
|
904
787
|
self.scada_data = self.__scenario.run_simulation()
|
|
905
788
|
|
|
@@ -909,39 +792,24 @@ class ScenarioVisualizer:
|
|
|
909
792
|
values = self.scada_data.pumps_energyconsumption_data_raw
|
|
910
793
|
elif parameter == 'state':
|
|
911
794
|
values = self.scada_data.pumps_state_data_raw
|
|
795
|
+
elif parameter == 'custom_data':
|
|
796
|
+
values = self.scada_data
|
|
912
797
|
else:
|
|
913
798
|
raise ValueError(
|
|
914
|
-
'Parameter must be efficiency, energy_consumption or
|
|
799
|
+
'Parameter must be efficiency, energy_consumption, state or custom_data')
|
|
915
800
|
|
|
916
801
|
if statistic == 'time_step' and isinstance(pit, tuple) and len(
|
|
917
802
|
pit) == 2 and all(isinstance(i, int) for i in pit):
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
self.animation_dict['pumps'] = []
|
|
923
|
-
vmin = min(sorted_values)
|
|
924
|
-
vmax = max(sorted_values)
|
|
925
|
-
for frame in range(*pit):
|
|
803
|
+
rng = pit
|
|
804
|
+
if pit[1] == -1:
|
|
805
|
+
rng = (pit[0], values.shape[0])
|
|
806
|
+
for frame in range(*rng):
|
|
926
807
|
if frame > values.shape[0] - 1:
|
|
927
808
|
break
|
|
928
|
-
|
|
929
|
-
|
|
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
|
|
809
|
+
self.pump_parameters.add_frame(statistic, values, frame,
|
|
810
|
+
intervals)
|
|
937
811
|
else:
|
|
938
|
-
|
|
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)})
|
|
812
|
+
self.pump_parameters.add_frame(statistic, values, pit, intervals)
|
|
945
813
|
|
|
946
814
|
if show_colorbar:
|
|
947
815
|
if statistic == 'time_step':
|
|
@@ -951,14 +819,14 @@ class ScenarioVisualizer:
|
|
|
951
819
|
label = str(statistic).capitalize() + ' ' + str(
|
|
952
820
|
parameter).replace('_', ' ')
|
|
953
821
|
self.colorbars['pumps'] = {'mappable': plt.cm.ScalarMappable(
|
|
954
|
-
norm=mpl.colors.Normalize(vmin=self.pump_parameters
|
|
955
|
-
vmax=self.pump_parameters
|
|
822
|
+
norm=mpl.colors.Normalize(vmin=self.pump_parameters.vmin,
|
|
823
|
+
vmax=self.pump_parameters.vmax),
|
|
956
824
|
cmap=colormap), 'label': label}
|
|
957
825
|
|
|
958
826
|
def color_tanks(
|
|
959
|
-
self,
|
|
827
|
+
self, data: Optional[Union[ScadaData, np.ndarray]] = None,
|
|
960
828
|
statistic: str = 'mean',
|
|
961
|
-
pit: Optional[Union[int, Tuple[int]]] = None,
|
|
829
|
+
pit: Optional[Union[int, Tuple[int, int]]] = None,
|
|
962
830
|
intervals: Optional[Union[int, List[Union[int, float]]]] = None,
|
|
963
831
|
colormap: str = 'viridis', show_colorbar: bool = False) -> None:
|
|
964
832
|
"""
|
|
@@ -971,10 +839,11 @@ class ScenarioVisualizer:
|
|
|
971
839
|
|
|
972
840
|
Parameters
|
|
973
841
|
----------
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
842
|
+
data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` or
|
|
843
|
+
`numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, optional
|
|
844
|
+
The SCADA data object containing tank data or a numpy array of the
|
|
845
|
+
shape tanks*timesteps. If `None`, a simulation is run to generate
|
|
846
|
+
SCADA data. Default is `None`.
|
|
978
847
|
statistic : `str`, optional
|
|
979
848
|
The statistic to calculate for the data. Can be 'mean', 'min',
|
|
980
849
|
'max', or 'time_step'. Default is 'mean'.
|
|
@@ -1001,59 +870,47 @@ class ScenarioVisualizer:
|
|
|
1001
870
|
If `pit` is not correctly provided for the 'time_step' statistic.
|
|
1002
871
|
|
|
1003
872
|
"""
|
|
1004
|
-
self.
|
|
873
|
+
self.tank_parameters.cmap = colormap
|
|
1005
874
|
|
|
1006
|
-
if
|
|
1007
|
-
self.scada_data =
|
|
875
|
+
if data is not None:
|
|
876
|
+
self.scada_data = data
|
|
1008
877
|
elif not self.scada_data:
|
|
1009
878
|
self.scada_data = self.__scenario.run_simulation()
|
|
1010
879
|
|
|
1011
|
-
|
|
880
|
+
if isinstance(self.scada_data, ScadaData):
|
|
881
|
+
values = self.scada_data.tanks_volume_data_raw
|
|
882
|
+
parameter = 'tank volume'
|
|
883
|
+
else:
|
|
884
|
+
values = self.scada_data
|
|
885
|
+
parameter = 'custom data'
|
|
1012
886
|
|
|
1013
887
|
if statistic == 'time_step' and isinstance(pit, tuple) and len(
|
|
1014
888
|
pit) == 2 and all(isinstance(i, int) for i in pit):
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
self.animation_dict['tanks'] = []
|
|
1020
|
-
vmin = min(sorted_values)
|
|
1021
|
-
vmax = max(sorted_values)
|
|
1022
|
-
for frame in range(*pit):
|
|
889
|
+
rng = pit
|
|
890
|
+
if pit[1] == -1:
|
|
891
|
+
rng = (pit[0], values.shape[0])
|
|
892
|
+
for frame in range(*rng):
|
|
1023
893
|
if frame > values.shape[0] - 1:
|
|
1024
894
|
break
|
|
1025
|
-
|
|
1026
|
-
|
|
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
|
|
895
|
+
self.tank_parameters.add_frame(statistic, values, frame,
|
|
896
|
+
intervals)
|
|
1034
897
|
else:
|
|
1035
|
-
|
|
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)})
|
|
898
|
+
self.tank_parameters.add_frame(statistic, values, pit, intervals)
|
|
1042
899
|
|
|
1043
900
|
if show_colorbar:
|
|
1044
901
|
if statistic == 'time_step':
|
|
1045
|
-
label =
|
|
902
|
+
label = parameter.capitalize() + ' at timestep ' + str(pit)
|
|
1046
903
|
else:
|
|
1047
|
-
label = str(statistic).capitalize() + ' ' +
|
|
904
|
+
label = str(statistic).capitalize() + ' ' + parameter
|
|
1048
905
|
self.colorbars['tanks'] = {'mappable': plt.cm.ScalarMappable(
|
|
1049
|
-
norm=mpl.colors.Normalize(vmin=self.tank_parameters
|
|
1050
|
-
vmax=self.tank_parameters
|
|
906
|
+
norm=mpl.colors.Normalize(vmin=self.tank_parameters.vmin,
|
|
907
|
+
vmax=self.tank_parameters.vmin),
|
|
1051
908
|
cmap=colormap), 'label': label}
|
|
1052
909
|
|
|
1053
910
|
def color_valves(
|
|
1054
|
-
self,
|
|
911
|
+
self, data: Optional[Union[ScadaData, np.ndarray]] = None,
|
|
1055
912
|
statistic: str = 'mean',
|
|
1056
|
-
pit: Optional[Union[int, Tuple[int]]] = None,
|
|
913
|
+
pit: Optional[Union[int, Tuple[int, int]]] = None,
|
|
1057
914
|
intervals: Optional[Union[int, List[Union[int, float]]]] = None,
|
|
1058
915
|
colormap: str = 'viridis', show_colorbar: bool = False) -> None:
|
|
1059
916
|
"""
|
|
@@ -1066,9 +923,11 @@ class ScenarioVisualizer:
|
|
|
1066
923
|
|
|
1067
924
|
Parameters
|
|
1068
925
|
----------
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
926
|
+
data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` or
|
|
927
|
+
`numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, optional
|
|
928
|
+
The SCADA data object containing valve data or a numpy array of the
|
|
929
|
+
shape valves*timesteps. If `None`, a simulation is run to generate
|
|
930
|
+
SCADA data. Default is `None`.
|
|
1072
931
|
statistic : `str`, optional
|
|
1073
932
|
The statistic to calculate for the data. Can be 'mean', 'min',
|
|
1074
933
|
'max', or 'time_step'. Default is 'mean'.
|
|
@@ -1096,60 +955,49 @@ class ScenarioVisualizer:
|
|
|
1096
955
|
|
|
1097
956
|
"""
|
|
1098
957
|
|
|
1099
|
-
self.valve_parameters.
|
|
958
|
+
self.valve_parameters.cmap = colormap
|
|
1100
959
|
|
|
1101
|
-
if
|
|
1102
|
-
self.scada_data =
|
|
960
|
+
if data is not None:
|
|
961
|
+
self.scada_data = data
|
|
1103
962
|
elif not self.scada_data:
|
|
1104
963
|
self.scada_data = self.__scenario.run_simulation()
|
|
1105
964
|
|
|
1106
|
-
|
|
965
|
+
if isinstance(self.scada_data, ScadaData):
|
|
966
|
+
values = self.scada_data.valves_state_data_raw
|
|
967
|
+
parameter = 'valve state'
|
|
968
|
+
else:
|
|
969
|
+
values = self.scada_data
|
|
970
|
+
parameter = 'custom data'
|
|
1107
971
|
|
|
1108
972
|
if statistic == 'time_step' and isinstance(pit, tuple) and len(
|
|
1109
973
|
pit) == 2 and all(isinstance(i, int) for i in pit):
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
self.animation_dict['valves'] = []
|
|
1115
|
-
vmin = min(sorted_values)
|
|
1116
|
-
vmax = max(sorted_values)
|
|
1117
|
-
for frame in range(*pit):
|
|
974
|
+
rng = pit
|
|
975
|
+
if pit[1] == -1:
|
|
976
|
+
rng = (pit[0], values.shape[0])
|
|
977
|
+
for frame in range(*rng):
|
|
1118
978
|
if frame > values.shape[0] - 1:
|
|
1119
979
|
break
|
|
1120
|
-
|
|
1121
|
-
|
|
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
|
|
980
|
+
self.valve_parameters.add_frame(statistic, values, frame,
|
|
981
|
+
intervals)
|
|
1129
982
|
else:
|
|
1130
|
-
|
|
1131
|
-
|
|
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)})
|
|
983
|
+
self.valve_parameters.add_frame(statistic, values, pit,
|
|
984
|
+
intervals)
|
|
1137
985
|
|
|
1138
986
|
if show_colorbar:
|
|
1139
987
|
if statistic == 'time_step':
|
|
1140
|
-
label =
|
|
988
|
+
label = parameter.capitalize() + ' at timestep ' + str(pit)
|
|
1141
989
|
else:
|
|
1142
990
|
label = str(statistic).capitalize() + ' ' + 'valve state'
|
|
1143
991
|
self.colorbars['valves'] = {'mappable': plt.cm.ScalarMappable(
|
|
1144
|
-
norm=mpl.colors.Normalize(vmin=self.valve_parameters
|
|
1145
|
-
vmax=self.valve_parameters
|
|
992
|
+
norm=mpl.colors.Normalize(vmin=self.valve_parameters.vmin,
|
|
993
|
+
vmax=self.valve_parameters.vmax),
|
|
1146
994
|
cmap=colormap), 'label': label}
|
|
1147
995
|
|
|
1148
996
|
def resize_links(
|
|
1149
|
-
self,
|
|
997
|
+
self, data: Optional[Union[ScadaData, np.ndarray]] = None,
|
|
1150
998
|
parameter: str = 'flow_rate', statistic: str = 'mean',
|
|
1151
|
-
line_widths: Tuple[int] = (1, 2),
|
|
1152
|
-
pit: Optional[Union[int, Tuple[int]]] = None,
|
|
999
|
+
line_widths: Tuple[int, int] = (1, 2),
|
|
1000
|
+
pit: Optional[Union[int, Tuple[int, int]]] = None,
|
|
1153
1001
|
intervals: Optional[Union[int, List[Union[int, float]]]] = None,
|
|
1154
1002
|
conversion: Optional[dict] = None) -> None:
|
|
1155
1003
|
"""
|
|
@@ -1162,9 +1010,11 @@ class ScenarioVisualizer:
|
|
|
1162
1010
|
|
|
1163
1011
|
Parameters
|
|
1164
1012
|
----------
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1013
|
+
data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` or
|
|
1014
|
+
`numpy.ndarray <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html>`_, optional
|
|
1015
|
+
The SCADA data object containing link data or a numpy array of the
|
|
1016
|
+
shape links*timesteps. If `None`, a simulation is run to generate
|
|
1017
|
+
SCADA data. Default is `None`.
|
|
1168
1018
|
parameter : `str`, optional
|
|
1169
1019
|
The data used to resize to. Default is 'flow_rate'.
|
|
1170
1020
|
statistic : `str`, optional
|
|
@@ -1187,37 +1037,37 @@ class ScenarioVisualizer:
|
|
|
1187
1037
|
A dictionary of conversion parameters to convert SCADA data units.
|
|
1188
1038
|
Default is `None`.
|
|
1189
1039
|
"""
|
|
1040
|
+
sim_length = None
|
|
1041
|
+
|
|
1042
|
+
if data is not None:
|
|
1043
|
+
self.scada_data = data
|
|
1044
|
+
if not isinstance(self.scada_data, ScadaData):
|
|
1045
|
+
sim_length = self.scada_data.shape[0]
|
|
1046
|
+
elif not self.scada_data:
|
|
1047
|
+
self.scada_data = self.__scenario.run_simulation()
|
|
1048
|
+
|
|
1049
|
+
if conversion:
|
|
1050
|
+
self.scada_data = self.scada_data.convert_units(**conversion)
|
|
1051
|
+
|
|
1052
|
+
if sim_length is None:
|
|
1053
|
+
sim_length = self.scada_data.sensor_readings_time.shape[0]
|
|
1190
1054
|
|
|
1191
1055
|
if statistic == 'time_step' and isinstance(pit, tuple) and len(
|
|
1192
1056
|
pit) == 2 and all(isinstance(i, int) for i in pit):
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
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:
|
|
1057
|
+
rng = pit
|
|
1058
|
+
if pit[1] == -1:
|
|
1059
|
+
rng = (pit[0], sim_length)
|
|
1060
|
+
for frame in range(*rng):
|
|
1061
|
+
if frame >= sim_length:
|
|
1203
1062
|
break
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
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)))
|
|
1063
|
+
self.pipe_parameters.add_frame(self.topology, 'edge_width',
|
|
1064
|
+
self.scada_data, parameter,
|
|
1065
|
+
statistic, frame, intervals)
|
|
1215
1066
|
else:
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
{'width': self.__rescale(sorted_values, line_widths)})
|
|
1067
|
+
self.pipe_parameters.add_frame(self.topology, 'edge_width',
|
|
1068
|
+
self.scada_data, parameter,
|
|
1069
|
+
statistic, pit, intervals)
|
|
1070
|
+
self.pipe_parameters.rescale_widths(line_widths)
|
|
1221
1071
|
|
|
1222
1072
|
def hide_nodes(self) -> None:
|
|
1223
1073
|
"""
|
|
@@ -1225,10 +1075,10 @@ class ScenarioVisualizer:
|
|
|
1225
1075
|
visualization.
|
|
1226
1076
|
|
|
1227
1077
|
This method clears the node list from the `junction_parameters`
|
|
1228
|
-
|
|
1078
|
+
class, effectively removing all nodes from view in the current
|
|
1229
1079
|
visualization.
|
|
1230
1080
|
"""
|
|
1231
|
-
self.junction_parameters
|
|
1081
|
+
self.junction_parameters.nodelist = []
|
|
1232
1082
|
|
|
1233
1083
|
def highlight_sensor_config(self) -> None:
|
|
1234
1084
|
"""
|
|
@@ -1236,30 +1086,23 @@ class ScenarioVisualizer:
|
|
|
1236
1086
|
the water distribution network visualization.
|
|
1237
1087
|
|
|
1238
1088
|
This method identifies nodes and links equipped with different types of
|
|
1239
|
-
sensors from the
|
|
1089
|
+
sensors from the
|
|
1090
|
+
:class:`~epyt_flow.simulation.sensor_config.SensorConfig` and
|
|
1240
1091
|
updates their visual appearance. Nodes with sensors are highlighted
|
|
1241
1092
|
with an orange border, while links with sensors are displayed with a
|
|
1242
1093
|
dashed line style.
|
|
1243
1094
|
"""
|
|
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)
|
|
1095
|
+
highlighted_nodes, highlighted_links = self._get_sensor_config_nodes_and_links()
|
|
1253
1096
|
|
|
1254
1097
|
node_edges = [
|
|
1255
1098
|
(17, 163, 252) if node in highlighted_nodes else (0, 0, 0) for node
|
|
1256
1099
|
in self.topology]
|
|
1257
|
-
pipe_style = ['dashed' if link in highlighted_links else 'solid' for
|
|
1258
|
-
link in self.topology]
|
|
1100
|
+
pipe_style = ['dashed' if link[0] in highlighted_links else 'solid' for
|
|
1101
|
+
link in self.topology.get_all_links()]
|
|
1259
1102
|
|
|
1260
|
-
self.junction_parameters.
|
|
1103
|
+
self.junction_parameters.add_attributes(
|
|
1261
1104
|
{'linewidths': 1, 'edgecolors': node_edges})
|
|
1262
|
-
self.pipe_parameters.
|
|
1105
|
+
self.pipe_parameters.add_attributes({'style': pipe_style})
|
|
1263
1106
|
|
|
1264
1107
|
@deprecated(reason="This function will be removed in feature versions, "
|
|
1265
1108
|
"please use show_plot() instead.")
|