epyt-flow 0.5.0__py3-none-any.whl → 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- epyt_flow/VERSION +1 -1
- epyt_flow/data/benchmarks/battledim.py +6 -2
- epyt_flow/data/benchmarks/leakdb.py +11 -9
- epyt_flow/data/networks.py +1 -2
- epyt_flow/gym/scenario_control_env.py +101 -6
- epyt_flow/metrics.py +66 -4
- epyt_flow/serialization.py +46 -3
- epyt_flow/simulation/scada/advanced_control.py +1 -1
- epyt_flow/simulation/scada/scada_data.py +160 -62
- epyt_flow/simulation/scenario_config.py +6 -2
- epyt_flow/simulation/scenario_simulator.py +362 -65
- epyt_flow/simulation/sensor_config.py +186 -15
- epyt_flow/topology.py +93 -5
- epyt_flow/uncertainty/uncertainties.py +8 -0
- epyt_flow/utils.py +69 -2
- {epyt_flow-0.5.0.dist-info → epyt_flow-0.7.0.dist-info}/METADATA +49 -6
- {epyt_flow-0.5.0.dist-info → epyt_flow-0.7.0.dist-info}/RECORD +20 -20
- {epyt_flow-0.5.0.dist-info → epyt_flow-0.7.0.dist-info}/WHEEL +1 -1
- {epyt_flow-0.5.0.dist-info → epyt_flow-0.7.0.dist-info}/LICENSE +0 -0
- {epyt_flow-0.5.0.dist-info → epyt_flow-0.7.0.dist-info}/top_level.txt +0 -0
|
@@ -11,17 +11,19 @@ from epyt.epanet import ToolkitConstants
|
|
|
11
11
|
from ..serialization import SENSOR_CONFIG_ID, JsonSerializable, serializable
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
SENSOR_TYPE_NODE_PRESSURE
|
|
15
|
-
SENSOR_TYPE_NODE_QUALITY
|
|
16
|
-
SENSOR_TYPE_NODE_DEMAND
|
|
17
|
-
SENSOR_TYPE_LINK_FLOW
|
|
18
|
-
SENSOR_TYPE_LINK_QUALITY
|
|
19
|
-
SENSOR_TYPE_VALVE_STATE
|
|
20
|
-
SENSOR_TYPE_PUMP_STATE
|
|
21
|
-
SENSOR_TYPE_TANK_VOLUME
|
|
22
|
-
SENSOR_TYPE_NODE_BULK_SPECIES
|
|
23
|
-
SENSOR_TYPE_LINK_BULK_SPECIES
|
|
24
|
-
SENSOR_TYPE_SURFACE_SPECIES
|
|
14
|
+
SENSOR_TYPE_NODE_PRESSURE = 1
|
|
15
|
+
SENSOR_TYPE_NODE_QUALITY = 2
|
|
16
|
+
SENSOR_TYPE_NODE_DEMAND = 3
|
|
17
|
+
SENSOR_TYPE_LINK_FLOW = 4
|
|
18
|
+
SENSOR_TYPE_LINK_QUALITY = 5
|
|
19
|
+
SENSOR_TYPE_VALVE_STATE = 6
|
|
20
|
+
SENSOR_TYPE_PUMP_STATE = 7
|
|
21
|
+
SENSOR_TYPE_TANK_VOLUME = 8
|
|
22
|
+
SENSOR_TYPE_NODE_BULK_SPECIES = 9
|
|
23
|
+
SENSOR_TYPE_LINK_BULK_SPECIES = 10
|
|
24
|
+
SENSOR_TYPE_SURFACE_SPECIES = 11
|
|
25
|
+
SENSOR_TYPE_PUMP_EFFICIENCY = 12
|
|
26
|
+
SENSOR_TYPE_PUMP_ENERGYCONSUMPTION = 13
|
|
25
27
|
|
|
26
28
|
AREA_UNIT_FT2 = 1
|
|
27
29
|
AREA_UNIT_M2 = 2
|
|
@@ -282,7 +284,7 @@ def is_flowunit_simetric(unit_id: int) -> bool:
|
|
|
282
284
|
`bool`
|
|
283
285
|
True if the fiven unit is a SI metric unit, False otherwise.
|
|
284
286
|
"""
|
|
285
|
-
return unit_id in [ToolkitConstants.EN_LPM, ToolkitConstants.EN_MLD,
|
|
287
|
+
return unit_id in [ToolkitConstants.EN_LPS, ToolkitConstants.EN_LPM, ToolkitConstants.EN_MLD,
|
|
286
288
|
ToolkitConstants.EN_CMH, ToolkitConstants.EN_CMD]
|
|
287
289
|
|
|
288
290
|
|
|
@@ -462,6 +464,8 @@ class SensorConfig(JsonSerializable):
|
|
|
462
464
|
quality_link_sensors: list[str] = [],
|
|
463
465
|
valve_state_sensors: list[str] = [],
|
|
464
466
|
pump_state_sensors: list[str] = [],
|
|
467
|
+
pump_efficiency_sensors: list[str] = [],
|
|
468
|
+
pump_energyconsumption_sensors: list[str] = [],
|
|
465
469
|
tank_volume_sensors: list[str] = [],
|
|
466
470
|
bulk_species_node_sensors: dict = {},
|
|
467
471
|
bulk_species_link_sensors: dict = {},
|
|
@@ -572,6 +576,20 @@ class SensorConfig(JsonSerializable):
|
|
|
572
576
|
raise ValueError("Each item in 'pump_state_sensors' must be in 'pumps' -- cannot " +
|
|
573
577
|
"place a sensor at a non-existing pump.")
|
|
574
578
|
|
|
579
|
+
if not isinstance(pump_efficiency_sensors, list):
|
|
580
|
+
raise TypeError("'pump_efficiency_sensors' must be an instance of 'list[str]' " +
|
|
581
|
+
f"but not of '{type(pump_efficiency_sensors)}'")
|
|
582
|
+
if any(link not in pumps for link in pump_efficiency_sensors):
|
|
583
|
+
raise ValueError("Each item in 'pump_efficiency_sensors' must be in 'pumps' -- cannot " +
|
|
584
|
+
"place a sensor at a non-existing pump.")
|
|
585
|
+
|
|
586
|
+
if not isinstance(pump_energyconsumption_sensors, list):
|
|
587
|
+
raise TypeError("'pump_energyconsumption_sensors' must be an instance of 'list[str]' " +
|
|
588
|
+
f"but not of '{type(pump_energyconsumption_sensors)}'")
|
|
589
|
+
if any(link not in pumps for link in pump_energyconsumption_sensors):
|
|
590
|
+
raise ValueError("Each item in 'pump_energyconsumption_sensors' must be in 'pumps' -- cannot " +
|
|
591
|
+
"place a sensor at a non-existing pump.")
|
|
592
|
+
|
|
575
593
|
if not isinstance(tank_volume_sensors, list):
|
|
576
594
|
raise TypeError("'tank_volume_sensors' must be an instance of 'list[str]' " +
|
|
577
595
|
f"but not of '{type(tank_volume_sensors)}'")
|
|
@@ -714,6 +732,8 @@ class SensorConfig(JsonSerializable):
|
|
|
714
732
|
self.__quality_link_sensors = quality_link_sensors
|
|
715
733
|
self.__valve_state_sensors = valve_state_sensors
|
|
716
734
|
self.__pump_state_sensors = pump_state_sensors
|
|
735
|
+
self.__pump_energyconsumption_sensors = pump_energyconsumption_sensors
|
|
736
|
+
self.__pump_efficiency_sensors = pump_efficiency_sensors
|
|
717
737
|
self.__tank_volume_sensors = tank_volume_sensors
|
|
718
738
|
self.__bulk_species_node_sensors = bulk_species_node_sensors
|
|
719
739
|
self.__bulk_species_link_sensors = bulk_species_link_sensors
|
|
@@ -771,6 +791,51 @@ class SensorConfig(JsonSerializable):
|
|
|
771
791
|
bulkspecies_id_to_idx=sensor_config.bulkspecies_id_to_idx,
|
|
772
792
|
surfacespecies_id_to_idx=sensor_config.surfacespecies_id_to_idx)
|
|
773
793
|
|
|
794
|
+
def is_empty(self) -> bool:
|
|
795
|
+
"""
|
|
796
|
+
Checks if the sensor configuration is empty -- i.e. no sensors are placed.
|
|
797
|
+
|
|
798
|
+
Returns
|
|
799
|
+
-------
|
|
800
|
+
`bool`
|
|
801
|
+
True if no sensors are placed, False otherwise.
|
|
802
|
+
"""
|
|
803
|
+
if self.__pressure_sensors == [] and self.__flow_sensors == [] \
|
|
804
|
+
and self.__demand_sensors == [] and self.__quality_node_sensors == [] \
|
|
805
|
+
and self.__quality_link_sensors == [] and self.__valve_state_sensors == [] \
|
|
806
|
+
and self.__pump_state_sensors == [] \
|
|
807
|
+
and self.__pump_energyconsumption_sensors == [] \
|
|
808
|
+
and self.__pump_efficiency_sensors == [] and self.__tank_volume_sensors == [] \
|
|
809
|
+
and self.__bulk_species_node_sensors == [] \
|
|
810
|
+
and self.__bulk_species_link_sensors == [] \
|
|
811
|
+
and self.__surface_species_sensors == []:
|
|
812
|
+
return True
|
|
813
|
+
else:
|
|
814
|
+
return False
|
|
815
|
+
|
|
816
|
+
def place_sensors_everywhere(self) -> None:
|
|
817
|
+
"""
|
|
818
|
+
Places sensors everywhere -- i.e. every possible quantity is monitored
|
|
819
|
+
at every position in the network.
|
|
820
|
+
"""
|
|
821
|
+
self.__pressure_sensors = self.__nodes[:]
|
|
822
|
+
self.__demand_sensors = self.__nodes[:]
|
|
823
|
+
self.__flow_sensors = self.__links[:]
|
|
824
|
+
self.__quality_node_sensors = self.__nodes[:]
|
|
825
|
+
self.__quality_link_sensors = self.__links[:]
|
|
826
|
+
self.__pump_state_sensors = self.__pumps[:]
|
|
827
|
+
self.__pump_energyconsumption_sensors = self.__pumps[:]
|
|
828
|
+
self.__pump_efficiency_sensors = self.__pumps[:]
|
|
829
|
+
self.__tank_volume_sensors = self.__tanks[:]
|
|
830
|
+
self.__bulk_species_node_sensors = {species_id: self.__nodes[:]
|
|
831
|
+
for species_id in self.__bulk_species}
|
|
832
|
+
self.__bulk_species_link_sensors = {species_id: self.__links[:]
|
|
833
|
+
for species_id in self.__bulk_species}
|
|
834
|
+
self.__surface_species_sensors = {species_id: self.__links[:]
|
|
835
|
+
for species_id in self.__surface_species}
|
|
836
|
+
|
|
837
|
+
self.__compute_indices()
|
|
838
|
+
|
|
774
839
|
@property
|
|
775
840
|
def node_id_to_idx(self) -> dict:
|
|
776
841
|
"""
|
|
@@ -1032,6 +1097,12 @@ class SensorConfig(JsonSerializable):
|
|
|
1032
1097
|
for v in self.__valve_state_sensors], dtype=np.int32)
|
|
1033
1098
|
self.__pump_state_idx = np.array([self.map_pump_id_to_idx(p)
|
|
1034
1099
|
for p in self.__pump_state_sensors], dtype=np.int32)
|
|
1100
|
+
self.__pump_efficiency_idx = np.array([self.map_pump_id_to_idx(p)
|
|
1101
|
+
for p in self.__pump_efficiency_sensors],
|
|
1102
|
+
dtype=np.int32)
|
|
1103
|
+
self.__pump_energyconsumption_idx = np.array([self.map_pump_id_to_idx(p)
|
|
1104
|
+
for p in self.__pump_energyconsumption_sensors],
|
|
1105
|
+
dtype=np.int32)
|
|
1035
1106
|
self.__tank_volume_idx = np.array([self.map_tank_id_to_idx(t)
|
|
1036
1107
|
for t in self.__tank_volume_sensors], dtype=np.int32)
|
|
1037
1108
|
self.__bulk_species_node_idx = np.array([(self.map_bulkspecies_id_to_idx(s),
|
|
@@ -1057,6 +1128,8 @@ class SensorConfig(JsonSerializable):
|
|
|
1057
1128
|
n_link_quality_sensors = len(self.__quality_link_sensors)
|
|
1058
1129
|
n_valve_state_sensors = len(self.__valve_state_sensors)
|
|
1059
1130
|
n_pump_state_sensors = len(self.__pump_state_sensors)
|
|
1131
|
+
n_pump_efficiency_sensors = len(self.__pump_efficiency_sensors)
|
|
1132
|
+
n_pump_energyconsumption_sensors = len(self.__pump_energyconsumption_sensors)
|
|
1060
1133
|
n_tank_volume_sensors = len(self.__tank_volume_sensors)
|
|
1061
1134
|
n_bulk_species_node_sensors = len(list(itertools.chain(
|
|
1062
1135
|
*self.__bulk_species_node_sensors.values())))
|
|
@@ -1070,7 +1143,9 @@ class SensorConfig(JsonSerializable):
|
|
|
1070
1143
|
link_quality_idx_shift = node_quality_idx_shift + n_node_quality_sensors
|
|
1071
1144
|
valve_state_idx_shift = link_quality_idx_shift + n_link_quality_sensors
|
|
1072
1145
|
pump_state_idx_shift = valve_state_idx_shift + n_valve_state_sensors
|
|
1073
|
-
|
|
1146
|
+
pump_efficiency_idx_shift = pump_state_idx_shift + n_pump_state_sensors
|
|
1147
|
+
pump_energyconsumption_idx_shift = pump_efficiency_idx_shift + n_pump_efficiency_sensors
|
|
1148
|
+
tank_volume_idx_shift = pump_energyconsumption_idx_shift + n_pump_energyconsumption_sensors
|
|
1074
1149
|
bulk_species_node_idx_shift = tank_volume_idx_shift + n_tank_volume_sensors
|
|
1075
1150
|
bulk_species_link_idx_shift = bulk_species_node_idx_shift + n_bulk_species_node_sensors
|
|
1076
1151
|
surface_species_idx_shift = bulk_species_link_idx_shift + n_bulk_species_link_sensors
|
|
@@ -1103,6 +1178,11 @@ class SensorConfig(JsonSerializable):
|
|
|
1103
1178
|
valve_state_idx_shift),
|
|
1104
1179
|
"pump_state": __build_sensors_id_to_idx(self.__pump_state_sensors,
|
|
1105
1180
|
pump_state_idx_shift),
|
|
1181
|
+
"pump_efficiency": __build_sensors_id_to_idx(self.__pump_efficiency_sensors,
|
|
1182
|
+
pump_efficiency_idx_shift),
|
|
1183
|
+
"pump_energyconsumption":
|
|
1184
|
+
__build_sensors_id_to_idx(self.__pump_energyconsumption_sensors,
|
|
1185
|
+
pump_energyconsumption_idx_shift),
|
|
1106
1186
|
"tank_volume": __build_sensors_id_to_idx(self.__tank_volume_sensors,
|
|
1107
1187
|
tank_volume_idx_shift),
|
|
1108
1188
|
"bulk_species_node":
|
|
@@ -1526,6 +1606,58 @@ class SensorConfig(JsonSerializable):
|
|
|
1526
1606
|
|
|
1527
1607
|
self.__compute_indices()
|
|
1528
1608
|
|
|
1609
|
+
@property
|
|
1610
|
+
def pump_energyconsumption_sensors(self) -> list[str]:
|
|
1611
|
+
"""
|
|
1612
|
+
Gets all pump energy consumption sensors
|
|
1613
|
+
(i.e. IDs of pumps at which the energy consumption is monitored).
|
|
1614
|
+
|
|
1615
|
+
Returns
|
|
1616
|
+
-------
|
|
1617
|
+
`list[str]`
|
|
1618
|
+
All pump IDs with an energy consumption sensor.
|
|
1619
|
+
"""
|
|
1620
|
+
return self.__pump_energyconsumption_sensors.copy()
|
|
1621
|
+
|
|
1622
|
+
@pump_energyconsumption_sensors.setter
|
|
1623
|
+
def pump_energyconsumption_sensors(self, pump_energyconsumption_sensors: list[str]) -> None:
|
|
1624
|
+
if not isinstance(pump_energyconsumption_sensors, list):
|
|
1625
|
+
raise TypeError("'pump_energyconsumption_sensors' must be an instance of 'list[str]' " +
|
|
1626
|
+
f"but not of '{type(pump_energyconsumption_sensors)}'")
|
|
1627
|
+
if any(link not in self.__pumps for link in pump_energyconsumption_sensors):
|
|
1628
|
+
raise ValueError("Each item in 'pump_energyconsumption_sensors' must be in 'pumps' " +
|
|
1629
|
+
"-- cannot place a sensor at a non-existing pump.")
|
|
1630
|
+
|
|
1631
|
+
self.__pump_energyconsumption_sensors = pump_energyconsumption_sensors
|
|
1632
|
+
|
|
1633
|
+
self.__compute_indices()
|
|
1634
|
+
|
|
1635
|
+
@property
|
|
1636
|
+
def pump_efficiency_sensors(self) -> list[str]:
|
|
1637
|
+
"""
|
|
1638
|
+
Gets all pump efficiency sensors
|
|
1639
|
+
(i.e. IDs of pumps at which the efficiency is monitored).
|
|
1640
|
+
|
|
1641
|
+
Returns
|
|
1642
|
+
-------
|
|
1643
|
+
`list[str]`
|
|
1644
|
+
All pump IDs with an efficiency sensor.
|
|
1645
|
+
"""
|
|
1646
|
+
return self.__pump_efficiency_sensors.copy()
|
|
1647
|
+
|
|
1648
|
+
@pump_efficiency_sensors.setter
|
|
1649
|
+
def pump_efficiency_sensors(self, pump_efficiency_sensors: list[str]) -> None:
|
|
1650
|
+
if not isinstance(pump_efficiency_sensors, list):
|
|
1651
|
+
raise TypeError("'pump_efficiency_sensors' must be an instance of 'list[str]' " +
|
|
1652
|
+
f"but not of '{type(pump_efficiency_sensors)}'")
|
|
1653
|
+
if any(link not in self.__pumps for link in pump_efficiency_sensors):
|
|
1654
|
+
raise ValueError("Each item in 'pump_efficiency_sensors' must be in 'pumps' " +
|
|
1655
|
+
"-- cannot place a sensor at a non-existing pump.")
|
|
1656
|
+
|
|
1657
|
+
self.__pump_efficiency_sensors = pump_efficiency_sensors
|
|
1658
|
+
|
|
1659
|
+
self.__compute_indices()
|
|
1660
|
+
|
|
1529
1661
|
@property
|
|
1530
1662
|
def tank_volume_sensors(self) -> list[str]:
|
|
1531
1663
|
"""
|
|
@@ -1670,6 +1802,10 @@ class SensorConfig(JsonSerializable):
|
|
|
1670
1802
|
r["valve_state"] = self.__valve_state_sensors
|
|
1671
1803
|
if self.__pump_state_sensors != []:
|
|
1672
1804
|
r["pump_state"] = self.__pump_state_sensors
|
|
1805
|
+
if self.__pump_efficiency_sensors != []:
|
|
1806
|
+
r["pump_efficiency"] = self.__pump_efficiency_sensors
|
|
1807
|
+
if self.__pump_energyconsumption_sensors != []:
|
|
1808
|
+
r["pump_energyconsumption"] = self.__pump_energyconsumption_sensors
|
|
1673
1809
|
if self.__quality_node_sensors != []:
|
|
1674
1810
|
r["node_quality"] = self.__quality_node_sensors
|
|
1675
1811
|
if self.__quality_link_sensors != []:
|
|
@@ -1695,6 +1831,8 @@ class SensorConfig(JsonSerializable):
|
|
|
1695
1831
|
"quality_link_sensors": self.__quality_link_sensors,
|
|
1696
1832
|
"valve_state_sensors": self.__valve_state_sensors,
|
|
1697
1833
|
"pump_state_sensors": self.__pump_state_sensors,
|
|
1834
|
+
"pump_efficiency_sensors": self.__pump_efficiency_sensors,
|
|
1835
|
+
"pump_energyconsumption_sensors": self.__pump_energyconsumption_sensors,
|
|
1698
1836
|
"tank_volume_sensors": self.__tank_volume_sensors,
|
|
1699
1837
|
"bulk_species_node_sensors": self.__bulk_species_node_sensors,
|
|
1700
1838
|
"bulk_species_link_sensors": self.__bulk_species_link_sensors,
|
|
@@ -1730,6 +1868,8 @@ class SensorConfig(JsonSerializable):
|
|
|
1730
1868
|
and self.__quality_link_sensors == other.quality_link_sensors \
|
|
1731
1869
|
and self.__valve_state_sensors == other.valve_state_sensors \
|
|
1732
1870
|
and self.__pump_state_sensors == other.pump_state_sensors \
|
|
1871
|
+
and self.__pump_efficiency_sensors == other.pump_efficiency_sensors \
|
|
1872
|
+
and self.__pump_energyconsumption_sensors == other.pump_energyconsumption_sensors \
|
|
1733
1873
|
and self.__tank_volume_sensors == other.tank_volume_sensors \
|
|
1734
1874
|
and self.__bulk_species_node_sensors == other.bulk_species_node_sensors \
|
|
1735
1875
|
and self.__bulk_species_link_sensors == other.bulk_species_link_sensors \
|
|
@@ -1762,6 +1902,8 @@ class SensorConfig(JsonSerializable):
|
|
|
1762
1902
|
f"quality_link_sensors: {self.__quality_link_sensors} " +\
|
|
1763
1903
|
f"valve_state_sensors: {self.__valve_state_sensors} " +\
|
|
1764
1904
|
f"pump_state_sensors: {self.__pump_state_sensors} " +\
|
|
1905
|
+
f"pump_efficiency_sensors: {self.__pump_efficiency_sensors} " +\
|
|
1906
|
+
f"pump_energyconsumption_sensors: {self.__pump_energyconsumption_sensors} " +\
|
|
1765
1907
|
f"tank_volume_sensors: {self.__tank_volume_sensors} " +\
|
|
1766
1908
|
f"bulk_species_node_sensors: {self.__bulk_species_node_sensors} " +\
|
|
1767
1909
|
f"bulk_species_link_sensors: {self.__bulk_species_link_sensors} " +\
|
|
@@ -1776,7 +1918,8 @@ class SensorConfig(JsonSerializable):
|
|
|
1776
1918
|
|
|
1777
1919
|
def compute_readings(self, pressures: np.ndarray, flows: np.ndarray, demands: np.ndarray,
|
|
1778
1920
|
nodes_quality: np.ndarray, links_quality: np.ndarray,
|
|
1779
|
-
pumps_state: np.ndarray,
|
|
1921
|
+
pumps_state: np.ndarray, pumps_efficiency: np.ndarray,
|
|
1922
|
+
pumps_energyconsumption: np.ndarray, valves_state: np.ndarray,
|
|
1780
1923
|
tanks_volume: np.ndarray, bulk_species_node_concentrations: np.ndarray,
|
|
1781
1924
|
bulk_species_link_concentrations: np.ndarray,
|
|
1782
1925
|
surface_species_concentrations: np.ndarray) -> np.ndarray:
|
|
@@ -1798,6 +1941,10 @@ class SensorConfig(JsonSerializable):
|
|
|
1798
1941
|
Quality values at all links/pipes.
|
|
1799
1942
|
pumps_state : `numpy.ndarray`
|
|
1800
1943
|
States of all pumps.
|
|
1944
|
+
pumps_efficiency : `numpy.ndarray`
|
|
1945
|
+
Efficiency of all pumps.
|
|
1946
|
+
pumps_energyconsumption : `numpy.ndarray`
|
|
1947
|
+
Energy consumption of all pumps.
|
|
1801
1948
|
valves_state : `numpy.ndarray`
|
|
1802
1949
|
States of all valves.
|
|
1803
1950
|
tanks_volume : `numpy.ndarray`
|
|
@@ -1874,6 +2021,20 @@ class SensorConfig(JsonSerializable):
|
|
|
1874
2021
|
raise ValueError("Pump states readings requested " +
|
|
1875
2022
|
"but no pump state data is given")
|
|
1876
2023
|
|
|
2024
|
+
if pumps_efficiency is not None:
|
|
2025
|
+
data.append(pumps_efficiency[:, self.__pump_efficiency_idx])
|
|
2026
|
+
else:
|
|
2027
|
+
if len(self.__pump_efficiency_sensors) != 0:
|
|
2028
|
+
raise ValueError("Pump efficiency readings requested " +
|
|
2029
|
+
"but no pump efficiency data is given")
|
|
2030
|
+
|
|
2031
|
+
if pumps_energyconsumption is not None:
|
|
2032
|
+
data.append(pumps_energyconsumption[:, self.__pump_energyconsumption_idx])
|
|
2033
|
+
else:
|
|
2034
|
+
if len(self.__pump_energyconsumption_sensors) != 0:
|
|
2035
|
+
raise ValueError("Pump energy consumption readings requested " +
|
|
2036
|
+
"but no pump energy consumption data is given")
|
|
2037
|
+
|
|
1877
2038
|
if tanks_volume is not None:
|
|
1878
2039
|
data.append(tanks_volume[:, self.__tank_volume_idx])
|
|
1879
2040
|
else:
|
|
@@ -1913,7 +2074,9 @@ class SensorConfig(JsonSerializable):
|
|
|
1913
2074
|
def get_index_of_reading(self, pressure_sensor: str = None, flow_sensor: str = None,
|
|
1914
2075
|
demand_sensor: str = None, node_quality_sensor: str = None,
|
|
1915
2076
|
link_quality_sensor: str = None, valve_state_sensor: str = None,
|
|
1916
|
-
pump_state_sensor: str = None,
|
|
2077
|
+
pump_state_sensor: str = None, pump_efficiency_sensor: str = None,
|
|
2078
|
+
pump_energyconsumption_sensor: str = None,
|
|
2079
|
+
tank_volume_sensor: str = None,
|
|
1917
2080
|
bulk_species_node_sensor: tuple[str, str] = None,
|
|
1918
2081
|
bulk_species_link_sensor: tuple[str, str] = None,
|
|
1919
2082
|
surface_species_sensor: tuple[str, str] = None) -> int:
|
|
@@ -1943,6 +2106,10 @@ class SensorConfig(JsonSerializable):
|
|
|
1943
2106
|
ID of the state sensor (at a valve).
|
|
1944
2107
|
pump_state_sensor : `str`
|
|
1945
2108
|
ID of the state sensor (at a pump).
|
|
2109
|
+
pump_efficiency_sensor : `str`
|
|
2110
|
+
ID of the efficiency sensor (at a pump).
|
|
2111
|
+
pump_energyconsumption_sensor : `str`
|
|
2112
|
+
ID of the energy consumption sensor (at a pump).
|
|
1946
2113
|
tank_volume_sensor : `str`
|
|
1947
2114
|
ID of the water volume sensor (at a tank)
|
|
1948
2115
|
bulk_species_node_sensor : `tuple[str, str]`
|
|
@@ -1966,6 +2133,10 @@ class SensorConfig(JsonSerializable):
|
|
|
1966
2133
|
return self.__sensors_id_to_idx["valve_state"][valve_state_sensor]
|
|
1967
2134
|
elif pump_state_sensor is not None:
|
|
1968
2135
|
return self.__sensors_id_to_idx["pump_state"][pump_state_sensor]
|
|
2136
|
+
elif pump_efficiency_sensor is not None:
|
|
2137
|
+
return self.__sensors_id_to_idx["pump_efficiency"][pump_efficiency_sensor]
|
|
2138
|
+
elif pump_energyconsumption_sensor is not None:
|
|
2139
|
+
return self.__sensors_id_to_idx["pump_energyconsumption"][pump_energyconsumption_sensor]
|
|
1969
2140
|
elif tank_volume_sensor is not None:
|
|
1970
2141
|
return self.__sensors_id_to_idx["tank_volume"][tank_volume_sensor]
|
|
1971
2142
|
elif surface_species_sensor is not None:
|
epyt_flow/topology.py
CHANGED
|
@@ -188,6 +188,17 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
188
188
|
"""
|
|
189
189
|
return [node_id for node_id, _ in self.__nodes]
|
|
190
190
|
|
|
191
|
+
def get_number_of_nodes(self) -> int:
|
|
192
|
+
"""
|
|
193
|
+
Returns the number of nodes.
|
|
194
|
+
|
|
195
|
+
Returns
|
|
196
|
+
-------
|
|
197
|
+
`int`
|
|
198
|
+
Number of nodes.
|
|
199
|
+
"""
|
|
200
|
+
return len(self.get_all_nodes())
|
|
201
|
+
|
|
191
202
|
def get_all_links(self) -> list[tuple[str, tuple[str, str]]]:
|
|
192
203
|
"""
|
|
193
204
|
Gets a list of all links/pipes (incl. their end points).
|
|
@@ -199,6 +210,17 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
199
210
|
"""
|
|
200
211
|
return [(link_id, end_points) for link_id, end_points, _ in self.__links]
|
|
201
212
|
|
|
213
|
+
def get_number_of_links(self) -> int:
|
|
214
|
+
"""
|
|
215
|
+
Returns the number of links.
|
|
216
|
+
|
|
217
|
+
Returns
|
|
218
|
+
-------
|
|
219
|
+
`int`
|
|
220
|
+
Number of links.
|
|
221
|
+
"""
|
|
222
|
+
return len(self.get_all_links())
|
|
223
|
+
|
|
202
224
|
def get_all_junctions(self) -> list[str]:
|
|
203
225
|
"""
|
|
204
226
|
Gets all junctions -- i.e. nodes that are not tanks or reservoirs.
|
|
@@ -216,6 +238,17 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
216
238
|
|
|
217
239
|
return r
|
|
218
240
|
|
|
241
|
+
def get_number_of_junctions(self) -> int:
|
|
242
|
+
"""
|
|
243
|
+
Returns the number of junctions.
|
|
244
|
+
|
|
245
|
+
Returns
|
|
246
|
+
-------
|
|
247
|
+
`int`
|
|
248
|
+
Number of junctions.
|
|
249
|
+
"""
|
|
250
|
+
return len(self.get_all_junctions())
|
|
251
|
+
|
|
219
252
|
def get_all_tanks(self) -> list[str]:
|
|
220
253
|
"""
|
|
221
254
|
Gets all tanks -- i.e. nodes that are not junctions or reservoirs.
|
|
@@ -233,6 +266,17 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
233
266
|
|
|
234
267
|
return r
|
|
235
268
|
|
|
269
|
+
def get_number_of_tanks(self) -> int:
|
|
270
|
+
"""
|
|
271
|
+
Returns the number of tanks.
|
|
272
|
+
|
|
273
|
+
Returns
|
|
274
|
+
-------
|
|
275
|
+
`int`
|
|
276
|
+
Number of tanks.
|
|
277
|
+
"""
|
|
278
|
+
return len(self.get_all_tanks())
|
|
279
|
+
|
|
236
280
|
def get_all_reservoirs(self) -> list[str]:
|
|
237
281
|
"""
|
|
238
282
|
Gets all reservoirs -- i.e. nodes that are not junctions or tanks.
|
|
@@ -250,6 +294,17 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
250
294
|
|
|
251
295
|
return r
|
|
252
296
|
|
|
297
|
+
def get_number_of_reservoirs(self) -> int:
|
|
298
|
+
"""
|
|
299
|
+
Returns the number of reservoirs.
|
|
300
|
+
|
|
301
|
+
Returns
|
|
302
|
+
-------
|
|
303
|
+
`int`
|
|
304
|
+
Number of reservoirs.
|
|
305
|
+
"""
|
|
306
|
+
return len(self.get_all_reservoirs())
|
|
307
|
+
|
|
253
308
|
def get_all_pipes(self) -> list[tuple[str, tuple[str, str]]]:
|
|
254
309
|
"""
|
|
255
310
|
Gets all pipes -- i.e. links that not valves or pumps.
|
|
@@ -269,6 +324,17 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
269
324
|
|
|
270
325
|
return r
|
|
271
326
|
|
|
327
|
+
def get_number_of_pipes(self) -> int:
|
|
328
|
+
"""
|
|
329
|
+
Returns the number of pipes.
|
|
330
|
+
|
|
331
|
+
Returns
|
|
332
|
+
-------
|
|
333
|
+
`int`
|
|
334
|
+
Number of pipes.
|
|
335
|
+
"""
|
|
336
|
+
return len(self.get_all_pipes())
|
|
337
|
+
|
|
272
338
|
def get_all_pumps(self) -> list[str]:
|
|
273
339
|
"""
|
|
274
340
|
Gets the IDs of all pumps.
|
|
@@ -278,7 +344,18 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
278
344
|
`list[str]`
|
|
279
345
|
Pump IDs.
|
|
280
346
|
"""
|
|
281
|
-
return self.__pumps.keys()
|
|
347
|
+
return list(self.__pumps.keys())
|
|
348
|
+
|
|
349
|
+
def get_number_of_pumps(self) -> int:
|
|
350
|
+
"""
|
|
351
|
+
Returns the number of pumps.
|
|
352
|
+
|
|
353
|
+
Returns
|
|
354
|
+
-------
|
|
355
|
+
`int`
|
|
356
|
+
Number of pumps.
|
|
357
|
+
"""
|
|
358
|
+
return len(self.get_all_pumps())
|
|
282
359
|
|
|
283
360
|
def get_all_valves(self) -> list[str]:
|
|
284
361
|
"""
|
|
@@ -289,7 +366,18 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
289
366
|
`list[str]`
|
|
290
367
|
Valve IDs.
|
|
291
368
|
"""
|
|
292
|
-
return self.__valves.keys()
|
|
369
|
+
return list(self.__valves.keys())
|
|
370
|
+
|
|
371
|
+
def get_number_of_valves(self) -> int:
|
|
372
|
+
"""
|
|
373
|
+
Returns the number of valves.
|
|
374
|
+
|
|
375
|
+
Returns
|
|
376
|
+
-------
|
|
377
|
+
`int`
|
|
378
|
+
Number of valves.
|
|
379
|
+
"""
|
|
380
|
+
return len(self.get_all_valves())
|
|
293
381
|
|
|
294
382
|
def get_node_info(self, node_id: str) -> dict:
|
|
295
383
|
"""
|
|
@@ -419,11 +507,11 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
419
507
|
|
|
420
508
|
return super().__eq__(other) and \
|
|
421
509
|
self.get_all_nodes() == other.get_all_nodes() \
|
|
422
|
-
and all(link_a[0] == link_b[0] and
|
|
510
|
+
and all(link_a[0] == link_b[0] and link_a[1] == link_b[1]
|
|
423
511
|
for link_a, link_b in zip(self.get_all_links(), other.get_all_links())) \
|
|
424
512
|
and self.__units == other.units \
|
|
425
|
-
and self.
|
|
426
|
-
and self.
|
|
513
|
+
and self.get_all_pumps() == other.get_all_pumps() \
|
|
514
|
+
and self.get_all_valves() == other.get_all_valves()
|
|
427
515
|
|
|
428
516
|
def __str__(self) -> str:
|
|
429
517
|
return f"f_inp: {self.name} nodes: {self.__nodes} links: {self.__links} " +\
|
|
@@ -334,8 +334,16 @@ class PercentageDeviationUncertainty(UniformUncertainty, JsonSerializable):
|
|
|
334
334
|
if not 0 < deviation_percentage < 1:
|
|
335
335
|
raise ValueError("'deviation_percentage' must be in (0,1)")
|
|
336
336
|
|
|
337
|
+
if "low" in kwds:
|
|
338
|
+
del kwds["low"]
|
|
339
|
+
if "high" in kwds:
|
|
340
|
+
del kwds["high"]
|
|
341
|
+
|
|
337
342
|
super().__init__(low=1. - deviation_percentage, high=1. + deviation_percentage, **kwds)
|
|
338
343
|
|
|
344
|
+
def get_attributes(self) -> dict:
|
|
345
|
+
return super().get_attributes() | {"deviation_percentage": self.high - 1.}
|
|
346
|
+
|
|
339
347
|
def apply(self, data: float) -> float:
|
|
340
348
|
data *= np.random.uniform(low=self.low, high=self.high)
|
|
341
349
|
|
epyt_flow/utils.py
CHANGED
|
@@ -68,7 +68,8 @@ def volume_to_level(tank_volume: float, tank_diameter: float) -> float:
|
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
def plot_timeseries_data(data: np.ndarray, labels: list[str] = None, x_axis_label: str = None,
|
|
71
|
-
y_axis_label: str = None,
|
|
71
|
+
y_axis_label: str = None, y_ticks: tuple[list[float], list[str]] = None,
|
|
72
|
+
show: bool = True, save_to_file: str = None,
|
|
72
73
|
ax: matplotlib.axes.Axes = None) -> matplotlib.axes.Axes:
|
|
73
74
|
"""
|
|
74
75
|
Plots a single or multiple time series.
|
|
@@ -89,6 +90,10 @@ def plot_timeseries_data(data: np.ndarray, labels: list[str] = None, x_axis_labe
|
|
|
89
90
|
y_axis_label : `str`, optional
|
|
90
91
|
Y axis label.
|
|
91
92
|
|
|
93
|
+
The default is None.
|
|
94
|
+
y_ticks: `(list[float], list[str])`, optional
|
|
95
|
+
Tuple of ticks (numbers) and labels (strings) for the y-axis.
|
|
96
|
+
|
|
92
97
|
The default is None.
|
|
93
98
|
show : `bool`, optional
|
|
94
99
|
If True, the plot/figure is shown in a window.
|
|
@@ -96,6 +101,13 @@ def plot_timeseries_data(data: np.ndarray, labels: list[str] = None, x_axis_labe
|
|
|
96
101
|
Only considered when 'ax' is None.
|
|
97
102
|
|
|
98
103
|
The default is True.
|
|
104
|
+
save_to_file : `str`, optional
|
|
105
|
+
File to which the plot is saved.
|
|
106
|
+
|
|
107
|
+
If specified, 'show' must be set to False --
|
|
108
|
+
i.e. a plot can not be shown and saved to a file at the same time!
|
|
109
|
+
|
|
110
|
+
The default is None.
|
|
99
111
|
ax : `matplotlib.axes.Axes`, optional
|
|
100
112
|
If not None, 'ax' is used for plotting.
|
|
101
113
|
|
|
@@ -122,8 +134,18 @@ def plot_timeseries_data(data: np.ndarray, labels: list[str] = None, x_axis_labe
|
|
|
122
134
|
if not isinstance(y_axis_label, str):
|
|
123
135
|
raise TypeError("'y_axis_label' must be an instance of 'str' " +
|
|
124
136
|
f"but not of '{type(y_axis_label)}'")
|
|
137
|
+
if y_ticks is not None:
|
|
138
|
+
if len(y_ticks) != 2:
|
|
139
|
+
raise ValueError("'y_ticks' must be a tuple ticks (numbers) and labels (strings)")
|
|
125
140
|
if not isinstance(show, bool):
|
|
126
141
|
raise TypeError(f"'show' must be an instance of 'bool' but not of '{type(show)}'")
|
|
142
|
+
if save_to_file is not None:
|
|
143
|
+
if show is True:
|
|
144
|
+
raise ValueError("'show' must be False if 'save_to_file' is set")
|
|
145
|
+
|
|
146
|
+
if not isinstance(save_to_file, str):
|
|
147
|
+
raise TypeError("'save_to_file' must be an instance of 'str' but not of " +
|
|
148
|
+
f"'{type(save_to_file)}'")
|
|
127
149
|
if ax is not None:
|
|
128
150
|
if not isinstance(ax, matplotlib.axes.Axes):
|
|
129
151
|
raise TypeError("ax' must be an instance of 'matplotlib.axes.Axes'" +
|
|
@@ -145,9 +167,20 @@ def plot_timeseries_data(data: np.ndarray, labels: list[str] = None, x_axis_labe
|
|
|
145
167
|
ax.set_xlabel(x_axis_label)
|
|
146
168
|
if y_axis_label is not None:
|
|
147
169
|
ax.set_ylabel(y_axis_label)
|
|
170
|
+
if y_ticks is not None:
|
|
171
|
+
yticks_pos, yticks_labels = y_ticks
|
|
172
|
+
ax.set_yticks(yticks_pos, labels=yticks_labels)
|
|
148
173
|
|
|
149
174
|
if show is True and fig is not None:
|
|
150
175
|
plt.show()
|
|
176
|
+
if save_to_file is not None:
|
|
177
|
+
folder_path = str(Path(save_to_file).parent.absolute())
|
|
178
|
+
create_path_if_not_exist(folder_path)
|
|
179
|
+
|
|
180
|
+
if fig is None:
|
|
181
|
+
plt.savefig(save_to_file, bbox_inches='tight')
|
|
182
|
+
else:
|
|
183
|
+
fig.savefig(save_to_file, bbox_inches='tight')
|
|
151
184
|
|
|
152
185
|
return ax
|
|
153
186
|
|
|
@@ -155,7 +188,9 @@ def plot_timeseries_data(data: np.ndarray, labels: list[str] = None, x_axis_labe
|
|
|
155
188
|
def plot_timeseries_prediction(y: np.ndarray, y_pred: np.ndarray,
|
|
156
189
|
confidence_interval: np.ndarray = None,
|
|
157
190
|
x_axis_label: str = None, y_axis_label: str = None,
|
|
158
|
-
|
|
191
|
+
y_ticks: tuple[list[float], list[str]] = None,
|
|
192
|
+
show: bool = True, save_to_file: str = None,
|
|
193
|
+
ax: matplotlib.axes.Axes = None
|
|
159
194
|
) -> matplotlib.axes.Axes:
|
|
160
195
|
"""
|
|
161
196
|
Plots the prediction (e.g. forecast) of *single* time series together with the
|
|
@@ -179,6 +214,10 @@ def plot_timeseries_prediction(y: np.ndarray, y_pred: np.ndarray,
|
|
|
179
214
|
y_axis_label : `str`, optional
|
|
180
215
|
Y axis label.
|
|
181
216
|
|
|
217
|
+
The default is None.
|
|
218
|
+
y_ticks: `(list[float], list[str])`, optional
|
|
219
|
+
Tuple of ticks (numbers) and labels (strings) for the y-axis.
|
|
220
|
+
|
|
182
221
|
The default is None.
|
|
183
222
|
show : `bool`, optional
|
|
184
223
|
If True, the plot/figure is shown in a window.
|
|
@@ -186,6 +225,13 @@ def plot_timeseries_prediction(y: np.ndarray, y_pred: np.ndarray,
|
|
|
186
225
|
Only considered when 'ax' is None.
|
|
187
226
|
|
|
188
227
|
The default is True.
|
|
228
|
+
save_to_file : `str`, optional
|
|
229
|
+
File to which the plot is saved.
|
|
230
|
+
|
|
231
|
+
If specified, 'show' must be set to False --
|
|
232
|
+
i.e. a plot can not be shown and saved to a file at the same time!
|
|
233
|
+
|
|
234
|
+
The default is None.
|
|
189
235
|
ax : `matplotlib.axes.Axes`, optional
|
|
190
236
|
If not None, 'axes' is used for plotting.
|
|
191
237
|
|
|
@@ -216,8 +262,18 @@ def plot_timeseries_prediction(y: np.ndarray, y_pred: np.ndarray,
|
|
|
216
262
|
if not isinstance(y_axis_label, str):
|
|
217
263
|
raise TypeError("'y_axis_label' must be an instance of 'str' " +
|
|
218
264
|
f"but not of '{type(y_axis_label)}'")
|
|
265
|
+
if y_ticks is not None:
|
|
266
|
+
if len(y_ticks) != 2:
|
|
267
|
+
raise ValueError("'y_ticks' must be a tuple ticks (numbers) and labels (strings)")
|
|
219
268
|
if not isinstance(show, bool):
|
|
220
269
|
raise TypeError(f"'show' must be an instance of 'bool' but not of '{type(show)}'")
|
|
270
|
+
if save_to_file is not None:
|
|
271
|
+
if show is True:
|
|
272
|
+
raise ValueError("'show' must be False if 'save_to_file' is set")
|
|
273
|
+
|
|
274
|
+
if not isinstance(save_to_file, str):
|
|
275
|
+
raise TypeError("'save_to_file' must be an instance of 'str' but not of " +
|
|
276
|
+
f"'{type(save_to_file)}'")
|
|
221
277
|
if ax is not None:
|
|
222
278
|
if not isinstance(ax, matplotlib.axes.Axes):
|
|
223
279
|
raise TypeError("ax' must be an instance of 'matplotlib.axes.Axes'" +
|
|
@@ -240,9 +296,20 @@ def plot_timeseries_prediction(y: np.ndarray, y_pred: np.ndarray,
|
|
|
240
296
|
ax.set_xlabel(x_axis_label)
|
|
241
297
|
if y_axis_label is not None:
|
|
242
298
|
ax.set_ylabel(y_axis_label)
|
|
299
|
+
if y_ticks is not None:
|
|
300
|
+
yticks_pos, yticks_labels = y_ticks
|
|
301
|
+
ax.set_yticks(yticks_pos, labels=yticks_labels)
|
|
243
302
|
|
|
244
303
|
if show is True and fig is not None:
|
|
245
304
|
plt.show()
|
|
305
|
+
if save_to_file is not None:
|
|
306
|
+
folder_path = str(Path(save_to_file).parent.absolute())
|
|
307
|
+
create_path_if_not_exist(folder_path)
|
|
308
|
+
|
|
309
|
+
if fig is None:
|
|
310
|
+
plt.savefig(save_to_file, bbox_inches='tight')
|
|
311
|
+
else:
|
|
312
|
+
fig.savefig(save_to_file, bbox_inches='tight')
|
|
246
313
|
|
|
247
314
|
return ax
|
|
248
315
|
|