epyt-flow 0.14.1__py3-none-any.whl → 0.15.0b1__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/__init__.py +0 -37
- epyt_flow/data/benchmarks/battledim.py +2 -2
- epyt_flow/data/benchmarks/leakdb.py +12 -9
- epyt_flow/gym/scenario_control_env.py +32 -33
- epyt_flow/simulation/events/actuator_events.py +24 -18
- epyt_flow/simulation/events/leakages.py +59 -57
- epyt_flow/simulation/events/quality_events.py +21 -30
- epyt_flow/simulation/events/system_event.py +3 -3
- epyt_flow/simulation/scada/complex_control.py +14 -12
- epyt_flow/simulation/scada/custom_control.py +22 -21
- epyt_flow/simulation/scada/scada_data.py +108 -105
- epyt_flow/simulation/scada/simple_control.py +38 -31
- epyt_flow/simulation/scenario_simulator.py +367 -395
- epyt_flow/simulation/sensor_config.py +31 -32
- epyt_flow/topology.py +11 -10
- epyt_flow/uncertainty/model_uncertainty.py +146 -122
- {epyt_flow-0.14.1.dist-info → epyt_flow-0.15.0b1.dist-info}/METADATA +12 -18
- epyt_flow-0.15.0b1.dist-info/RECORD +65 -0
- epyt_flow/EPANET/EPANET/SRC_engines/AUTHORS +0 -60
- epyt_flow/EPANET/EPANET/SRC_engines/LICENSE +0 -21
- epyt_flow/EPANET/EPANET/SRC_engines/enumstxt.h +0 -151
- epyt_flow/EPANET/EPANET/SRC_engines/epanet.c +0 -5930
- epyt_flow/EPANET/EPANET/SRC_engines/epanet2.c +0 -961
- epyt_flow/EPANET/EPANET/SRC_engines/errors.dat +0 -79
- epyt_flow/EPANET/EPANET/SRC_engines/flowbalance.c +0 -186
- epyt_flow/EPANET/EPANET/SRC_engines/funcs.h +0 -219
- epyt_flow/EPANET/EPANET/SRC_engines/genmmd.c +0 -1000
- epyt_flow/EPANET/EPANET/SRC_engines/hash.c +0 -177
- epyt_flow/EPANET/EPANET/SRC_engines/hash.h +0 -28
- epyt_flow/EPANET/EPANET/SRC_engines/hydcoeffs.c +0 -1303
- epyt_flow/EPANET/EPANET/SRC_engines/hydraul.c +0 -1172
- epyt_flow/EPANET/EPANET/SRC_engines/hydsolver.c +0 -781
- epyt_flow/EPANET/EPANET/SRC_engines/hydstatus.c +0 -442
- epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2.h +0 -464
- epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_2.h +0 -1960
- epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_enums.h +0 -518
- epyt_flow/EPANET/EPANET/SRC_engines/inpfile.c +0 -884
- epyt_flow/EPANET/EPANET/SRC_engines/input1.c +0 -672
- epyt_flow/EPANET/EPANET/SRC_engines/input2.c +0 -735
- epyt_flow/EPANET/EPANET/SRC_engines/input3.c +0 -2265
- epyt_flow/EPANET/EPANET/SRC_engines/leakage.c +0 -527
- epyt_flow/EPANET/EPANET/SRC_engines/mempool.c +0 -146
- epyt_flow/EPANET/EPANET/SRC_engines/mempool.h +0 -24
- epyt_flow/EPANET/EPANET/SRC_engines/output.c +0 -853
- epyt_flow/EPANET/EPANET/SRC_engines/project.c +0 -1691
- epyt_flow/EPANET/EPANET/SRC_engines/quality.c +0 -695
- epyt_flow/EPANET/EPANET/SRC_engines/qualreact.c +0 -800
- epyt_flow/EPANET/EPANET/SRC_engines/qualroute.c +0 -696
- epyt_flow/EPANET/EPANET/SRC_engines/report.c +0 -1557
- epyt_flow/EPANET/EPANET/SRC_engines/rules.c +0 -1500
- epyt_flow/EPANET/EPANET/SRC_engines/smatrix.c +0 -871
- epyt_flow/EPANET/EPANET/SRC_engines/text.h +0 -508
- epyt_flow/EPANET/EPANET/SRC_engines/types.h +0 -928
- epyt_flow/EPANET/EPANET/SRC_engines/util/cstr_helper.c +0 -59
- epyt_flow/EPANET/EPANET/SRC_engines/util/cstr_helper.h +0 -38
- epyt_flow/EPANET/EPANET/SRC_engines/util/errormanager.c +0 -92
- epyt_flow/EPANET/EPANET/SRC_engines/util/errormanager.h +0 -39
- epyt_flow/EPANET/EPANET/SRC_engines/util/filemanager.c +0 -212
- epyt_flow/EPANET/EPANET/SRC_engines/util/filemanager.h +0 -81
- epyt_flow/EPANET/EPANET/SRC_engines/validate.c +0 -408
- epyt_flow/EPANET/EPANET-MSX/MSX_Updates.txt +0 -53
- epyt_flow/EPANET/EPANET-MSX/Src/dispersion.h +0 -27
- epyt_flow/EPANET/EPANET-MSX/Src/hash.c +0 -107
- epyt_flow/EPANET/EPANET-MSX/Src/hash.h +0 -28
- epyt_flow/EPANET/EPANET-MSX/Src/include/epanetmsx.h +0 -102
- epyt_flow/EPANET/EPANET-MSX/Src/include/epanetmsx_export.h +0 -42
- epyt_flow/EPANET/EPANET-MSX/Src/mathexpr.c +0 -937
- epyt_flow/EPANET/EPANET-MSX/Src/mathexpr.h +0 -39
- epyt_flow/EPANET/EPANET-MSX/Src/mempool.c +0 -204
- epyt_flow/EPANET/EPANET-MSX/Src/mempool.h +0 -24
- epyt_flow/EPANET/EPANET-MSX/Src/msxchem.c +0 -1285
- epyt_flow/EPANET/EPANET-MSX/Src/msxcompiler.c +0 -368
- epyt_flow/EPANET/EPANET-MSX/Src/msxdict.h +0 -42
- epyt_flow/EPANET/EPANET-MSX/Src/msxdispersion.c +0 -586
- epyt_flow/EPANET/EPANET-MSX/Src/msxerr.c +0 -116
- epyt_flow/EPANET/EPANET-MSX/Src/msxfile.c +0 -260
- epyt_flow/EPANET/EPANET-MSX/Src/msxfuncs.c +0 -175
- epyt_flow/EPANET/EPANET-MSX/Src/msxfuncs.h +0 -35
- epyt_flow/EPANET/EPANET-MSX/Src/msxinp.c +0 -1504
- epyt_flow/EPANET/EPANET-MSX/Src/msxout.c +0 -401
- epyt_flow/EPANET/EPANET-MSX/Src/msxproj.c +0 -791
- epyt_flow/EPANET/EPANET-MSX/Src/msxqual.c +0 -2010
- epyt_flow/EPANET/EPANET-MSX/Src/msxrpt.c +0 -400
- epyt_flow/EPANET/EPANET-MSX/Src/msxtank.c +0 -422
- epyt_flow/EPANET/EPANET-MSX/Src/msxtoolkit.c +0 -1164
- epyt_flow/EPANET/EPANET-MSX/Src/msxtypes.h +0 -551
- epyt_flow/EPANET/EPANET-MSX/Src/msxutils.c +0 -524
- epyt_flow/EPANET/EPANET-MSX/Src/msxutils.h +0 -56
- epyt_flow/EPANET/EPANET-MSX/Src/newton.c +0 -158
- epyt_flow/EPANET/EPANET-MSX/Src/newton.h +0 -34
- epyt_flow/EPANET/EPANET-MSX/Src/rk5.c +0 -287
- epyt_flow/EPANET/EPANET-MSX/Src/rk5.h +0 -39
- epyt_flow/EPANET/EPANET-MSX/Src/ros2.c +0 -293
- epyt_flow/EPANET/EPANET-MSX/Src/ros2.h +0 -35
- epyt_flow/EPANET/EPANET-MSX/Src/smatrix.c +0 -816
- epyt_flow/EPANET/EPANET-MSX/Src/smatrix.h +0 -29
- epyt_flow/EPANET/EPANET-MSX/readme.txt +0 -14
- epyt_flow/EPANET/compile_linux.sh +0 -4
- epyt_flow/EPANET/compile_macos.sh +0 -4
- epyt_flow/simulation/backend/__init__.py +0 -1
- epyt_flow/simulation/backend/my_epyt.py +0 -1101
- epyt_flow-0.14.1.dist-info/RECORD +0 -148
- {epyt_flow-0.14.1.dist-info → epyt_flow-0.15.0b1.dist-info}/WHEEL +0 -0
- {epyt_flow-0.14.1.dist-info → epyt_flow-0.15.0b1.dist-info}/licenses/LICENSE +0 -0
- {epyt_flow-0.14.1.dist-info → epyt_flow-0.15.0b1.dist-info}/top_level.txt +0 -0
epyt_flow/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.15.0b1
|
epyt_flow/__init__.py
CHANGED
|
@@ -1,42 +1,5 @@
|
|
|
1
|
-
import subprocess
|
|
2
|
-
import warnings
|
|
3
|
-
import shutil
|
|
4
|
-
import sys
|
|
5
1
|
import os
|
|
6
2
|
|
|
7
3
|
with open(os.path.join(os.path.dirname(__file__), 'VERSION'), encoding="utf-8") as f:
|
|
8
4
|
VERSION = f.read().strip()
|
|
9
5
|
__version__ = VERSION
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def compile_libraries_unix(lib_epanet_name: str, compile_script_name: str,
|
|
13
|
-
gcc_name: str = "gcc") -> None:
|
|
14
|
-
"""Compile EPANET and EPANET-MSX libraries if needed."""
|
|
15
|
-
path_to_custom_libs = os.path.join(os.path.dirname(__file__), "customlibs")
|
|
16
|
-
path_to_lib_epanet = os.path.join(path_to_custom_libs, lib_epanet_name)
|
|
17
|
-
path_to_epanet = os.path.join(os.path.dirname(__file__), "EPANET")
|
|
18
|
-
|
|
19
|
-
update = False
|
|
20
|
-
if os.path.isfile(path_to_lib_epanet):
|
|
21
|
-
if os.path.getmtime(__file__) > os.path.getmtime(path_to_lib_epanet):
|
|
22
|
-
update = True
|
|
23
|
-
|
|
24
|
-
if not os.path.isfile(path_to_lib_epanet) or update:
|
|
25
|
-
if shutil.which(gcc_name) is not None:
|
|
26
|
-
print("Compiling EPANET and EPANET-MSX...")
|
|
27
|
-
try:
|
|
28
|
-
subprocess.check_call(f"cd \"{path_to_epanet}\"; bash {compile_script_name}",
|
|
29
|
-
shell=True)
|
|
30
|
-
print("Done")
|
|
31
|
-
except subprocess.CalledProcessError as ex:
|
|
32
|
-
print(f"Compilation failed\n{ex}")
|
|
33
|
-
warnings.warn("Falling back to pre-compiled library shipped by EPyT.")
|
|
34
|
-
else:
|
|
35
|
-
warnings.warn("GCC is not available to compile the required libraries.\n" +
|
|
36
|
-
"Falling back to pre-compiled library shipped by EPyT.")
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if sys.platform.startswith("linux"):
|
|
40
|
-
compile_libraries_unix("libepanet2_2.so", "compile_linux.sh")
|
|
41
|
-
elif sys.platform.startswith("darwin"):
|
|
42
|
-
compile_libraries_unix("libepanet2_2.dylib", "compile_macos.sh", gcc_name="gcc-15")
|
|
@@ -32,7 +32,7 @@ from .battledim_data import START_TIME_TEST, START_TIME_TRAIN, LEAKS_CONFIG_TEST
|
|
|
32
32
|
LEAKS_CONFIG_TRAIN
|
|
33
33
|
from ..networks import load_ltown
|
|
34
34
|
from ...simulation.events import AbruptLeakage, IncipientLeakage, Leakage
|
|
35
|
-
from ...simulation import ScenarioConfig
|
|
35
|
+
from ...simulation import ScenarioConfig, EpanetConstants
|
|
36
36
|
from ...topology import NetworkTopology
|
|
37
37
|
from ...simulation.scada import ScadaData
|
|
38
38
|
from ...utils import get_temp_folder, to_seconds, create_path_if_not_exist, download_if_necessary
|
|
@@ -463,7 +463,7 @@ def load_scenario(return_test_scenario: bool, download_dir: str = None,
|
|
|
463
463
|
general_params = {"simulation_duration": to_seconds(days=365), # One year
|
|
464
464
|
"hydraulic_time_step": to_seconds(minutes=5), # 5min time steps
|
|
465
465
|
"reporting_time_step": to_seconds(minutes=5),
|
|
466
|
-
"demand_model": {"type":
|
|
466
|
+
"demand_model": {"type": EpanetConstants.EN_PDA, "pressure_min": 0,
|
|
467
467
|
"pressure_required": 0.1,
|
|
468
468
|
"pressure_exponent": 0.5}
|
|
469
469
|
} | ltown_config.general_params
|
|
@@ -28,7 +28,7 @@ from ..networks import load_net1, load_hanoi
|
|
|
28
28
|
from .leakdb_data import NET1_LEAKAGES, HANOI_LEAKAGES
|
|
29
29
|
from ...utils import get_temp_folder, to_seconds, unpack_zip_archive, create_path_if_not_exist, \
|
|
30
30
|
download_if_necessary
|
|
31
|
-
from ...simulation import ScenarioSimulator,
|
|
31
|
+
from ...simulation import ScenarioSimulator, EpanetConstants
|
|
32
32
|
from ...simulation.events import AbruptLeakage, IncipientLeakage
|
|
33
33
|
from ...simulation import ScenarioConfig
|
|
34
34
|
from ...simulation.scada import ScadaData
|
|
@@ -275,6 +275,8 @@ def load_data(scenarios_id: list[int], use_net1: bool, download_dir: str = None,
|
|
|
275
275
|
create_path_if_not_exist(scenario_data_folder_in)
|
|
276
276
|
unpack_zip_archive(scenario_data_file_in, scenario_data_folder_in)
|
|
277
277
|
|
|
278
|
+
scenario_data_folder_in = os.path.join(scenario_data_folder_in, f"Scenario-{s_id}")
|
|
279
|
+
|
|
278
280
|
# Load and parse data
|
|
279
281
|
pressure_files = list(filter(lambda d: d.endswith(".csv"),
|
|
280
282
|
os.listdir(os.path.join(scenario_data_folder_in,
|
|
@@ -464,8 +466,8 @@ def load_scenarios(scenarios_id: list[int], use_net1: bool = True,
|
|
|
464
466
|
general_params = {"simulation_duration": to_seconds(days=365), # One year
|
|
465
467
|
"hydraulic_time_step": hydraulic_time_step,
|
|
466
468
|
"reporting_time_step": hydraulic_time_step,
|
|
467
|
-
"flow_units_id":
|
|
468
|
-
"demand_model": {"type":
|
|
469
|
+
"flow_units_id": EpanetConstants.EN_CMH,
|
|
470
|
+
"demand_model": {"type": EpanetConstants.EN_PDA, "pressure_min": 0,
|
|
469
471
|
"pressure_required": 0.1,
|
|
470
472
|
"pressure_exponent": 0.5}
|
|
471
473
|
} | network_config.general_params
|
|
@@ -544,18 +546,19 @@ def load_scenarios(scenarios_id: list[int], use_net1: bool = True,
|
|
|
544
546
|
if not os.path.exists(f_inp_in):
|
|
545
547
|
with ScenarioSimulator(f_inp_in=network_config.f_inp_in) as wdn:
|
|
546
548
|
wdn.set_general_parameters(**general_params)
|
|
547
|
-
wdn.epanet_api.
|
|
549
|
+
wdn.epanet_api.set_hydraulic_time_step(hydraulic_time_step)
|
|
548
550
|
|
|
549
|
-
wdn.epanet_api.
|
|
551
|
+
for idx in range(1, wdn.epanet_api.getcount(EpanetConstants.EN_PATCOUNT) + 1):
|
|
552
|
+
wdn.epanet_api.deletepattern(idx)
|
|
550
553
|
|
|
551
|
-
reservoir_nodes_id = wdn.epanet_api.
|
|
554
|
+
reservoir_nodes_id = wdn.epanet_api.get_all_reservoirs_id()
|
|
552
555
|
for node_id in network_config.sensor_config.nodes:
|
|
553
556
|
if node_id in network_config.sensor_config.tanks or\
|
|
554
557
|
node_id in reservoir_nodes_id:
|
|
555
558
|
continue
|
|
556
559
|
|
|
557
|
-
node_idx = wdn.epanet_api.
|
|
558
|
-
base_demand = wdn.epanet_api.
|
|
560
|
+
node_idx = wdn.epanet_api.get_node_idx(node_id)
|
|
561
|
+
base_demand = wdn.epanet_api.get_node_base_demand(node_idx)
|
|
559
562
|
|
|
560
563
|
my_demand_pattern = np.array(gen_dem(download_dir))
|
|
561
564
|
|
|
@@ -563,7 +566,7 @@ def load_scenarios(scenarios_id: list[int], use_net1: bool = True,
|
|
|
563
566
|
demand_pattern_id=f"demand_{node_id}",
|
|
564
567
|
demand_pattern=my_demand_pattern)
|
|
565
568
|
|
|
566
|
-
wdn.epanet_api.
|
|
569
|
+
wdn.epanet_api.saveinpfile(f_inp_in)
|
|
567
570
|
|
|
568
571
|
# Create uncertainties
|
|
569
572
|
class MyUniformUncertainty(UniformUncertainty):
|
|
@@ -6,9 +6,9 @@ import uuid
|
|
|
6
6
|
from abc import abstractmethod, ABC
|
|
7
7
|
from typing import Union
|
|
8
8
|
import warnings
|
|
9
|
-
|
|
9
|
+
from epanet_plus import EpanetConstants
|
|
10
10
|
|
|
11
|
-
from ..simulation import ScenarioSimulator, ScenarioConfig, ScadaData
|
|
11
|
+
from ..simulation import ScenarioSimulator, ScenarioConfig, ScadaData
|
|
12
12
|
from ..utils import get_temp_folder
|
|
13
13
|
|
|
14
14
|
|
|
@@ -193,14 +193,14 @@ class ScenarioControlEnv(ABC):
|
|
|
193
193
|
raise RuntimeError("Can not execute actions affecting the hydraulics "+
|
|
194
194
|
"when running EPANET-MSX")
|
|
195
195
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
pattern_idx = self._scenario_sim.epanet_api.getLinkPumpPatternIndex(pump_idx)
|
|
196
|
+
pump_link_idx = self._scenario_sim.epanet_api.get_link_idx(pump_id)
|
|
197
|
+
pattern_idx = self._scenario_sim.epanet_api.getlinkvalue(pump_link_idx,
|
|
198
|
+
EpanetConstants.EN_LINKPATTERN)
|
|
200
199
|
if pattern_idx != 0:
|
|
201
200
|
warnings.warn(f"Can not set pump state of pump {pump_id} because a pump pattern exists")
|
|
202
201
|
else:
|
|
203
|
-
self._scenario_sim.epanet_api.
|
|
202
|
+
self._scenario_sim.epanet_api.setlinkvalue(pump_link_idx, EpanetConstants.EN_STATUS,
|
|
203
|
+
status)
|
|
204
204
|
|
|
205
205
|
def set_pump_speed(self, pump_id: str, speed: float) -> None:
|
|
206
206
|
"""
|
|
@@ -217,15 +217,19 @@ class ScenarioControlEnv(ABC):
|
|
|
217
217
|
raise RuntimeError("Can not execute actions affecting the hydraulics "+
|
|
218
218
|
"when running EPANET-MSX")
|
|
219
219
|
|
|
220
|
-
pump_idx = self._scenario_sim.epanet_api.
|
|
221
|
-
pattern_idx = self._scenario_sim.epanet_api.
|
|
220
|
+
pump_idx = self._scenario_sim.epanet_api.get_link_idx(pump_id)
|
|
221
|
+
pattern_idx = int(self._scenario_sim.epanet_api.getlinkvalue(pump_idx,
|
|
222
|
+
EpanetConstants.EN_LINKPATTERN))
|
|
222
223
|
|
|
223
224
|
if pattern_idx == 0:
|
|
224
225
|
warnings.warn(f"No pattern for pump '{pump_id}' found -- a new pattern is created")
|
|
225
|
-
|
|
226
|
-
self._scenario_sim.epanet_api.
|
|
226
|
+
pattern_id = f"pump_speed_{pump_id}"
|
|
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)
|
|
227
231
|
|
|
228
|
-
self._scenario_sim.epanet_api.
|
|
232
|
+
self._scenario_sim.epanet_api.setpattern(pattern_idx, [speed], 1)
|
|
229
233
|
|
|
230
234
|
def set_valve_status(self, valve_id: str, status: int) -> None:
|
|
231
235
|
"""
|
|
@@ -248,9 +252,9 @@ class ScenarioControlEnv(ABC):
|
|
|
248
252
|
raise RuntimeError("Can not execute actions affecting the hydraulics "+
|
|
249
253
|
"when running EPANET-MSX")
|
|
250
254
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
255
|
+
valve_link_idx = self._scenario_sim.epanet_api.get_link_idx(valve_id)
|
|
256
|
+
self._scenario_sim.epanet_api.setlinkvalue(valve_link_idx, EpanetConstants.EN_STATUS,
|
|
257
|
+
status)
|
|
254
258
|
|
|
255
259
|
def set_node_quality_source_value(self, node_id: str, pattern_id: str,
|
|
256
260
|
qual_value: float) -> None:
|
|
@@ -271,10 +275,10 @@ class ScenarioControlEnv(ABC):
|
|
|
271
275
|
raise RuntimeError("Can not execute actions affecting the hydraulics "+
|
|
272
276
|
"when running EPANET-MSX")
|
|
273
277
|
|
|
274
|
-
node_idx = self._scenario_sim.epanet_api.
|
|
275
|
-
pattern_idx = self._scenario_sim.epanet_api.
|
|
276
|
-
self._scenario_sim.epanet_api.
|
|
277
|
-
self._scenario_sim.epanet_api.
|
|
278
|
+
node_idx = self._scenario_sim.epanet_api.get_node_idx(node_id)
|
|
279
|
+
pattern_idx = self._scenario_sim.epanet_api.getpatternindex(pattern_id)
|
|
280
|
+
self._scenario_sim.epanet_api.setnodevalue(node_idx, EpanetConstants.EN_SOURCEQUAL, 1)
|
|
281
|
+
self._scenario_sim.epanet_api.set_pattern(pattern_idx, [qual_value])
|
|
278
282
|
|
|
279
283
|
def set_node_species_source_value(self, species_id: str, node_id: str, source_type: int,
|
|
280
284
|
pattern_id: str, source_strength: float) -> None:
|
|
@@ -290,7 +294,7 @@ class ScenarioControlEnv(ABC):
|
|
|
290
294
|
ID of the node.
|
|
291
295
|
source_type : `int`
|
|
292
296
|
Type of the external species injection source -- must be one of
|
|
293
|
-
the following EPANET
|
|
297
|
+
the following EPANET constants:
|
|
294
298
|
|
|
295
299
|
- EN_CONCEN = 0
|
|
296
300
|
- EN_MASS = 1
|
|
@@ -312,19 +316,14 @@ class ScenarioControlEnv(ABC):
|
|
|
312
316
|
if self._scenario_sim.f_msx_in is None:
|
|
313
317
|
raise RuntimeError("You are not running EPANET-MSX")
|
|
314
318
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
source_type_ = "FLOWPACED"
|
|
324
|
-
|
|
325
|
-
self._scenario_sim.epanet_api.setMSXPattern(pattern_id, [1])
|
|
326
|
-
self._scenario_sim.epanet_api.setMSXSources(node_id, species_id, source_type_,
|
|
327
|
-
source_strength, pattern_id)
|
|
319
|
+
pattern_idx = self._scenario_sim.epanet_api.MSXgetindex(EpanetConstants.MSX_PATTERN,
|
|
320
|
+
pattern_id)
|
|
321
|
+
self._scenario_sim.epanet_api.MSXsetpattern(pattern_idx, [1], 1)
|
|
322
|
+
|
|
323
|
+
node_idx = self._scenario_sim.epanet_api.get_node_idx(node_id)
|
|
324
|
+
species_idx = self._scenario_sim.epanet_api.get_msx_species_idx(species_id)
|
|
325
|
+
self._scenario_sim.epanet_api.MSXsetsource(node_idx, species_idx, source_type,
|
|
326
|
+
source_strength, pattern_idx)
|
|
328
327
|
|
|
329
328
|
@abstractmethod
|
|
330
329
|
def step(self, *actions) -> Union[tuple[ScadaData, float, bool], tuple[ScadaData, float]]:
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
Module provides implementations of different types of actuator events.
|
|
3
3
|
"""
|
|
4
4
|
import warnings
|
|
5
|
-
from
|
|
6
|
-
import numpy as np
|
|
5
|
+
from epanet_plus import EPyT, EpanetConstants
|
|
7
6
|
|
|
8
7
|
from .system_event import SystemEvent
|
|
9
8
|
from ...serialization import serializable, JsonSerializable, PUMP_STATE_EVENT_ID, \
|
|
@@ -20,9 +19,15 @@ class ActuatorConstants:
|
|
|
20
19
|
Valve or pump is closed.
|
|
21
20
|
EN_OPEN
|
|
22
21
|
Valve or pump is open -- i.e. active.
|
|
22
|
+
EN_SET_CLOSED
|
|
23
|
+
Link set closed indicator
|
|
24
|
+
EN_SET_OPEN
|
|
25
|
+
Link set open indicator
|
|
23
26
|
"""
|
|
24
27
|
EN_CLOSED = 0
|
|
25
28
|
EN_OPEN = 1
|
|
29
|
+
EN_SET_CLOSED = -1e10
|
|
30
|
+
EN_SET_OPEN = 1e10
|
|
26
31
|
|
|
27
32
|
|
|
28
33
|
class ActuatorEvent(SystemEvent):
|
|
@@ -59,8 +64,8 @@ class PumpEvent(ActuatorEvent):
|
|
|
59
64
|
|
|
60
65
|
super().__init__(**kwds)
|
|
61
66
|
|
|
62
|
-
def init(self, epanet_api:
|
|
63
|
-
if self.__pump_id not in epanet_api.
|
|
67
|
+
def init(self, epanet_api: EPyT) -> None:
|
|
68
|
+
if self.__pump_id not in epanet_api.get_all_pumps_id():
|
|
64
69
|
raise ValueError(f"Invalid pump ID '{self.__pump_id}'")
|
|
65
70
|
|
|
66
71
|
super().init(epanet_api)
|
|
@@ -132,15 +137,15 @@ class PumpStateEvent(PumpEvent, JsonSerializable):
|
|
|
132
137
|
return self.__pump_state
|
|
133
138
|
|
|
134
139
|
def apply(self, cur_time: int) -> None:
|
|
135
|
-
|
|
136
|
-
pump_link_idx = self._epanet_api.getLinkPumpIndex(pump_idx)
|
|
140
|
+
pump_link_idx = self._epanet_api.getlinkindex(self.pump_id)
|
|
137
141
|
|
|
138
|
-
pattern_idx = self._epanet_api.
|
|
142
|
+
pattern_idx = self._epanet_api.getlinkvalue(pump_link_idx, EpanetConstants.EN_LINKPATTERN)
|
|
139
143
|
if pattern_idx != 0:
|
|
140
144
|
warnings.warn(f"Can not set pump state of pump {self.pump_id} " +
|
|
141
145
|
"because a pump pattern exists")
|
|
142
146
|
else:
|
|
143
|
-
self._epanet_api.
|
|
147
|
+
self._epanet_api.setlinkvalue(pump_link_idx, EpanetConstants.EN_STATUS,
|
|
148
|
+
self.__pump_state)
|
|
144
149
|
|
|
145
150
|
|
|
146
151
|
@serializable(PUMP_SPEED_EVENT_ID, ".epytflow_pump_speed_event")
|
|
@@ -180,15 +185,17 @@ class PumpSpeedEvent(PumpEvent, JsonSerializable):
|
|
|
180
185
|
return self.__pump_speed
|
|
181
186
|
|
|
182
187
|
def apply(self, cur_time: int) -> None:
|
|
183
|
-
pump_idx = self._epanet_api.
|
|
184
|
-
pattern_idx = self._epanet_api.
|
|
188
|
+
pump_idx = self._epanet_api.get_link_idx(self.pump_id)
|
|
189
|
+
pattern_idx = self._epanet_api.getlinkvalue(pump_idx, EpanetConstants.EN_LINKPATTERN)
|
|
185
190
|
|
|
186
191
|
if pattern_idx == 0:
|
|
187
192
|
warnings.warn(f"No pattern for pump '{self.pump_id}' found -- a new pattern is created")
|
|
188
|
-
|
|
189
|
-
self._epanet_api.
|
|
193
|
+
pattern_id = f"pump_speed_{self.pump_id}"
|
|
194
|
+
self._epanet_api.add_pattern(pattern_id, [self.__pump_speed])
|
|
195
|
+
pattern_idx = self._epanet_api.getpatternindex(pattern_id)
|
|
196
|
+
self._epanet_api.setlinkvalue(pump_idx, EpanetConstants.EN_LINKPATTERN, pattern_idx)
|
|
190
197
|
|
|
191
|
-
self._epanet_api.
|
|
198
|
+
self._epanet_api.setpattern(pattern_idx, [self.__pump_speed], 1)
|
|
192
199
|
|
|
193
200
|
|
|
194
201
|
@serializable(VALVE_STATE_EVENT_ID, ".epytflow_valve_state_event")
|
|
@@ -222,8 +229,8 @@ class ValveStateEvent(ActuatorEvent, JsonSerializable):
|
|
|
222
229
|
|
|
223
230
|
super().__init__(**kwds)
|
|
224
231
|
|
|
225
|
-
def init(self, epanet_api:
|
|
226
|
-
if self.__valve_id not in epanet_api.
|
|
232
|
+
def init(self, epanet_api: EPyT) -> None:
|
|
233
|
+
if self.__valve_id not in epanet_api.get_all_valves_id():
|
|
227
234
|
raise ValueError(f"Invalid valve ID '{self.__valve_id}'")
|
|
228
235
|
|
|
229
236
|
super().init(epanet_api)
|
|
@@ -260,6 +267,5 @@ class ValveStateEvent(ActuatorEvent, JsonSerializable):
|
|
|
260
267
|
return self.__valve_state
|
|
261
268
|
|
|
262
269
|
def apply(self, cur_time: int) -> None:
|
|
263
|
-
|
|
264
|
-
valve_link_idx
|
|
265
|
-
self._epanet_api.setLinkStatus(valve_link_idx, self.__valve_state)
|
|
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)
|
|
@@ -4,8 +4,7 @@ Module provides classes for implementing leakages.
|
|
|
4
4
|
from copy import deepcopy
|
|
5
5
|
import math
|
|
6
6
|
import numpy as np
|
|
7
|
-
import
|
|
8
|
-
from epyt.epanet import ToolkitConstants
|
|
7
|
+
from epanet_plus import EPyT, EpanetConstants
|
|
9
8
|
|
|
10
9
|
from .system_event import SystemEvent
|
|
11
10
|
from ...serialization import serializable, JsonSerializable, \
|
|
@@ -231,10 +230,10 @@ class Leakage(SystemEvent, JsonSerializable):
|
|
|
231
230
|
`float`
|
|
232
231
|
Leak emitter coefficient.
|
|
233
232
|
"""
|
|
234
|
-
flow_unit = self._epanet_api.
|
|
235
|
-
if flow_unit ==
|
|
233
|
+
flow_unit = self._epanet_api.getflowunits()
|
|
234
|
+
if flow_unit == EpanetConstants.EN_CMH:
|
|
236
235
|
g = 127137600 # m/h^2
|
|
237
|
-
elif flow_unit ==
|
|
236
|
+
elif flow_unit == EpanetConstants.EN_CFS:
|
|
238
237
|
g = 32.17405 # feet/s^2
|
|
239
238
|
else:
|
|
240
239
|
raise ValueError("Leakages are only implemented for the following flow units:\n" +
|
|
@@ -248,30 +247,30 @@ class Leakage(SystemEvent, JsonSerializable):
|
|
|
248
247
|
def _get_new_node_id(self) -> str:
|
|
249
248
|
return f"leak_node_{self.__link_id}"
|
|
250
249
|
|
|
251
|
-
def init(self, epanet_api:
|
|
250
|
+
def init(self, epanet_api: EPyT) -> None:
|
|
252
251
|
super().init(epanet_api)
|
|
253
252
|
|
|
254
253
|
# Split pipe if leak is placed at a link/pipe
|
|
255
254
|
if self.__link_id is not None:
|
|
256
|
-
if self.__link_id not in self._epanet_api.
|
|
255
|
+
if self.__link_id not in self._epanet_api.get_all_links_id():
|
|
257
256
|
raise ValueError(f"Unknown link/pipe '{self.__link_id}'")
|
|
258
257
|
|
|
259
258
|
new_link_id = self._get_new_link_id()
|
|
260
259
|
new_node_id = self._get_new_node_id()
|
|
261
260
|
|
|
262
|
-
all_nodes_id = self._epanet_api.
|
|
261
|
+
all_nodes_id = self._epanet_api.get_all_nodes_id()
|
|
263
262
|
if new_node_id in all_nodes_id:
|
|
264
263
|
raise ValueError(f"There is already a leak at pipe {self.link_id}")
|
|
265
264
|
|
|
266
|
-
self._epanet_api.
|
|
267
|
-
self.__leaky_node_idx = self._epanet_api.
|
|
265
|
+
self._epanet_api.split_pipe(self.link_id, new_link_id, new_node_id)
|
|
266
|
+
self.__leaky_node_idx = self._epanet_api.get_node_idx(new_node_id)
|
|
268
267
|
else:
|
|
269
|
-
if self.__node_id not in self._epanet_api.
|
|
268
|
+
if self.__node_id not in self._epanet_api.get_all_nodes_id():
|
|
270
269
|
raise ValueError(f"Unknown node '{self.__node_id}'")
|
|
271
270
|
|
|
272
|
-
self.__leaky_node_idx = self._epanet_api.
|
|
271
|
+
self.__leaky_node_idx = self._epanet_api.get_node_idx(self.__node_id)
|
|
273
272
|
|
|
274
|
-
self._epanet_api.
|
|
273
|
+
self._epanet_api.setnodevalue(self.__leaky_node_idx, EpanetConstants.EN_EMITTER, 0.)
|
|
275
274
|
|
|
276
275
|
# Compute leak emitter coefficient
|
|
277
276
|
self.__leak_emitter_coef = self.compute_leak_emitter_coefficient(
|
|
@@ -279,51 +278,54 @@ class Leakage(SystemEvent, JsonSerializable):
|
|
|
279
278
|
|
|
280
279
|
def cleanup(self) -> None:
|
|
281
280
|
if self.__link_id is not None:
|
|
282
|
-
pipe_idx = self._epanet_api.
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
self._epanet_api.
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
self._epanet_api.
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
self._epanet_api.
|
|
312
|
-
|
|
313
|
-
self._epanet_api.
|
|
314
|
-
|
|
315
|
-
self._epanet_api.
|
|
281
|
+
pipe_idx = self._epanet_api.get_link_idx(self.__link_id)
|
|
282
|
+
link_diameter = self._epanet_api.get_link_diameter(pipe_idx)
|
|
283
|
+
link_length = self._epanet_api.get_link_length(pipe_idx)
|
|
284
|
+
link_roughness_coeff = self._epanet_api.get_link_roughness(pipe_idx)
|
|
285
|
+
link_minor_loss_coeff = self._epanet_api.get_link_minorloss(pipe_idx)
|
|
286
|
+
link_initial_status = self._epanet_api.get_link_init_status(pipe_idx)
|
|
287
|
+
link_initial_setting = self._epanet_api.get_link_init_setting(pipe_idx)
|
|
288
|
+
link_bulk_reaction_coeff = self._epanet_api.get_link_bulk_raction_coeff(pipe_idx)
|
|
289
|
+
link_wall_reaction_coeff = self._epanet_api.get_link_wall_raction_coeff(pipe_idx)
|
|
290
|
+
|
|
291
|
+
node_a_idx = int(self._epanet_api.getlinknodes(pipe_idx)[0])
|
|
292
|
+
node_b_idx = int(self._epanet_api.getlinknodes(self._epanet_api.get_link_idx(self._get_new_link_id()))[1])
|
|
293
|
+
|
|
294
|
+
self._epanet_api.deletelink(self._epanet_api.get_link_idx(self._get_new_link_id()),
|
|
295
|
+
EpanetConstants.EN_UNCONDITIONAL)
|
|
296
|
+
self._epanet_api.deletelink(self._epanet_api.get_link_idx(self.__link_id),
|
|
297
|
+
EpanetConstants.EN_UNCONDITIONAL)
|
|
298
|
+
self._epanet_api.deletenode(self._epanet_api.get_node_idx(self._get_new_node_id()),
|
|
299
|
+
EpanetConstants.EN_UNCONDITIONAL)
|
|
300
|
+
|
|
301
|
+
self._epanet_api.addlink(self.__link_id, EpanetConstants.EN_PIPE,
|
|
302
|
+
self._epanet_api.get_node_id(node_a_idx),
|
|
303
|
+
self._epanet_api.get_node_id(node_b_idx))
|
|
304
|
+
link_idx = self._epanet_api.get_link_idx(self.__link_id)
|
|
305
|
+
self._epanet_api.setlinknodes(link_idx, node_a_idx, node_b_idx)
|
|
306
|
+
self._epanet_api.setlinktype(link_idx, EpanetConstants.EN_PIPE,
|
|
307
|
+
EpanetConstants.EN_UNCONDITIONAL)
|
|
308
|
+
self._epanet_api.setpipedata(link_idx, link_length, link_diameter, link_roughness_coeff,
|
|
309
|
+
link_minor_loss_coeff)
|
|
310
|
+
self._epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_INITSTATUS,
|
|
311
|
+
link_initial_status)
|
|
312
|
+
self._epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_INITSETTING,
|
|
313
|
+
link_initial_setting)
|
|
314
|
+
self._epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_KBULK,
|
|
315
|
+
link_bulk_reaction_coeff)
|
|
316
|
+
self._epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_KWALL,
|
|
317
|
+
link_wall_reaction_coeff)
|
|
316
318
|
|
|
317
319
|
def reset(self) -> None:
|
|
318
320
|
self.__time_pattern_idx = 0
|
|
319
321
|
|
|
320
322
|
def exit(self, cur_time) -> None:
|
|
321
|
-
self._epanet_api.
|
|
323
|
+
self._epanet_api.setnodevalue(self.__leaky_node_idx, EpanetConstants.EN_EMITTER, 0.)
|
|
322
324
|
|
|
323
325
|
def apply(self, cur_time: int) -> None:
|
|
324
|
-
self._epanet_api.
|
|
325
|
-
|
|
326
|
-
|
|
326
|
+
self._epanet_api.setnodevalue(self.__leaky_node_idx, EpanetConstants.EN_EMITTER,
|
|
327
|
+
self.__leak_emitter_coef *
|
|
328
|
+
self.__profile[self.__time_pattern_idx])
|
|
327
329
|
self.__time_pattern_idx += 1
|
|
328
330
|
|
|
329
331
|
|
|
@@ -357,12 +359,12 @@ class AbruptLeakage(Leakage):
|
|
|
357
359
|
else:
|
|
358
360
|
super().__init__(link_id=link_id, diameter=diameter, area=area, **kwds)
|
|
359
361
|
|
|
360
|
-
def init(self, epanet_api:
|
|
362
|
+
def init(self, epanet_api: EPyT) -> None:
|
|
361
363
|
super().init(epanet_api)
|
|
362
364
|
|
|
363
365
|
# Set pattern
|
|
364
|
-
total_sim_duration = self._epanet_api.
|
|
365
|
-
time_step = self._epanet_api.
|
|
366
|
+
total_sim_duration = self._epanet_api.get_simulation_duration()
|
|
367
|
+
time_step = self._epanet_api.get_hydraulic_time_step()
|
|
366
368
|
|
|
367
369
|
if self.end_time is not None:
|
|
368
370
|
n_leaky_time_points = math.ceil((self.end_time - self.start_time) / time_step)
|
|
@@ -436,12 +438,12 @@ class IncipientLeakage(Leakage):
|
|
|
436
438
|
def __str__(self) -> str:
|
|
437
439
|
return f"{super().__str__()} peak_time: {self.peak_time}"
|
|
438
440
|
|
|
439
|
-
def init(self, epanet_api:
|
|
441
|
+
def init(self, epanet_api: EPyT) -> None:
|
|
440
442
|
super().init(epanet_api)
|
|
441
443
|
|
|
442
444
|
# Set pattern
|
|
443
|
-
total_sim_duration = self._epanet_api.
|
|
444
|
-
time_step = self._epanet_api.
|
|
445
|
+
total_sim_duration = self._epanet_api.get_simulation_duration()
|
|
446
|
+
time_step = self._epanet_api.get_hydraulic_time_step()
|
|
445
447
|
|
|
446
448
|
if self.end_time is not None:
|
|
447
449
|
n_leaky_time_points = math.ceil((self.end_time - self.start_time) / time_step)
|
|
@@ -5,8 +5,7 @@ from copy import deepcopy
|
|
|
5
5
|
import warnings
|
|
6
6
|
import math
|
|
7
7
|
import numpy as np
|
|
8
|
-
import
|
|
9
|
-
from epyt.epanet import ToolkitConstants
|
|
8
|
+
from epanet_plus import EPyT, EpanetConstants
|
|
10
9
|
|
|
11
10
|
from .system_event import SystemEvent
|
|
12
11
|
from ...serialization import serializable, JsonSerializable, \
|
|
@@ -31,7 +30,7 @@ class SpeciesInjectionEvent(SystemEvent, JsonSerializable):
|
|
|
31
30
|
Note that the pattern time step is equivalent to the EPANET pattern time step.
|
|
32
31
|
source_type : `int`
|
|
33
32
|
Type of the bulk species injection source -- must be one of
|
|
34
|
-
the following EPANET
|
|
33
|
+
the following EPANET constants:
|
|
35
34
|
|
|
36
35
|
- EN_CONCEN = 0
|
|
37
36
|
- EN_MASS = 1
|
|
@@ -140,18 +139,18 @@ class SpeciesInjectionEvent(SystemEvent, JsonSerializable):
|
|
|
140
139
|
def _get_pattern_id(self) -> str:
|
|
141
140
|
return f"{self.__species_id}_{self.__node_id}"
|
|
142
141
|
|
|
143
|
-
def init(self, epanet_api:
|
|
142
|
+
def init(self, epanet_api: EPyT) -> None:
|
|
144
143
|
super().init(epanet_api)
|
|
145
144
|
|
|
146
145
|
# Check parameters
|
|
147
|
-
if self.__species_id not in self._epanet_api.
|
|
146
|
+
if self.__species_id not in self._epanet_api.get_all_msx_species_id():
|
|
148
147
|
raise ValueError(f"Unknown species '{self.__species_id}'")
|
|
149
|
-
if self.__node_id not in self._epanet_api.
|
|
148
|
+
if self.__node_id not in self._epanet_api.get_all_nodes_id():
|
|
150
149
|
raise ValueError(f"Unknown node '{self.__node_id}'")
|
|
151
150
|
|
|
152
151
|
# Create final injection strength pattern
|
|
153
|
-
total_sim_duration = self._epanet_api.
|
|
154
|
-
time_step = self._epanet_api.
|
|
152
|
+
total_sim_duration = self._epanet_api.get_simulation_duration()
|
|
153
|
+
time_step = self._epanet_api.get_hydraulic_time_step()
|
|
155
154
|
|
|
156
155
|
pattern = np.zeros(math.ceil(total_sim_duration / time_step))
|
|
157
156
|
|
|
@@ -170,34 +169,26 @@ class SpeciesInjectionEvent(SystemEvent, JsonSerializable):
|
|
|
170
169
|
injection_time_start_idx + injection_pattern_length] = injection_pattern
|
|
171
170
|
|
|
172
171
|
# Create injection
|
|
173
|
-
source_type_ = "None"
|
|
174
|
-
if self.__source_type == ToolkitConstants.EN_CONCEN:
|
|
175
|
-
source_type_ = "CONCEN"
|
|
176
|
-
elif self.__source_type == ToolkitConstants.EN_MASS:
|
|
177
|
-
source_type_ = "MASS"
|
|
178
|
-
elif self.__source_type == ToolkitConstants.EN_SETPOINT:
|
|
179
|
-
source_type_ = "SETPOINT"
|
|
180
|
-
elif self.__source_type == ToolkitConstants.EN_FLOWPACED:
|
|
181
|
-
source_type_ = "FLOWPACED"
|
|
182
|
-
|
|
183
172
|
pattern_id = self._get_pattern_id()
|
|
184
|
-
if pattern_id in self._epanet_api.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
173
|
+
if pattern_id in [self._epanet_api.MSXgetID(EpanetConstants.MSX_PATTERN, pattern_idx + 1)
|
|
174
|
+
for pattern_idx in
|
|
175
|
+
range(self._epanet_api.MSXgetcount(EpanetConstants.MSX_PATTERN))]:
|
|
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
|
+
cur_source_type = self._epanet_api.MSXgetsource(node_idx, species_idx)
|
|
179
|
+
if cur_source_type[0] != self.__source_type:
|
|
189
180
|
raise ValueError("Source type does not match existing source type")
|
|
190
181
|
|
|
191
182
|
# Add new injection amount to existing injection --
|
|
192
183
|
# i.e. two injection events at the same node
|
|
193
|
-
pattern_idx
|
|
194
|
-
cur_pattern = self._epanet_api.
|
|
195
|
-
cur_pattern
|
|
196
|
-
self._epanet_api.
|
|
184
|
+
pattern_idx = self._epanet_api.MSXgetindex(EpanetConstants.MSX_PATTERN, pattern_id)
|
|
185
|
+
cur_pattern = self._epanet_api.get_msx_pattern(pattern_idx)
|
|
186
|
+
cur_pattern = np.array(cur_pattern) + pattern
|
|
187
|
+
self._epanet_api.MSXsetpattern(pattern_idx, cur_pattern.tolist(), len(cur_pattern))
|
|
197
188
|
else:
|
|
198
|
-
self._epanet_api.
|
|
199
|
-
self._epanet_api.
|
|
200
|
-
|
|
189
|
+
self._epanet_api.add_msx_pattern(pattern_id, pattern.tolist())
|
|
190
|
+
self._epanet_api.set_msx_source(self.__node_id, self.__species_id, self.__source_type, 1,
|
|
191
|
+
pattern_id)
|
|
201
192
|
|
|
202
193
|
def cleanup(self) -> None:
|
|
203
194
|
warnings.warn("Can not undo SpeciesInjectionEvent -- " +
|