epyt-flow 0.15.0__py3-none-any.whl → 0.16.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.
@@ -5,6 +5,7 @@ from copy import deepcopy
5
5
  import itertools
6
6
  import numpy as np
7
7
  from epanet_plus import EpanetConstants, EPyT
8
+ from typing import Optional
8
9
 
9
10
  from ..serialization import SENSOR_CONFIG_ID, JsonSerializable, serializable
10
11
 
@@ -34,6 +35,25 @@ TIME_UNIT_HRS = 8
34
35
  MASS_UNIT_CUSTOM = 9
35
36
 
36
37
 
38
+ def valid_sensor_types() -> str:
39
+ """Returns a description of all valid sensor types."""
40
+ return (
41
+ f"{SENSOR_TYPE_NODE_PRESSURE} (Node Pressure), "
42
+ f"{SENSOR_TYPE_NODE_QUALITY} (Node Quality), "
43
+ f"{SENSOR_TYPE_NODE_DEMAND} (Node Demand), "
44
+ f"{SENSOR_TYPE_LINK_FLOW} (Link Flow), "
45
+ f"{SENSOR_TYPE_LINK_QUALITY} (Link Quality), "
46
+ f"{SENSOR_TYPE_VALVE_STATE} (Valve State), "
47
+ f"{SENSOR_TYPE_PUMP_STATE} (Pump State), "
48
+ f"{SENSOR_TYPE_TANK_VOLUME} (Tank Volume), "
49
+ f"{SENSOR_TYPE_NODE_BULK_SPECIES} (Node Bulk Species), "
50
+ f"{SENSOR_TYPE_LINK_BULK_SPECIES} (Link Bulk Species), "
51
+ f"{SENSOR_TYPE_SURFACE_SPECIES} (Surface Species), "
52
+ f"{SENSOR_TYPE_PUMP_EFFICIENCY} (Pump Efficiency), "
53
+ f"{SENSOR_TYPE_PUMP_ENERGYCONSUMPTION} (Pump Energy Consumption)"
54
+ )
55
+
56
+
37
57
  def areaunit_to_id(unit_desc: str) -> int:
38
58
  """
39
59
  Converts a given area units string to the corresponding ID.
@@ -754,6 +774,21 @@ class SensorConfig(JsonSerializable):
754
774
  self.__bulk_species_mass_unit = bulk_species_mass_unit
755
775
  self.__surface_species_mass_unit = surface_species_mass_unit
756
776
  self.__surface_species_area_unit = surface_species_area_unit
777
+ self.__sensor_ordering = [
778
+ SENSOR_TYPE_NODE_PRESSURE,
779
+ SENSOR_TYPE_LINK_FLOW,
780
+ SENSOR_TYPE_NODE_DEMAND,
781
+ SENSOR_TYPE_NODE_QUALITY,
782
+ SENSOR_TYPE_LINK_QUALITY,
783
+ SENSOR_TYPE_VALVE_STATE,
784
+ SENSOR_TYPE_PUMP_STATE,
785
+ SENSOR_TYPE_PUMP_EFFICIENCY,
786
+ SENSOR_TYPE_PUMP_ENERGYCONSUMPTION,
787
+ SENSOR_TYPE_TANK_VOLUME,
788
+ SENSOR_TYPE_SURFACE_SPECIES,
789
+ SENSOR_TYPE_NODE_BULK_SPECIES,
790
+ SENSOR_TYPE_LINK_BULK_SPECIES,
791
+ ]
757
792
 
758
793
  self.__compute_indices() # Compute indices
759
794
 
@@ -952,6 +987,29 @@ class SensorConfig(JsonSerializable):
952
987
  """
953
988
  return self.__surfacespecies_id_to_idx
954
989
 
990
+ @property
991
+ def sensor_ordering(self) -> list[int]:
992
+ """
993
+ Returns the order in which sensors are included in ScadaData objects
994
+ i.e. if you call a ScadaData's get_data() method, the resulting array
995
+ will contain sensor readings in the order returned by this method.
996
+ Constants have the following meaning:
997
+ 1 -> pressure sensor
998
+ 2 -> node quality sensor
999
+ 3 -> demand sensor
1000
+ 4 -> flow sensor
1001
+ 5 -> link quality sensor
1002
+ 6 -> valve state sensor
1003
+ 7 -> pump state sensor
1004
+ 8 -> tank volume sensor
1005
+ 9 -> node bulk species sensor
1006
+ 10 -> link bulk species sensor
1007
+ 11 -> surface species sensor
1008
+ 12 -> pump efficiency sensor
1009
+ 13 -> pump energy consumption sensor
1010
+ """
1011
+ return self.__sensor_ordering
1012
+
955
1013
  def map_node_id_to_idx(self, node_id: str) -> int:
956
1014
  """
957
1015
  Gets the index of a given node ID.
@@ -1139,20 +1197,55 @@ class SensorConfig(JsonSerializable):
1139
1197
  *self.__bulk_species_node_sensors.values())))
1140
1198
  n_bulk_species_link_sensors = len(list(itertools.chain(
1141
1199
  *self.__bulk_species_link_sensors.values())))
1142
-
1143
- pressure_idx_shift = 0
1144
- flow_idx_shift = pressure_idx_shift + n_pressure_sensors
1145
- demand_idx_shift = flow_idx_shift + n_flow_sensors
1146
- node_quality_idx_shift = demand_idx_shift + n_demand_sensors
1147
- link_quality_idx_shift = node_quality_idx_shift + n_node_quality_sensors
1148
- valve_state_idx_shift = link_quality_idx_shift + n_link_quality_sensors
1149
- pump_state_idx_shift = valve_state_idx_shift + n_valve_state_sensors
1150
- pump_efficiency_idx_shift = pump_state_idx_shift + n_pump_state_sensors
1151
- pump_energyconsumption_idx_shift = pump_efficiency_idx_shift + n_pump_efficiency_sensors
1152
- tank_volume_idx_shift = pump_energyconsumption_idx_shift + n_pump_energyconsumption_sensors
1153
- bulk_species_node_idx_shift = tank_volume_idx_shift + n_tank_volume_sensors
1154
- bulk_species_link_idx_shift = bulk_species_node_idx_shift + n_bulk_species_node_sensors
1155
- surface_species_idx_shift = bulk_species_link_idx_shift + n_bulk_species_link_sensors
1200
+ n_surface_species_sensors = len(list(itertools.chain(
1201
+ *self.__surface_species_sensors.values())))
1202
+
1203
+ current_shift = 0
1204
+ for sensor_type in self.__sensor_ordering:
1205
+ if sensor_type == SENSOR_TYPE_NODE_PRESSURE:
1206
+ pressure_idx_shift = current_shift
1207
+ current_shift += n_pressure_sensors
1208
+ elif sensor_type == SENSOR_TYPE_LINK_FLOW:
1209
+ flow_idx_shift = current_shift
1210
+ current_shift += n_flow_sensors
1211
+ elif sensor_type == SENSOR_TYPE_NODE_QUALITY:
1212
+ node_quality_idx_shift = current_shift
1213
+ current_shift += n_node_quality_sensors
1214
+ elif sensor_type == SENSOR_TYPE_NODE_DEMAND:
1215
+ demand_idx_shift = current_shift
1216
+ current_shift += n_demand_sensors
1217
+ elif sensor_type == SENSOR_TYPE_LINK_QUALITY:
1218
+ link_quality_idx_shift = current_shift
1219
+ current_shift += n_link_quality_sensors
1220
+ elif sensor_type == SENSOR_TYPE_VALVE_STATE:
1221
+ valve_state_idx_shift = current_shift
1222
+ current_shift += n_valve_state_sensors
1223
+ elif sensor_type == SENSOR_TYPE_PUMP_STATE:
1224
+ pump_state_idx_shift = current_shift
1225
+ current_shift += n_pump_state_sensors
1226
+ elif sensor_type == SENSOR_TYPE_PUMP_EFFICIENCY:
1227
+ pump_efficiency_idx_shift = current_shift
1228
+ current_shift += n_pump_efficiency_sensors
1229
+ elif sensor_type == SENSOR_TYPE_PUMP_ENERGYCONSUMPTION:
1230
+ pump_energyconsumption_idx_shift = current_shift
1231
+ current_shift += n_pump_energyconsumption_sensors
1232
+ elif sensor_type == SENSOR_TYPE_TANK_VOLUME:
1233
+ tank_volume_idx_shift = current_shift
1234
+ current_shift += n_tank_volume_sensors
1235
+ elif sensor_type == SENSOR_TYPE_NODE_BULK_SPECIES:
1236
+ bulk_species_node_idx_shift = current_shift
1237
+ current_shift += n_bulk_species_node_sensors
1238
+ elif sensor_type == SENSOR_TYPE_LINK_BULK_SPECIES:
1239
+ bulk_species_link_idx_shift = current_shift
1240
+ current_shift += n_bulk_species_link_sensors
1241
+ elif sensor_type == SENSOR_TYPE_SURFACE_SPECIES:
1242
+ surface_species_idx_shift = current_shift
1243
+ current_shift += n_surface_species_sensors
1244
+ else:
1245
+ raise ValueError(
1246
+ f"Invalid sensor type: {sensor_type}. "
1247
+ f"Valid sensor types are:\n{valid_sensor_types()}"
1248
+ )
1156
1249
 
1157
1250
  def __build_sensors_id_to_idx(sensors: list[str], initial_idx_shift: int) -> dict:
1158
1251
  return {sensor_id: i + initial_idx_shift
@@ -1983,6 +2076,17 @@ class SensorConfig(JsonSerializable):
1983
2076
  return self.__surface_species_mass_unit[self.map_surfacespecies_id_to_idx(
1984
2077
  surface_species_id)]
1985
2078
 
2079
+ def _append_readings_if_possible(self, data: list, reading: Optional[np.ndarray], reading_idx: list, request_condition: bool, sensor_description: str) -> list:
2080
+ if reading is not None:
2081
+ data.append(reading[:, reading_idx])
2082
+ else:
2083
+ if request_condition:
2084
+ raise ValueError(
2085
+ f"{sensor_description} readings requested, "
2086
+ f"but no such data is given"
2087
+ )
2088
+ return data
2089
+
1986
2090
  def compute_readings(self, pressures: np.ndarray, flows: np.ndarray, demands: np.ndarray,
1987
2091
  nodes_quality: np.ndarray, links_quality: np.ndarray,
1988
2092
  pumps_state: np.ndarray, pumps_efficiency: np.ndarray,
@@ -2058,100 +2162,108 @@ class SensorConfig(JsonSerializable):
2058
2162
  """
2059
2163
  data = []
2060
2164
 
2061
- if pressures is not None:
2062
- data.append(pressures[:, self.__pressure_idx])
2063
- else:
2064
- if len(self.__pressure_sensors) != 0:
2065
- raise ValueError("Pressure readings requested but no pressure data is given")
2066
-
2067
- if flows is not None:
2068
- data.append(flows[:, self.__flow_idx])
2069
- else:
2070
- if len(self.__flow_sensors) != 0:
2071
- raise ValueError("Flow readings requested but no flow data is given")
2072
-
2073
- if demands is not None:
2074
- data.append(demands[:, self.__demand_idx])
2075
- else:
2076
- if len(self.__demand_sensors) != 0:
2077
- raise ValueError("Demand readings requested but no demand data is given")
2078
-
2079
- if nodes_quality is not None:
2080
- data.append(nodes_quality[:, self.__quality_node_idx])
2081
- else:
2082
- if len(self.__quality_node_sensors) != 0:
2083
- raise ValueError("Node water quality readings requested " +
2084
- "but no water quality data at nodes is given")
2085
-
2086
- if links_quality is not None:
2087
- data.append(links_quality[:, self.__quality_link_idx])
2088
- else:
2089
- if len(self.__quality_link_sensors) != 0:
2090
- raise ValueError("Link/Pipe water quality readings requested " +
2091
- "but no water quality data at links/pipes is given")
2092
-
2093
- if valves_state is not None:
2094
- data.append(valves_state[:, self.__valve_state_idx])
2095
- else:
2096
- if len(self.__valve_state_sensors) != 0:
2097
- raise ValueError("Valve states readings requested " +
2098
- "but no valve state data is given")
2099
-
2100
- if pumps_state is not None:
2101
- data.append(pumps_state[:, self.__pump_state_idx])
2102
- else:
2103
- if len(self.__pump_state_sensors) != 0:
2104
- raise ValueError("Pump states readings requested " +
2105
- "but no pump state data is given")
2106
-
2107
- if pumps_efficiency is not None:
2108
- data.append(pumps_efficiency[:, self.__pump_efficiency_idx])
2109
- else:
2110
- if len(self.__pump_efficiency_sensors) != 0:
2111
- raise ValueError("Pump efficiency readings requested " +
2112
- "but no pump efficiency data is given")
2113
-
2114
- if pumps_energyconsumption is not None:
2115
- data.append(pumps_energyconsumption[:, self.__pump_energyconsumption_idx])
2116
- else:
2117
- if len(self.__pump_energyconsumption_sensors) != 0:
2118
- raise ValueError("Pump energy consumption readings requested " +
2119
- "but no pump energy consumption data is given")
2120
-
2121
- if tanks_volume is not None:
2122
- data.append(tanks_volume[:, self.__tank_volume_idx])
2123
- else:
2124
- if len(self.__tank_volume_sensors) != 0:
2125
- raise ValueError("Water volumes in tanks is requested but no " +
2126
- "tank water volume data is given")
2127
-
2128
- if surface_species_concentrations is not None:
2129
- for species_idx, links_idx in self.__surface_species_idx:
2130
- data.append(surface_species_concentrations[:, species_idx, links_idx].
2131
- reshape(-1, len(links_idx)))
2132
- else:
2133
- if len(self.__surface_species_sensors) != 0:
2134
- raise ValueError("Surface species concentratinons requested but no " +
2135
- "surface species concentration data is given")
2136
-
2137
- if bulk_species_node_concentrations is not None:
2138
- for species_idx, nodes_idx in self.__bulk_species_node_idx:
2139
- data.append(bulk_species_node_concentrations[:, species_idx, nodes_idx].
2140
- reshape(-1, len(nodes_idx)))
2141
- else:
2142
- if len(self.__bulk_species_node_sensors) != 0:
2143
- raise ValueError("Bulk species concentratinons requested but no " +
2144
- "bulk species node concentration data is given")
2145
-
2146
- if bulk_species_link_concentrations is not None:
2147
- for species_idx, links_idx in self.__bulk_species_link_idx:
2148
- data.append(bulk_species_link_concentrations[:, species_idx, links_idx].
2149
- reshape(-1, len(links_idx)))
2150
- else:
2151
- if len(self.__bulk_species_link_sensors) != 0:
2152
- raise ValueError("Bulk species concentratinons requested but no " +
2153
- "bulk species link/pipe concentration data is given")
2154
-
2165
+ for sensor_type in self.sensor_ordering:
2166
+ if sensor_type==SENSOR_TYPE_NODE_PRESSURE:
2167
+ data = self._append_readings_if_possible(
2168
+ data, pressures, self.__pressure_idx,
2169
+ len(self.__pressure_sensors) != 0,
2170
+ "Pressure"
2171
+ )
2172
+ elif sensor_type==SENSOR_TYPE_NODE_QUALITY:
2173
+ data = self._append_readings_if_possible(
2174
+ data, nodes_quality, self.__quality_node_idx,
2175
+ len(self.__quality_node_sensors) != 0,
2176
+ "Node water quality"
2177
+ )
2178
+ elif sensor_type==SENSOR_TYPE_NODE_DEMAND:
2179
+ data = self._append_readings_if_possible(
2180
+ data, demands, self.__demand_idx,
2181
+ len(self.__demand_sensors) != 0,
2182
+ "Demand"
2183
+ )
2184
+ elif sensor_type==SENSOR_TYPE_LINK_FLOW:
2185
+ data = self._append_readings_if_possible(
2186
+ data, flows, self.__flow_idx,
2187
+ len(self.__flow_sensors) != 0,
2188
+ "Flow"
2189
+ )
2190
+ elif sensor_type==SENSOR_TYPE_LINK_QUALITY:
2191
+ data = self._append_readings_if_possible(
2192
+ data, links_quality, self.__quality_link_idx,
2193
+ len(self.__quality_link_sensors) != 0,
2194
+ "Link/Pipe water quality"
2195
+ )
2196
+ elif sensor_type==SENSOR_TYPE_VALVE_STATE:
2197
+ data = self._append_readings_if_possible(
2198
+ data, valves_state, self.__valve_state_idx,
2199
+ len(self.__valve_state_sensors) != 0,
2200
+ "Valve state"
2201
+ )
2202
+ elif sensor_type==SENSOR_TYPE_PUMP_STATE:
2203
+ data = self._append_readings_if_possible(
2204
+ data, pumps_state, self.__pump_state_idx,
2205
+ len(self.__pump_state_sensors) != 0,
2206
+ "Pump state"
2207
+ )
2208
+ elif sensor_type==SENSOR_TYPE_TANK_VOLUME:
2209
+ data = self._append_readings_if_possible(
2210
+ data, tanks_volume, self.__tank_volume_idx,
2211
+ len(self.__tank_volume_sensors) != 0,
2212
+ "Tank water volume"
2213
+ )
2214
+ elif sensor_type==SENSOR_TYPE_NODE_BULK_SPECIES:
2215
+ if bulk_species_node_concentrations is not None:
2216
+ for species_idx, nodes_idx in self.__bulk_species_node_idx:
2217
+ data.append(
2218
+ bulk_species_node_concentrations[
2219
+ :, species_idx, nodes_idx
2220
+ ].reshape(-1, len(nodes_idx))
2221
+ )
2222
+ else:
2223
+ if len(self.__bulk_species_node_sensors) != 0:
2224
+ raise ValueError("Bulk species concentratinons requested but no " +
2225
+ "bulk species node concentration data is given")
2226
+ elif sensor_type==SENSOR_TYPE_LINK_BULK_SPECIES:
2227
+ if bulk_species_link_concentrations is not None:
2228
+ for species_idx, links_idx in self.__bulk_species_link_idx:
2229
+ data.append(
2230
+ bulk_species_link_concentrations[
2231
+ :, species_idx, links_idx
2232
+ ].reshape(-1, len(links_idx))
2233
+ )
2234
+ else:
2235
+ if len(self.__bulk_species_link_sensors) != 0:
2236
+ raise ValueError("Bulk species concentratinons requested but no " +
2237
+ "bulk species link/pipe concentration data is given")
2238
+ elif sensor_type==SENSOR_TYPE_SURFACE_SPECIES:
2239
+ if surface_species_concentrations is not None:
2240
+ for species_idx, links_idx in self.__surface_species_idx:
2241
+ data.append(
2242
+ surface_species_concentrations[
2243
+ :, species_idx, links_idx
2244
+ ].reshape(-1, len(links_idx))
2245
+ )
2246
+ else:
2247
+ if len(self.__surface_species_sensors) != 0:
2248
+ raise ValueError("Surface species concentratinons requested but no " +
2249
+ "surface species concentration data is given")
2250
+ elif sensor_type==SENSOR_TYPE_PUMP_EFFICIENCY:
2251
+ data = self._append_readings_if_possible(
2252
+ data, pumps_efficiency, self.__pump_efficiency_idx,
2253
+ len(self.__pump_efficiency_sensors) != 0,
2254
+ "Pump efficiency"
2255
+ )
2256
+ elif sensor_type==SENSOR_TYPE_PUMP_ENERGYCONSUMPTION:
2257
+ data = self._append_readings_if_possible(
2258
+ data, pumps_energyconsumption, self.__pump_energyconsumption_idx,
2259
+ len(self.__pump_energyconsumption_sensors) != 0,
2260
+ "Pump energy consumption"
2261
+ )
2262
+ else:
2263
+ raise ValueError(
2264
+ f"Unknown sensor type '{sensor_type}'. "
2265
+ f"Valid sensor types are\n{valid_sensor_types()}"
2266
+ )
2155
2267
  return np.concatenate(data, axis=1)
2156
2268
 
2157
2269
  def get_index_of_reading(self, pressure_sensor: str = None, flow_sensor: str = None,