epyt-flow 0.11.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.
Files changed (30) hide show
  1. epyt_flow/VERSION +1 -1
  2. epyt_flow/data/benchmarks/gecco_water_quality.py +2 -2
  3. epyt_flow/data/benchmarks/leakdb.py +40 -5
  4. epyt_flow/data/benchmarks/water_usage.py +4 -3
  5. epyt_flow/gym/__init__.py +0 -3
  6. epyt_flow/gym/scenario_control_env.py +3 -10
  7. epyt_flow/rest_api/scenario/control_handlers.py +118 -0
  8. epyt_flow/rest_api/scenario/event_handlers.py +114 -1
  9. epyt_flow/rest_api/scenario/handlers.py +33 -0
  10. epyt_flow/rest_api/server.py +14 -2
  11. epyt_flow/simulation/backend/__init__.py +1 -0
  12. epyt_flow/simulation/backend/my_epyt.py +1056 -0
  13. epyt_flow/simulation/events/quality_events.py +3 -1
  14. epyt_flow/simulation/scada/scada_data.py +201 -12
  15. epyt_flow/simulation/scenario_simulator.py +142 -59
  16. epyt_flow/topology.py +8 -7
  17. epyt_flow/utils.py +30 -0
  18. epyt_flow/visualization/scenario_visualizer.py +159 -69
  19. epyt_flow/visualization/visualization_utils.py +144 -17
  20. {epyt_flow-0.11.0.dist-info → epyt_flow-0.12.0.dist-info}/METADATA +4 -4
  21. {epyt_flow-0.11.0.dist-info → epyt_flow-0.12.0.dist-info}/RECORD +24 -27
  22. {epyt_flow-0.11.0.dist-info → epyt_flow-0.12.0.dist-info}/WHEEL +1 -1
  23. epyt_flow/gym/control_gyms.py +0 -55
  24. epyt_flow/metrics.py +0 -471
  25. epyt_flow/models/__init__.py +0 -2
  26. epyt_flow/models/event_detector.py +0 -36
  27. epyt_flow/models/sensor_interpolation_detector.py +0 -123
  28. epyt_flow/simulation/scada/advanced_control.py +0 -138
  29. {epyt_flow-0.11.0.dist-info → epyt_flow-0.12.0.dist-info/licenses}/LICENSE +0 -0
  30. {epyt_flow-0.11.0.dist-info → epyt_flow-0.12.0.dist-info}/top_level.txt +0 -0
@@ -4,7 +4,6 @@ Module provides a class for visualizing scenarios.
4
4
  from typing import Optional, Union, List, Tuple
5
5
 
6
6
  import numpy as np
7
- from deprecated import deprecated
8
7
 
9
8
  import matplotlib.pyplot as plt
10
9
  from matplotlib.animation import FuncAnimation
@@ -14,7 +13,8 @@ from svgpath2mpl import parse_path
14
13
 
15
14
  from ..simulation.scenario_simulator import ScenarioSimulator
16
15
  from ..simulation.scada.scada_data import ScadaData
17
- from ..visualization import JunctionObject, EdgeObject, ColorScheme, epyt_flow_colors
16
+ from ..visualization import JunctionObject, EdgeObject, ColorScheme, \
17
+ epyt_flow_colors
18
18
 
19
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 '
20
20
  '41.5 42 0 0 0 244 135 A 41.5 42 0 0 0 241.94922 122 L 278 122 '
@@ -220,8 +220,10 @@ class ScenarioVisualizer:
220
220
 
221
221
  self.colorbars = {}
222
222
  self.labels = {}
223
+ self.masks = {}
223
224
 
224
- def _get_midpoints(self, elements: List[str]) -> dict[str, tuple[float, float]]:
225
+ def _get_midpoints(self, elements: List[str]) -> dict[
226
+ str, tuple[float, float]]:
225
227
  """
226
228
  Computes and returns the midpoints for drawing either valves or pumps
227
229
  in a water distribution network.
@@ -298,6 +300,34 @@ class ScenarioVisualizer:
298
300
  ax=self.ax, label='Pumps',
299
301
  **self.pump_parameters.get_frame(frame_number))
300
302
 
303
+ for key, mask in self.masks.items():
304
+ if key == 'nodes':
305
+ nxp.draw_networkx_nodes(self.topology, ax=self.ax,
306
+ **self.junction_parameters.get_frame_mask(
307
+ mask,
308
+ self.color_scheme.node_color))
309
+ if key == 'pumps':
310
+ nxp.draw_networkx_nodes(
311
+ self.topology,
312
+ ax=self.ax,
313
+ **self.pump_parameters.get_frame_mask(mask,
314
+ self.color_scheme.pump_color))
315
+ if key == 'links':
316
+ nxp.draw_networkx_edges(self.topology, ax=self.ax,
317
+ **self.pipe_parameters.get_frame_mask(
318
+ frame_number,
319
+ self.color_scheme.pipe_color))
320
+ if key == 'tanks':
321
+ nxp.draw_networkx_nodes(self.topology, ax=self.ax,
322
+ **self.tank_parameters.get_frame_mask(
323
+ mask,
324
+ self.color_scheme.tank_color))
325
+ if key == 'valves':
326
+ nxp.draw_networkx_nodes(
327
+ self.topology, ax=self.ax,
328
+ **self.valve_parameters.get_frame_mask(mask,
329
+ self.color_scheme.valve_color))
330
+
301
331
  self._draw_labels()
302
332
  self.ax.legend(fontsize=6)
303
333
 
@@ -361,7 +391,7 @@ class ScenarioVisualizer:
361
391
  + sensor_config.quality_link_sensors)
362
392
  return highlighted_nodes, highlighted_links
363
393
 
364
- def add_labels(self, components: list or tuple = () or str,
394
+ def add_labels(self, components: str or list or tuple = (),
365
395
  font_size: int = 8):
366
396
  """
367
397
  Adds labels to hydraulic components according to the specified
@@ -509,7 +539,7 @@ class ScenarioVisualizer:
509
539
 
510
540
  if export_to_file is not None:
511
541
  plt.savefig(export_to_file, transparent=True, bbox_inches='tight',
512
- dpi=200)
542
+ dpi=900)
513
543
  if not suppress_plot:
514
544
  plt.show()
515
545
  else:
@@ -520,10 +550,12 @@ class ScenarioVisualizer:
520
550
  self, data: Optional[Union[ScadaData, np.ndarray]] = None,
521
551
  parameter: str = 'pressure', statistic: str = 'mean',
522
552
  pit: Optional[Union[int, Tuple[int, int]]] = None,
553
+ species: str = None,
523
554
  colormap: str = 'viridis',
524
555
  intervals: Optional[Union[int, List[Union[int, float]]]] = None,
525
556
  conversion: Optional[dict] = None,
526
- show_colorbar: bool = False) -> None:
557
+ show_colorbar: bool = False,
558
+ use_sensor_data: bool = False) -> None:
527
559
  """
528
560
  Colors the nodes (junctions) in the water distribution network based on
529
561
  the SCADA data and the specified parameters.
@@ -551,6 +583,9 @@ class ScenarioVisualizer:
551
583
  representing the start and end time steps. A tuple is necessary to
552
584
  process the data for the :meth:`~ScenarioVisualizer.show_animation`
553
585
  method. Default is `None`.
586
+ species: `str`, optional
587
+ Key of species. Only necessary for parameter
588
+ 'bulk_species_concentration'.
554
589
  colormap : `str`, optional
555
590
  The colormap to use for visualizing node values. Default is
556
591
  'viridis'.
@@ -564,6 +599,11 @@ class ScenarioVisualizer:
564
599
  show_colorbar : `bool`, optional
565
600
  If `True`, a colorbar will be displayed on the plot to indicate the
566
601
  range of node values. Default is `False`.
602
+ use_sensor_data : `bool`, optional
603
+ If `True`, instead of using raw simulation data, the data recorded
604
+ by the corresponding sensors in the system is used for the
605
+ visualization. Note: Not all components may have a sensor attached
606
+ and sensors may be subject to sensor faults or noise.
567
607
 
568
608
  Raises
569
609
  ------
@@ -583,15 +623,45 @@ class ScenarioVisualizer:
583
623
  if conversion:
584
624
  self.scada_data = self.scada_data.convert_units(**conversion)
585
625
 
626
+ # TODO: is there any way to make this look better (e.g. do a mapping somewhere??)
586
627
  if parameter == 'pressure':
587
- values = self.scada_data.pressure_data_raw
628
+ if use_sensor_data:
629
+ values, self.masks[
630
+ 'nodes'] = self.scada_data.get_data_pressures_as_node_features()
631
+ else:
632
+ values = self.scada_data.pressure_data_raw
588
633
  elif parameter == 'demand':
589
- values = self.scada_data.demand_data_raw
634
+ if use_sensor_data:
635
+ values, self.masks[
636
+ 'nodes'] = self.scada_data.get_data_demands_as_node_features()
637
+ else:
638
+ values = self.scada_data.demand_data_raw
590
639
  elif parameter == 'node_quality':
591
- values = self.scada_data.node_quality_data_raw
640
+ if use_sensor_data:
641
+ values, self.masks[
642
+ 'nodes'] = self.scada_data.get_data_nodes_quality_as_node_features()
643
+ else:
644
+ values = self.scada_data.node_quality_data_raw
592
645
  elif parameter == 'custom_data':
593
646
  # Custom should have the dimensions (timesteps, nodes)
594
647
  values = self.scada_data
648
+ elif parameter == 'bulk_species_concentration':
649
+ if not species:
650
+ raise ValueError('Species must be set when using bulk_species_'
651
+ 'concentration.')
652
+ if use_sensor_data:
653
+ values, self.masks[
654
+ 'nodes'] = self.scada_data.get_data_bulk_species_concentrations_as_node_features()
655
+ self.masks['nodes'] = self.masks['nodes'][:,
656
+ self.scada_data.sensor_config.bulk_species.index(
657
+ species)]
658
+ values = values[:, :,
659
+ self.scada_data.sensor_config.bulk_species.index(
660
+ species)]
661
+ else:
662
+ values = self.scada_data.bulk_species_node_concentration_raw[:,
663
+ self.scada_data.sensor_config.bulk_species.index(
664
+ species), :]
595
665
  else:
596
666
  raise ValueError(
597
667
  'Parameter must be pressure, demand, node_quality or custom_'
@@ -621,17 +691,19 @@ class ScenarioVisualizer:
621
691
  self.colorbars['junctions'] = {'mappable': plt.cm.ScalarMappable(
622
692
  norm=mpl.colors.Normalize(
623
693
  vmin=self.junction_parameters.vmin,
624
- vmax=self.junction_parameters.vmin), cmap=colormap),
694
+ vmax=self.junction_parameters.vmax), cmap=colormap),
625
695
  'label': label}
626
696
 
627
697
  def color_links(
628
698
  self, data: Optional[Union[ScadaData, np.ndarray]] = None,
629
699
  parameter: str = 'flow_rate', statistic: str = 'mean',
630
700
  pit: Optional[Union[int, Tuple[int, int]]] = None,
701
+ species: str = None,
631
702
  colormap: str = 'coolwarm',
632
703
  intervals: Optional[Union[int, List[Union[int, float]]]] = None,
633
704
  conversion: Optional[dict] = None,
634
- show_colorbar: bool = False) -> None:
705
+ show_colorbar: bool = False,
706
+ use_sensor_data: bool = False) -> None:
635
707
  """
636
708
  Colors the links (pipes) in the water distribution network based on the
637
709
  SCADA data and the specified parameters.
@@ -659,6 +731,9 @@ class ScenarioVisualizer:
659
731
  representing the start and end time steps. A tuple is necessary to
660
732
  process the data for the :func:`~ScenarioVisualizer.show_animation`
661
733
  method. Default is `None`.
734
+ species: `str`, optional
735
+ Key of species. Only necessary for parameter
736
+ 'bulk_species_concentration'.
662
737
  colormap : `str`, optional
663
738
  The colormap to use for visualizing link values. Default is
664
739
  'coolwarm'.
@@ -672,6 +747,11 @@ class ScenarioVisualizer:
672
747
  show_colorbar : `bool`, optional
673
748
  If `True`, a colorbar will be displayed on the plot to indicate the
674
749
  range of values. Default is `False`.
750
+ use_sensor_data : `bool`, optional
751
+ If `True`, instead of using raw simulation data, the data recorded
752
+ by the corresponding sensors in the system is used for the
753
+ visualization. Note: Not all components may have a sensor attached
754
+ and sensors may be subject to sensor faults or noise.
675
755
 
676
756
  Raises
677
757
  ------
@@ -707,11 +787,16 @@ class ScenarioVisualizer:
707
787
  break
708
788
  self.pipe_parameters.add_frame(self.topology, 'edge_color',
709
789
  self.scada_data, parameter,
710
- statistic, frame, intervals)
790
+ statistic, frame, species,
791
+ intervals, use_sensor_data)
711
792
  else:
712
793
  self.pipe_parameters.add_frame(self.topology, 'edge_color',
713
794
  self.scada_data, parameter,
714
- statistic, pit, intervals)
795
+ statistic, pit, species, intervals,
796
+ use_sensor_data)
797
+
798
+ if hasattr(self.pipe_parameters, 'mask'):
799
+ self.masks['links'] = self.pipe_parameters.mask
715
800
 
716
801
  if show_colorbar:
717
802
  if statistic == 'time_step':
@@ -731,7 +816,8 @@ class ScenarioVisualizer:
731
816
  parameter: str = 'efficiency', statistic: str = 'mean',
732
817
  pit: Optional[Union[int, Tuple[int]]] = None,
733
818
  intervals: Optional[Union[int, List[Union[int, float]]]] = None,
734
- colormap: str = 'viridis', show_colorbar: bool = False) -> None:
819
+ colormap: str = 'viridis', show_colorbar: bool = False,
820
+ use_sensor_data: bool = False) -> None:
735
821
  """
736
822
  Colors the pumps in the water distribution network based on SCADA data
737
823
  and the specified parameters.
@@ -769,6 +855,11 @@ class ScenarioVisualizer:
769
855
  show_colorbar : `bool`, optional
770
856
  If `True`, a colorbar will be displayed on the plot to indicate the
771
857
  range of pump values. Default is `False`.
858
+ use_sensor_data : `bool`, optional
859
+ If `True`, instead of using raw simulation data, the data recorded
860
+ by the corresponding sensors in the system is used for the
861
+ visualization. Note: Not all components may have a sensor attached
862
+ and sensors may be subject to sensor faults or noise.
772
863
 
773
864
  Raises
774
865
  ------
@@ -787,11 +878,23 @@ class ScenarioVisualizer:
787
878
  self.scada_data = self.__scenario.run_simulation()
788
879
 
789
880
  if parameter == 'efficiency':
790
- values = self.scada_data.pumps_efficiency_data_raw
881
+ if use_sensor_data:
882
+ values, self.masks[
883
+ 'pumps'] = self.scada_data.get_data_pumps_efficiency_as_node_features()
884
+ else:
885
+ values = self.scada_data.pumps_efficiency_data_raw
791
886
  elif parameter == 'energy_consumption':
792
- values = self.scada_data.pumps_energyconsumption_data_raw
887
+ if use_sensor_data:
888
+ values, self.masks[
889
+ 'pumps'] = self.scada_data.get_data_pumps_energyconsumption_as_node_features()
890
+ else:
891
+ values = self.scada_data.pumps_energyconsumption_data_raw
793
892
  elif parameter == 'state':
794
- values = self.scada_data.pumps_state_data_raw
893
+ if use_sensor_data:
894
+ values, self.masks[
895
+ 'pumps'] = self.scada_data.get_data_pumps_state_as_node_features()
896
+ else:
897
+ values = self.scada_data.pumps_state_data_raw
795
898
  elif parameter == 'custom_data':
796
899
  values = self.scada_data
797
900
  else:
@@ -828,7 +931,8 @@ class ScenarioVisualizer:
828
931
  statistic: str = 'mean',
829
932
  pit: Optional[Union[int, Tuple[int, int]]] = None,
830
933
  intervals: Optional[Union[int, List[Union[int, float]]]] = None,
831
- colormap: str = 'viridis', show_colorbar: bool = False) -> None:
934
+ colormap: str = 'viridis', show_colorbar: bool = False,
935
+ use_sensor_data: bool = False) -> None:
832
936
  """
833
937
  Colors the tanks in the water distribution network based on the SCADA
834
938
  tank volume data and the specified statistic.
@@ -863,6 +967,11 @@ class ScenarioVisualizer:
863
967
  show_colorbar : `bool`, optional
864
968
  If `True`, a colorbar will be displayed on the plot to indicate the
865
969
  range of tank volume values. Default is `False`.
970
+ use_sensor_data : `bool`, optional
971
+ If `True`, instead of using raw simulation data, the data recorded
972
+ by the corresponding sensors in the system is used for the
973
+ visualization. Note: Not all components may have a sensor attached
974
+ and sensors may be subject to sensor faults or noise.
866
975
 
867
976
  Raises
868
977
  ------
@@ -878,7 +987,11 @@ class ScenarioVisualizer:
878
987
  self.scada_data = self.__scenario.run_simulation()
879
988
 
880
989
  if isinstance(self.scada_data, ScadaData):
881
- values = self.scada_data.tanks_volume_data_raw
990
+ if use_sensor_data:
991
+ values, self.masks[
992
+ 'tanks'] = self.scada_data.get_data_tanks_water_volume_as_node_features()
993
+ else:
994
+ values = self.scada_data.tanks_volume_data_raw
882
995
  parameter = 'tank volume'
883
996
  else:
884
997
  values = self.scada_data
@@ -912,7 +1025,8 @@ class ScenarioVisualizer:
912
1025
  statistic: str = 'mean',
913
1026
  pit: Optional[Union[int, Tuple[int, int]]] = None,
914
1027
  intervals: Optional[Union[int, List[Union[int, float]]]] = None,
915
- colormap: str = 'viridis', show_colorbar: bool = False) -> None:
1028
+ colormap: str = 'viridis', show_colorbar: bool = False,
1029
+ use_sensor_data: bool = False) -> None:
916
1030
  """
917
1031
  Colors the valves in the water distribution network based on SCADA
918
1032
  valve state data and the specified statistic.
@@ -947,6 +1061,11 @@ class ScenarioVisualizer:
947
1061
  show_colorbar : `bool`, optional
948
1062
  If `True`, a colorbar will be displayed on the plot to indicate the
949
1063
  range of valve state values. Default is `False`.
1064
+ use_sensor_data : `bool`, optional
1065
+ If `True`, instead of using raw simulation data, the data recorded
1066
+ by the corresponding sensors in the system is used for the
1067
+ visualization. Note: Not all components may have a sensor attached
1068
+ and sensors may be subject to sensor faults or noise.
950
1069
 
951
1070
  Raises
952
1071
  ------
@@ -963,7 +1082,11 @@ class ScenarioVisualizer:
963
1082
  self.scada_data = self.__scenario.run_simulation()
964
1083
 
965
1084
  if isinstance(self.scada_data, ScadaData):
966
- values = self.scada_data.valves_state_data_raw
1085
+ if use_sensor_data:
1086
+ values, self.masks[
1087
+ 'valves'] = self.scada_data.get_data_valves_state_as_node_features()
1088
+ else:
1089
+ values = self.scada_data.valves_state_data_raw
967
1090
  parameter = 'valve state'
968
1091
  else:
969
1092
  values = self.scada_data
@@ -998,8 +1121,10 @@ class ScenarioVisualizer:
998
1121
  parameter: str = 'flow_rate', statistic: str = 'mean',
999
1122
  line_widths: Tuple[int, int] = (1, 2),
1000
1123
  pit: Optional[Union[int, Tuple[int, int]]] = None,
1124
+ species: str = None,
1001
1125
  intervals: Optional[Union[int, List[Union[int, float]]]] = None,
1002
- conversion: Optional[dict] = None) -> None:
1126
+ conversion: Optional[dict] = None,
1127
+ use_sensor_data: bool = False) -> None:
1003
1128
  """
1004
1129
  Resizes the width of the links (pipes) in the water distribution
1005
1130
  network based on SCADA data and the specified parameters.
@@ -1029,6 +1154,9 @@ class ScenarioVisualizer:
1029
1154
  representing the start and end time steps. A tuple is necessary to
1030
1155
  process the data for the :meth:`~ScenarioVisualizer.show_animation`
1031
1156
  method. Default is `None`.
1157
+ species: `str`, optional
1158
+ Key of species. Only necessary for parameter
1159
+ 'bulk_species_concentration'.
1032
1160
  intervals : `int` or `list[int]` or `list[float]`, optional
1033
1161
  If provided, the data will be grouped into intervals. It can be an
1034
1162
  integer specifying the number of groups or a list of boundary
@@ -1036,6 +1164,11 @@ class ScenarioVisualizer:
1036
1164
  conversion : `dict`, optional
1037
1165
  A dictionary of conversion parameters to convert SCADA data units.
1038
1166
  Default is `None`.
1167
+ use_sensor_data : `bool`, optional
1168
+ If `True`, instead of using raw simulation data, the data recorded
1169
+ by the corresponding sensors in the system is used for the
1170
+ visualization. Note: Not all components may have a sensor attached
1171
+ and sensors may be subject to sensor faults or noise.
1039
1172
  """
1040
1173
  sim_length = None
1041
1174
 
@@ -1062,11 +1195,13 @@ class ScenarioVisualizer:
1062
1195
  break
1063
1196
  self.pipe_parameters.add_frame(self.topology, 'edge_width',
1064
1197
  self.scada_data, parameter,
1065
- statistic, frame, intervals)
1198
+ statistic, frame, species,
1199
+ intervals, use_sensor_data)
1066
1200
  else:
1067
1201
  self.pipe_parameters.add_frame(self.topology, 'edge_width',
1068
1202
  self.scada_data, parameter,
1069
- statistic, pit, intervals)
1203
+ statistic, pit, species, intervals,
1204
+ use_sensor_data)
1070
1205
  self.pipe_parameters.rescale_widths(line_widths)
1071
1206
 
1072
1207
  def hide_nodes(self) -> None:
@@ -1103,48 +1238,3 @@ class ScenarioVisualizer:
1103
1238
  self.junction_parameters.add_attributes(
1104
1239
  {'linewidths': 1, 'edgecolors': node_edges})
1105
1240
  self.pipe_parameters.add_attributes({'style': pipe_style})
1106
-
1107
- @deprecated(reason="This function will be removed in feature versions, "
1108
- "please use show_plot() instead.")
1109
- def plot_topology(self, show_sensor_config: bool = False,
1110
- export_to_file: str = None) -> None:
1111
- """
1112
- Plots the topology of the water distribution network in the given
1113
- scenario.
1114
-
1115
- Parameters
1116
- ----------
1117
- show_sensor_config : `bool`, optional
1118
- Indicates whether the sensor configuration should be shown as well.
1119
-
1120
- The default is False.
1121
- export_to_file : `str`, optional
1122
- Path to the file where the visualization will be stored.
1123
- If None, visualization will be just shown but NOT be stored
1124
- anywhere.
1125
-
1126
- The default is None.
1127
- """
1128
- _ = plt.figure()
1129
-
1130
- highlighted_links = None
1131
- highlighted_nodes = None
1132
- if show_sensor_config is True:
1133
- highlighted_nodes = []
1134
- highlighted_links = []
1135
-
1136
- sensor_config = self.__scenario.sensor_config
1137
- highlighted_nodes += (sensor_config.pressure_sensors
1138
- + sensor_config.demand_sensors
1139
- + sensor_config.quality_node_sensors)
1140
- highlighted_links += (sensor_config.flow_sensors
1141
- + sensor_config.quality_link_sensors)
1142
-
1143
- self.__scenario.epanet_api.plot(highlightlink=highlighted_links,
1144
- highlightnode=highlighted_nodes,
1145
- figure=False)
1146
-
1147
- if export_to_file is not None:
1148
- plt.savefig(export_to_file, transparent=True, bbox_inches='tight')
1149
- else:
1150
- plt.show()