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.
@@ -10,7 +10,8 @@ import matplotlib
10
10
  import pandas as pd
11
11
  from epanet_plus import EpanetConstants
12
12
 
13
- from ..sensor_config import SensorConfig, is_flowunit_simetric, massunit_to_str, flowunit_to_str,\
13
+ from ..sensor_config import SensorConfig, valid_sensor_types, \
14
+ is_flowunit_simetric, massunit_to_str, flowunit_to_str,\
14
15
  qualityunit_to_str, areaunit_to_str,\
15
16
  MASS_UNIT_MG, MASS_UNIT_UG, TIME_UNIT_HRS, MASS_UNIT_MOL, MASS_UNIT_MMOL, \
16
17
  AREA_UNIT_CM2, AREA_UNIT_FT2, AREA_UNIT_M2, \
@@ -22,7 +23,7 @@ from ..sensor_config import SensorConfig, is_flowunit_simetric, massunit_to_str,
22
23
  from ..events import SensorFault, SensorReadingAttack, SensorReadingEvent
23
24
  from ...uncertainty import SensorNoise
24
25
  from ...serialization import serializable, Serializable, SCADA_DATA_ID
25
- from ...topology import NetworkTopology
26
+ from ...topology import NetworkTopology, UNITS_USCUSTOM, UNITS_SIMETRIC
26
27
  from ...utils import plot_timeseries_data
27
28
 
28
29
 
@@ -1056,7 +1057,15 @@ class ScadaData(Serializable):
1056
1057
  surface_species_mass_unit=new_surface_species_mass_unit,
1057
1058
  surface_species_area_unit=new_surface_species_area_unit)
1058
1059
 
1059
- return ScadaData(network_topo=self.network_topo,
1060
+ if flow_unit is not None:
1061
+ if is_flowunit_simetric(flow_unit):
1062
+ network_topo = self.network_topo.convert_units(UNITS_SIMETRIC)
1063
+ else:
1064
+ network_topo = self.network_topo.convert_units(UNITS_USCUSTOM)
1065
+ else:
1066
+ network_topo = self.network_topo
1067
+
1068
+ return ScadaData(network_topo=network_topo,
1060
1069
  warnings_code=self.warnings_code,
1061
1070
  sensor_config=sensor_config,
1062
1071
  sensor_readings_time=self.sensor_readings_time,
@@ -2141,33 +2150,52 @@ class ScadaData(Serializable):
2141
2150
  else:
2142
2151
  data = []
2143
2152
 
2144
- if self.__pressure_data_raw is not None:
2145
- data.append(self.__pressure_data_raw)
2146
- if self.__flow_data_raw is not None:
2147
- data.append(self.__flow_data_raw)
2148
- if self.__demand_data_raw is not None:
2149
- data.append(self.__demand_data_raw)
2150
- if self.__node_quality_data_raw is not None:
2151
- data.append(self.__node_quality_data_raw)
2152
- if self.__link_quality_data_raw is not None:
2153
- data.append(self.__link_quality_data_raw)
2154
- if self.__valves_state_data_raw is not None:
2155
- data.append(self.__valves_state_data_raw)
2156
- if self.__pumps_state_data_raw is not None:
2157
- data.append(self.__pumps_state_data_raw)
2158
- if self.__pumps_efficiency_data_raw is not None:
2159
- data.append(self.__pumps_efficiency_data_raw)
2160
- if self.__pumps_energy_usage_data_raw is not None:
2161
- data.append(self.__pumps_energy_usage_data_raw)
2162
- if self.__tanks_volume_data_raw is not None:
2163
- data.append(self.__tanks_volume_data_raw)
2164
- if self.__surface_species_concentration_raw is not None:
2165
- data.append(self.__surface_species_concentration_raw)
2166
- if self.__bulk_species_node_concentration_raw is not None:
2167
- data.append(self.__bulk_species_node_concentration_raw)
2168
- if self.__bulk_species_link_concentration_raw is not None:
2169
- data.append(self.__bulk_species_link_concentration_raw)
2170
-
2153
+ for sensor_type in self.__sensor_config.sensor_ordering:
2154
+ if sensor_type==SENSOR_TYPE_NODE_PRESSURE \
2155
+ and self.__pressure_data_raw is not None:
2156
+ data.append(self.__pressure_data_raw)
2157
+ elif sensor_type==SENSOR_TYPE_NODE_QUALITY \
2158
+ and self.__node_quality_data_raw is not None:
2159
+ data.append(self.__node_quality_data_raw)
2160
+ elif sensor_type==SENSOR_TYPE_NODE_DEMAND \
2161
+ and self.__demand_data_raw is not None:
2162
+ data.append(self.__demand_data_raw)
2163
+ elif sensor_type==SENSOR_TYPE_LINK_FLOW \
2164
+ and self.__flow_data_raw is not None:
2165
+ data.append(self.__flow_data_raw)
2166
+ elif sensor_type==SENSOR_TYPE_LINK_QUALITY \
2167
+ and self.__link_quality_data_raw is not None:
2168
+ data.append(self.__link_quality_data_raw)
2169
+ elif sensor_type==SENSOR_TYPE_VALVE_STATE \
2170
+ and self.__valves_state_data_raw is not None:
2171
+ data.append(self.__valves_state_data_raw)
2172
+ elif sensor_type==SENSOR_TYPE_PUMP_STATE \
2173
+ and self.__pumps_state_data_raw is not None:
2174
+ data.append(self.__pumps_state_data_raw)
2175
+ elif sensor_type==SENSOR_TYPE_TANK_VOLUME \
2176
+ and self.__tanks_volume_data_raw is not None:
2177
+ data.append(self.__tanks_volume_data_raw)
2178
+ elif sensor_type==SENSOR_TYPE_NODE_BULK_SPECIES \
2179
+ and self.__bulk_species_node_concentration_raw is not None:
2180
+ data.append(self.__bulk_species_node_concentration_raw)
2181
+ elif sensor_type==SENSOR_TYPE_LINK_BULK_SPECIES \
2182
+ and self.__bulk_species_link_concentration_raw is not None:
2183
+ data.append(self.__bulk_species_link_concentration_raw)
2184
+ elif sensor_type==SENSOR_TYPE_SURFACE_SPECIES \
2185
+ and self.__surface_species_concentration_raw is not None:
2186
+ data.append(self.__surface_species_concentration_raw)
2187
+ elif sensor_type==SENSOR_TYPE_PUMP_EFFICIENCY \
2188
+ and self.__pumps_efficiency_data_raw is not None:
2189
+ data.append(self.__pumps_efficiency_data_raw)
2190
+ elif sensor_type==SENSOR_TYPE_PUMP_ENERGYCONSUMPTION \
2191
+ and self.__pumps_energy_usage_data_raw is not None:
2192
+ data.append(self.__pumps_energy_usage_data_raw)
2193
+ elif sensor_type not in range(1,14):
2194
+ raise ValueError(
2195
+ f"Unknown sensor type '{sensor_type}' found in "
2196
+ f"'sensor_ordering'. Valid sensor types are:\n"
2197
+ f"{valid_sensor_types()}"
2198
+ )
2171
2199
  sensor_readings = np.concatenate(data, axis=1)
2172
2200
 
2173
2201
  # Apply sensor uncertainties
@@ -93,11 +93,11 @@ class SimpleControlModule(JsonSerializable):
93
93
  if cond_comp_value < 0:
94
94
  raise ValueError("'cond_comp_value' can not be negative")
95
95
 
96
- self.__link_id = link_id
97
- self.__link_status = link_status
98
- self.__cond_type = cond_type
99
- self.__cond_var_value = cond_var_value
100
- self.__cond_comp_value = cond_comp_value
96
+ self._link_id = link_id
97
+ self._link_status = link_status
98
+ self._cond_type = cond_type
99
+ self._cond_var_value = cond_var_value
100
+ self._cond_comp_value = cond_comp_value
101
101
 
102
102
  super().__init__(**kwds)
103
103
 
@@ -111,7 +111,7 @@ class SimpleControlModule(JsonSerializable):
111
111
  `str`
112
112
  Link ID.
113
113
  """
114
- return self.__link_id
114
+ return self._link_id
115
115
 
116
116
  @property
117
117
  def link_status(self) -> Union[int, float]:
@@ -130,7 +130,7 @@ class SimpleControlModule(JsonSerializable):
130
130
  - EN_SET_OPEN = 1e10
131
131
 
132
132
  """
133
- return self.__link_status
133
+ return self._link_status
134
134
 
135
135
  @property
136
136
  def cond_type(self) -> int:
@@ -147,7 +147,7 @@ class SimpleControlModule(JsonSerializable):
147
147
  - EN_TIMER = 2
148
148
  - EN_TIMEOFDAY = 3
149
149
  """
150
- return self.__cond_type
150
+ return self._cond_type
151
151
 
152
152
  @property
153
153
  def cond_var_value(self) -> Union[str, int]:
@@ -163,7 +163,7 @@ class SimpleControlModule(JsonSerializable):
163
163
  `str` or `int`
164
164
  Condition/rule variable value.
165
165
  """
166
- return self.__cond_var_value
166
+ return self._cond_var_value
167
167
 
168
168
  @property
169
169
  def cond_comp_value(self) -> float:
@@ -178,37 +178,37 @@ class SimpleControlModule(JsonSerializable):
178
178
  `float`
179
179
  Condition/Rule comparison value.
180
180
  """
181
- return self.__cond_comp_value
181
+ return self._cond_comp_value
182
182
 
183
183
  def get_attributes(self) -> dict:
184
- return super().get_attributes() | {"link_id": self.__link_id,
185
- "link_status": self.__link_status,
186
- "cond_type": self.__cond_type,
187
- "cond_var_value": self.__cond_var_value,
188
- "cond_comp_value": self.__cond_comp_value}
184
+ return super().get_attributes() | {"link_id": self._link_id,
185
+ "link_status": self._link_status,
186
+ "cond_type": self._cond_type,
187
+ "cond_var_value": self._cond_var_value,
188
+ "cond_comp_value": self._cond_comp_value}
189
189
 
190
190
  def __eq__(self, other) -> bool:
191
- return super().__eq__(other) and self.__link_id == other.link_id and \
192
- self.__link_status == other.link_status and self.__cond_type == other.cond_type and \
193
- self.__cond_var_value == other.cond_var_value and \
194
- self.__cond_comp_value == other.cond_comp_value
191
+ return super().__eq__(other) and self._link_id == other.link_id and \
192
+ self._link_status == other.link_status and self._cond_type == other.cond_type and \
193
+ self._cond_var_value == other.cond_var_value and \
194
+ self._cond_comp_value == other.cond_comp_value
195
195
 
196
196
  def __str__(self) -> str:
197
- control_rule_str = f"LINK {self.__link_id} "
198
- if isinstance(self.__link_status, int):
199
- control_rule_str += "OPEN " if self.__link_status == ActuatorConstants.EN_OPEN or \
200
- self.__link_status == ActuatorConstants.EN_SET_OPEN else "CLOSED "
197
+ control_rule_str = f"LINK {self._link_id} "
198
+ if isinstance(self._link_status, int):
199
+ control_rule_str += "OPEN " if self._link_status == ActuatorConstants.EN_OPEN or \
200
+ self._link_status == ActuatorConstants.EN_SET_OPEN else "CLOSED "
201
201
  else:
202
- control_rule_str += f"{self.__link_status} "
203
-
204
- if self.__cond_type == EpanetConstants.EN_TIMER:
205
- control_rule_str += f"AT TIME {self.__cond_var_value}"
206
- elif self.__cond_type == EpanetConstants.EN_TIMEOFDAY:
207
- control_rule_str += f"AT CLOCKTIME {self.__cond_var_value}"
208
- elif self.__cond_type == EpanetConstants.EN_LOWLEVEL:
209
- control_rule_str += f"IF NODE {self.__cond_var_value} BELOW {self.__cond_comp_value}"
210
- elif self.__cond_type == EpanetConstants.EN_HILEVEL:
211
- control_rule_str += f"IF NODE {self.__cond_var_value} ABOVE {self.__cond_comp_value}"
202
+ control_rule_str += f"{self._link_status} "
203
+
204
+ if self._cond_type == EpanetConstants.EN_TIMER:
205
+ control_rule_str += f"AT TIME {self._cond_var_value}"
206
+ elif self._cond_type == EpanetConstants.EN_TIMEOFDAY:
207
+ control_rule_str += f"AT CLOCKTIME {self._cond_var_value}"
208
+ elif self._cond_type == EpanetConstants.EN_LOWLEVEL:
209
+ control_rule_str += f"IF NODE {self._cond_var_value} BELOW {self._cond_comp_value}"
210
+ elif self._cond_type == EpanetConstants.EN_HILEVEL:
211
+ control_rule_str += f"IF NODE {self._cond_var_value} ABOVE {self._cond_comp_value}"
212
212
 
213
213
  return control_rule_str
214
214
 
@@ -18,6 +18,7 @@ from .events.sensor_faults import SensorFaultConstant, SensorFaultDrift, SensorF
18
18
  SensorFaultPercentage, SensorFaultStuckZero
19
19
  from .events.leakages import AbruptLeakage, IncipientLeakage
20
20
  from ..serialization import serializable, Serializable, SCENARIO_CONFIG_ID
21
+ from ..topology import NetworkTopology
21
22
 
22
23
 
23
24
  @serializable(SCENARIO_CONFIG_ID, ".epytflow_scenario_config")
@@ -46,6 +47,11 @@ class ScenarioConfig(Serializable):
46
47
  Path to the .msx file -- optional, only necessary if EPANET-MSX is used.
47
48
 
48
49
  The default is None
50
+ network_topology : :class:`~epyt_flow.topology.NetworkTopology`, optional
51
+ Specification of the network topology -- necessary if .inp file does not exist
52
+ or is not shared.
53
+
54
+ The default is None.
49
55
  general_params : `dict`, optional
50
56
  General parameters such as the demand model, hydraulic time steps, etc.
51
57
 
@@ -88,6 +94,7 @@ class ScenarioConfig(Serializable):
88
94
  """
89
95
 
90
96
  def __init__(self, scenario_config: Any = None, f_inp_in: str = None, f_msx_in: str = None,
97
+ network_topology: NetworkTopology = None,
91
98
  general_params: dict = None, sensor_config: SensorConfig = None,
92
99
  memory_consumption_estimate: float = None,
93
100
  custom_controls: list[CustomControlModule] = [],
@@ -112,6 +119,10 @@ class ScenarioConfig(Serializable):
112
119
  if not isinstance(f_msx_in, str):
113
120
  raise TypeError("'f_msx_in' must be an instance of 'str' " +
114
121
  f"but no of '{type(f_msx_in)}'")
122
+ if network_topology is not None:
123
+ if not isinstance(network_topology, NetworkTopology):
124
+ raise TypeError("'network_topology' msut be an instance of 'NetworkTopology' " +
125
+ f"but not of '{type(network_topology)}'")
115
126
  if general_params is not None:
116
127
  if not isinstance(general_params, dict):
117
128
  raise TypeError("'general_params' must be an instance of 'dict' " +
@@ -172,6 +183,11 @@ class ScenarioConfig(Serializable):
172
183
  self.__f_inp_in = scenario_config.f_inp_in
173
184
  self.__f_msx_in = scenario_config.f_msx_in if f_msx_in is None else f_msx_in
174
185
 
186
+ if network_topology is None:
187
+ self.__network_topology = scenario_config.network_topology
188
+ else:
189
+ self.__network_topology = network_topology
190
+
175
191
  if general_params is None:
176
192
  self.__general_params = scenario_config.general_params
177
193
  else:
@@ -224,6 +240,7 @@ class ScenarioConfig(Serializable):
224
240
  else:
225
241
  self.__f_inp_in = f_inp_in
226
242
  self.__f_msx_in = f_msx_in
243
+ self.__network_topology = network_topology
227
244
  self.__general_params = general_params
228
245
  self.__sensor_config = sensor_config
229
246
  self.__memory_consumption_estimate = memory_consumption_estimate
@@ -278,6 +295,18 @@ class ScenarioConfig(Serializable):
278
295
  else:
279
296
  return self.__f_msx_in
280
297
 
298
+ @property
299
+ def network_topology(self) -> NetworkTopology:
300
+ """
301
+ Returns the specification of the network topology.
302
+
303
+ Returns
304
+ -------
305
+ :class:`~epyt_flow.topology.NetworkTopology`
306
+ Network topology.
307
+ """
308
+ return deepcopy(self.__network_topology)
309
+
281
310
  @property
282
311
  def general_params(self) -> dict:
283
312
  """
@@ -402,6 +431,7 @@ class ScenarioConfig(Serializable):
402
431
 
403
432
  def get_attributes(self) -> dict:
404
433
  my_attributes = {"f_inp_in": self.__f_inp_in, "f_msx_in": self.__f_msx_in,
434
+ "network_topology": self.__network_topology,
405
435
  "general_params": self.__general_params,
406
436
  "sensor_config": self.__sensor_config,
407
437
  "memory_consumption_estimate": self.__memory_consumption_estimate,
@@ -421,6 +451,7 @@ class ScenarioConfig(Serializable):
421
451
  f"with '{type(other)}' instance")
422
452
 
423
453
  return self.__f_inp_in == other.f_inp_in and self.__f_msx_in == other.f_msx_in \
454
+ and self.__network_topology == other.network_topology \
424
455
  and self.__general_params == other.general_params \
425
456
  and self.__memory_consumption_estimate == other.memory_consumption_estimate \
426
457
  and self.__sensor_config == other.sensor_config \