epyt-flow 0.15.0b1__tar.gz → 0.16.0__tar.gz
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-0.15.0b1 → epyt_flow-0.16.0}/PKG-INFO +8 -5
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/README.md +1 -1
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/REQUIREMENTS.txt +1 -1
- epyt_flow-0.16.0/epyt_flow/VERSION +1 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/data/benchmarks/batadal.py +1 -1
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/gym/scenario_control_env.py +6 -12
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/serialization.py +19 -3
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/simulation/events/actuator_events.py +24 -24
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/simulation/events/leakages.py +45 -45
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/simulation/events/quality_events.py +23 -23
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/simulation/events/sensor_reading_attack.py +27 -27
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/simulation/events/sensor_reading_event.py +33 -33
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/simulation/scada/complex_control.py +103 -103
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/simulation/scada/scada_data.py +58 -30
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/simulation/scada/simple_control.py +33 -33
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/simulation/scenario_config.py +31 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/simulation/scenario_simulator.py +218 -82
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/simulation/sensor_config.py +220 -108
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/topology.py +197 -6
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/uncertainty/model_uncertainty.py +23 -20
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/uncertainty/sensor_noise.py +16 -16
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/uncertainty/uncertainties.py +6 -4
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/utils.py +240 -15
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/visualization/scenario_visualizer.py +14 -5
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/visualization/visualization_utils.py +22 -18
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow.egg-info/PKG-INFO +8 -5
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow.egg-info/requires.txt +1 -1
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/pyproject.toml +6 -3
- epyt_flow-0.15.0b1/epyt_flow/VERSION +0 -1
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/CITATION.cff +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/CODE_OF_CONDUCT.md +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/LICENSE +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/MANIFEST.in +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/__init__.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/data/__init__.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/data/benchmarks/__init__.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/data/benchmarks/batadal_data.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/data/benchmarks/battledim.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/data/benchmarks/battledim_data.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/data/benchmarks/gecco_water_quality.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/data/benchmarks/leakdb.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/data/benchmarks/leakdb_data.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/data/benchmarks/water_usage.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/data/networks.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/gym/__init__.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/rest_api/__init__.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/rest_api/base_handler.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/rest_api/res_manager.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/rest_api/scada_data/__init__.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/rest_api/scada_data/data_handlers.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/rest_api/scada_data/export_handlers.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/rest_api/scada_data/handlers.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/rest_api/scenario/__init__.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/rest_api/scenario/control_handlers.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/rest_api/scenario/event_handlers.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/rest_api/scenario/handlers.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/rest_api/scenario/simulation_handlers.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/rest_api/scenario/uncertainty_handlers.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/rest_api/server.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/simulation/__init__.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/simulation/events/__init__.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/simulation/events/event.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/simulation/events/sensor_faults.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/simulation/events/system_event.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/simulation/parallel_simulation.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/simulation/scada/__init__.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/simulation/scada/custom_control.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/simulation/scada/scada_data_export.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/uncertainty/__init__.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/uncertainty/utils.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow/visualization/__init__.py +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow.egg-info/SOURCES.txt +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow.egg-info/dependency_links.txt +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/epyt_flow.egg-info/top_level.txt +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/net2-cl2.inp +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/net2-cl2.msx +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/setup.cfg +0 -0
- {epyt_flow-0.15.0b1 → epyt_flow-0.16.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: epyt-flow
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.16.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-Expression: MIT
|
|
@@ -11,16 +11,19 @@ Project-URL: Issues, https://github.com/WaterFutures/EPyT-Flow/issues
|
|
|
11
11
|
Keywords: epanet,water,networks,hydraulics,quality,simulations
|
|
12
12
|
Classifier: Development Status :: 4 - Beta
|
|
13
13
|
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
14
17
|
Classifier: Programming Language :: Python :: 3
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
16
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
-
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Requires-Python: !=3.14.1,>=3.10
|
|
21
24
|
Description-Content-Type: text/markdown
|
|
22
25
|
License-File: LICENSE
|
|
23
|
-
Requires-Dist: epanet-plus>=0.0
|
|
26
|
+
Requires-Dist: epanet-plus>=0.2.0
|
|
24
27
|
Requires-Dist: requests>=2.31.0
|
|
25
28
|
Requires-Dist: scipy>=1.11.4
|
|
26
29
|
Requires-Dist: u-msgpack-python>=2.8.0
|
|
@@ -79,7 +82,7 @@ Unique features of EPyT-Flow that make it superior to other (Python) toolboxes a
|
|
|
79
82
|
|
|
80
83
|
## Installation
|
|
81
84
|
|
|
82
|
-
EPyT-Flow supports Python 3.
|
|
85
|
+
EPyT-Flow supports Python 3.10 - 3.14
|
|
83
86
|
|
|
84
87
|
Note that EPyT-Flow builds upon [EPANET-PLUS](https://github.com/WaterFutures/EPANET-PLUS) which
|
|
85
88
|
constitutes a C extension and Python package.
|
|
@@ -41,7 +41,7 @@ Unique features of EPyT-Flow that make it superior to other (Python) toolboxes a
|
|
|
41
41
|
|
|
42
42
|
## Installation
|
|
43
43
|
|
|
44
|
-
EPyT-Flow supports Python 3.
|
|
44
|
+
EPyT-Flow supports Python 3.10 - 3.14
|
|
45
45
|
|
|
46
46
|
Note that EPyT-Flow builds upon [EPANET-PLUS](https://github.com/WaterFutures/EPANET-PLUS) which
|
|
47
47
|
constitutes a C extension and Python package.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.16.0
|
|
@@ -125,7 +125,7 @@ def load_data(download_dir: str = None, return_X_y: bool = False,
|
|
|
125
125
|
del df_train_1["DATETIME"]
|
|
126
126
|
X_train_1 = df_train_1.to_numpy()
|
|
127
127
|
|
|
128
|
-
y_train_2 = df_train_2[" ATT_FLAG"].to_numpy()
|
|
128
|
+
y_train_2 = df_train_2[" ATT_FLAG"].to_numpy(copy=True)
|
|
129
129
|
idx = np.argwhere(y_train_2 == -999)
|
|
130
130
|
y_train_2[idx] = 0
|
|
131
131
|
y_train_2 = y_train_2.astype(np.int8)
|
|
@@ -218,18 +218,12 @@ class ScenarioControlEnv(ABC):
|
|
|
218
218
|
"when running EPANET-MSX")
|
|
219
219
|
|
|
220
220
|
pump_idx = self._scenario_sim.epanet_api.get_link_idx(pump_id)
|
|
221
|
-
pattern_idx =
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
self._scenario_sim.epanet_api.add_pattern(pattern_id, [speed])
|
|
228
|
-
pattern_idx = self._scenario_sim.epanet_api.getpatternindex(pattern_id)
|
|
229
|
-
self._scenario_sim.epanet_api.setlinkvalue(pump_idx, EpanetConstants.EN_LINKPATTERN,
|
|
230
|
-
pattern_idx)
|
|
231
|
-
|
|
232
|
-
self._scenario_sim.epanet_api.setpattern(pattern_idx, [speed], 1)
|
|
221
|
+
pattern_idx = self._scenario_sim.epanet_api.getlinkvalue(pump_idx,
|
|
222
|
+
EpanetConstants.EN_LINKPATTERN)
|
|
223
|
+
if pattern_idx != 0:
|
|
224
|
+
warnings.warn(f"Pump setting of pump {pump_id} will be multiplied with existing pump pattern")
|
|
225
|
+
|
|
226
|
+
self._scenario_sim.epanet_api.setlinkvalue(pump_idx, EpanetConstants.EN_SETTING, speed)
|
|
233
227
|
|
|
234
228
|
def set_valve_status(self, valve_id: str, status: int) -> None:
|
|
235
229
|
"""
|
|
@@ -111,6 +111,13 @@ class Serializable(ABC):
|
|
|
111
111
|
|
|
112
112
|
super().__init__(**kwds)
|
|
113
113
|
|
|
114
|
+
def __eq__(self, other) -> bool:
|
|
115
|
+
return self._parent_path == other.parent_path
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def parent_path(self) -> str:
|
|
119
|
+
return self._parent_path
|
|
120
|
+
|
|
114
121
|
@abstractmethod
|
|
115
122
|
def get_attributes(self) -> dict:
|
|
116
123
|
"""
|
|
@@ -480,16 +487,25 @@ def __decode_bsr_array(ext_data: tuple[tuple[int, int],
|
|
|
480
487
|
return scipy.sparse.bsr_array((data[0], (data[1][0], data[1][1])), shape=(shape[0], shape[1]))
|
|
481
488
|
|
|
482
489
|
|
|
490
|
+
def __encode_numpy_array(array: np.ndarray) -> tuple[str, list[int], bytes]:
|
|
491
|
+
return array.dtype.descr[0][1], array.shape, array.tobytes()
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def __decode_numpy_array(ext_data: tuple[str, list[int], bytes]) -> np.ndarray:
|
|
495
|
+
dtype_descr, shape, buffer = ext_data
|
|
496
|
+
return np.frombuffer(buffer, dtype=np.dtype(dtype_descr)).reshape(shape)
|
|
497
|
+
|
|
498
|
+
|
|
483
499
|
ext_handler_pack = {np.ndarray:
|
|
484
|
-
lambda arr: umsgpack.Ext(NUMPY_ARRAY_ID, umsgpack.packb(arr
|
|
500
|
+
lambda arr: umsgpack.Ext(NUMPY_ARRAY_ID, umsgpack.packb(__encode_numpy_array(arr))),
|
|
485
501
|
networkx.Graph:
|
|
486
502
|
lambda graph:
|
|
487
503
|
umsgpack.Ext(NETWORKX_GRAPH_ID,
|
|
488
|
-
umsgpack.packb(networkx.
|
|
504
|
+
umsgpack.packb(networkx.edges(graph))),
|
|
489
505
|
scipy.sparse.bsr_array:
|
|
490
506
|
lambda arr: umsgpack.Ext(SCIPY_BSRARRAY_ID,
|
|
491
507
|
umsgpack.packb(__encode_bsr_array(arr)))}
|
|
492
|
-
ext_handler_unpack = {NUMPY_ARRAY_ID: lambda ext:
|
|
508
|
+
ext_handler_unpack = {NUMPY_ARRAY_ID: lambda ext: __decode_numpy_array(umsgpack.unpackb(ext.data)),
|
|
493
509
|
NETWORKX_GRAPH_ID:
|
|
494
510
|
lambda ext: networkx.node_link_graph(umsgpack.unpackb(ext.data)),
|
|
495
511
|
SCIPY_BSRARRAY_ID: lambda ext: __decode_bsr_array(umsgpack.unpackb(ext.data))}
|
|
@@ -60,18 +60,18 @@ class PumpEvent(ActuatorEvent):
|
|
|
60
60
|
ID of the pump that is affected by this event.
|
|
61
61
|
"""
|
|
62
62
|
def __init__(self, pump_id: str, **kwds):
|
|
63
|
-
self.
|
|
63
|
+
self._pump_id = pump_id
|
|
64
64
|
|
|
65
65
|
super().__init__(**kwds)
|
|
66
66
|
|
|
67
67
|
def init(self, epanet_api: EPyT) -> None:
|
|
68
|
-
if self.
|
|
69
|
-
raise ValueError(f"Invalid pump ID '{self.
|
|
68
|
+
if self._pump_id not in epanet_api.get_all_pumps_id():
|
|
69
|
+
raise ValueError(f"Invalid pump ID '{self._pump_id}'")
|
|
70
70
|
|
|
71
71
|
super().init(epanet_api)
|
|
72
72
|
|
|
73
73
|
def get_attributes(self) -> dict:
|
|
74
|
-
return super().get_attributes() | {"pump_id": self.
|
|
74
|
+
return super().get_attributes() | {"pump_id": self._pump_id}
|
|
75
75
|
|
|
76
76
|
@property
|
|
77
77
|
def pump_id(self) -> str:
|
|
@@ -83,7 +83,7 @@ class PumpEvent(ActuatorEvent):
|
|
|
83
83
|
`str`
|
|
84
84
|
Pump ID.
|
|
85
85
|
"""
|
|
86
|
-
return self.
|
|
86
|
+
return self._pump_id
|
|
87
87
|
|
|
88
88
|
|
|
89
89
|
@serializable(PUMP_STATE_EVENT_ID, ".epytflow_pump_state_event")
|
|
@@ -111,12 +111,12 @@ class PumpStateEvent(PumpEvent, JsonSerializable):
|
|
|
111
111
|
raise ValueError(f"Invalid pump state '{pump_state}' -- " +
|
|
112
112
|
"must be either EN_CLOSED (0) or EN_OPEN (1)")
|
|
113
113
|
|
|
114
|
-
self.
|
|
114
|
+
self._pump_state = pump_state
|
|
115
115
|
|
|
116
116
|
super().__init__(**kwds)
|
|
117
117
|
|
|
118
118
|
def get_attributes(self) -> dict:
|
|
119
|
-
return super().get_attributes() | {"pump_state": self.
|
|
119
|
+
return super().get_attributes() | {"pump_state": self._pump_state}
|
|
120
120
|
|
|
121
121
|
@property
|
|
122
122
|
def pump_state(self) -> int:
|
|
@@ -134,7 +134,7 @@ class PumpStateEvent(PumpEvent, JsonSerializable):
|
|
|
134
134
|
- EN_CLOSED = 0
|
|
135
135
|
- EN_OPEN = 1
|
|
136
136
|
"""
|
|
137
|
-
return self.
|
|
137
|
+
return self._pump_state
|
|
138
138
|
|
|
139
139
|
def apply(self, cur_time: int) -> None:
|
|
140
140
|
pump_link_idx = self._epanet_api.getlinkindex(self.pump_id)
|
|
@@ -145,7 +145,7 @@ class PumpStateEvent(PumpEvent, JsonSerializable):
|
|
|
145
145
|
"because a pump pattern exists")
|
|
146
146
|
else:
|
|
147
147
|
self._epanet_api.setlinkvalue(pump_link_idx, EpanetConstants.EN_STATUS,
|
|
148
|
-
self.
|
|
148
|
+
self._pump_state)
|
|
149
149
|
|
|
150
150
|
|
|
151
151
|
@serializable(PUMP_SPEED_EVENT_ID, ".epytflow_pump_speed_event")
|
|
@@ -165,12 +165,12 @@ class PumpSpeedEvent(PumpEvent, JsonSerializable):
|
|
|
165
165
|
if pump_speed <= 0:
|
|
166
166
|
raise ValueError("Pump speed must be positive")
|
|
167
167
|
|
|
168
|
-
self.
|
|
168
|
+
self._pump_speed = pump_speed
|
|
169
169
|
|
|
170
170
|
super().__init__(**kwds)
|
|
171
171
|
|
|
172
172
|
def get_attributes(self) -> dict:
|
|
173
|
-
return super().get_attributes() | {"pump_speed": self.
|
|
173
|
+
return super().get_attributes() | {"pump_speed": self._pump_speed}
|
|
174
174
|
|
|
175
175
|
@property
|
|
176
176
|
def pump_speed(self) -> float:
|
|
@@ -182,7 +182,7 @@ class PumpSpeedEvent(PumpEvent, JsonSerializable):
|
|
|
182
182
|
`float`
|
|
183
183
|
New pump speed.
|
|
184
184
|
"""
|
|
185
|
-
return self.
|
|
185
|
+
return self._pump_speed
|
|
186
186
|
|
|
187
187
|
def apply(self, cur_time: int) -> None:
|
|
188
188
|
pump_idx = self._epanet_api.get_link_idx(self.pump_id)
|
|
@@ -191,11 +191,11 @@ class PumpSpeedEvent(PumpEvent, JsonSerializable):
|
|
|
191
191
|
if pattern_idx == 0:
|
|
192
192
|
warnings.warn(f"No pattern for pump '{self.pump_id}' found -- a new pattern is created")
|
|
193
193
|
pattern_id = f"pump_speed_{self.pump_id}"
|
|
194
|
-
self._epanet_api.add_pattern(pattern_id, [self.
|
|
194
|
+
self._epanet_api.add_pattern(pattern_id, [self._pump_speed])
|
|
195
195
|
pattern_idx = self._epanet_api.getpatternindex(pattern_id)
|
|
196
196
|
self._epanet_api.setlinkvalue(pump_idx, EpanetConstants.EN_LINKPATTERN, pattern_idx)
|
|
197
197
|
|
|
198
|
-
self._epanet_api.setpattern(pattern_idx, [self.
|
|
198
|
+
self._epanet_api.setpattern(pattern_idx, [self._pump_speed], 1)
|
|
199
199
|
|
|
200
200
|
|
|
201
201
|
@serializable(VALVE_STATE_EVENT_ID, ".epytflow_valve_state_event")
|
|
@@ -224,20 +224,20 @@ class ValveStateEvent(ActuatorEvent, JsonSerializable):
|
|
|
224
224
|
raise ValueError(f"Invalid valve state '{valve_state}' -- " +
|
|
225
225
|
"must be either EN_CLOSED (0) or EN_OPEN (1)")
|
|
226
226
|
|
|
227
|
-
self.
|
|
228
|
-
self.
|
|
227
|
+
self._valve_id = valve_id
|
|
228
|
+
self._valve_state = valve_state
|
|
229
229
|
|
|
230
230
|
super().__init__(**kwds)
|
|
231
231
|
|
|
232
232
|
def init(self, epanet_api: EPyT) -> None:
|
|
233
|
-
if self.
|
|
234
|
-
raise ValueError(f"Invalid valve ID '{self.
|
|
233
|
+
if self._valve_id not in epanet_api.get_all_valves_id():
|
|
234
|
+
raise ValueError(f"Invalid valve ID '{self._valve_id}'")
|
|
235
235
|
|
|
236
236
|
super().init(epanet_api)
|
|
237
237
|
|
|
238
238
|
def get_attributes(self) -> dict:
|
|
239
|
-
return super().get_attributes() | {"valve_id": self.
|
|
240
|
-
"valve_state": self.
|
|
239
|
+
return super().get_attributes() | {"valve_id": self._valve_id,
|
|
240
|
+
"valve_state": self._valve_state}
|
|
241
241
|
|
|
242
242
|
@property
|
|
243
243
|
def valve_id(self) -> str:
|
|
@@ -249,7 +249,7 @@ class ValveStateEvent(ActuatorEvent, JsonSerializable):
|
|
|
249
249
|
`str`
|
|
250
250
|
Valve ID.
|
|
251
251
|
"""
|
|
252
|
-
return self.
|
|
252
|
+
return self._valve_id
|
|
253
253
|
|
|
254
254
|
@property
|
|
255
255
|
def valve_state(self) -> int:
|
|
@@ -264,8 +264,8 @@ class ValveStateEvent(ActuatorEvent, JsonSerializable):
|
|
|
264
264
|
One of the following constants defined in
|
|
265
265
|
:class:`~epyt_flow.simulation.events.actuator_events.ActuatorConstants`:
|
|
266
266
|
"""
|
|
267
|
-
return self.
|
|
267
|
+
return self._valve_state
|
|
268
268
|
|
|
269
269
|
def apply(self, cur_time: int) -> None:
|
|
270
|
-
valve_link_idx = self._epanet_api.get_link_idx(self.
|
|
271
|
-
self._epanet_api.setlinkvalue(valve_link_idx, EpanetConstants.EN_STATUS, self.
|
|
270
|
+
valve_link_idx = self._epanet_api.get_link_idx(self._valve_id)
|
|
271
|
+
self._epanet_api.setlinkvalue(valve_link_idx, EpanetConstants.EN_STATUS, self._valve_state)
|
|
@@ -85,15 +85,15 @@ class Leakage(SystemEvent, JsonSerializable):
|
|
|
85
85
|
raise TypeError("'node_id' must be an instance of 'str' " +
|
|
86
86
|
f"but not of '{type(node_id)}'")
|
|
87
87
|
|
|
88
|
-
self.
|
|
89
|
-
self.
|
|
90
|
-
self.
|
|
91
|
-
self.
|
|
92
|
-
self.
|
|
88
|
+
self._link_id = link_id
|
|
89
|
+
self._node_id = node_id
|
|
90
|
+
self._diameter = diameter
|
|
91
|
+
self._area = area
|
|
92
|
+
self._profile = profile
|
|
93
93
|
|
|
94
|
-
self.
|
|
95
|
-
self.
|
|
96
|
-
self.
|
|
94
|
+
self._leaky_node_idx = None
|
|
95
|
+
self._leak_emitter_coef = None
|
|
96
|
+
self._time_pattern_idx = 0
|
|
97
97
|
|
|
98
98
|
super().__init__(**kwds)
|
|
99
99
|
|
|
@@ -107,7 +107,7 @@ class Leakage(SystemEvent, JsonSerializable):
|
|
|
107
107
|
`str`
|
|
108
108
|
ID of the link at which the leak is placed.
|
|
109
109
|
"""
|
|
110
|
-
return self.
|
|
110
|
+
return self._link_id
|
|
111
111
|
|
|
112
112
|
@property
|
|
113
113
|
def node_id(self) -> str:
|
|
@@ -119,7 +119,7 @@ class Leakage(SystemEvent, JsonSerializable):
|
|
|
119
119
|
`str`
|
|
120
120
|
ID of the node at which the leak is placed.
|
|
121
121
|
"""
|
|
122
|
-
return self.
|
|
122
|
+
return self._node_id
|
|
123
123
|
|
|
124
124
|
@property
|
|
125
125
|
def diameter(self) -> float:
|
|
@@ -132,7 +132,7 @@ class Leakage(SystemEvent, JsonSerializable):
|
|
|
132
132
|
`float`
|
|
133
133
|
Diameter (*foot* or *meter*) of the leak.
|
|
134
134
|
"""
|
|
135
|
-
return self.
|
|
135
|
+
return self._diameter
|
|
136
136
|
|
|
137
137
|
@property
|
|
138
138
|
def area(self) -> float:
|
|
@@ -145,7 +145,7 @@ class Leakage(SystemEvent, JsonSerializable):
|
|
|
145
145
|
`float`
|
|
146
146
|
Area of the leak.
|
|
147
147
|
"""
|
|
148
|
-
return self.
|
|
148
|
+
return self._area if self._area is not None else self.compute_leak_area(self._diameter)
|
|
149
149
|
|
|
150
150
|
@property
|
|
151
151
|
def profile(self) -> np.ndarray:
|
|
@@ -157,7 +157,7 @@ class Leakage(SystemEvent, JsonSerializable):
|
|
|
157
157
|
`numpy.ndarray`
|
|
158
158
|
Pattern of the leak.
|
|
159
159
|
"""
|
|
160
|
-
return deepcopy(self.
|
|
160
|
+
return deepcopy(self._profile)
|
|
161
161
|
|
|
162
162
|
@profile.setter
|
|
163
163
|
def profile(self, pattern: np.ndarray):
|
|
@@ -168,25 +168,25 @@ class Leakage(SystemEvent, JsonSerializable):
|
|
|
168
168
|
raise ValueError("'profile' must be a one-dimensional array " +
|
|
169
169
|
f"but not of shape '{pattern.shape}'")
|
|
170
170
|
|
|
171
|
-
self.
|
|
171
|
+
self._profile = pattern
|
|
172
172
|
|
|
173
173
|
def get_attributes(self) -> dict:
|
|
174
|
-
return super().get_attributes() | {"link_id": self.
|
|
175
|
-
"area": self.
|
|
176
|
-
"node_id": self.
|
|
177
|
-
if self.
|
|
174
|
+
return super().get_attributes() | {"link_id": self._link_id, "diameter": self._diameter,
|
|
175
|
+
"area": self._area, "profile": self._profile,
|
|
176
|
+
"node_id": self._node_id
|
|
177
|
+
if self._link_id is None else None}
|
|
178
178
|
|
|
179
179
|
def __eq__(self, other) -> bool:
|
|
180
180
|
if not isinstance(other, Leakage):
|
|
181
181
|
raise TypeError(f"Can not compare 'Leakage' instance with '{type(other)}' instance")
|
|
182
182
|
|
|
183
|
-
return super().__eq__(other) and self.
|
|
184
|
-
and self.
|
|
185
|
-
and self.
|
|
183
|
+
return super().__eq__(other) and self._link_id == other.link_id \
|
|
184
|
+
and self._diameter == other.diameter and np.all(self._profile == other.profile) \
|
|
185
|
+
and self._node_id == other.node_id and self.area == other.area
|
|
186
186
|
|
|
187
187
|
def __str__(self) -> str:
|
|
188
|
-
return f"{super().__str__()} link_id: {self.
|
|
189
|
-
f"area: {self.
|
|
188
|
+
return f"{super().__str__()} link_id: {self._link_id} diameter: {self._diameter} " +\
|
|
189
|
+
f"area: {self._area} profile: {self._profile} node_id: {self._node_id}"
|
|
190
190
|
|
|
191
191
|
def compute_leak_area(self, diameter: float) -> float:
|
|
192
192
|
"""
|
|
@@ -242,18 +242,18 @@ class Leakage(SystemEvent, JsonSerializable):
|
|
|
242
242
|
return discharge_coef * area * np.sqrt(2. * g)
|
|
243
243
|
|
|
244
244
|
def _get_new_link_id(self) -> str:
|
|
245
|
-
return f"leak_pipe_{self.
|
|
245
|
+
return f"leak_pipe_{self._link_id}"
|
|
246
246
|
|
|
247
247
|
def _get_new_node_id(self) -> str:
|
|
248
|
-
return f"leak_node_{self.
|
|
248
|
+
return f"leak_node_{self._link_id}"
|
|
249
249
|
|
|
250
250
|
def init(self, epanet_api: EPyT) -> None:
|
|
251
251
|
super().init(epanet_api)
|
|
252
252
|
|
|
253
253
|
# Split pipe if leak is placed at a link/pipe
|
|
254
|
-
if self.
|
|
255
|
-
if self.
|
|
256
|
-
raise ValueError(f"Unknown link/pipe '{self.
|
|
254
|
+
if self._link_id is not None:
|
|
255
|
+
if self._link_id not in self._epanet_api.get_all_links_id():
|
|
256
|
+
raise ValueError(f"Unknown link/pipe '{self._link_id}'")
|
|
257
257
|
|
|
258
258
|
new_link_id = self._get_new_link_id()
|
|
259
259
|
new_node_id = self._get_new_node_id()
|
|
@@ -263,22 +263,22 @@ class Leakage(SystemEvent, JsonSerializable):
|
|
|
263
263
|
raise ValueError(f"There is already a leak at pipe {self.link_id}")
|
|
264
264
|
|
|
265
265
|
self._epanet_api.split_pipe(self.link_id, new_link_id, new_node_id)
|
|
266
|
-
self.
|
|
266
|
+
self._leaky_node_idx = self._epanet_api.get_node_idx(new_node_id)
|
|
267
267
|
else:
|
|
268
|
-
if self.
|
|
269
|
-
raise ValueError(f"Unknown node '{self.
|
|
268
|
+
if self._node_id not in self._epanet_api.get_all_nodes_id():
|
|
269
|
+
raise ValueError(f"Unknown node '{self._node_id}'")
|
|
270
270
|
|
|
271
|
-
self.
|
|
271
|
+
self._leaky_node_idx = self._epanet_api.get_node_idx(self._node_id)
|
|
272
272
|
|
|
273
|
-
self._epanet_api.setnodevalue(self.
|
|
273
|
+
self._epanet_api.setnodevalue(self._leaky_node_idx, EpanetConstants.EN_EMITTER, 0.)
|
|
274
274
|
|
|
275
275
|
# Compute leak emitter coefficient
|
|
276
|
-
self.
|
|
276
|
+
self._leak_emitter_coef = self.compute_leak_emitter_coefficient(
|
|
277
277
|
self.compute_leak_area(self.area))
|
|
278
278
|
|
|
279
279
|
def cleanup(self) -> None:
|
|
280
|
-
if self.
|
|
281
|
-
pipe_idx = self._epanet_api.get_link_idx(self.
|
|
280
|
+
if self._link_id is not None:
|
|
281
|
+
pipe_idx = self._epanet_api.get_link_idx(self._link_id)
|
|
282
282
|
link_diameter = self._epanet_api.get_link_diameter(pipe_idx)
|
|
283
283
|
link_length = self._epanet_api.get_link_length(pipe_idx)
|
|
284
284
|
link_roughness_coeff = self._epanet_api.get_link_roughness(pipe_idx)
|
|
@@ -293,15 +293,15 @@ class Leakage(SystemEvent, JsonSerializable):
|
|
|
293
293
|
|
|
294
294
|
self._epanet_api.deletelink(self._epanet_api.get_link_idx(self._get_new_link_id()),
|
|
295
295
|
EpanetConstants.EN_UNCONDITIONAL)
|
|
296
|
-
self._epanet_api.deletelink(self._epanet_api.get_link_idx(self.
|
|
296
|
+
self._epanet_api.deletelink(self._epanet_api.get_link_idx(self._link_id),
|
|
297
297
|
EpanetConstants.EN_UNCONDITIONAL)
|
|
298
298
|
self._epanet_api.deletenode(self._epanet_api.get_node_idx(self._get_new_node_id()),
|
|
299
299
|
EpanetConstants.EN_UNCONDITIONAL)
|
|
300
300
|
|
|
301
|
-
self._epanet_api.addlink(self.
|
|
301
|
+
self._epanet_api.addlink(self._link_id, EpanetConstants.EN_PIPE,
|
|
302
302
|
self._epanet_api.get_node_id(node_a_idx),
|
|
303
303
|
self._epanet_api.get_node_id(node_b_idx))
|
|
304
|
-
link_idx = self._epanet_api.get_link_idx(self.
|
|
304
|
+
link_idx = self._epanet_api.get_link_idx(self._link_id)
|
|
305
305
|
self._epanet_api.setlinknodes(link_idx, node_a_idx, node_b_idx)
|
|
306
306
|
self._epanet_api.setlinktype(link_idx, EpanetConstants.EN_PIPE,
|
|
307
307
|
EpanetConstants.EN_UNCONDITIONAL)
|
|
@@ -317,16 +317,16 @@ class Leakage(SystemEvent, JsonSerializable):
|
|
|
317
317
|
link_wall_reaction_coeff)
|
|
318
318
|
|
|
319
319
|
def reset(self) -> None:
|
|
320
|
-
self.
|
|
320
|
+
self._time_pattern_idx = 0
|
|
321
321
|
|
|
322
322
|
def exit(self, cur_time) -> None:
|
|
323
|
-
self._epanet_api.setnodevalue(self.
|
|
323
|
+
self._epanet_api.setnodevalue(self._leaky_node_idx, EpanetConstants.EN_EMITTER, 0.)
|
|
324
324
|
|
|
325
325
|
def apply(self, cur_time: int) -> None:
|
|
326
|
-
self._epanet_api.setnodevalue(self.
|
|
327
|
-
self.
|
|
328
|
-
self.
|
|
329
|
-
self.
|
|
326
|
+
self._epanet_api.setnodevalue(self._leaky_node_idx, EpanetConstants.EN_EMITTER,
|
|
327
|
+
self._leak_emitter_coef *
|
|
328
|
+
self._profile[self._time_pattern_idx])
|
|
329
|
+
self._time_pattern_idx += 1
|
|
330
330
|
|
|
331
331
|
|
|
332
332
|
@serializable(ABRUPT_LEAKAGE_ID, ".epytflow_leakage_abrupt")
|
|
@@ -61,10 +61,10 @@ class SpeciesInjectionEvent(SystemEvent, JsonSerializable):
|
|
|
61
61
|
if not 0 <= source_type <= 3:
|
|
62
62
|
raise ValueError("'source_tye' must be in [0, 3]")
|
|
63
63
|
|
|
64
|
-
self.
|
|
65
|
-
self.
|
|
64
|
+
self._species_id = species_id
|
|
65
|
+
self._node_id = node_id
|
|
66
66
|
self._profile = profile
|
|
67
|
-
self.
|
|
67
|
+
self._source_type = source_type
|
|
68
68
|
|
|
69
69
|
super().__init__(**kwds)
|
|
70
70
|
|
|
@@ -78,7 +78,7 @@ class SpeciesInjectionEvent(SystemEvent, JsonSerializable):
|
|
|
78
78
|
`str`
|
|
79
79
|
Bulk species ID.
|
|
80
80
|
"""
|
|
81
|
-
return self.
|
|
81
|
+
return self._species_id
|
|
82
82
|
|
|
83
83
|
@property
|
|
84
84
|
def node_id(self) -> str:
|
|
@@ -90,7 +90,7 @@ class SpeciesInjectionEvent(SystemEvent, JsonSerializable):
|
|
|
90
90
|
`str`
|
|
91
91
|
Node ID.
|
|
92
92
|
"""
|
|
93
|
-
return self.
|
|
93
|
+
return self._node_id
|
|
94
94
|
|
|
95
95
|
@property
|
|
96
96
|
def profile(self) -> np.ndarray:
|
|
@@ -120,33 +120,33 @@ class SpeciesInjectionEvent(SystemEvent, JsonSerializable):
|
|
|
120
120
|
`int`
|
|
121
121
|
Type of the injection source.
|
|
122
122
|
"""
|
|
123
|
-
return self.
|
|
123
|
+
return self._source_type
|
|
124
124
|
|
|
125
125
|
def get_attributes(self) -> dict:
|
|
126
|
-
return super().get_attributes() | {"species_id": self.
|
|
127
|
-
"node_id": self.
|
|
128
|
-
"source_type": self.
|
|
126
|
+
return super().get_attributes() | {"species_id": self._species_id,
|
|
127
|
+
"node_id": self._node_id, "profile": self._profile,
|
|
128
|
+
"source_type": self._source_type}
|
|
129
129
|
|
|
130
130
|
def __eq__(self, other) -> bool:
|
|
131
|
-
return super().__eq__(other) and self.
|
|
132
|
-
self.
|
|
133
|
-
self.
|
|
131
|
+
return super().__eq__(other) and self._species_id == other.species_id and \
|
|
132
|
+
self._node_id == other.node_id and np.all(self._profile == other.profile) and \
|
|
133
|
+
self._source_type == other.source_type
|
|
134
134
|
|
|
135
135
|
def __str__(self) -> str:
|
|
136
|
-
return f"{super().__str__()} species_id: {self.
|
|
137
|
-
f"node_id: {self.
|
|
136
|
+
return f"{super().__str__()} species_id: {self._species_id} " +\
|
|
137
|
+
f"node_id: {self._node_id} profile: {self._profile} source_type: {self._source_type}"
|
|
138
138
|
|
|
139
139
|
def _get_pattern_id(self) -> str:
|
|
140
|
-
return f"{self.
|
|
140
|
+
return f"{self._species_id}_{self._node_id}"
|
|
141
141
|
|
|
142
142
|
def init(self, epanet_api: EPyT) -> None:
|
|
143
143
|
super().init(epanet_api)
|
|
144
144
|
|
|
145
145
|
# Check parameters
|
|
146
|
-
if self.
|
|
147
|
-
raise ValueError(f"Unknown species '{self.
|
|
148
|
-
if self.
|
|
149
|
-
raise ValueError(f"Unknown node '{self.
|
|
146
|
+
if self._species_id not in self._epanet_api.get_all_msx_species_id():
|
|
147
|
+
raise ValueError(f"Unknown species '{self._species_id}'")
|
|
148
|
+
if self._node_id not in self._epanet_api.get_all_nodes_id():
|
|
149
|
+
raise ValueError(f"Unknown node '{self._node_id}'")
|
|
150
150
|
|
|
151
151
|
# Create final injection strength pattern
|
|
152
152
|
total_sim_duration = self._epanet_api.get_simulation_duration()
|
|
@@ -173,10 +173,10 @@ class SpeciesInjectionEvent(SystemEvent, JsonSerializable):
|
|
|
173
173
|
if pattern_id in [self._epanet_api.MSXgetID(EpanetConstants.MSX_PATTERN, pattern_idx + 1)
|
|
174
174
|
for pattern_idx in
|
|
175
175
|
range(self._epanet_api.MSXgetcount(EpanetConstants.MSX_PATTERN))]:
|
|
176
|
-
node_idx = self._epanet_api.get_node_idx(self.
|
|
177
|
-
species_idx = self._epanet_api.get_msx_species_idx(self.
|
|
176
|
+
node_idx = self._epanet_api.get_node_idx(self._node_id)
|
|
177
|
+
species_idx = self._epanet_api.get_msx_species_idx(self._species_id)
|
|
178
178
|
cur_source_type = self._epanet_api.MSXgetsource(node_idx, species_idx)
|
|
179
|
-
if cur_source_type[0] != self.
|
|
179
|
+
if cur_source_type[0] != self._source_type:
|
|
180
180
|
raise ValueError("Source type does not match existing source type")
|
|
181
181
|
|
|
182
182
|
# Add new injection amount to existing injection --
|
|
@@ -187,7 +187,7 @@ class SpeciesInjectionEvent(SystemEvent, JsonSerializable):
|
|
|
187
187
|
self._epanet_api.MSXsetpattern(pattern_idx, cur_pattern.tolist(), len(cur_pattern))
|
|
188
188
|
else:
|
|
189
189
|
self._epanet_api.add_msx_pattern(pattern_id, pattern.tolist())
|
|
190
|
-
self._epanet_api.set_msx_source(self.
|
|
190
|
+
self._epanet_api.set_msx_source(self._node_id, self._species_id, self._source_type, 1,
|
|
191
191
|
pattern_id)
|
|
192
192
|
|
|
193
193
|
def cleanup(self) -> None:
|