epyt-flow 0.4.0__py3-none-any.whl → 0.6.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/serialization.py +13 -3
- epyt_flow/simulation/scada/advanced_control.py +1 -1
- epyt_flow/simulation/scada/scada_data.py +159 -55
- epyt_flow/simulation/scenario_config.py +6 -2
- epyt_flow/simulation/scenario_simulator.py +274 -64
- epyt_flow/simulation/scenario_visualizer.py +1 -1
- epyt_flow/simulation/sensor_config.py +222 -15
- epyt_flow/topology.py +162 -4
- epyt_flow/utils.py +25 -2
- {epyt_flow-0.4.0.dist-info → epyt_flow-0.6.0.dist-info}/METADATA +2 -2
- {epyt_flow-0.4.0.dist-info → epyt_flow-0.6.0.dist-info}/RECORD +19 -19
- {epyt_flow-0.4.0.dist-info → epyt_flow-0.6.0.dist-info}/WHEEL +1 -1
- {epyt_flow-0.4.0.dist-info → epyt_flow-0.6.0.dist-info}/LICENSE +0 -0
- {epyt_flow-0.4.0.dist-info → epyt_flow-0.6.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
|
|
@@ -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
|
"""
|
|
@@ -1647,6 +1779,46 @@ class SensorConfig(JsonSerializable):
|
|
|
1647
1779
|
"""
|
|
1648
1780
|
return deepcopy(self.__sensors_id_to_idx)
|
|
1649
1781
|
|
|
1782
|
+
def get_as_dict(self) -> dict:
|
|
1783
|
+
"""
|
|
1784
|
+
Gets the sensor configuration as a dictionary.
|
|
1785
|
+
|
|
1786
|
+
Returns
|
|
1787
|
+
-------
|
|
1788
|
+
`dict`
|
|
1789
|
+
Dictionary of set sensors -- the keys are the sensor types.
|
|
1790
|
+
"""
|
|
1791
|
+
r = {}
|
|
1792
|
+
|
|
1793
|
+
if self.__pressure_sensors != []:
|
|
1794
|
+
r["pressure"] = self.__pressure_sensors
|
|
1795
|
+
if self.__flow_sensors != []:
|
|
1796
|
+
r["flow"] = self.__flow_sensors
|
|
1797
|
+
if self.__demand_sensors != []:
|
|
1798
|
+
r["demand"] = self.__demand_sensors
|
|
1799
|
+
if self.__tank_volume_sensors != []:
|
|
1800
|
+
r["tank_volume"] = self.__tank_volume_sensors
|
|
1801
|
+
if self.__valve_state_sensors != []:
|
|
1802
|
+
r["valve_state"] = self.__valve_state_sensors
|
|
1803
|
+
if self.__pump_state_sensors != []:
|
|
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
|
|
1809
|
+
if self.__quality_node_sensors != []:
|
|
1810
|
+
r["node_quality"] = self.__quality_node_sensors
|
|
1811
|
+
if self.__quality_link_sensors != []:
|
|
1812
|
+
r["link_quality"] = self.__quality_link_sensors
|
|
1813
|
+
if self.__bulk_species_node_sensors != {}:
|
|
1814
|
+
r["node_bulk_species"] = self.__bulk_species_node_sensors
|
|
1815
|
+
if self.__bulk_species_link_sensors != {}:
|
|
1816
|
+
r["link_bulk_species"] = self.__bulk_species_link_sensors
|
|
1817
|
+
if self.__surface_species_sensors != {}:
|
|
1818
|
+
r["surface_species"] = self.__surface_species_sensors
|
|
1819
|
+
|
|
1820
|
+
return r
|
|
1821
|
+
|
|
1650
1822
|
def get_attributes(self) -> dict:
|
|
1651
1823
|
attr = {"nodes": self.__nodes, "links": self.__links,
|
|
1652
1824
|
"valves": self.__valves, "pumps": self.__pumps,
|
|
@@ -1659,6 +1831,8 @@ class SensorConfig(JsonSerializable):
|
|
|
1659
1831
|
"quality_link_sensors": self.__quality_link_sensors,
|
|
1660
1832
|
"valve_state_sensors": self.__valve_state_sensors,
|
|
1661
1833
|
"pump_state_sensors": self.__pump_state_sensors,
|
|
1834
|
+
"pump_efficiency_sensors": self.__pump_efficiency_sensors,
|
|
1835
|
+
"pump_energyconsumption_sensors": self.__pump_energyconsumption_sensors,
|
|
1662
1836
|
"tank_volume_sensors": self.__tank_volume_sensors,
|
|
1663
1837
|
"bulk_species_node_sensors": self.__bulk_species_node_sensors,
|
|
1664
1838
|
"bulk_species_link_sensors": self.__bulk_species_link_sensors,
|
|
@@ -1694,6 +1868,8 @@ class SensorConfig(JsonSerializable):
|
|
|
1694
1868
|
and self.__quality_link_sensors == other.quality_link_sensors \
|
|
1695
1869
|
and self.__valve_state_sensors == other.valve_state_sensors \
|
|
1696
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 \
|
|
1697
1873
|
and self.__tank_volume_sensors == other.tank_volume_sensors \
|
|
1698
1874
|
and self.__bulk_species_node_sensors == other.bulk_species_node_sensors \
|
|
1699
1875
|
and self.__bulk_species_link_sensors == other.bulk_species_link_sensors \
|
|
@@ -1719,13 +1895,15 @@ class SensorConfig(JsonSerializable):
|
|
|
1719
1895
|
f"pump_id_to_idx: {self.__pump_id_to_idx} tank_id_to_idx: {self.__tank_id_to_idx} " +\
|
|
1720
1896
|
f"valve_id_to_idx: {self.__valve_id_to_idx} " +\
|
|
1721
1897
|
f"bulkspecies_id_to_idx: {self.__bulkspecies_id_to_idx} " +\
|
|
1722
|
-
f"surfacespecies_id_to_idx: {self.__surfacespecies_id_to_idx}" +\
|
|
1898
|
+
f"surfacespecies_id_to_idx: {self.__surfacespecies_id_to_idx} " +\
|
|
1723
1899
|
f"pressure_sensors: {self.__pressure_sensors} flow_sensors: {self.__flow_sensors} " +\
|
|
1724
1900
|
f"demand_sensors: {self.__demand_sensors} " +\
|
|
1725
1901
|
f"quality_node_sensors: {self.__quality_node_sensors} " +\
|
|
1726
1902
|
f"quality_link_sensors: {self.__quality_link_sensors} " +\
|
|
1727
1903
|
f"valve_state_sensors: {self.__valve_state_sensors} " +\
|
|
1728
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} " +\
|
|
1729
1907
|
f"tank_volume_sensors: {self.__tank_volume_sensors} " +\
|
|
1730
1908
|
f"bulk_species_node_sensors: {self.__bulk_species_node_sensors} " +\
|
|
1731
1909
|
f"bulk_species_link_sensors: {self.__bulk_species_link_sensors} " +\
|
|
@@ -1740,7 +1918,8 @@ class SensorConfig(JsonSerializable):
|
|
|
1740
1918
|
|
|
1741
1919
|
def compute_readings(self, pressures: np.ndarray, flows: np.ndarray, demands: np.ndarray,
|
|
1742
1920
|
nodes_quality: np.ndarray, links_quality: np.ndarray,
|
|
1743
|
-
pumps_state: np.ndarray,
|
|
1921
|
+
pumps_state: np.ndarray, pumps_efficiency: np.ndarray,
|
|
1922
|
+
pumps_energyconsumption: np.ndarray, valves_state: np.ndarray,
|
|
1744
1923
|
tanks_volume: np.ndarray, bulk_species_node_concentrations: np.ndarray,
|
|
1745
1924
|
bulk_species_link_concentrations: np.ndarray,
|
|
1746
1925
|
surface_species_concentrations: np.ndarray) -> np.ndarray:
|
|
@@ -1762,6 +1941,10 @@ class SensorConfig(JsonSerializable):
|
|
|
1762
1941
|
Quality values at all links/pipes.
|
|
1763
1942
|
pumps_state : `numpy.ndarray`
|
|
1764
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.
|
|
1765
1948
|
valves_state : `numpy.ndarray`
|
|
1766
1949
|
States of all valves.
|
|
1767
1950
|
tanks_volume : `numpy.ndarray`
|
|
@@ -1838,6 +2021,20 @@ class SensorConfig(JsonSerializable):
|
|
|
1838
2021
|
raise ValueError("Pump states readings requested " +
|
|
1839
2022
|
"but no pump state data is given")
|
|
1840
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
|
+
|
|
1841
2038
|
if tanks_volume is not None:
|
|
1842
2039
|
data.append(tanks_volume[:, self.__tank_volume_idx])
|
|
1843
2040
|
else:
|
|
@@ -1877,7 +2074,9 @@ class SensorConfig(JsonSerializable):
|
|
|
1877
2074
|
def get_index_of_reading(self, pressure_sensor: str = None, flow_sensor: str = None,
|
|
1878
2075
|
demand_sensor: str = None, node_quality_sensor: str = None,
|
|
1879
2076
|
link_quality_sensor: str = None, valve_state_sensor: str = None,
|
|
1880
|
-
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,
|
|
1881
2080
|
bulk_species_node_sensor: tuple[str, str] = None,
|
|
1882
2081
|
bulk_species_link_sensor: tuple[str, str] = None,
|
|
1883
2082
|
surface_species_sensor: tuple[str, str] = None) -> int:
|
|
@@ -1907,6 +2106,10 @@ class SensorConfig(JsonSerializable):
|
|
|
1907
2106
|
ID of the state sensor (at a valve).
|
|
1908
2107
|
pump_state_sensor : `str`
|
|
1909
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).
|
|
1910
2113
|
tank_volume_sensor : `str`
|
|
1911
2114
|
ID of the water volume sensor (at a tank)
|
|
1912
2115
|
bulk_species_node_sensor : `tuple[str, str]`
|
|
@@ -1930,6 +2133,10 @@ class SensorConfig(JsonSerializable):
|
|
|
1930
2133
|
return self.__sensors_id_to_idx["valve_state"][valve_state_sensor]
|
|
1931
2134
|
elif pump_state_sensor is not None:
|
|
1932
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]
|
|
1933
2140
|
elif tank_volume_sensor is not None:
|
|
1934
2141
|
return self.__sensors_id_to_idx["tank_volume"][tank_volume_sensor]
|
|
1935
2142
|
elif surface_species_sensor is not None:
|
epyt_flow/topology.py
CHANGED
|
@@ -108,7 +108,7 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
108
108
|
|
|
109
109
|
def convert_units(self, units: int) -> Any:
|
|
110
110
|
"""
|
|
111
|
-
Converts this instance to a :class
|
|
111
|
+
Converts this instance to a :class:`~epyt_flow.topology.NetworkTopology` instance
|
|
112
112
|
where everything is measured in given measurement units category
|
|
113
113
|
(US Customary or SI Metric).
|
|
114
114
|
|
|
@@ -124,7 +124,7 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
124
124
|
|
|
125
125
|
Returns
|
|
126
126
|
-------
|
|
127
|
-
:class
|
|
127
|
+
:class:`~epyt_flow.topology.NetworkTopology`
|
|
128
128
|
Network topology with the new measurements units.
|
|
129
129
|
"""
|
|
130
130
|
if self.__units is None:
|
|
@@ -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,131 @@ 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
|
+
|
|
224
|
+
def get_all_junctions(self) -> list[str]:
|
|
225
|
+
"""
|
|
226
|
+
Gets all junctions -- i.e. nodes that are not tanks or reservoirs.
|
|
227
|
+
|
|
228
|
+
Returns
|
|
229
|
+
-------
|
|
230
|
+
`list[str]`
|
|
231
|
+
List of all junctions.
|
|
232
|
+
"""
|
|
233
|
+
r = []
|
|
234
|
+
|
|
235
|
+
for node_id in self.get_all_nodes():
|
|
236
|
+
if self.get_node_info(node_id)["type"] == "JUNCTION":
|
|
237
|
+
r.append(node_id)
|
|
238
|
+
|
|
239
|
+
return r
|
|
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
|
+
|
|
252
|
+
def get_all_tanks(self) -> list[str]:
|
|
253
|
+
"""
|
|
254
|
+
Gets all tanks -- i.e. nodes that are not junctions or reservoirs.
|
|
255
|
+
|
|
256
|
+
Returns
|
|
257
|
+
-------
|
|
258
|
+
`list[str]`
|
|
259
|
+
List of all tanks.
|
|
260
|
+
"""
|
|
261
|
+
r = []
|
|
262
|
+
|
|
263
|
+
for node_id in self.get_all_nodes():
|
|
264
|
+
if self.get_node_info(node_id)["type"] == "TANK":
|
|
265
|
+
r.append(node_id)
|
|
266
|
+
|
|
267
|
+
return r
|
|
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
|
+
|
|
280
|
+
def get_all_reservoirs(self) -> list[str]:
|
|
281
|
+
"""
|
|
282
|
+
Gets all reservoirs -- i.e. nodes that are not junctions or tanks.
|
|
283
|
+
|
|
284
|
+
Returns
|
|
285
|
+
-------
|
|
286
|
+
`list[str]`
|
|
287
|
+
List of all reservoirs.
|
|
288
|
+
"""
|
|
289
|
+
r = []
|
|
290
|
+
|
|
291
|
+
for node_id in self.get_all_nodes():
|
|
292
|
+
if self.get_node_info(node_id)["type"] == "RESERVOIR":
|
|
293
|
+
r.append(node_id)
|
|
294
|
+
|
|
295
|
+
return r
|
|
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
|
+
|
|
308
|
+
def get_all_pipes(self) -> list[tuple[str, tuple[str, str]]]:
|
|
309
|
+
"""
|
|
310
|
+
Gets all pipes -- i.e. links that not valves or pumps.
|
|
311
|
+
|
|
312
|
+
Returns
|
|
313
|
+
-------
|
|
314
|
+
`list[tuple[str, tuple[str, str]]]`
|
|
315
|
+
List of all pipes -- (link ID, (left node ID, right node ID)).
|
|
316
|
+
"""
|
|
317
|
+
r = []
|
|
318
|
+
|
|
319
|
+
for link_id, link_nodes in self.get_all_links():
|
|
320
|
+
link_info = self.get_link_info(link_id)
|
|
321
|
+
|
|
322
|
+
if link_info["type"] == "PIPE":
|
|
323
|
+
r.append((link_id, link_nodes))
|
|
324
|
+
|
|
325
|
+
return r
|
|
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
|
+
|
|
202
338
|
def get_all_pumps(self) -> list[str]:
|
|
203
339
|
"""
|
|
204
340
|
Gets the IDs of all pumps.
|
|
@@ -208,7 +344,18 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
208
344
|
`list[str]`
|
|
209
345
|
Pump IDs.
|
|
210
346
|
"""
|
|
211
|
-
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())
|
|
212
359
|
|
|
213
360
|
def get_all_valves(self) -> list[str]:
|
|
214
361
|
"""
|
|
@@ -219,7 +366,18 @@ class NetworkTopology(nx.Graph, JsonSerializable):
|
|
|
219
366
|
`list[str]`
|
|
220
367
|
Valve IDs.
|
|
221
368
|
"""
|
|
222
|
-
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())
|
|
223
381
|
|
|
224
382
|
def get_node_info(self, node_id: str) -> dict:
|
|
225
383
|
"""
|
epyt_flow/utils.py
CHANGED
|
@@ -153,8 +153,10 @@ def plot_timeseries_data(data: np.ndarray, labels: list[str] = None, x_axis_labe
|
|
|
153
153
|
|
|
154
154
|
|
|
155
155
|
def plot_timeseries_prediction(y: np.ndarray, y_pred: np.ndarray,
|
|
156
|
-
confidence_interval: np.ndarray = None,
|
|
157
|
-
|
|
156
|
+
confidence_interval: np.ndarray = None,
|
|
157
|
+
x_axis_label: str = None, y_axis_label: str = None,
|
|
158
|
+
show: bool = True, ax: matplotlib.axes.Axes = None
|
|
159
|
+
) -> matplotlib.axes.Axes:
|
|
158
160
|
"""
|
|
159
161
|
Plots the prediction (e.g. forecast) of *single* time series together with the
|
|
160
162
|
ground truth time series. In addition, confidence intervals can be plotted as well.
|
|
@@ -169,6 +171,14 @@ def plot_timeseries_prediction(y: np.ndarray, y_pred: np.ndarray,
|
|
|
169
171
|
Confidence interval (upper and lower value) for each prediction in `y_pred`.
|
|
170
172
|
If not None, the confidence interval is plotted as well.
|
|
171
173
|
|
|
174
|
+
The default is None.
|
|
175
|
+
x_axis_label : `str`, optional
|
|
176
|
+
X axis label.
|
|
177
|
+
|
|
178
|
+
The default is None.
|
|
179
|
+
y_axis_label : `str`, optional
|
|
180
|
+
Y axis label.
|
|
181
|
+
|
|
172
182
|
The default is None.
|
|
173
183
|
show : `bool`, optional
|
|
174
184
|
If True, the plot/figure is shown in a window.
|
|
@@ -198,6 +208,14 @@ def plot_timeseries_prediction(y: np.ndarray, y_pred: np.ndarray,
|
|
|
198
208
|
raise ValueError("'y_pred' must be a 1d array")
|
|
199
209
|
if len(y.shape) != 1:
|
|
200
210
|
raise ValueError("'y' must be a 1d array")
|
|
211
|
+
if x_axis_label is not None:
|
|
212
|
+
if not isinstance(x_axis_label, str):
|
|
213
|
+
raise TypeError("'x_axis_label' must be an instance of 'str' " +
|
|
214
|
+
f"but not of '{type(x_axis_label)}'")
|
|
215
|
+
if y_axis_label is not None:
|
|
216
|
+
if not isinstance(y_axis_label, str):
|
|
217
|
+
raise TypeError("'y_axis_label' must be an instance of 'str' " +
|
|
218
|
+
f"but not of '{type(y_axis_label)}'")
|
|
201
219
|
if not isinstance(show, bool):
|
|
202
220
|
raise TypeError(f"'show' must be an instance of 'bool' but not of '{type(show)}'")
|
|
203
221
|
if ax is not None:
|
|
@@ -218,6 +236,11 @@ def plot_timeseries_prediction(y: np.ndarray, y_pred: np.ndarray,
|
|
|
218
236
|
ax.plot(y_pred, ".-", label="Prediction")
|
|
219
237
|
ax.legend()
|
|
220
238
|
|
|
239
|
+
if x_axis_label is not None:
|
|
240
|
+
ax.set_xlabel(x_axis_label)
|
|
241
|
+
if y_axis_label is not None:
|
|
242
|
+
ax.set_ylabel(y_axis_label)
|
|
243
|
+
|
|
221
244
|
if show is True and fig is not None:
|
|
222
245
|
plt.show()
|
|
223
246
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: epyt-flow
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: EPyT-Flow -- EPANET Python Toolkit - Flow
|
|
5
5
|
Author-email: André Artelt <aartelt@techfak.uni-bielefeld.de>, "Marios S. Kyriakou" <kiriakou.marios@ucy.ac.cy>, "Stelios G. Vrachimis" <vrachimis.stelios@ucy.ac.cy>
|
|
6
6
|
License: MIT License
|
|
@@ -20,7 +20,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
20
20
|
Requires-Python: >=3.9
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
|
-
Requires-Dist: epyt >=1.1.
|
|
23
|
+
Requires-Dist: epyt >=1.1.9
|
|
24
24
|
Requires-Dist: requests >=2.31.0
|
|
25
25
|
Requires-Dist: scipy >=1.11.4
|
|
26
26
|
Requires-Dist: u-msgpack-python >=2.8.0
|