epyt-flow 0.7.0__py3-none-any.whl → 0.7.2__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.
@@ -5,9 +5,11 @@ import warnings
5
5
  from typing import Callable, Any
6
6
  from copy import deepcopy
7
7
  import numpy as np
8
+ import matplotlib
8
9
  from epyt.epanet import ToolkitConstants
9
10
 
10
- from ..sensor_config import SensorConfig, is_flowunit_simetric, massunit_to_str, \
11
+ from ..sensor_config import SensorConfig, is_flowunit_simetric, massunit_to_str, flowunit_to_str,\
12
+ qualityunit_to_str, areaunit_to_str,\
11
13
  MASS_UNIT_MG, MASS_UNIT_UG, TIME_UNIT_HRS, MASS_UNIT_MOL, MASS_UNIT_MMOL, \
12
14
  AREA_UNIT_CM2, AREA_UNIT_FT2, AREA_UNIT_M2, \
13
15
  SENSOR_TYPE_LINK_FLOW, SENSOR_TYPE_LINK_QUALITY, SENSOR_TYPE_NODE_DEMAND, \
@@ -18,6 +20,7 @@ from ..sensor_config import SensorConfig, is_flowunit_simetric, massunit_to_str,
18
20
  from ..events import SensorFault, SensorReadingAttack, SensorReadingEvent
19
21
  from ...uncertainty import SensorNoise
20
22
  from ...serialization import serializable, Serializable, SCADA_DATA_ID
23
+ from ...utils import plot_timeseries_data
21
24
 
22
25
 
23
26
  @serializable(SCADA_DATA_ID, ".epytflow_scada_data")
@@ -312,11 +315,11 @@ class ScadaData(Serializable):
312
315
  else:
313
316
  sensor_config = self.__sensor_config
314
317
 
315
- node_to_idx = sensor_config.node_id_to_idx
316
- link_to_idx = sensor_config.link_id_to_idx
317
- pump_to_idx = sensor_config.pump_id_to_idx
318
- valve_to_idx = sensor_config.valve_id_to_idx
319
- tank_to_idx = sensor_config.tank_id_to_idx
318
+ node_to_idx = sensor_config.map_node_id_to_idx
319
+ link_to_idx = sensor_config.map_link_id_to_idx
320
+ pump_to_idx = sensor_config.map_pump_id_to_idx
321
+ valve_to_idx = sensor_config.map_valve_id_to_idx
322
+ tank_to_idx = sensor_config.map_tank_id_to_idx
320
323
 
321
324
  # EPANET quantities
322
325
  def __reduce_data(data: np.ndarray, sensors: list[str],
@@ -349,7 +352,7 @@ class ScadaData(Serializable):
349
352
  self.__pumps_energy_usage_data_raw = \
350
353
  __reduce_data(data=pumps_energy_usage_data_raw,
351
354
  item_to_idx=pump_to_idx,
352
- sensors=sensor_config.pump_enegeryconsumption_sensors)
355
+ sensors=sensor_config.pump_energyconsumption_sensors)
353
356
  self.__pumps_efficiency_data_raw = \
354
357
  __reduce_data(data=pumps_efficiency_data_raw,
355
358
  item_to_idx=pump_to_idx,
@@ -373,24 +376,24 @@ class ScadaData(Serializable):
373
376
 
374
377
  return np.concatenate(r, axis=1)
375
378
 
376
- node_bulk_species_idx = [(sensor_config.bulkspecies_id_to_idx(s),
377
- [sensor_config.node_id_to_idx(node_id)
379
+ node_bulk_species_idx = [(sensor_config.map_bulkspecies_id_to_idx(s),
380
+ [sensor_config.map_node_id_to_idx(node_id)
378
381
  for node_id in sensor_config.bulk_species_node_sensors[s]
379
382
  ]) for s in sensor_config.bulk_species_node_sensors.keys()]
380
383
  self.__bulk_species_node_concentration_raw = \
381
384
  __reduce_msx_data(data=bulk_species_node_concentration_raw,
382
385
  sensors=node_bulk_species_idx)
383
386
 
384
- bulk_species_link_idx = [(sensor_config.bulkspecies_id_to_idx(s),
385
- [sensor_config.link_id_to_idx(link_id)
387
+ bulk_species_link_idx = [(sensor_config.map_bulkspecies_id_to_idx(s),
388
+ [sensor_config.map_link_id_to_idx(link_id)
386
389
  for link_id in sensor_config.bulk_species_link_sensors[s]
387
390
  ]) for s in sensor_config.bulk_species_link_sensors.keys()]
388
391
  self.__bulk_species_link_concentration_raw = \
389
392
  __reduce_msx_data(data=bulk_species_link_concentration_raw,
390
393
  sensors=bulk_species_link_idx)
391
394
 
392
- surface_species_idx = [(sensor_config.surfacespecies_id_to_idx(s),
393
- [sensor_config.link_id_to_idx(link_id)
395
+ surface_species_idx = [(sensor_config.map_surfacespecies_id_to_idx(s),
396
+ [sensor_config.map_link_id_to_idx(link_id)
394
397
  for link_id in sensor_config.surface_species_sensors[s]
395
398
  ]) for s in sensor_config.surface_species_sensors.keys()]
396
399
  self.__surface_species_concentration_raw = \
@@ -1503,8 +1506,8 @@ class ScadaData(Serializable):
1503
1506
  other.sensor_config.surface_species_sensors
1504
1507
 
1505
1508
  if self.__pumps_energy_usage_data_raw is None and \
1506
- other.pumps_energy_usage_data_raw is not None:
1507
- self.__pumps_energy_usage_data_raw = other.pumps_energy_usage_data_raw
1509
+ other.pumps_energyconsumption_data_raw is not None:
1510
+ self.__pumps_energy_usage_data_raw = other.pumps_energyconsumption_data_raw
1508
1511
 
1509
1512
  if self.__pumps_efficiency_data_raw is None and \
1510
1513
  other.pumps_efficiency_data_raw is not None:
@@ -1684,6 +1687,19 @@ class ScadaData(Serializable):
1684
1687
 
1685
1688
  return sensor_readings
1686
1689
 
1690
+ def __get_x_axis_label(self) -> str:
1691
+ if len(self.__sensor_readings_time) > 1:
1692
+ time_step = self.__sensor_readings_time[1] - self.__sensor_readings_time[0]
1693
+ if time_step > 60:
1694
+ time_steps_desc = f"{int(time_step / 60)}min"
1695
+ if time_step > 60*60:
1696
+ time_steps_desc = f"{int(time_step / 60)}hr"
1697
+ else:
1698
+ time_steps_desc = f"{time_step}s"
1699
+ return f"Time ({time_steps_desc} steps)"
1700
+ else:
1701
+ return "Time"
1702
+
1687
1703
  def get_data_pressures(self, sensor_locations: list[str] = None) -> np.ndarray:
1688
1704
  """
1689
1705
  Gets the final pressure sensor readings -- note that those might be subject to
@@ -1722,6 +1738,55 @@ class ScadaData(Serializable):
1722
1738
  for s_id in sensor_locations]
1723
1739
  return self.__sensor_readings[:, idx]
1724
1740
 
1741
+ def plot_pressures(self, sensor_locations: list[str] = None, show: bool = True,
1742
+ save_to_file: str = None, ax: matplotlib.axes.Axes = None
1743
+ ) -> matplotlib.axes.Axes:
1744
+ """
1745
+ Plots the final pressure sensor readings -- note that those might be subject to
1746
+ given sensor faults and sensor noise/uncertainty.
1747
+
1748
+ Parameters
1749
+ ----------
1750
+ sensor_locations : `list[str]`, optional
1751
+ Existing pressure sensor locations for which the sensor readings have to be plotted.
1752
+ If None, the readings from all pressure sensors are plotted.
1753
+
1754
+ The default is None.
1755
+ show : `bool`, optional
1756
+ If True, the plot/figure is shown in a window.
1757
+
1758
+ Only considered when 'ax' is None.
1759
+
1760
+ The default is True.
1761
+ save_to_file : `str`, optional
1762
+ File to which the plot is saved.
1763
+
1764
+ If specified, 'show' must be set to False --
1765
+ i.e. a plot can not be shown and saved to a file at the same time!
1766
+
1767
+ The default is None.
1768
+ ax : `matplotlib.axes.Axes`, optional
1769
+ If not None, 'ax' is used for plotting.
1770
+
1771
+ The default is None.
1772
+
1773
+ Returns
1774
+ -------
1775
+ `matplotlib.axes.Axes`
1776
+ Plot.
1777
+ """
1778
+ data = self.get_data_pressures(sensor_locations)
1779
+ pressure_sensors = sensor_locations if sensor_locations is not None else \
1780
+ self.__sensor_config.pressure_sensors
1781
+
1782
+ pressure_unit = "m" if is_flowunit_simetric(self.__sensor_config.flow_unit) else "psi"
1783
+ y_axis_label = f"Pressure in ${pressure_unit}$"
1784
+
1785
+ return plot_timeseries_data(data.T, labels=[f"Node {n_id}" for n_id in pressure_sensors],
1786
+ x_axis_label=self.__get_x_axis_label(),
1787
+ y_axis_label=y_axis_label,
1788
+ show=show, save_to_file=save_to_file, ax=ax)
1789
+
1725
1790
  def get_data_flows(self, sensor_locations: list[str] = None) -> np.ndarray:
1726
1791
  """
1727
1792
  Gets the final flow sensor readings -- note that those might be subject to
@@ -1760,6 +1825,54 @@ class ScadaData(Serializable):
1760
1825
  for s_id in sensor_locations]
1761
1826
  return self.__sensor_readings[:, idx]
1762
1827
 
1828
+ def plot_flows(self, sensor_locations: list[str] = None, show: bool = True,
1829
+ save_to_file: str = None, ax: matplotlib.axes.Axes = None
1830
+ ) -> matplotlib.axes.Axes:
1831
+ """
1832
+ Plots the final flow sensor readings -- note that those might be subject to
1833
+ given sensor faults and sensor noise/uncertainty.
1834
+
1835
+ Parameters
1836
+ ----------
1837
+ sensor_locations : `list[str]`, optional
1838
+ Existing flow sensor locations for which the sensor readings have to be plotted.
1839
+ If None, the readings from all flow sensors are plotted.
1840
+
1841
+ The default is None.
1842
+ show : `bool`, optional
1843
+ If True, the plot/figure is shown in a window.
1844
+
1845
+ Only considered when 'ax' is None.
1846
+
1847
+ The default is True.
1848
+ save_to_file : `str`, optional
1849
+ File to which the plot is saved.
1850
+
1851
+ If specified, 'show' must be set to False --
1852
+ i.e. a plot can not be shown and saved to a file at the same time!
1853
+
1854
+ The default is None.
1855
+ ax : `matplotlib.axes.Axes`, optional
1856
+ If not None, 'ax' is used for plotting.
1857
+
1858
+ The default is None.
1859
+
1860
+ Returns
1861
+ -------
1862
+ `matplotlib.axes.Axes`
1863
+ Plot.
1864
+ """
1865
+ data = self.get_data_flows(sensor_locations)
1866
+ flow_sensors = sensor_locations if sensor_locations is not None else \
1867
+ self.__sensor_config.flow_sensors
1868
+
1869
+ y_axis_label = f"Flow rate in ${flowunit_to_str(self.__sensor_config.flow_unit)}$"
1870
+
1871
+ return plot_timeseries_data(data.T, labels=[f"Link {n_id}" for n_id in flow_sensors],
1872
+ x_axis_label=self.__get_x_axis_label(),
1873
+ y_axis_label=y_axis_label,
1874
+ show=show, save_to_file=save_to_file, ax=ax)
1875
+
1763
1876
  def get_data_demands(self, sensor_locations: list[str] = None) -> np.ndarray:
1764
1877
  """
1765
1878
  Gets the final demand sensor readings -- note that those might be subject to
@@ -1798,6 +1911,54 @@ class ScadaData(Serializable):
1798
1911
  for s_id in sensor_locations]
1799
1912
  return self.__sensor_readings[:, idx]
1800
1913
 
1914
+ def plot_demands(self, sensor_locations: list[str] = None, show: bool = True,
1915
+ save_to_file: str = None, ax: matplotlib.axes.Axes = None
1916
+ ) -> matplotlib.axes.Axes:
1917
+ """
1918
+ Plots the final demand sensor readings -- note that those might be subject to
1919
+ given sensor faults and sensor noise/uncertainty.
1920
+
1921
+ Parameters
1922
+ ----------
1923
+ sensor_locations : `list[str]`, optional
1924
+ Existing demand sensor locations for which the sensor readings have to be plotted.
1925
+ If None, the readings from all demand sensors are plotted.
1926
+
1927
+ The default is None.
1928
+ show : `bool`, optional
1929
+ If True, the plot/figure is shown in a window.
1930
+
1931
+ Only considered when 'ax' is None.
1932
+
1933
+ The default is True.
1934
+ save_to_file : `str`, optional
1935
+ File to which the plot is saved.
1936
+
1937
+ If specified, 'show' must be set to False --
1938
+ i.e. a plot can not be shown and saved to a file at the same time!
1939
+
1940
+ The default is None.
1941
+ ax : `matplotlib.axes.Axes`, optional
1942
+ If not None, 'ax' is used for plotting.
1943
+
1944
+ The default is None.
1945
+
1946
+ Returns
1947
+ -------
1948
+ `matplotlib.axes.Axes`
1949
+ Plot.
1950
+ """
1951
+ data = self.get_data_demands(sensor_locations)
1952
+ demand_sensors = sensor_locations if sensor_locations is not None else \
1953
+ self.__sensor_config.demand_sensors
1954
+
1955
+ y_axis_label = f"Demand in ${flowunit_to_str(self.__sensor_config.flow_unit)}$"
1956
+
1957
+ return plot_timeseries_data(data.T, labels=[f"Node {n_id}" for n_id in demand_sensors],
1958
+ x_axis_label=self.__get_x_axis_label(),
1959
+ y_axis_label=y_axis_label,
1960
+ show=show, save_to_file=save_to_file, ax=ax)
1961
+
1801
1962
  def get_data_nodes_quality(self, sensor_locations: list[str] = None) -> np.ndarray:
1802
1963
  """
1803
1964
  Gets the final node quality sensor readings -- note that those might be subject to
@@ -1837,6 +1998,56 @@ class ScadaData(Serializable):
1837
1998
  for s_id in sensor_locations]
1838
1999
  return self.__sensor_readings[:, idx]
1839
2000
 
2001
+ def plot_nodes_quality(self, sensor_locations: list[str] = None, show: bool = True,
2002
+ save_to_file: str = None, ax: matplotlib.axes.Axes = None
2003
+ ) -> matplotlib.axes.Axes:
2004
+ """
2005
+ Plots the final node quality sensor readings -- note that those might be subject to
2006
+ given sensor faults and sensor noise/uncertainty.
2007
+
2008
+ Parameters
2009
+ ----------
2010
+ sensor_locations : `list[str]`, optional
2011
+ Existing node quality sensor locations for which the sensor readings
2012
+ have to be plotted.
2013
+ If None, the readings from all node quality sensors are plotted.
2014
+
2015
+ The default is None.
2016
+ show : `bool`, optional
2017
+ If True, the plot/figure is shown in a window.
2018
+
2019
+ Only considered when 'ax' is None.
2020
+
2021
+ The default is True.
2022
+ save_to_file : `str`, optional
2023
+ File to which the plot is saved.
2024
+
2025
+ If specified, 'show' must be set to False --
2026
+ i.e. a plot can not be shown and saved to a file at the same time!
2027
+
2028
+ The default is None.
2029
+ ax : `matplotlib.axes.Axes`, optional
2030
+ If not None, 'ax' is used for plotting.
2031
+
2032
+ The default is None.
2033
+
2034
+ Returns
2035
+ -------
2036
+ `matplotlib.axes.Axes`
2037
+ Plot.
2038
+ """
2039
+ data = self.get_data_nodes_quality(sensor_locations)
2040
+ nodes_quality_sensors = sensor_locations if sensor_locations is not None else \
2041
+ self.__sensor_config.quality_node_sensors
2042
+
2043
+ y_axis_label = f"${qualityunit_to_str(self.__sensor_config.quality_unit)}$"
2044
+
2045
+ return plot_timeseries_data(data.T, labels=[f"Node {n_id}"
2046
+ for n_id in nodes_quality_sensors],
2047
+ x_axis_label=self.__get_x_axis_label(),
2048
+ y_axis_label=y_axis_label,
2049
+ show=show, save_to_file=save_to_file, ax=ax)
2050
+
1840
2051
  def get_data_links_quality(self, sensor_locations: list[str] = None) -> np.ndarray:
1841
2052
  """
1842
2053
  Gets the final link quality sensor readings -- note that those might be subject to
@@ -1876,6 +2087,56 @@ class ScadaData(Serializable):
1876
2087
  for s_id in sensor_locations]
1877
2088
  return self.__sensor_readings[:, idx]
1878
2089
 
2090
+ def plot_links_quality(self, sensor_locations: list[str] = None, show: bool = True,
2091
+ save_to_file: str = None, ax: matplotlib.axes.Axes = None
2092
+ ) -> matplotlib.axes.Axes:
2093
+ """
2094
+ Plots the final link/pipe quality sensor readings -- note that those might be subject to
2095
+ given sensor faults and sensor noise/uncertainty.
2096
+
2097
+ Parameters
2098
+ ----------
2099
+ sensor_locations : `list[str]`, optional
2100
+ Existing link quality sensor locations for which the sensor readings
2101
+ have to be plotted.
2102
+ If None, the readings from all link quality sensors are plotted.
2103
+
2104
+ The default is None.
2105
+ show : `bool`, optional
2106
+ If True, the plot/figure is shown in a window.
2107
+
2108
+ Only considered when 'ax' is None.
2109
+
2110
+ The default is True.
2111
+ save_to_file : `str`, optional
2112
+ File to which the plot is saved.
2113
+
2114
+ If specified, 'show' must be set to False --
2115
+ i.e. a plot can not be shown and saved to a file at the same time!
2116
+
2117
+ The default is None.
2118
+ ax : `matplotlib.axes.Axes`, optional
2119
+ If not None, 'ax' is used for plotting.
2120
+
2121
+ The default is None.
2122
+
2123
+ Returns
2124
+ -------
2125
+ `matplotlib.axes.Axes`
2126
+ Plot.
2127
+ """
2128
+ data = self.get_data_links_quality(sensor_locations)
2129
+ links_quality_sensors = sensor_locations if sensor_locations is not None else \
2130
+ self.__sensor_config.quality_link_sensors
2131
+
2132
+ y_axis_label = f"${qualityunit_to_str(self.__sensor_config.quality_unit)}$"
2133
+
2134
+ return plot_timeseries_data(data.T, labels=[f"Link {n_id}"
2135
+ for n_id in links_quality_sensors],
2136
+ x_axis_label=self.__get_x_axis_label(),
2137
+ y_axis_label=y_axis_label,
2138
+ show=show, save_to_file=save_to_file, ax=ax)
2139
+
1879
2140
  def get_data_pumps_state(self, sensor_locations: list[str] = None) -> np.ndarray:
1880
2141
  """
1881
2142
  Gets the final pump state sensor readings -- note that those might be subject to
@@ -1915,6 +2176,54 @@ class ScadaData(Serializable):
1915
2176
  for s_id in sensor_locations]
1916
2177
  return self.__sensor_readings[:, idx]
1917
2178
 
2179
+ def plot_pumps_state(self, sensor_locations: list[str] = None, show: bool = True,
2180
+ save_to_file: str = None, ax: matplotlib.axes.Axes = None
2181
+ ) -> matplotlib.axes.Axes:
2182
+ """
2183
+ Plots the final pump state sensor readings -- note that those might be subject to
2184
+ given sensor faults and sensor noise/uncertainty.
2185
+
2186
+ Parameters
2187
+ ----------
2188
+ sensor_locations : `list[str]`, optional
2189
+ Existing pump state sensor locations for which the sensor readings have to be plotted.
2190
+ If None, the readings from all pump state sensors are plotted.
2191
+
2192
+ The default is None.
2193
+ show : `bool`, optional
2194
+ If True, the plot/figure is shown in a window.
2195
+
2196
+ Only considered when 'ax' is None.
2197
+
2198
+ The default is True.
2199
+ save_to_file : `str`, optional
2200
+ File to which the plot is saved.
2201
+
2202
+ If specified, 'show' must be set to False --
2203
+ i.e. a plot can not be shown and saved to a file at the same time!
2204
+
2205
+ The default is None.
2206
+ ax : `matplotlib.axes.Axes`, optional
2207
+ If not None, 'ax' is used for plotting.
2208
+
2209
+ The default is None.
2210
+
2211
+ Returns
2212
+ -------
2213
+ `matplotlib.axes.Axes`
2214
+ Plot.
2215
+ """
2216
+ data = self.get_data_pumps_state(sensor_locations)
2217
+ pump_state_sensors = sensor_locations if sensor_locations is not None else \
2218
+ self.__sensor_config.pump_state_sensors
2219
+
2220
+ return plot_timeseries_data(data.T, labels=[f"Pump {n_id}"
2221
+ for n_id in pump_state_sensors],
2222
+ x_axis_label=self.__get_x_axis_label(),
2223
+ y_axis_label="Pump state",
2224
+ y_ticks=([2.0, 3.0], ["Off", "On"]),
2225
+ show=show, save_to_file=save_to_file, ax=ax)
2226
+
1918
2227
  def get_data_pumps_efficiency(self, sensor_locations: list[str] = None) -> np.ndarray:
1919
2228
  """
1920
2229
  Gets the final pump efficiency sensor readings -- note that those might be subject to
@@ -1954,6 +2263,54 @@ class ScadaData(Serializable):
1954
2263
  for s_id in sensor_locations]
1955
2264
  return self.__sensor_readings[:, idx]
1956
2265
 
2266
+ def plot_pumps_efficiency(self, sensor_locations: list[str] = None, show: bool = True,
2267
+ save_to_file: str = None, ax: matplotlib.axes.Axes = None
2268
+ ) -> matplotlib.axes.Axes:
2269
+ """
2270
+ Plots the final pump efficiency sensor readings -- note that those might be subject to
2271
+ given sensor faults and sensor noise/uncertainty.
2272
+
2273
+ Parameters
2274
+ ----------
2275
+ sensor_locations : `list[str]`, optional
2276
+ Existing pump efficiency sensor locations for which the sensor readings
2277
+ have to be plotted.
2278
+ If None, the readings from all pump efficiency sensors are plotted.
2279
+
2280
+ The default is None.
2281
+ show : `bool`, optional
2282
+ If True, the plot/figure is shown in a window.
2283
+
2284
+ Only considered when 'ax' is None.
2285
+
2286
+ The default is True.
2287
+ save_to_file : `str`, optional
2288
+ File to which the plot is saved.
2289
+
2290
+ If specified, 'show' must be set to False --
2291
+ i.e. a plot can not be shown and saved to a file at the same time!
2292
+
2293
+ The default is None.
2294
+ ax : `matplotlib.axes.Axes`, optional
2295
+ If not None, 'ax' is used for plotting.
2296
+
2297
+ The default is None.
2298
+
2299
+ Returns
2300
+ -------
2301
+ `matplotlib.axes.Axes`
2302
+ Plot.
2303
+ """
2304
+ data = self.get_data_pumps_efficiency(sensor_locations)
2305
+ pump_efficiency_sensors = sensor_locations if sensor_locations is not None else \
2306
+ self.__sensor_config.pump_efficiency_sensors
2307
+
2308
+ return plot_timeseries_data(data.T, labels=[f"Pump {n_id}"
2309
+ for n_id in pump_efficiency_sensors],
2310
+ x_axis_label=self.__get_x_axis_label(),
2311
+ y_axis_label="Efficiency in $\%$",
2312
+ show=show, save_to_file=save_to_file, ax=ax)
2313
+
1957
2314
  def get_data_pumps_energyconsumption(self, sensor_locations: list[str] = None) -> np.ndarray:
1958
2315
  """
1959
2316
  Gets the final pump energy consumption sensor readings -- note that those might be subject
@@ -1994,6 +2351,54 @@ class ScadaData(Serializable):
1994
2351
  for s_id in sensor_locations]
1995
2352
  return self.__sensor_readings[:, idx]
1996
2353
 
2354
+ def plot_pumps_energyconsumption(self, sensor_locations: list[str] = None, show: bool = True,
2355
+ save_to_file: str = None, ax: matplotlib.axes.Axes = None
2356
+ ) -> matplotlib.axes.Axes:
2357
+ """
2358
+ Plots the final pump energy consumption sensor readings -- note that those might be
2359
+ subject to given sensor faults and sensor noise/uncertainty.
2360
+
2361
+ Parameters
2362
+ ----------
2363
+ sensor_locations : `list[str]`, optional
2364
+ Existing pump energy consumption sensor locations for which the sensor readings
2365
+ have to be plotted.
2366
+ If None, the readings from all pump energy consumption sensors are plotted.
2367
+
2368
+ The default is None.
2369
+ show : `bool`, optional
2370
+ If True, the plot/figure is shown in a window.
2371
+
2372
+ Only considered when 'ax' is None.
2373
+
2374
+ The default is True.
2375
+ save_to_file : `str`, optional
2376
+ File to which the plot is saved.
2377
+
2378
+ If specified, 'show' must be set to False --
2379
+ i.e. a plot can not be shown and saved to a file at the same time!
2380
+
2381
+ The default is None.
2382
+ ax : `matplotlib.axes.Axes`, optional
2383
+ If not None, 'ax' is used for plotting.
2384
+
2385
+ The default is None.
2386
+
2387
+ Returns
2388
+ -------
2389
+ `matplotlib.axes.Axes`
2390
+ Plot.
2391
+ """
2392
+ data = self.get_data_pumps_energyconsumption(sensor_locations)
2393
+ pump_energyconsumption_sensors = sensor_locations if sensor_locations is not None else \
2394
+ self.__sensor_config.pump_energyconsumption_sensors
2395
+
2396
+ return plot_timeseries_data(data.T, labels=[f"Pump {n_id}"
2397
+ for n_id in pump_energyconsumption_sensors],
2398
+ x_axis_label=self.__get_x_axis_label(),
2399
+ y_axis_label="Energy consumption in $kilowatt - hour$",
2400
+ show=show, save_to_file=save_to_file, ax=ax)
2401
+
1997
2402
  def get_data_valves_state(self, sensor_locations: list[str] = None) -> np.ndarray:
1998
2403
  """
1999
2404
  Gets the final valve state sensor readings -- note that those might be subject to
@@ -2033,6 +2438,54 @@ class ScadaData(Serializable):
2033
2438
  for s_id in sensor_locations]
2034
2439
  return self.__sensor_readings[:, idx]
2035
2440
 
2441
+ def plot_valves_state(self, sensor_locations: list[str] = None, show: bool = True,
2442
+ save_to_file: str = None, ax: matplotlib.axes.Axes = None
2443
+ ) -> matplotlib.axes.Axes:
2444
+ """
2445
+ Plots the final valve state sensor readings -- note that those might be subject to
2446
+ given sensor faults and sensor noise/uncertainty.
2447
+
2448
+ Parameters
2449
+ ----------
2450
+ sensor_locations : `list[str]`, optional
2451
+ Existing valve state sensor locations for which the sensor readings have to be plotted.
2452
+ If None, the readings from all valve state sensors are plotted.
2453
+
2454
+ The default is None.
2455
+ show : `bool`, optional
2456
+ If True, the plot/figure is shown in a window.
2457
+
2458
+ Only considered when 'ax' is None.
2459
+
2460
+ The default is True.
2461
+ save_to_file : `str`, optional
2462
+ File to which the plot is saved.
2463
+
2464
+ If specified, 'show' must be set to False --
2465
+ i.e. a plot can not be shown and saved to a file at the same time!
2466
+
2467
+ The default is None.
2468
+ ax : `matplotlib.axes.Axes`, optional
2469
+ If not None, 'ax' is used for plotting.
2470
+
2471
+ The default is None.
2472
+
2473
+ Returns
2474
+ -------
2475
+ `matplotlib.axes.Axes`
2476
+ Plot.
2477
+ """
2478
+ data = self.get_data_valves_state(sensor_locations)
2479
+ valve_state_sensors = sensor_locations if sensor_locations is not None else \
2480
+ self.__sensor_config.valve_state_sensors
2481
+
2482
+ return plot_timeseries_data(data.T, labels=[f"Valve {n_id}"
2483
+ for n_id in valve_state_sensors],
2484
+ x_axis_label=self.__get_x_axis_label(),
2485
+ y_axis_label="Valve state",
2486
+ y_ticks=([2.0, 3.0], ["Closed", "Open"]),
2487
+ show=show, save_to_file=save_to_file, ax=ax)
2488
+
2036
2489
  def get_data_tanks_water_volume(self, sensor_locations: list[str] = None) -> np.ndarray:
2037
2490
  """
2038
2491
  Gets the final water tanks volume sensor readings -- note that those might be subject to
@@ -2072,6 +2525,56 @@ class ScadaData(Serializable):
2072
2525
  for s_id in sensor_locations]
2073
2526
  return self.__sensor_readings[:, idx]
2074
2527
 
2528
+ def plot_tanks_water_volume(self, sensor_locations: list[str] = None, show: bool = True,
2529
+ save_to_file: str = None, ax: matplotlib.axes.Axes = None
2530
+ ) -> matplotlib.axes.Axes:
2531
+ """
2532
+ Plots the final water tanks volume sensor readings -- note that those might be subject to
2533
+ given sensor faults and sensor noise/uncertainty.
2534
+
2535
+ Parameters
2536
+ ----------
2537
+ sensor_locations : `list[str]`, optional
2538
+ Existing flow sensor locations for which the sensor readings have to be plotted.
2539
+ If None, the readings from all water tanks volume sensors are plotted.
2540
+
2541
+ The default is None.
2542
+ show : `bool`, optional
2543
+ If True, the plot/figure is shown in a window.
2544
+
2545
+ Only considered when 'ax' is None.
2546
+
2547
+ The default is True.
2548
+ save_to_file : `str`, optional
2549
+ File to which the plot is saved.
2550
+
2551
+ If specified, 'show' must be set to False --
2552
+ i.e. a plot can not be shown and saved to a file at the same time!
2553
+
2554
+ The default is None.
2555
+ ax : `matplotlib.axes.Axes`, optional
2556
+ If not None, 'ax' is used for plotting.
2557
+
2558
+ The default is None.
2559
+
2560
+ Returns
2561
+ -------
2562
+ `matplotlib.axes.Axes`
2563
+ Plot.
2564
+ """
2565
+ data = self.get_data_tanks_water_volume(sensor_locations)
2566
+ tank_volume_sensors = sensor_locations if sensor_locations is not None else \
2567
+ self.__sensor_config.tank_volume_sensors
2568
+
2569
+ volume_unit = "m^3" if is_flowunit_simetric(self.__sensor_config.flow_unit) else "feet^3"
2570
+ y_axis_label = f"Water volume in ${volume_unit}$"
2571
+
2572
+ return plot_timeseries_data(data.T, labels=[f"Tank {n_id}"
2573
+ for n_id in tank_volume_sensors],
2574
+ x_axis_label=self.__get_x_axis_label(),
2575
+ y_axis_label=y_axis_label,
2576
+ show=show, save_to_file=save_to_file, ax=ax)
2577
+
2075
2578
  def get_data_surface_species_concentration(self,
2076
2579
  surface_species_sensor_locations: dict = None
2077
2580
  ) -> np.ndarray:
@@ -2122,6 +2625,72 @@ class ScadaData(Serializable):
2122
2625
  for link_id in surface_species_sensor_locations[species_id]]
2123
2626
  return self.__sensor_readings[:, idx]
2124
2627
 
2628
+ def plot_surface_species_concentration(self, surface_species_sensor_locations: dict = None,
2629
+ show: bool = True, save_to_file: str = None,
2630
+ ax: matplotlib.axes.Axes = None
2631
+ ) -> matplotlib.axes.Axes:
2632
+ """
2633
+ Plots the final surface species concentration sensor readings -- note that those might be
2634
+ subject to given sensor faults and sensor noise/uncertainty.
2635
+
2636
+ Parameters
2637
+ ----------
2638
+ surface_species_sensor_locations : `dict`, optional
2639
+ Existing surface species concentration sensors (species ID and link/pipe IDs) for which
2640
+ the sensor readings have to be plotted.
2641
+ If None, the readings from all surface species concentration sensors are plotted.
2642
+
2643
+ The default is None.
2644
+ show : `bool`, optional
2645
+ If True, the plot/figure is shown in a window.
2646
+
2647
+ Only considered when 'ax' is None.
2648
+
2649
+ The default is True.
2650
+ save_to_file : `str`, optional
2651
+ File to which the plot is saved.
2652
+
2653
+ If specified, 'show' must be set to False --
2654
+ i.e. a plot can not be shown and saved to a file at the same time!
2655
+
2656
+ The default is None.
2657
+ ax : `matplotlib.axes.Axes`, optional
2658
+ If not None, 'ax' is used for plotting.
2659
+
2660
+ The default is None.
2661
+
2662
+ Returns
2663
+ -------
2664
+ `matplotlib.axes.Axes`
2665
+ Plot.
2666
+ """
2667
+ data = self.get_data_surface_species_concentration(surface_species_sensor_locations)
2668
+ if surface_species_sensor_locations is None:
2669
+ surface_species_sensor_locations = self.__sensor_config.surface_species_sensors
2670
+
2671
+ area_unit = self.__sensor_config.surface_species_area_unit
2672
+ concentration_unit = None
2673
+ labels = []
2674
+ for species_id in surface_species_sensor_locations:
2675
+ mass_unit = self.__sensor_config.get_surface_species_mass_unit_id(species_id)
2676
+ if concentration_unit is not None:
2677
+ if concentration_unit != mass_unit:
2678
+ raise ValueError("Can not plot species with different mass units")
2679
+ concentration_unit = mass_unit
2680
+ else:
2681
+ concentration_unit = mass_unit
2682
+
2683
+ for link_id in surface_species_sensor_locations[species_id]:
2684
+ labels.append(f"{species_id} @ link {link_id}")
2685
+
2686
+ y_axis_label = f"Concentration in ${massunit_to_str(concentration_unit)}/" +\
2687
+ f"{areaunit_to_str(area_unit)}$"
2688
+
2689
+ return plot_timeseries_data(data.T, labels=labels,
2690
+ x_axis_label=self.__get_x_axis_label(),
2691
+ y_axis_label=y_axis_label,
2692
+ show=show, save_to_file=save_to_file, ax=ax)
2693
+
2125
2694
  def get_data_bulk_species_node_concentration(self,
2126
2695
  bulk_species_sensor_locations: dict = None
2127
2696
  ) -> np.ndarray:
@@ -2172,6 +2741,70 @@ class ScadaData(Serializable):
2172
2741
  for node_id in bulk_species_sensor_locations[species_id]]
2173
2742
  return self.__sensor_readings[:, idx]
2174
2743
 
2744
+ def plot_bulk_species_node_concentration(self, bulk_species_node_sensors: dict = None,
2745
+ show: bool = True, save_to_file: str = None,
2746
+ ax: matplotlib.axes.Axes = None
2747
+ ) -> matplotlib.axes.Axes:
2748
+ """
2749
+ Plots the final bulk species node concentration sensor readings --
2750
+ note that those might be subject to given sensor faults and sensor noise/uncertainty.
2751
+
2752
+ Parameters
2753
+ ----------
2754
+ bulk_species_node_sensors : `dict`, optional
2755
+ Existing bulk species concentration sensors (species ID and node IDs) for which
2756
+ the sensor readings are requested.
2757
+ If None, the readings from all bulk species node concentration sensors are returned.
2758
+
2759
+ The default is None.
2760
+ show : `bool`, optional
2761
+ If True, the plot/figure is shown in a window.
2762
+
2763
+ Only considered when 'ax' is None.
2764
+
2765
+ The default is True.
2766
+ save_to_file : `str`, optional
2767
+ File to which the plot is saved.
2768
+
2769
+ If specified, 'show' must be set to False --
2770
+ i.e. a plot can not be shown and saved to a file at the same time!
2771
+
2772
+ The default is None.
2773
+ ax : `matplotlib.axes.Axes`, optional
2774
+ If not None, 'ax' is used for plotting.
2775
+
2776
+ The default is None.
2777
+
2778
+ Returns
2779
+ -------
2780
+ `matplotlib.axes.Axes`
2781
+ Plot.
2782
+ """
2783
+ data = self.get_data_bulk_species_node_concentration(bulk_species_node_sensors)
2784
+ if bulk_species_node_sensors is None:
2785
+ bulk_species_node_sensors = self.__sensor_config.bulk_species_node_sensors
2786
+
2787
+ concentration_unit = None
2788
+ labels = []
2789
+ for species_id in bulk_species_node_sensors:
2790
+ mass_unit = self.__sensor_config.get_bulk_species_mass_unit_id(species_id)
2791
+ if concentration_unit is not None:
2792
+ if concentration_unit != mass_unit:
2793
+ raise ValueError("Can not plot species with different mass units")
2794
+ concentration_unit = mass_unit
2795
+ else:
2796
+ concentration_unit = mass_unit
2797
+
2798
+ for node_id in bulk_species_node_sensors[species_id]:
2799
+ labels.append(f"{species_id} @ node {node_id}")
2800
+
2801
+ y_axis_label = f"Concentration in ${massunit_to_str(concentration_unit)}/L$"
2802
+
2803
+ return plot_timeseries_data(data.T, labels=labels,
2804
+ x_axis_label=self.__get_x_axis_label(),
2805
+ y_axis_label=y_axis_label,
2806
+ show=show, save_to_file=save_to_file, ax=ax)
2807
+
2175
2808
  def get_data_bulk_species_link_concentration(self,
2176
2809
  bulk_species_sensor_locations: dict = None
2177
2810
  ) -> np.ndarray:
@@ -2222,3 +2855,69 @@ class ScadaData(Serializable):
2222
2855
  for species_id in bulk_species_sensor_locations
2223
2856
  for node_id in bulk_species_sensor_locations[species_id]]
2224
2857
  return self.__sensor_readings[:, idx]
2858
+
2859
+ def plot_bulk_species_link_concentration(self, bulk_species_link_sensors: dict = None,
2860
+ show: bool = True, save_to_file: str = None,
2861
+ ax: matplotlib.axes.Axes = None
2862
+ ) -> matplotlib.axes.Axes:
2863
+ """
2864
+ Plots the final bulk species link concentration sensor readings -- note that those might be
2865
+ subject to given sensor faults and sensor noise/uncertainty.
2866
+
2867
+ Parameters
2868
+ ----------
2869
+ bulk_species_link_sensors : `dict`, optional
2870
+ Existing bulk species link concentration sensors (species ID and link/pipe IDs) for which
2871
+ the sensor readings have to be plotted.
2872
+ If None, the readings from all bulk species link concentration sensors are plotted.
2873
+
2874
+ The default is None.
2875
+ show : `bool`, optional
2876
+ If True, the plot/figure is shown in a window.
2877
+
2878
+ Only considered when 'ax' is None.
2879
+
2880
+ The default is True.
2881
+ save_to_file : `str`, optional
2882
+ File to which the plot is saved.
2883
+
2884
+ If specified, 'show' must be set to False --
2885
+ i.e. a plot can not be shown and saved to a file at the same time!
2886
+
2887
+ The default is None.
2888
+ ax : `matplotlib.axes.Axes`, optional
2889
+ If not None, 'ax' is used for plotting.
2890
+
2891
+ The default is None.
2892
+
2893
+ Returns
2894
+ -------
2895
+ `matplotlib.axes.Axes`
2896
+ Plot.
2897
+ """
2898
+ data = self.get_data_bulk_species_link_concentration(bulk_species_link_sensors)
2899
+ if bulk_species_link_sensors is None:
2900
+ bulk_species_link_sensors = self.__sensor_config.bulk_species_link_sensors
2901
+
2902
+ area_unit = self.__sensor_config.surface_species_area_unit
2903
+ concentration_unit = None
2904
+ labels = []
2905
+ for species_id in bulk_species_link_sensors:
2906
+ mass_unit = self.__sensor_config.get_bulk_species_mass_unit_id(species_id)
2907
+ if concentration_unit is not None:
2908
+ if concentration_unit != mass_unit:
2909
+ raise ValueError("Can not plot species with different mass units")
2910
+ concentration_unit = mass_unit
2911
+ else:
2912
+ concentration_unit = mass_unit
2913
+
2914
+ for link_id in bulk_species_link_sensors[species_id]:
2915
+ labels.append(f"{species_id} @ link {link_id}")
2916
+
2917
+ y_axis_label = f"Concentration in ${massunit_to_str(concentration_unit)}/" +\
2918
+ f"{areaunit_to_str(area_unit)}$"
2919
+
2920
+ return plot_timeseries_data(data.T, labels=labels,
2921
+ x_axis_label=self.__get_x_axis_label(),
2922
+ y_axis_label=y_axis_label,
2923
+ show=show, save_to_file=save_to_file, ax=ax)