epyt-flow 0.14.1__py3-none-any.whl → 0.15.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- epyt_flow/VERSION +1 -1
- epyt_flow/__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 +368 -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/utils.py +66 -0
- epyt_flow/visualization/visualization_utils.py +4 -2
- {epyt_flow-0.14.1.dist-info → epyt_flow-0.15.0.dist-info}/METADATA +14 -19
- epyt_flow-0.15.0.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.0.dist-info}/WHEEL +0 -0
- {epyt_flow-0.14.1.dist-info → epyt_flow-0.15.0.dist-info}/licenses/LICENSE +0 -0
- {epyt_flow-0.14.1.dist-info → epyt_flow-0.15.0.dist-info}/top_level.txt +0 -0
|
@@ -17,7 +17,7 @@ import math
|
|
|
17
17
|
import uuid
|
|
18
18
|
import numpy as np
|
|
19
19
|
from tqdm import tqdm
|
|
20
|
-
from
|
|
20
|
+
from epanet_plus import EPyT, EpanetConstants
|
|
21
21
|
|
|
22
22
|
from .scenario_config import ScenarioConfig
|
|
23
23
|
from .sensor_config import SensorConfig, areaunit_to_id, massunit_to_id, qualityunit_to_id, \
|
|
@@ -31,7 +31,7 @@ from ..uncertainty import ModelUncertainty, SensorNoise
|
|
|
31
31
|
from .events import SystemEvent, Leakage, ActuatorEvent, SensorFault, SensorReadingAttack, \
|
|
32
32
|
SensorReadingEvent
|
|
33
33
|
from .scada import ScadaData, CustomControlModule, SimpleControlModule, ComplexControlModule, \
|
|
34
|
-
RuleCondition, RuleAction, ActuatorConstants, EN_R_ACTION_SETTING
|
|
34
|
+
RuleCondition, RuleAction, ActuatorConstants, EN_R_ACTION_SETTING, RULESTATUS
|
|
35
35
|
from ..topology import NetworkTopology, UNITS_SIMETRIC, UNITS_USCUSTOM
|
|
36
36
|
from ..utils import get_temp_folder
|
|
37
37
|
|
|
@@ -136,23 +136,6 @@ class ScenarioSimulator():
|
|
|
136
136
|
self.__running_simulation = False
|
|
137
137
|
self.__uncertainties_applied = False
|
|
138
138
|
|
|
139
|
-
# Check availability of custom EPANET libraries
|
|
140
|
-
custom_epanet_lib = None
|
|
141
|
-
custom_epanetmsx_lib = None
|
|
142
|
-
if sys.platform.startswith("linux") or sys.platform.startswith("darwin") :
|
|
143
|
-
path_to_custom_libs = os.path.join(pathlib.Path(__file__).parent.resolve(),
|
|
144
|
-
"..", "customlibs")
|
|
145
|
-
|
|
146
|
-
libepanet_name = "libepanet2_2.so" if sys.platform.startswith("linux") \
|
|
147
|
-
else "libepanet2_2.dylib"
|
|
148
|
-
libepanetmsx_name = "libepanetmsx2_2_0.so" if sys.platform.startswith("linux") \
|
|
149
|
-
else "libepanetmsx2_2_0.dylib"
|
|
150
|
-
|
|
151
|
-
if os.path.isfile(os.path.join(path_to_custom_libs, libepanet_name)):
|
|
152
|
-
custom_epanet_lib = os.path.join(path_to_custom_libs, libepanet_name)
|
|
153
|
-
if os.path.isfile(os.path.join(path_to_custom_libs, libepanetmsx_name)):
|
|
154
|
-
custom_epanetmsx_lib = os.path.join(path_to_custom_libs, libepanetmsx_name)
|
|
155
|
-
|
|
156
139
|
# Workaround for EPyT bug concerning parallel simulations (see EPyT issue #54):
|
|
157
140
|
# 1. Create random tmp folder (make sure it is unique!)
|
|
158
141
|
# 2. Copy .inp and .msx file there
|
|
@@ -183,14 +166,11 @@ class ScenarioSimulator():
|
|
|
183
166
|
else:
|
|
184
167
|
my_f_msx_in = None
|
|
185
168
|
|
|
186
|
-
from
|
|
187
|
-
self.epanet_api = EPyT(my_f_inp_in,
|
|
188
|
-
customlib=custom_epanet_lib, loadfile=True,
|
|
189
|
-
display_msg=epanet_verbose,
|
|
190
|
-
display_warnings=False)
|
|
169
|
+
from epanet_plus import EPyT # Workaround: Sphinx autodoc "importlib.import_module TypeError: __mro_entries__"
|
|
170
|
+
self.epanet_api = EPyT(my_f_inp_in, use_project=self.__f_msx_in is None)
|
|
191
171
|
|
|
192
172
|
if self.__f_msx_in is not None:
|
|
193
|
-
self.epanet_api.
|
|
173
|
+
self.epanet_api.load_msx_file(my_f_msx_in)
|
|
194
174
|
|
|
195
175
|
# Do not raise exceptions in the case of EPANET warnings and errors
|
|
196
176
|
self.epanet_api.set_error_handling(raise_exception_on_error=raise_exception_on_error,
|
|
@@ -225,8 +205,8 @@ class ScenarioSimulator():
|
|
|
225
205
|
valve_id_to_idx: dict = None, pump_id_to_idx: dict = None,
|
|
226
206
|
tank_id_to_idx: dict = None, bulkspecies_id_to_idx: dict = None,
|
|
227
207
|
surfacespecies_id_to_idx: dict = None) -> SensorConfig:
|
|
228
|
-
flow_unit = self.epanet_api.
|
|
229
|
-
quality_unit = qualityunit_to_id(self.epanet_api.
|
|
208
|
+
flow_unit = self.epanet_api.getflowunits()
|
|
209
|
+
quality_unit = qualityunit_to_id(self.epanet_api.get_quality_info()["chemUnits"])
|
|
230
210
|
bulk_species = []
|
|
231
211
|
surface_species = []
|
|
232
212
|
bulk_species_mass_unit = []
|
|
@@ -234,23 +214,22 @@ class ScenarioSimulator():
|
|
|
234
214
|
surface_species_area_unit = None
|
|
235
215
|
|
|
236
216
|
if self.__f_msx_in is not None:
|
|
237
|
-
surface_species_area_unit = areaunit_to_id(self.epanet_api.
|
|
217
|
+
surface_species_area_unit = areaunit_to_id("FT2")#self.epanet_api.get_msx_options()["areaUnits"])
|
|
238
218
|
|
|
239
|
-
for species_id,
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if species_type == "BULK":
|
|
219
|
+
for species_id, species_info in zip(self.epanet_api.get_all_msx_species_id(),
|
|
220
|
+
self.epanet_api.get_all_msx_species_info()):
|
|
221
|
+
if species_info["type"] == EpanetConstants.MSX_BULK:
|
|
243
222
|
bulk_species.append(species_id)
|
|
244
|
-
bulk_species_mass_unit.append(massunit_to_id(
|
|
245
|
-
elif
|
|
223
|
+
bulk_species_mass_unit.append(massunit_to_id(species_info["units"]))
|
|
224
|
+
elif species_info["type"] == EpanetConstants.MSX_WALL:
|
|
246
225
|
surface_species.append(species_id)
|
|
247
|
-
surface_species_mass_unit.append(massunit_to_id(
|
|
226
|
+
surface_species_mass_unit.append(massunit_to_id(species_info["units"]))
|
|
248
227
|
|
|
249
|
-
return SensorConfig(nodes=self.epanet_api.
|
|
250
|
-
links=self.epanet_api.
|
|
251
|
-
valves=self.epanet_api.
|
|
252
|
-
pumps=self.epanet_api.
|
|
253
|
-
tanks=self.epanet_api.
|
|
228
|
+
return SensorConfig(nodes=self.epanet_api.get_all_nodes_id(),
|
|
229
|
+
links=self.epanet_api.get_all_links_id(),
|
|
230
|
+
valves=self.epanet_api.get_all_valves_id(),
|
|
231
|
+
pumps=self.epanet_api.get_all_pumps_id(),
|
|
232
|
+
tanks=self.epanet_api.get_all_tanks_id(),
|
|
254
233
|
bulk_species=bulk_species,
|
|
255
234
|
surface_species=surface_species,
|
|
256
235
|
flow_unit=flow_unit,
|
|
@@ -490,31 +469,18 @@ class ScenarioSimulator():
|
|
|
490
469
|
def _parse_simple_control_rules(self) -> list[SimpleControlModule]:
|
|
491
470
|
controls = []
|
|
492
471
|
|
|
493
|
-
for idx in self.epanet_api.
|
|
494
|
-
|
|
472
|
+
for idx in range(self.epanet_api.get_num_controls()):
|
|
473
|
+
cond_type, link_idx, link_status, node_idx, level = \
|
|
474
|
+
self.epanet_api.getcontrol(idx + 1)
|
|
495
475
|
|
|
496
|
-
if
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
link_status = ActuatorConstants.EN_CLOSED
|
|
500
|
-
|
|
501
|
-
if control.Type == "LOWLEVEL":
|
|
502
|
-
cond_type = ToolkitConstants.EN_LOWLEVEL
|
|
503
|
-
elif control.Type == "HIGHLEVEL":
|
|
504
|
-
cond_type = ToolkitConstants.EN_HILEVEL
|
|
505
|
-
elif control.Type == "TIMER":
|
|
506
|
-
cond_type = ToolkitConstants.EN_TIMER
|
|
507
|
-
elif control.Type == "TIMEOFDAY":
|
|
508
|
-
cond_type = ToolkitConstants.EN_TIMEOFDAY
|
|
509
|
-
|
|
510
|
-
if control.NodeID is not None:
|
|
511
|
-
cond_var_value = control.NodeID
|
|
512
|
-
cond_comp_value = control.Value
|
|
476
|
+
if node_idx != 0:
|
|
477
|
+
cond_var_value = self.epanet_api.get_node_id(node_idx)
|
|
478
|
+
cond_comp_value = level
|
|
513
479
|
else:
|
|
514
|
-
if cond_type ==
|
|
515
|
-
cond_var_value = int(
|
|
516
|
-
elif cond_type ==
|
|
517
|
-
sec =
|
|
480
|
+
if cond_type == EpanetConstants.EN_TIMER:
|
|
481
|
+
cond_var_value = int(level / 3600)
|
|
482
|
+
elif cond_type == EpanetConstants.EN_TIMEOFDAY:
|
|
483
|
+
sec = level
|
|
518
484
|
if sec <= 43200:
|
|
519
485
|
cond_var_value = \
|
|
520
486
|
f"{':'.join(str(timedelta(seconds=sec)).split(':')[:2])} AM"
|
|
@@ -524,7 +490,7 @@ class ScenarioSimulator():
|
|
|
524
490
|
f"{':'.join(str(timedelta(seconds=sec)).split(':')[:2])} PM"
|
|
525
491
|
cond_comp_value = None
|
|
526
492
|
|
|
527
|
-
controls.append(SimpleControlModule(link_id=
|
|
493
|
+
controls.append(SimpleControlModule(link_id=self.epanet_api.get_link_id(link_idx),
|
|
528
494
|
link_status=link_status,
|
|
529
495
|
cond_type=cond_type,
|
|
530
496
|
cond_var_value=cond_var_value,
|
|
@@ -535,35 +501,30 @@ class ScenarioSimulator():
|
|
|
535
501
|
def _parse_complex_control_rules(self) -> list[ComplexControlModule]:
|
|
536
502
|
controls = []
|
|
537
503
|
|
|
538
|
-
|
|
539
|
-
for rule_idx,
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
rule_id = rule["Rule_ID"]
|
|
543
|
-
rule_priority, *_ = rule_info.Priority
|
|
544
|
-
|
|
545
|
-
# Parse conditions
|
|
546
|
-
n_rule_premises, *_ = rule_info.Premises
|
|
504
|
+
all_rules_id = self.epanet_api.get_all_rules_id()
|
|
505
|
+
for rule_idx, rule_id in enumerate(all_rules_id, start=1):
|
|
506
|
+
n_rule_premises, n_rule_then_actions, n_rule_else_actions, rule_priority = \
|
|
507
|
+
self.epanet_api.getrule(rule_idx)
|
|
547
508
|
|
|
548
509
|
condition_1 = None
|
|
549
510
|
additional_conditions = []
|
|
550
511
|
for j in range(1, n_rule_premises + 1):
|
|
551
512
|
[logop, object_type_id, obj_idx, variable_type_id, relop, status, value_premise] = \
|
|
552
|
-
self.epanet_api.
|
|
513
|
+
self.epanet_api.getpremise(rule_idx, j)
|
|
553
514
|
|
|
554
515
|
object_id = None
|
|
555
|
-
if object_type_id ==
|
|
556
|
-
object_id = self.epanet_api.
|
|
557
|
-
elif object_type_id ==
|
|
558
|
-
object_id = self.epanet_api.
|
|
559
|
-
elif object_type_id ==
|
|
516
|
+
if object_type_id == EpanetConstants.EN_R_NODE:
|
|
517
|
+
object_id = self.epanet_api.get_node_id(obj_idx)
|
|
518
|
+
elif object_type_id == EpanetConstants.EN_R_LINK:
|
|
519
|
+
object_id = self.epanet_api.get_link_id(obj_idx)
|
|
520
|
+
elif object_type_id == EpanetConstants.EN_R_SYSTEM:
|
|
560
521
|
object_id = ""
|
|
561
522
|
|
|
562
|
-
if variable_type_id >=
|
|
523
|
+
if variable_type_id >= EpanetConstants.EN_R_TIME:
|
|
563
524
|
value_premise = datetime.fromtimestamp(value_premise)\
|
|
564
525
|
.strftime("%I:%M %p")
|
|
565
526
|
if status != 0:
|
|
566
|
-
value_premise =
|
|
527
|
+
value_premise = RULESTATUS[status - 1]
|
|
567
528
|
|
|
568
529
|
condition = RuleCondition(object_type_id, object_id, variable_type_id,
|
|
569
530
|
relop, value_premise)
|
|
@@ -573,14 +534,13 @@ class ScenarioSimulator():
|
|
|
573
534
|
additional_conditions.append((logop, condition))
|
|
574
535
|
|
|
575
536
|
# Parse actions
|
|
576
|
-
n_rule_then_actions, *_ = rule_info.ThenActions
|
|
577
537
|
actions = []
|
|
578
538
|
for j in range(1, n_rule_then_actions + 1):
|
|
579
539
|
[link_idx, link_status, link_setting] = \
|
|
580
|
-
self.epanet_api.
|
|
540
|
+
self.epanet_api.getthenaction(rule_idx, j)
|
|
581
541
|
|
|
582
|
-
link_type_id = self.epanet_api.
|
|
583
|
-
link_id = self.epanet_api.
|
|
542
|
+
link_type_id = self.epanet_api.getlinktype(link_idx)
|
|
543
|
+
link_id = self.epanet_api.get_link_id(link_idx)
|
|
584
544
|
if link_status >= 0:
|
|
585
545
|
action_type_id = link_status
|
|
586
546
|
action_value = link_status
|
|
@@ -590,14 +550,13 @@ class ScenarioSimulator():
|
|
|
590
550
|
|
|
591
551
|
actions.append(RuleAction(link_type_id, link_id, action_type_id, action_value))
|
|
592
552
|
|
|
593
|
-
n_rule_else_actions, *_ = rule_info.ElseActions
|
|
594
553
|
else_actions = []
|
|
595
554
|
for j in range(1, n_rule_else_actions + 1):
|
|
596
555
|
[link_idx, link_status, link_setting] = \
|
|
597
|
-
self.epanet_api.
|
|
556
|
+
self.epanet_api.getelseaction(rule_idx, j)
|
|
598
557
|
|
|
599
|
-
link_type_id = self.epanet_api.
|
|
600
|
-
link_id = self.epanet_api.
|
|
558
|
+
link_type_id = self.epanet_api.getlinktype(link_idx)
|
|
559
|
+
link_id = self.epanet_api.get_link_id(link_idx)
|
|
601
560
|
if link_status <= 3:
|
|
602
561
|
action_type_id = link_status
|
|
603
562
|
action_value = link_status
|
|
@@ -614,11 +573,11 @@ class ScenarioSimulator():
|
|
|
614
573
|
return controls
|
|
615
574
|
|
|
616
575
|
def _adapt_to_network_changes(self):
|
|
617
|
-
nodes = self.epanet_api.
|
|
618
|
-
links = self.epanet_api.
|
|
576
|
+
nodes = self.epanet_api.get_all_nodes_id()
|
|
577
|
+
links = self.epanet_api.get_all_links_id()
|
|
619
578
|
|
|
620
|
-
node_id_to_idx = {node_id: self.epanet_api.
|
|
621
|
-
link_id_to_idx = {link_id: self.epanet_api.
|
|
579
|
+
node_id_to_idx = {node_id: self.epanet_api.get_node_idx(node_id) - 1 for node_id in nodes}
|
|
580
|
+
link_id_to_idx = {link_id: self.epanet_api.get_link_idx(link_id) - 1 for link_id in links}
|
|
622
581
|
valve_id_to_idx = None # {valve_id: self.epanet_api.getLinkValveIndex(valve_id) for valve_id in valves}
|
|
623
582
|
pump_id_to_idx = None # {pump_id: self.epanet_api.getLinkPumpIndex(pump_id) - 1 for pump_id in pumps}
|
|
624
583
|
tank_id_to_idx = None # {tank_id: self.epanet_api.getNodeTankIndex(tank_id) - 1 for tank_id in tanks}
|
|
@@ -653,10 +612,7 @@ class ScenarioSimulator():
|
|
|
653
612
|
|
|
654
613
|
Call this function after the simulation is done -- do not call this function before!
|
|
655
614
|
"""
|
|
656
|
-
|
|
657
|
-
self.epanet_api.unloadMSX()
|
|
658
|
-
|
|
659
|
-
self.epanet_api.unload()
|
|
615
|
+
self.epanet_api.close()
|
|
660
616
|
|
|
661
617
|
if self.__my_f_inp_in is not None:
|
|
662
618
|
shutil.rmtree(pathlib.Path(self.__my_f_inp_in).parent)
|
|
@@ -737,7 +693,7 @@ class ScenarioSimulator():
|
|
|
737
693
|
event.cleanup()
|
|
738
694
|
|
|
739
695
|
if inp_file_path is not None:
|
|
740
|
-
self.epanet_api.
|
|
696
|
+
self.epanet_api.saveinpfile(inp_file_path)
|
|
741
697
|
self.__f_inp_in = inp_file_path
|
|
742
698
|
|
|
743
699
|
if export_sensor_config is True:
|
|
@@ -792,7 +748,7 @@ class ScenarioSimulator():
|
|
|
792
748
|
__override_report_section(inp_file_path, report_desc)
|
|
793
749
|
|
|
794
750
|
if self.__f_msx_in is not None and msx_file_path is not None:
|
|
795
|
-
self.epanet_api.
|
|
751
|
+
self.epanet_api.MSXsavemsxfile(msx_file_path)
|
|
796
752
|
self.__f_msx_in = msx_file_path
|
|
797
753
|
|
|
798
754
|
if export_sensor_config is True:
|
|
@@ -845,7 +801,7 @@ class ScenarioSimulator():
|
|
|
845
801
|
"""
|
|
846
802
|
Gets the flow units.
|
|
847
803
|
|
|
848
|
-
Will be one of the following EPANET
|
|
804
|
+
Will be one of the following EPANET constants:
|
|
849
805
|
|
|
850
806
|
- EN_CFS = 0 (cu foot/sec)
|
|
851
807
|
- EN_GPM = 1 (gal/min)
|
|
@@ -863,7 +819,7 @@ class ScenarioSimulator():
|
|
|
863
819
|
`int`
|
|
864
820
|
Flow units.
|
|
865
821
|
"""
|
|
866
|
-
return self.epanet_api.
|
|
822
|
+
return self.epanet_api.getflowunits()
|
|
867
823
|
|
|
868
824
|
def get_units_category(self) -> int:
|
|
869
825
|
"""
|
|
@@ -879,9 +835,9 @@ class ScenarioSimulator():
|
|
|
879
835
|
`int`
|
|
880
836
|
Units category.
|
|
881
837
|
"""
|
|
882
|
-
if self.get_flow_units() in [
|
|
883
|
-
|
|
884
|
-
|
|
838
|
+
if self.get_flow_units() in [EpanetConstants.EN_CFS, EpanetConstants.EN_GPM,
|
|
839
|
+
EpanetConstants.EN_MGD, EpanetConstants.EN_IMGD,
|
|
840
|
+
EpanetConstants.EN_AFD]:
|
|
885
841
|
return UNITS_USCUSTOM
|
|
886
842
|
else:
|
|
887
843
|
return UNITS_SIMETRIC
|
|
@@ -895,7 +851,7 @@ class ScenarioSimulator():
|
|
|
895
851
|
`int`
|
|
896
852
|
Hydraulic time step in seconds.
|
|
897
853
|
"""
|
|
898
|
-
return self.epanet_api.
|
|
854
|
+
return self.epanet_api.get_hydraulic_time_step()
|
|
899
855
|
|
|
900
856
|
def get_quality_time_step(self) -> int:
|
|
901
857
|
"""
|
|
@@ -906,7 +862,7 @@ class ScenarioSimulator():
|
|
|
906
862
|
`int`
|
|
907
863
|
Quality time step in seconds.
|
|
908
864
|
"""
|
|
909
|
-
return self.epanet_api.
|
|
865
|
+
return self.epanet_api.get_quality_time_step()
|
|
910
866
|
|
|
911
867
|
def get_simulation_duration(self) -> int:
|
|
912
868
|
"""
|
|
@@ -917,7 +873,7 @@ class ScenarioSimulator():
|
|
|
917
873
|
`int`
|
|
918
874
|
Simulation duration in seconds.
|
|
919
875
|
"""
|
|
920
|
-
return self.epanet_api.
|
|
876
|
+
return self.epanet_api.get_simulation_duration()
|
|
921
877
|
|
|
922
878
|
def get_demand_model(self) -> dict:
|
|
923
879
|
"""
|
|
@@ -928,12 +884,12 @@ class ScenarioSimulator():
|
|
|
928
884
|
`dict`
|
|
929
885
|
Demand model.
|
|
930
886
|
"""
|
|
931
|
-
demand_info = self.epanet_api.
|
|
887
|
+
demand_info = self.epanet_api.get_demand_model()
|
|
932
888
|
|
|
933
|
-
return {"type":
|
|
934
|
-
"pressure_min": demand_info
|
|
935
|
-
"pressure_required": demand_info
|
|
936
|
-
"pressure_exponent": demand_info
|
|
889
|
+
return {"type": demand_info["type"],
|
|
890
|
+
"pressure_min": demand_info["pmin"],
|
|
891
|
+
"pressure_required": demand_info["preq"],
|
|
892
|
+
"pressure_exponent": demand_info["pexp"]}
|
|
937
893
|
|
|
938
894
|
def get_quality_model(self) -> dict:
|
|
939
895
|
"""
|
|
@@ -947,13 +903,13 @@ class ScenarioSimulator():
|
|
|
947
903
|
`dict`
|
|
948
904
|
Quality model.
|
|
949
905
|
"""
|
|
950
|
-
qual_info = self.epanet_api.
|
|
906
|
+
qual_info = self.epanet_api.get_quality_info()
|
|
951
907
|
|
|
952
|
-
return {"code":
|
|
953
|
-
"type": qual_info
|
|
954
|
-
"chemical_name": qual_info
|
|
955
|
-
"units": qualityunit_to_id(qual_info
|
|
956
|
-
"trace_node_id": qual_info
|
|
908
|
+
return {"code": self.epanet_api.getqualtype(),
|
|
909
|
+
"type": qual_info["qualType"],
|
|
910
|
+
"chemical_name": qual_info["chemName"],
|
|
911
|
+
"units": qualityunit_to_id(qual_info["chemUnits"]),
|
|
912
|
+
"trace_node_id": qual_info["traceNode"]}
|
|
957
913
|
|
|
958
914
|
def get_reporting_time_step(self) -> int:
|
|
959
915
|
"""
|
|
@@ -966,7 +922,7 @@ class ScenarioSimulator():
|
|
|
966
922
|
`int`
|
|
967
923
|
Reporting time steps in seconds.
|
|
968
924
|
"""
|
|
969
|
-
return self.epanet_api.
|
|
925
|
+
return self.epanet_api.get_reporting_time_step()
|
|
970
926
|
|
|
971
927
|
def get_scenario_config(self) -> ScenarioConfig:
|
|
972
928
|
"""
|
|
@@ -1011,14 +967,15 @@ class ScenarioSimulator():
|
|
|
1011
967
|
"""
|
|
1012
968
|
self._adapt_to_network_changes()
|
|
1013
969
|
|
|
1014
|
-
n_time_steps = int(self.epanet_api.
|
|
1015
|
-
self.epanet_api.
|
|
1016
|
-
n_quantities = self.epanet_api.
|
|
1017
|
-
self.epanet_api.
|
|
1018
|
-
self.epanet_api.
|
|
970
|
+
n_time_steps = int(self.epanet_api.get_simulation_duration() /
|
|
971
|
+
self.epanet_api.get_reporting_time_step())
|
|
972
|
+
n_quantities = self.epanet_api.get_num_nodes() * 3 + self.epanet_api.get_num_tanks() + \
|
|
973
|
+
self.epanet_api.get_num_valves() + self.epanet_api.get_num_pumps() + \
|
|
974
|
+
self.epanet_api.get_num_links() * 2
|
|
1019
975
|
|
|
1020
976
|
if self.__f_msx_in is not None:
|
|
1021
|
-
n_quantities += self.epanet_api.
|
|
977
|
+
n_quantities += self.epanet_api.get_num_links() * 2 * self.epanet_api.get_num_msx_species() + \
|
|
978
|
+
self.epanet_api.get_num_nodes() * self.epanet_api.get_num_msx_species()
|
|
1022
979
|
|
|
1023
980
|
n_bytes_per_quantity = 64
|
|
1024
981
|
|
|
@@ -1036,28 +993,39 @@ class ScenarioSimulator():
|
|
|
1036
993
|
self._adapt_to_network_changes()
|
|
1037
994
|
|
|
1038
995
|
# Collect information about the topology of the water distribution network
|
|
1039
|
-
nodes_id = self.epanet_api.
|
|
1040
|
-
nodes_elevation = self.epanet_api.
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
996
|
+
nodes_id = self.epanet_api.get_all_nodes_id()
|
|
997
|
+
nodes_elevation = [self.epanet_api.get_node_elevation(node_idx)
|
|
998
|
+
for node_idx in self.epanet_api.get_all_nodes_idx()]
|
|
999
|
+
nodes_type = [self.epanet_api.get_node_type(node_idx)
|
|
1000
|
+
for node_idx in self.epanet_api.get_all_nodes_idx()]
|
|
1001
|
+
nodes_coord = [self.epanet_api.getcoord(node_idx)
|
|
1002
|
+
for node_idx in self.epanet_api.get_all_nodes_idx()]
|
|
1003
|
+
nodes_comments = [self.epanet_api.get_node_comment(node_idx)
|
|
1004
|
+
for node_idx in self.epanet_api.get_all_nodes_idx()]
|
|
1005
|
+
node_tank_names = self.epanet_api.get_all_tanks_id()
|
|
1006
|
+
|
|
1007
|
+
links_id = self.epanet_api.get_all_links_id()
|
|
1008
|
+
links_type = [self.epanet_api.get_link_type(link_idx)
|
|
1009
|
+
for link_idx in self.epanet_api.get_all_links_idx()]
|
|
1010
|
+
links_data = self.epanet_api.get_all_links_connecting_nodes_id()
|
|
1011
|
+
links_diameter = [self.epanet_api.get_link_diameter(link_idx)
|
|
1012
|
+
for link_idx in self.epanet_api.get_all_links_idx()]
|
|
1013
|
+
links_length = [self.epanet_api.get_link_length(link_idx)
|
|
1014
|
+
for link_idx in self.epanet_api.get_all_links_idx()]
|
|
1015
|
+
links_roughness_coeff = [self.epanet_api.get_link_roughness(link_idx)
|
|
1016
|
+
for link_idx in self.epanet_api.get_all_links_idx()]
|
|
1017
|
+
links_bulk_coeff = [self.epanet_api.get_link_bulk_raction_coeff(link_idx)
|
|
1018
|
+
for link_idx in self.epanet_api.get_all_links_idx()]
|
|
1019
|
+
links_wall_coeff = [self.epanet_api.get_link_wall_raction_coeff(link_idx)
|
|
1020
|
+
for link_idx in self.epanet_api.get_all_links_idx()]
|
|
1021
|
+
links_loss_coeff = [self.epanet_api.get_link_minorloss(link_idx)
|
|
1022
|
+
for link_idx in self.epanet_api.get_all_links_idx()]
|
|
1023
|
+
|
|
1024
|
+
pumps_id = self.epanet_api.get_all_pumps_id()
|
|
1025
|
+
pumps_type = [self.epanet_api.get_pump_type(pump_idx)
|
|
1026
|
+
for pump_idx in self.epanet_api.get_all_pumps_idx()]
|
|
1027
|
+
|
|
1028
|
+
valves_id = self.epanet_api.get_all_valves_id()
|
|
1061
1029
|
|
|
1062
1030
|
# Build graph describing the topology
|
|
1063
1031
|
nodes = []
|
|
@@ -1068,14 +1036,15 @@ class ScenarioSimulator():
|
|
|
1068
1036
|
"coord": node_coord,
|
|
1069
1037
|
"comment": node_comment,
|
|
1070
1038
|
"type": node_type}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
node_info["
|
|
1075
|
-
node_info["
|
|
1076
|
-
node_info["
|
|
1077
|
-
node_info["
|
|
1078
|
-
|
|
1039
|
+
|
|
1040
|
+
if node_type == EpanetConstants.EN_TANK:
|
|
1041
|
+
node_tank_idx = self.epanet_api.get_node_idx(node_id)
|
|
1042
|
+
node_info["diameter"] = float(self.epanet_api.get_tank_diameter(node_tank_idx))
|
|
1043
|
+
node_info["volume"] = float(self.epanet_api.get_tank_volume(node_tank_idx))
|
|
1044
|
+
node_info["max_level"] = float(self.epanet_api.get_tank_max_level(node_tank_idx))
|
|
1045
|
+
node_info["min_level"] = float(self.epanet_api.get_tank_min_level(node_tank_idx))
|
|
1046
|
+
node_info["mixing_fraction"] = float(self.epanet_api.get_tank_mix_fraction(node_tank_idx))
|
|
1047
|
+
node_info["mixing_model"] = int(self.epanet_api.get_tank_mix_model(node_tank_idx))
|
|
1079
1048
|
|
|
1080
1049
|
nodes.append((node_id, node_info))
|
|
1081
1050
|
|
|
@@ -1132,23 +1101,21 @@ class ScenarioSimulator():
|
|
|
1132
1101
|
self._adapt_to_network_changes()
|
|
1133
1102
|
|
|
1134
1103
|
# Get all demand patterns
|
|
1135
|
-
demand_patterns_idx = self.epanet_api.
|
|
1136
|
-
|
|
1104
|
+
demand_patterns_idx = [self.epanet_api.get_node_demand_patterns_idx(node_idx)
|
|
1105
|
+
for node_idx in self.epanet_api.get_all_nodes_idx()]
|
|
1106
|
+
demand_patterns_idx = list(set(itertools.chain.from_iterable(demand_patterns_idx)))
|
|
1137
1107
|
|
|
1138
1108
|
# Process each pattern separately
|
|
1139
|
-
for
|
|
1140
|
-
|
|
1141
|
-
continue
|
|
1142
|
-
|
|
1143
|
-
pattern_length = self.epanet_api.getPatternLengths(pattern_id)
|
|
1109
|
+
for pattern_idx in demand_patterns_idx:
|
|
1110
|
+
pattern_length = self.epanet_api.getpatternlen(pattern_idx)
|
|
1144
1111
|
pattern = []
|
|
1145
1112
|
for t in range(pattern_length): # Get pattern
|
|
1146
|
-
pattern.append(self.epanet_api.
|
|
1113
|
+
pattern.append(self.epanet_api.getpatternvalue(pattern_idx, t + 1))
|
|
1147
1114
|
|
|
1148
1115
|
random.shuffle(pattern) # Shuffle pattern
|
|
1149
1116
|
|
|
1150
1117
|
for t in range(pattern_length): # Set shuffled/randomized pattern
|
|
1151
|
-
self.epanet_api.
|
|
1118
|
+
self.epanet_api.setpatternvalue(pattern_idx, t + 1, pattern[t])
|
|
1152
1119
|
|
|
1153
1120
|
def get_pattern(self, pattern_id: str) -> np.ndarray:
|
|
1154
1121
|
"""
|
|
@@ -1168,13 +1135,11 @@ class ScenarioSimulator():
|
|
|
1168
1135
|
raise TypeError("'pattern_id' must be an instance of 'str' " +
|
|
1169
1136
|
f"but not of '{type(pattern_id)}'")
|
|
1170
1137
|
|
|
1171
|
-
pattern_idx = self.epanet_api.
|
|
1138
|
+
pattern_idx = self.epanet_api.getpatternindex(pattern_id)
|
|
1172
1139
|
if pattern_idx == 0:
|
|
1173
1140
|
raise ValueError(f"Unknown pattern '{pattern_id}'")
|
|
1174
1141
|
|
|
1175
|
-
|
|
1176
|
-
return np.array([self.epanet_api.getPatternValue(pattern_idx, t+1)
|
|
1177
|
-
for t in range(pattern_length)])
|
|
1142
|
+
return np.array(self.epanet_api.get_pattern(pattern_idx))
|
|
1178
1143
|
|
|
1179
1144
|
def add_pattern(self, pattern_id: str, pattern: np.ndarray) -> None:
|
|
1180
1145
|
"""
|
|
@@ -1199,7 +1164,7 @@ class ScenarioSimulator():
|
|
|
1199
1164
|
raise ValueError(f"Inconsistent pattern shape '{pattern.shape}' " +
|
|
1200
1165
|
"detected. Expected a one dimensional array!")
|
|
1201
1166
|
|
|
1202
|
-
pattern_idx = self.epanet_api.
|
|
1167
|
+
pattern_idx = self.epanet_api.add_pattern(pattern_id, pattern.tolist())
|
|
1203
1168
|
if pattern_idx == 0:
|
|
1204
1169
|
raise RuntimeError("Failed to add pattern! " +
|
|
1205
1170
|
"Maybe pattern name contains invalid characters or is too long?")
|
|
@@ -1223,21 +1188,21 @@ class ScenarioSimulator():
|
|
|
1223
1188
|
if node_id not in self._sensor_config.nodes:
|
|
1224
1189
|
raise ValueError(f"Unknown node '{node_id}'")
|
|
1225
1190
|
|
|
1226
|
-
node_idx = self.epanet_api.
|
|
1227
|
-
n_demand_categories = self.epanet_api.
|
|
1191
|
+
node_idx = self.epanet_api.get_node_idx(node_id)
|
|
1192
|
+
n_demand_categories = self.epanet_api.getnumdemands(node_idx)
|
|
1228
1193
|
|
|
1229
1194
|
if n_demand_categories == 0:
|
|
1230
1195
|
return None
|
|
1231
1196
|
else:
|
|
1232
1197
|
base_demand = 0
|
|
1233
|
-
for
|
|
1234
|
-
base_demand += self.epanet_api.
|
|
1198
|
+
for demand_idx in range(n_demand_categories):
|
|
1199
|
+
base_demand += self.epanet_api.getbasedemand(node_idx, demand_idx + 1)
|
|
1235
1200
|
|
|
1236
1201
|
return base_demand
|
|
1237
1202
|
|
|
1238
1203
|
def get_node_demand_pattern(self, node_id: str) -> np.ndarray:
|
|
1239
1204
|
"""
|
|
1240
|
-
Returns the values of the demand pattern of a given node --
|
|
1205
|
+
Returns the values of the primary demand pattern of a given node --
|
|
1241
1206
|
i.e. multiplier factors that are applied to the base demand.
|
|
1242
1207
|
|
|
1243
1208
|
Parameters
|
|
@@ -1256,10 +1221,14 @@ class ScenarioSimulator():
|
|
|
1256
1221
|
if node_id not in self._sensor_config.nodes:
|
|
1257
1222
|
raise ValueError(f"Unknown node '{node_id}'")
|
|
1258
1223
|
|
|
1259
|
-
node_idx = self.epanet_api.
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1224
|
+
node_idx = self.epanet_api.get_node_idx(node_id)
|
|
1225
|
+
|
|
1226
|
+
if self.epanet_api.getnodetype(node_idx) != EpanetConstants.EN_RESERVOIR:
|
|
1227
|
+
demand_pattern_idx = self.epanet_api.getdemandpattern(node_idx)
|
|
1228
|
+
else:
|
|
1229
|
+
demand_pattern_idx = self.epanet_api.getnodevalue(node_idx, EpanetConstants.EN_PATTERN)
|
|
1230
|
+
|
|
1231
|
+
return self.get_pattern(self.epanet_api.getpatternid(demand_pattern_idx))
|
|
1263
1232
|
|
|
1264
1233
|
def set_node_demand_pattern(self, node_id: str, base_demand: float, demand_pattern_id: str,
|
|
1265
1234
|
demand_pattern: np.ndarray = None) -> None:
|
|
@@ -1298,20 +1267,20 @@ class ScenarioSimulator():
|
|
|
1298
1267
|
raise ValueError(f"Inconsistent demand pattern shape '{demand_pattern.shape}' " +
|
|
1299
1268
|
"detected. Expected a one dimensional array!")
|
|
1300
1269
|
|
|
1301
|
-
node_idx = self.epanet_api.
|
|
1270
|
+
node_idx = self.epanet_api.get_node_idx(node_id)
|
|
1302
1271
|
|
|
1303
|
-
if demand_pattern_id not in self.epanet_api.
|
|
1272
|
+
if demand_pattern_id not in self.epanet_api.get_all_patterns_id():
|
|
1304
1273
|
if demand_pattern is None:
|
|
1305
1274
|
raise ValueError("'demand_pattern' can not be None if " +
|
|
1306
1275
|
"'demand_pattern_id' does not already exist.")
|
|
1307
|
-
self.epanet_api.
|
|
1276
|
+
self.epanet_api.add_pattern(demand_pattern_id, demand_pattern.tolist())
|
|
1308
1277
|
else:
|
|
1309
1278
|
if demand_pattern is not None:
|
|
1310
|
-
pattern_idx = self.epanet_api.
|
|
1311
|
-
self.epanet_api.
|
|
1279
|
+
pattern_idx = self.epanet_api.get_node_pattern_idx(demand_pattern_id)
|
|
1280
|
+
self.epanet_api.set_pattern(pattern_idx, demand_pattern.tolist())
|
|
1312
1281
|
|
|
1313
|
-
self.epanet_api.
|
|
1314
|
-
|
|
1282
|
+
self.epanet_api.setjuncdata(node_idx, self.epanet_api.get_node_elevation(node_idx),
|
|
1283
|
+
base_demand, demand_pattern_id)
|
|
1315
1284
|
|
|
1316
1285
|
def add_custom_control(self, control: CustomControlModule) -> None:
|
|
1317
1286
|
"""
|
|
@@ -1349,13 +1318,20 @@ class ScenarioSimulator():
|
|
|
1349
1318
|
|
|
1350
1319
|
if not any(c == control for c in self._simple_controls):
|
|
1351
1320
|
self._simple_controls.append(control)
|
|
1352
|
-
|
|
1321
|
+
|
|
1322
|
+
link_idx = self.epanet_api.get_link_idx(control.link_id)
|
|
1323
|
+
cond_var_value = control.cond_var_value
|
|
1324
|
+
if control.cond_type == EpanetConstants.EN_LOWLEVEL or \
|
|
1325
|
+
control.cond_type == EpanetConstants.EN_HILEVEL:
|
|
1326
|
+
cond_var_value = self.epanet_api.get_node_idx(cond_var_value)
|
|
1327
|
+
self.epanet_api.addcontrol(control.cond_type, link_idx, control.link_status,
|
|
1328
|
+
cond_var_value, control.cond_comp_value)
|
|
1353
1329
|
|
|
1354
1330
|
def remove_all_simple_controls(self) -> None:
|
|
1355
1331
|
"""
|
|
1356
1332
|
Removes all simple EPANET controls from the scenario.
|
|
1357
1333
|
"""
|
|
1358
|
-
self.epanet_api.
|
|
1334
|
+
self.epanet_api.remove_all_controls()
|
|
1359
1335
|
self._simple_controls = []
|
|
1360
1336
|
|
|
1361
1337
|
def remove_simple_control(self, control: SimpleControlModule) -> None:
|
|
@@ -1382,7 +1358,7 @@ class ScenarioSimulator():
|
|
|
1382
1358
|
if control_idx is None:
|
|
1383
1359
|
raise ValueError("Invalid/Unknown control module.")
|
|
1384
1360
|
|
|
1385
|
-
self.epanet_api.
|
|
1361
|
+
self.epanet_api.deletecontrol(control_idx)
|
|
1386
1362
|
self._simple_controls.remove(control)
|
|
1387
1363
|
|
|
1388
1364
|
def add_complex_control(self, control: ComplexControlModule) -> None:
|
|
@@ -1403,13 +1379,13 @@ class ScenarioSimulator():
|
|
|
1403
1379
|
|
|
1404
1380
|
if not any(c == control for c in self._complex_controls):
|
|
1405
1381
|
self._complex_controls.append(control)
|
|
1406
|
-
self.epanet_api.
|
|
1382
|
+
self.epanet_api.addrule(str(control))
|
|
1407
1383
|
|
|
1408
1384
|
def remove_all_complex_controls(self) -> None:
|
|
1409
1385
|
"""
|
|
1410
1386
|
Removes all complex EPANET controls from the scenario.
|
|
1411
1387
|
"""
|
|
1412
|
-
self.epanet_api.
|
|
1388
|
+
self.epanet_api.remove_all_rules()
|
|
1413
1389
|
self._complex_controls = []
|
|
1414
1390
|
|
|
1415
1391
|
def remove_complex_control(self, control: ComplexControlModule) -> None:
|
|
@@ -1428,12 +1404,13 @@ class ScenarioSimulator():
|
|
|
1428
1404
|
"'epyt_flow.simulation.scada.ComplexControlModule' not of " +
|
|
1429
1405
|
f"'{type(control)}'")
|
|
1430
1406
|
|
|
1431
|
-
|
|
1407
|
+
all_rules_id = self.epanet_api.get_all_rules_id()
|
|
1408
|
+
if control.rule_id not in all_rules_id:
|
|
1432
1409
|
raise ValueError("Invalid/Unknown control module. " +
|
|
1433
1410
|
f"Can not find rule ID '{control.rule_id}'")
|
|
1434
1411
|
|
|
1435
|
-
rule_idx =
|
|
1436
|
-
self.epanet_api.
|
|
1412
|
+
rule_idx = all_rules_id.index(control.rule_id) + 1
|
|
1413
|
+
self.epanet_api.deleterule(rule_idx)
|
|
1437
1414
|
self._complex_controls.remove(control)
|
|
1438
1415
|
|
|
1439
1416
|
def add_leakage(self, leakage_event: Leakage) -> None:
|
|
@@ -1643,7 +1620,7 @@ class ScenarioSimulator():
|
|
|
1643
1620
|
The default is False.
|
|
1644
1621
|
"""
|
|
1645
1622
|
if junctions_only is True:
|
|
1646
|
-
self.set_pressure_sensors(self.epanet_api.
|
|
1623
|
+
self.set_pressure_sensors(self.epanet_api.get_all_junctions_id())
|
|
1647
1624
|
else:
|
|
1648
1625
|
self.set_pressure_sensors(self._sensor_config.nodes)
|
|
1649
1626
|
|
|
@@ -2112,42 +2089,43 @@ class ScenarioSimulator():
|
|
|
2112
2089
|
self._prepare_simulation(reapply_uncertainties)
|
|
2113
2090
|
|
|
2114
2091
|
# Load pre-computed hydraulics
|
|
2115
|
-
self.epanet_api.
|
|
2092
|
+
self.epanet_api.MSXusehydfile(hyd_file_in)
|
|
2116
2093
|
|
|
2117
2094
|
# Initialize simulation
|
|
2118
|
-
n_nodes = self.epanet_api.
|
|
2119
|
-
n_links = self.epanet_api.
|
|
2095
|
+
n_nodes = self.epanet_api.get_num_nodes()
|
|
2096
|
+
n_links = self.epanet_api.get_num_links()
|
|
2120
2097
|
|
|
2121
|
-
reporting_time_start = self.epanet_api.
|
|
2122
|
-
reporting_time_step = self.epanet_api.
|
|
2123
|
-
hyd_time_step = self.epanet_api.
|
|
2098
|
+
reporting_time_start = self.epanet_api.get_reporting_start_time()
|
|
2099
|
+
reporting_time_step = self.epanet_api.get_reporting_time_step()
|
|
2100
|
+
hyd_time_step = self.epanet_api.get_hydraulic_time_step()
|
|
2124
2101
|
|
|
2125
2102
|
network_topo = self.get_topology()
|
|
2126
2103
|
|
|
2127
2104
|
if use_quality_time_step_as_reporting_time_step is True:
|
|
2128
|
-
quality_time_step = self.epanet_api.
|
|
2105
|
+
quality_time_step = self.epanet_api.get_msx_time_step()
|
|
2129
2106
|
reporting_time_step = quality_time_step
|
|
2130
2107
|
hyd_time_step = quality_time_step
|
|
2131
2108
|
|
|
2132
|
-
self.epanet_api.
|
|
2109
|
+
self.epanet_api.MSXinit(EpanetConstants.EN_NOSAVE)
|
|
2133
2110
|
|
|
2134
2111
|
self.__running_simulation = True
|
|
2135
2112
|
|
|
2136
|
-
bulk_species_idx = self.epanet_api.
|
|
2137
|
-
|
|
2138
|
-
|
|
2113
|
+
bulk_species_idx = [self.epanet_api.get_msx_species_idx(species_id)
|
|
2114
|
+
for species_id in self._sensor_config.bulk_species]
|
|
2115
|
+
surface_species_idx = [self.epanet_api.get_msx_species_idx(species_id)
|
|
2116
|
+
for species_id in self._sensor_config.surface_species]
|
|
2139
2117
|
|
|
2140
2118
|
if verbose is True:
|
|
2141
2119
|
print("Running EPANET-MSX ...")
|
|
2142
|
-
n_iterations = math.ceil(self.epanet_api.
|
|
2120
|
+
n_iterations = math.ceil(self.epanet_api.get_simulation_duration() /
|
|
2143
2121
|
hyd_time_step)
|
|
2144
2122
|
progress_bar = iter(tqdm(range(n_iterations + 1), ascii=True, desc="Time steps"))
|
|
2145
2123
|
|
|
2146
2124
|
def __get_concentrations(init_qual=False):
|
|
2147
2125
|
if init_qual is True:
|
|
2148
|
-
msx_get_cur_value = self.epanet_api.
|
|
2126
|
+
msx_get_cur_value = self.epanet_api.MSXgetinitqual
|
|
2149
2127
|
else:
|
|
2150
|
-
msx_get_cur_value = self.epanet_api.
|
|
2128
|
+
msx_get_cur_value = self.epanet_api.get_msx_species_concentration
|
|
2151
2129
|
|
|
2152
2130
|
# Bulk species
|
|
2153
2131
|
bulk_species_node_concentrations = []
|
|
@@ -2155,13 +2133,13 @@ class ScenarioSimulator():
|
|
|
2155
2133
|
for species_idx in bulk_species_idx:
|
|
2156
2134
|
cur_species_concentrations = []
|
|
2157
2135
|
for node_idx in range(1, n_nodes + 1):
|
|
2158
|
-
concen = msx_get_cur_value(
|
|
2136
|
+
concen = msx_get_cur_value(EpanetConstants.MSX_NODE, node_idx, species_idx)
|
|
2159
2137
|
cur_species_concentrations.append(concen)
|
|
2160
2138
|
bulk_species_node_concentrations.append(cur_species_concentrations)
|
|
2161
2139
|
|
|
2162
2140
|
cur_species_concentrations = []
|
|
2163
2141
|
for link_idx in range(1, n_links + 1):
|
|
2164
|
-
concen = msx_get_cur_value(
|
|
2142
|
+
concen = msx_get_cur_value(EpanetConstants.MSX_LINK, link_idx, species_idx)
|
|
2165
2143
|
cur_species_concentrations.append(concen)
|
|
2166
2144
|
bulk_species_link_concentrations.append(cur_species_concentrations)
|
|
2167
2145
|
|
|
@@ -2183,7 +2161,7 @@ class ScenarioSimulator():
|
|
|
2183
2161
|
cur_species_concentrations = []
|
|
2184
2162
|
|
|
2185
2163
|
for link_idx in range(1, n_links + 1):
|
|
2186
|
-
concen = msx_get_cur_value(
|
|
2164
|
+
concen = msx_get_cur_value(EpanetConstants.MSX_LINK, link_idx, species_idx)
|
|
2187
2165
|
cur_species_concentrations.append(concen)
|
|
2188
2166
|
|
|
2189
2167
|
surface_species_concentrations.append(cur_species_concentrations)
|
|
@@ -2208,7 +2186,7 @@ class ScenarioSimulator():
|
|
|
2208
2186
|
pass
|
|
2209
2187
|
|
|
2210
2188
|
if reporting_time_start == 0:
|
|
2211
|
-
msx_error_code = self.epanet_api.
|
|
2189
|
+
msx_error_code = self.epanet_api.get_last_error_code()
|
|
2212
2190
|
|
|
2213
2191
|
if return_as_dict is True:
|
|
2214
2192
|
data = {"bulk_species_node_concentration_raw": bulk_species_node_concentrations,
|
|
@@ -2241,8 +2219,8 @@ class ScenarioSimulator():
|
|
|
2241
2219
|
last_msx_error_code = 0
|
|
2242
2220
|
while tleft > 0:
|
|
2243
2221
|
# Compute current time step
|
|
2244
|
-
total_time, tleft = self.epanet_api.
|
|
2245
|
-
msx_error_code = self.epanet_api.
|
|
2222
|
+
total_time, tleft = self.epanet_api.MSXstep()
|
|
2223
|
+
msx_error_code = self.epanet_api.get_last_error_code()
|
|
2246
2224
|
if last_msx_error_code == 0:
|
|
2247
2225
|
last_msx_error_code = msx_error_code
|
|
2248
2226
|
|
|
@@ -2404,26 +2382,26 @@ class ScenarioSimulator():
|
|
|
2404
2382
|
if self.__running_simulation is True:
|
|
2405
2383
|
raise RuntimeError("A simulation is already running.")
|
|
2406
2384
|
|
|
2407
|
-
requested_total_time = self.epanet_api.
|
|
2408
|
-
requested_time_step = self.epanet_api.
|
|
2409
|
-
reporting_time_start = self.epanet_api.
|
|
2410
|
-
reporting_time_step = self.epanet_api.
|
|
2385
|
+
requested_total_time = self.epanet_api.get_simulation_duration()
|
|
2386
|
+
requested_time_step = self.epanet_api.get_hydraulic_time_step()
|
|
2387
|
+
reporting_time_start = self.epanet_api.get_reporting_start_time()
|
|
2388
|
+
reporting_time_step = self.epanet_api.get_reporting_time_step()
|
|
2411
2389
|
|
|
2412
2390
|
if use_quality_time_step_as_reporting_time_step is True:
|
|
2413
|
-
quality_time_step = self.epanet_api.
|
|
2391
|
+
quality_time_step = self.epanet_api.get_quality_time_step()
|
|
2414
2392
|
requested_time_step = quality_time_step
|
|
2415
2393
|
reporting_time_step = quality_time_step
|
|
2416
2394
|
|
|
2417
2395
|
network_topo = self.get_topology()
|
|
2418
2396
|
|
|
2419
|
-
self.epanet_api.
|
|
2397
|
+
self.epanet_api.usehydfile(hyd_file_in)
|
|
2420
2398
|
|
|
2421
|
-
self.epanet_api.
|
|
2422
|
-
self.epanet_api.
|
|
2399
|
+
self.epanet_api.openQ()
|
|
2400
|
+
self.epanet_api.initQ(EpanetConstants.EN_NOSAVE)
|
|
2423
2401
|
|
|
2424
2402
|
if verbose is True:
|
|
2425
2403
|
print("Running basic quality analysis using EPANET ...")
|
|
2426
|
-
n_iterations = math.ceil(self.epanet_api.
|
|
2404
|
+
n_iterations = math.ceil(self.epanet_api.get_simulation_duration() /
|
|
2427
2405
|
requested_time_step)
|
|
2428
2406
|
progress_bar = iter(tqdm(range(n_iterations + 1), ascii=True, desc="Time steps"))
|
|
2429
2407
|
|
|
@@ -2445,15 +2423,15 @@ class ScenarioSimulator():
|
|
|
2445
2423
|
pass
|
|
2446
2424
|
|
|
2447
2425
|
# Compute current time step
|
|
2448
|
-
t = self.epanet_api.
|
|
2426
|
+
t = self.epanet_api.runQ()
|
|
2449
2427
|
total_time = t
|
|
2450
2428
|
|
|
2451
2429
|
# Fetch data
|
|
2452
2430
|
error_code = self.epanet_api.get_last_error_code()
|
|
2453
2431
|
if last_error_code == 0:
|
|
2454
2432
|
last_error_code = error_code
|
|
2455
|
-
quality_node_data = self.epanet_api.
|
|
2456
|
-
quality_link_data = self.epanet_api.
|
|
2433
|
+
quality_node_data = np.array(self.epanet_api.getnodevalues(EpanetConstants.EN_QUALITY)).reshape(1, -1)
|
|
2434
|
+
quality_link_data = np.array(self.epanet_api.getlinkvalues(EpanetConstants.EN_QUALITY)).reshape(1, -1)
|
|
2457
2435
|
|
|
2458
2436
|
# Yield results in a regular time interval only!
|
|
2459
2437
|
if total_time % reporting_time_step == 0 and total_time >= reporting_time_start:
|
|
@@ -2481,12 +2459,12 @@ class ScenarioSimulator():
|
|
|
2481
2459
|
yield (data, total_time >= requested_total_time)
|
|
2482
2460
|
|
|
2483
2461
|
# Next
|
|
2484
|
-
tstep = self.epanet_api.
|
|
2462
|
+
tstep = self.epanet_api.stepQ()
|
|
2485
2463
|
error_code = self.epanet_api.get_last_error_code()
|
|
2486
2464
|
if last_error_code == 0:
|
|
2487
2465
|
last_error_code = error_code
|
|
2488
2466
|
|
|
2489
|
-
self.epanet_api.
|
|
2467
|
+
self.epanet_api.closeQ()
|
|
2490
2468
|
|
|
2491
2469
|
def run_hydraulic_simulation(self, hyd_export: str = None, verbose: bool = False,
|
|
2492
2470
|
frozen_sensor_config: bool = False,
|
|
@@ -2541,13 +2519,18 @@ class ScenarioSimulator():
|
|
|
2541
2519
|
if result is None:
|
|
2542
2520
|
result = {}
|
|
2543
2521
|
for data_type, data in scada_data.items():
|
|
2544
|
-
|
|
2522
|
+
if data is None:
|
|
2523
|
+
result[data_type] = None
|
|
2524
|
+
else:
|
|
2525
|
+
result[data_type] = [data]
|
|
2545
2526
|
else:
|
|
2546
2527
|
for data_type, data in scada_data.items():
|
|
2547
|
-
result[data_type]
|
|
2528
|
+
if result[data_type] is not None:
|
|
2529
|
+
result[data_type].append(data)
|
|
2548
2530
|
|
|
2549
2531
|
for data_type in result:
|
|
2550
|
-
result[data_type]
|
|
2532
|
+
if result[data_type] is not None:
|
|
2533
|
+
result[data_type] = np.concatenate(result[data_type], axis=0)
|
|
2551
2534
|
|
|
2552
2535
|
result = ScadaData(**result,
|
|
2553
2536
|
network_topo=self.get_topology(),
|
|
@@ -2619,21 +2602,21 @@ class ScenarioSimulator():
|
|
|
2619
2602
|
|
|
2620
2603
|
self.__running_simulation = True
|
|
2621
2604
|
|
|
2622
|
-
self.epanet_api.
|
|
2623
|
-
self.epanet_api.
|
|
2624
|
-
self.epanet_api.
|
|
2625
|
-
self.epanet_api.
|
|
2605
|
+
self.epanet_api.openH()
|
|
2606
|
+
self.epanet_api.openQ()
|
|
2607
|
+
self.epanet_api.initH(EpanetConstants.EN_SAVE)
|
|
2608
|
+
self.epanet_api.initQ(EpanetConstants.EN_SAVE)
|
|
2626
2609
|
|
|
2627
|
-
requested_total_time = self.epanet_api.
|
|
2628
|
-
requested_time_step = self.epanet_api.
|
|
2629
|
-
reporting_time_start = self.epanet_api.
|
|
2630
|
-
reporting_time_step = self.epanet_api.
|
|
2610
|
+
requested_total_time = self.epanet_api.get_simulation_duration()
|
|
2611
|
+
requested_time_step = self.epanet_api.get_hydraulic_time_step()
|
|
2612
|
+
reporting_time_start = self.epanet_api.get_reporting_start_time()
|
|
2613
|
+
reporting_time_step = self.epanet_api.get_reporting_time_step()
|
|
2631
2614
|
|
|
2632
2615
|
network_topo = self.get_topology()
|
|
2633
2616
|
|
|
2634
2617
|
if verbose is True:
|
|
2635
2618
|
print("Running EPANET ...")
|
|
2636
|
-
n_iterations = math.ceil(self.epanet_api.
|
|
2619
|
+
n_iterations = math.ceil(self.epanet_api.get_simulation_duration() /
|
|
2637
2620
|
requested_time_step)
|
|
2638
2621
|
progress_bar = iter(tqdm(range(n_iterations + 1), ascii=True, desc="Time steps"))
|
|
2639
2622
|
|
|
@@ -2661,9 +2644,9 @@ class ScenarioSimulator():
|
|
|
2661
2644
|
event.step(total_time + tstep)
|
|
2662
2645
|
|
|
2663
2646
|
# Compute current time step
|
|
2664
|
-
t = self.epanet_api.
|
|
2647
|
+
t = self.epanet_api.runH()
|
|
2665
2648
|
error_code = self.epanet_api.get_last_error_code()
|
|
2666
|
-
self.epanet_api.
|
|
2649
|
+
self.epanet_api.runQ()
|
|
2667
2650
|
if error_code == 0:
|
|
2668
2651
|
error_code = self.epanet_api.get_last_error_code()
|
|
2669
2652
|
total_time = t
|
|
@@ -2671,20 +2654,32 @@ class ScenarioSimulator():
|
|
|
2671
2654
|
last_error_code = error_code
|
|
2672
2655
|
|
|
2673
2656
|
# Fetch data
|
|
2674
|
-
pressure_data = self.epanet_api.
|
|
2675
|
-
flow_data = self.epanet_api.
|
|
2676
|
-
demand_data = self.epanet_api.
|
|
2677
|
-
quality_node_data = self.epanet_api.
|
|
2678
|
-
quality_link_data = self.epanet_api.
|
|
2679
|
-
|
|
2680
|
-
tanks_volume_data =
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2657
|
+
pressure_data = np.array(self.epanet_api.getnodevalues(EpanetConstants.EN_PRESSURE)).reshape(1, -1)
|
|
2658
|
+
flow_data = np.array(self.epanet_api.getlinkvalues(EpanetConstants.EN_FLOW)).reshape(1, -1)
|
|
2659
|
+
demand_data = np.array(self.epanet_api.getnodevalues(EpanetConstants.EN_DEMAND)).reshape(1, -1)
|
|
2660
|
+
quality_node_data = np.array(self.epanet_api.getnodevalues(EpanetConstants.EN_QUALITY)).reshape(1, -1)
|
|
2661
|
+
quality_link_data = np.array(self.epanet_api.getlinkvalues(EpanetConstants.EN_QUALITY)).reshape(1, -1)
|
|
2662
|
+
|
|
2663
|
+
tanks_volume_data = None
|
|
2664
|
+
if len(self.epanet_api.get_all_tanks_idx()) > 0:
|
|
2665
|
+
tanks_volume_data = np.array([self.epanet_api.get_tank_volume(tank_idx)
|
|
2666
|
+
for tank_idx in self.epanet_api.get_all_tanks_idx()]).reshape(1, -1)
|
|
2667
|
+
|
|
2668
|
+
pumps_state_data = None
|
|
2669
|
+
pumps_energy_usage_data = None
|
|
2670
|
+
pumps_efficiency_data = None
|
|
2671
|
+
if len(self.epanet_api.get_all_pumps_idx()) > 0:
|
|
2672
|
+
pumps_state_data = np.array([self.epanet_api.getlinkvalue(link_idx, EpanetConstants.EN_PUMP_STATE)
|
|
2673
|
+
for link_idx in self.epanet_api.get_all_pumps_idx()]).reshape(1, -1)
|
|
2674
|
+
pumps_energy_usage_data = np.array([self.epanet_api.get_pump_energy_usage(pump_idx)
|
|
2675
|
+
for pump_idx in self.epanet_api.get_all_pumps_idx()]).reshape(1, -1)
|
|
2676
|
+
pumps_efficiency_data = np.array([self.epanet_api.get_pump_efficiency(pump_idx)
|
|
2677
|
+
for pump_idx in self.epanet_api.get_all_pumps_idx()]).reshape(1, -1)
|
|
2678
|
+
|
|
2679
|
+
valves_state_data = None
|
|
2680
|
+
if len(self.epanet_api.get_all_valves_idx()) > 0:
|
|
2681
|
+
valves_state_data = np.array([self.epanet_api.getlinkvalue(link_valve_idx, EpanetConstants.EN_STATUS)
|
|
2682
|
+
for link_valve_idx in self.epanet_api.get_all_valves_idx()]).reshape(1, -1)
|
|
2688
2683
|
|
|
2689
2684
|
scada_data = ScadaData(network_topo=network_topo,
|
|
2690
2685
|
sensor_config=self._sensor_config,
|
|
@@ -2736,23 +2731,23 @@ class ScenarioSimulator():
|
|
|
2736
2731
|
control.step(scada_data)
|
|
2737
2732
|
|
|
2738
2733
|
# Next
|
|
2739
|
-
tstep = self.epanet_api.
|
|
2734
|
+
tstep = self.epanet_api.nextH()
|
|
2740
2735
|
error_code = self.epanet_api.get_last_error_code()
|
|
2741
2736
|
if last_error_code == 0:
|
|
2742
2737
|
last_error_code = error_code
|
|
2743
2738
|
|
|
2744
|
-
self.epanet_api.
|
|
2739
|
+
self.epanet_api.nextQ()
|
|
2745
2740
|
error_code = self.epanet_api.get_last_error_code()
|
|
2746
2741
|
if last_error_code == 0:
|
|
2747
2742
|
last_error_code = error_code
|
|
2748
2743
|
|
|
2749
|
-
self.epanet_api.
|
|
2750
|
-
self.epanet_api.
|
|
2744
|
+
self.epanet_api.closeQ()
|
|
2745
|
+
self.epanet_api.closeH()
|
|
2751
2746
|
|
|
2752
2747
|
self.__running_simulation = False
|
|
2753
2748
|
|
|
2754
2749
|
if hyd_export is not None:
|
|
2755
|
-
self.epanet_api.
|
|
2750
|
+
self.epanet_api.savehydfile(hyd_export)
|
|
2756
2751
|
except Exception as ex:
|
|
2757
2752
|
self.__running_simulation = False
|
|
2758
2753
|
raise ex
|
|
@@ -2929,7 +2924,7 @@ class ScenarioSimulator():
|
|
|
2929
2924
|
Specifies the flow units -- i.e. all flows will be reported in these units.
|
|
2930
2925
|
If None, the units from the .inp file will be used.
|
|
2931
2926
|
|
|
2932
|
-
Must be one of the following EPANET
|
|
2927
|
+
Must be one of the following EPANET constants:
|
|
2933
2928
|
|
|
2934
2929
|
- EN_CFS = 0 (cubic foot/sec)
|
|
2935
2930
|
- EN_GPM = 1 (gal/min)
|
|
@@ -2956,39 +2951,39 @@ class ScenarioSimulator():
|
|
|
2956
2951
|
self._adapt_to_network_changes()
|
|
2957
2952
|
|
|
2958
2953
|
if flow_units_id is not None:
|
|
2959
|
-
if flow_units_id ==
|
|
2960
|
-
self.epanet_api.
|
|
2961
|
-
elif flow_units_id ==
|
|
2962
|
-
self.epanet_api.
|
|
2963
|
-
elif flow_units_id ==
|
|
2964
|
-
self.epanet_api.
|
|
2965
|
-
elif flow_units_id ==
|
|
2966
|
-
self.epanet_api.
|
|
2967
|
-
elif flow_units_id ==
|
|
2968
|
-
self.epanet_api.
|
|
2969
|
-
elif flow_units_id ==
|
|
2970
|
-
self.epanet_api.
|
|
2971
|
-
elif flow_units_id ==
|
|
2972
|
-
self.epanet_api.
|
|
2973
|
-
elif flow_units_id ==
|
|
2974
|
-
self.epanet_api.
|
|
2975
|
-
elif flow_units_id ==
|
|
2976
|
-
self.epanet_api.
|
|
2977
|
-
elif flow_units_id ==
|
|
2978
|
-
self.epanet_api.
|
|
2954
|
+
if flow_units_id == EpanetConstants.EN_CFS:
|
|
2955
|
+
self.epanet_api.setflowunits(flow_units_id)
|
|
2956
|
+
elif flow_units_id == EpanetConstants.EN_GPM:
|
|
2957
|
+
self.epanet_api.setflowunits(flow_units_id)
|
|
2958
|
+
elif flow_units_id == EpanetConstants.EN_MGD:
|
|
2959
|
+
self.epanet_api.setflowunits(flow_units_id)
|
|
2960
|
+
elif flow_units_id == EpanetConstants.EN_IMGD:
|
|
2961
|
+
self.epanet_api.setflowunits(flow_units_id)
|
|
2962
|
+
elif flow_units_id == EpanetConstants.EN_AFD:
|
|
2963
|
+
self.epanet_api.setflowunits(flow_units_id)
|
|
2964
|
+
elif flow_units_id == EpanetConstants.EN_LPS:
|
|
2965
|
+
self.epanet_api.setflowunits(flow_units_id)
|
|
2966
|
+
elif flow_units_id == EpanetConstants.EN_LPM:
|
|
2967
|
+
self.epanet_api.setflowunits(flow_units_id)
|
|
2968
|
+
elif flow_units_id == EpanetConstants.EN_MLD:
|
|
2969
|
+
self.epanet_api.setflowunits(flow_units_id)
|
|
2970
|
+
elif flow_units_id == EpanetConstants.EN_CMH:
|
|
2971
|
+
self.epanet_api.setflowunits(flow_units_id)
|
|
2972
|
+
elif flow_units_id == EpanetConstants.EN_CMD:
|
|
2973
|
+
self.epanet_api.setflowunits(flow_units_id)
|
|
2979
2974
|
else:
|
|
2980
2975
|
raise ValueError(f"Unknown flow units '{flow_units_id}'")
|
|
2981
2976
|
|
|
2982
2977
|
if demand_model is not None:
|
|
2983
|
-
self.epanet_api.
|
|
2984
|
-
|
|
2985
|
-
|
|
2978
|
+
self.epanet_api.set_demand_model(demand_model["type"], demand_model["pressure_min"],
|
|
2979
|
+
demand_model["pressure_required"],
|
|
2980
|
+
demand_model["pressure_exponent"])
|
|
2986
2981
|
|
|
2987
2982
|
if simulation_duration is not None:
|
|
2988
2983
|
if not isinstance(simulation_duration, int) or simulation_duration <= 0:
|
|
2989
2984
|
raise ValueError("'simulation_duration' must be a positive integer specifying " +
|
|
2990
2985
|
"the number of seconds to simulate")
|
|
2991
|
-
self.epanet_api.
|
|
2986
|
+
self.epanet_api.set_simulation_duration(simulation_duration)
|
|
2992
2987
|
|
|
2993
2988
|
if hydraulic_time_step is not None:
|
|
2994
2989
|
if not isinstance(hydraulic_time_step, int) or hydraulic_time_step <= 0:
|
|
@@ -2997,52 +2992,47 @@ class ScenarioSimulator():
|
|
|
2997
2992
|
if len(self._system_events) != 0:
|
|
2998
2993
|
raise RuntimeError("Hydraulic time step cannot be changed after system events " +
|
|
2999
2994
|
"such as leakages have been added to the scenario")
|
|
3000
|
-
self.epanet_api.
|
|
2995
|
+
self.epanet_api.set_hydraulic_time_step(hydraulic_time_step)
|
|
3001
2996
|
if reporting_time_step is None:
|
|
3002
2997
|
warnings.warn("No report time steps specified -- using 'hydraulic_time_step'")
|
|
3003
|
-
self.epanet_api.
|
|
2998
|
+
self.epanet_api.set_reporting_time_step(hydraulic_time_step)
|
|
3004
2999
|
|
|
3005
3000
|
if reporting_time_step is not None:
|
|
3006
|
-
hydraulic_time_step = self.epanet_api.
|
|
3001
|
+
hydraulic_time_step = self.epanet_api.get_hydraulic_time_step()
|
|
3007
3002
|
if not isinstance(reporting_time_step, int) or \
|
|
3008
3003
|
reporting_time_step % hydraulic_time_step != 0:
|
|
3009
3004
|
raise ValueError("'reporting_time_step' must be a positive integer " +
|
|
3010
3005
|
"and a multiple of 'hydraulic_time_step'")
|
|
3011
|
-
self.epanet_api.
|
|
3006
|
+
self.epanet_api.set_reporting_time_step(reporting_time_step)
|
|
3012
3007
|
|
|
3013
3008
|
if reporting_time_start is not None:
|
|
3014
3009
|
if not isinstance(reporting_time_start, int) or reporting_time_start <= 0:
|
|
3015
3010
|
raise ValueError("'reporting_time_start' must be a positive integer specifying " +
|
|
3016
3011
|
"the time at which reporting starts")
|
|
3017
|
-
self.epanet_api.
|
|
3012
|
+
self.epanet_api.set_reporting_start_time(reporting_time_start)
|
|
3018
3013
|
|
|
3019
3014
|
if quality_time_step is not None:
|
|
3020
3015
|
if not isinstance(quality_time_step, int) or quality_time_step <= 0 or \
|
|
3021
|
-
quality_time_step > self.epanet_api.
|
|
3016
|
+
quality_time_step > self.epanet_api.get_hydraulic_time_step():
|
|
3022
3017
|
raise ValueError("'quality_time_step' must be a positive integer that is not " +
|
|
3023
3018
|
"greater than the hydraulic time step")
|
|
3024
|
-
self.epanet_api.
|
|
3019
|
+
self.epanet_api.set_quality_time_step(quality_time_step)
|
|
3025
3020
|
|
|
3026
3021
|
if advanced_quality_time_step is not None:
|
|
3027
3022
|
if not isinstance(advanced_quality_time_step, int) or \
|
|
3028
3023
|
advanced_quality_time_step <= 0 or \
|
|
3029
|
-
advanced_quality_time_step > self.epanet_api.
|
|
3024
|
+
advanced_quality_time_step > self.epanet_api.get_hydraulic_time_step():
|
|
3030
3025
|
raise ValueError("'advanced_quality_time_step' must be a positive integer " +
|
|
3031
3026
|
"that is not greater than the hydraulic time step")
|
|
3032
|
-
self.epanet_api.
|
|
3027
|
+
self.epanet_api.set_msx_time_step(advanced_quality_time_step)
|
|
3033
3028
|
|
|
3034
3029
|
if quality_model is not None:
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
qualityunit_to_str(quality_model["units"]))
|
|
3042
|
-
elif quality_model["type"] == "TRACE":
|
|
3043
|
-
self.epanet_api.setQualityType("trace", quality_model["trace_node_id"])
|
|
3044
|
-
else:
|
|
3045
|
-
raise ValueError(f"Unknown quality type: {quality_model['type']}")
|
|
3030
|
+
chem_name = quality_model["chem_name"] if "chem_name" in quality_model else ""
|
|
3031
|
+
chem_units = quality_model["chem_units"] if "chem_units" in quality_model else ""
|
|
3032
|
+
trace_node_id = quality_model["trace_node_id"] \
|
|
3033
|
+
if "trace_node_id" in quality_model else ""
|
|
3034
|
+
self.epanet_api.set_quality_type(quality_model["type"], chem_name, chem_units,
|
|
3035
|
+
trace_node_id)
|
|
3046
3036
|
|
|
3047
3037
|
def get_events_active_time_points(self) -> list[int]:
|
|
3048
3038
|
"""
|
|
@@ -3056,7 +3046,7 @@ class ScenarioSimulator():
|
|
|
3056
3046
|
"""
|
|
3057
3047
|
events_times = []
|
|
3058
3048
|
|
|
3059
|
-
hyd_time_step = self.epanet_api.
|
|
3049
|
+
hyd_time_step = self.epanet_api.get_hydraulic_time_step()
|
|
3060
3050
|
|
|
3061
3051
|
def __process_event(event) -> None:
|
|
3062
3052
|
cur_time = event.start_time
|
|
@@ -3107,18 +3097,18 @@ class ScenarioSimulator():
|
|
|
3107
3097
|
else:
|
|
3108
3098
|
pattern_id = f"energy_price_{pump_id}"
|
|
3109
3099
|
|
|
3110
|
-
pattern_idx = self.epanet_api.
|
|
3100
|
+
pattern_idx = self.epanet_api.getpatternindex(pattern_id)
|
|
3111
3101
|
if pattern_idx != 0:
|
|
3112
3102
|
warnings.warn(f"Overwriting existing pattern '{pattern_id}'")
|
|
3113
3103
|
|
|
3114
|
-
pump_idx = self.epanet_api.
|
|
3115
|
-
pattern_idx = self.epanet_api.
|
|
3104
|
+
pump_idx = self.epanet_api.get_link_idx(pump_id)
|
|
3105
|
+
pattern_idx = self.epanet_api.getlinkvalue(pump_idx, EpanetConstants.EN_PUMP_EPAT)
|
|
3116
3106
|
if pattern_idx != 0:
|
|
3117
3107
|
warnings.warn(f"Overwriting existing energy price pattern of pump '{pump_id}'")
|
|
3118
3108
|
|
|
3119
|
-
self.add_pattern(pattern_id, pattern)
|
|
3120
|
-
pattern_idx = self.epanet_api.
|
|
3121
|
-
self.epanet_api.
|
|
3109
|
+
self.add_pattern(pattern_id, pattern.tolist())
|
|
3110
|
+
pattern_idx = self.epanet_api.getpatternindex(pattern_id)
|
|
3111
|
+
self.epanet_api.setlinkvalue(pump_idx, EpanetConstants.PUMP_EPAT, pattern_idx)
|
|
3122
3112
|
|
|
3123
3113
|
def get_pump_energy_price_pattern(self, pump_id: str) -> np.ndarray:
|
|
3124
3114
|
"""
|
|
@@ -3139,14 +3129,12 @@ class ScenarioSimulator():
|
|
|
3139
3129
|
if pump_id not in self._sensor_config.pumps:
|
|
3140
3130
|
raise ValueError(f"Unknown pump '{pump_id}'")
|
|
3141
3131
|
|
|
3142
|
-
pump_idx = self.epanet_api.
|
|
3143
|
-
pattern_idx = self.epanet_api.
|
|
3132
|
+
pump_idx = self.epanet_api.get_link_idx(pump_id)
|
|
3133
|
+
pattern_idx = self.epanet_api.getlinkvalue(pump_idx, EpanetConstants.EN_PUMP_EPAT)
|
|
3144
3134
|
if pattern_idx == 0:
|
|
3145
3135
|
return None
|
|
3146
3136
|
else:
|
|
3147
|
-
|
|
3148
|
-
return np.array([self.epanet_api.getPatternValue(pattern_idx, t+1)
|
|
3149
|
-
for t in range(pattern_length)])
|
|
3137
|
+
return np.array(self.epanet_api.get_pattern(pattern_idx))
|
|
3150
3138
|
|
|
3151
3139
|
def get_pump_energy_price(self, pump_id: str) -> float:
|
|
3152
3140
|
"""
|
|
@@ -3167,8 +3155,8 @@ class ScenarioSimulator():
|
|
|
3167
3155
|
if pump_id not in self._sensor_config.pumps:
|
|
3168
3156
|
raise ValueError(f"Unknown pump '{pump_id}'")
|
|
3169
3157
|
|
|
3170
|
-
pump_idx = self.epanet_api.
|
|
3171
|
-
return self.epanet_api.
|
|
3158
|
+
pump_idx = self.epanet_api.get_link_idx(pump_id)
|
|
3159
|
+
return self.epanet_api.get_pump_avg_energy_price(pump_idx)
|
|
3172
3160
|
|
|
3173
3161
|
def set_pump_energy_price(self, pump_id, price: float) -> None:
|
|
3174
3162
|
"""
|
|
@@ -3190,11 +3178,8 @@ class ScenarioSimulator():
|
|
|
3190
3178
|
if price <= 0:
|
|
3191
3179
|
raise ValueError("'price' must be positive")
|
|
3192
3180
|
|
|
3193
|
-
pump_idx = self.
|
|
3194
|
-
|
|
3195
|
-
pumps_energy_price[pump_idx - 1] = price
|
|
3196
|
-
|
|
3197
|
-
self.epanet_api.setLinkPumpECost(pumps_energy_price)
|
|
3181
|
+
pump_idx = self.epanet_api.get_link_idx(pump_id)
|
|
3182
|
+
self.epanet_api.setlinkvalue(pump_idx, EpanetConstants.EN_PUMP_ECOST, price)
|
|
3198
3183
|
|
|
3199
3184
|
def set_initial_link_status(self, link_id: str, status: int) -> None:
|
|
3200
3185
|
"""
|
|
@@ -3219,8 +3204,8 @@ class ScenarioSimulator():
|
|
|
3219
3204
|
if status not in [ActuatorConstants.EN_CLOSED, ActuatorConstants.EN_OPEN]:
|
|
3220
3205
|
raise ValueError(f"Invalid link status '{status}'")
|
|
3221
3206
|
|
|
3222
|
-
link_idx = self.epanet_api.
|
|
3223
|
-
self.epanet_api.
|
|
3207
|
+
link_idx = self.epanet_api.get_link_idx(link_id)
|
|
3208
|
+
self.epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_INITSTATUS, status)
|
|
3224
3209
|
|
|
3225
3210
|
def set_initial_pump_speed(self, pump_id: str, speed: float) -> None:
|
|
3226
3211
|
"""
|
|
@@ -3242,8 +3227,8 @@ class ScenarioSimulator():
|
|
|
3242
3227
|
if speed < 0:
|
|
3243
3228
|
raise ValueError("'speed' can not be negative")
|
|
3244
3229
|
|
|
3245
|
-
pump_idx = self.epanet_api.
|
|
3246
|
-
self.epanet_api.
|
|
3230
|
+
pump_idx = self.epanet_api.get_link_idx(pump_id)
|
|
3231
|
+
self.epanet_api.setlinkvalue(pump_idx, EpanetConstants.EN_INITSETTING, speed)
|
|
3247
3232
|
|
|
3248
3233
|
def set_initial_tank_level(self, tank_id, level: int) -> None:
|
|
3249
3234
|
"""
|
|
@@ -3265,14 +3250,14 @@ class ScenarioSimulator():
|
|
|
3265
3250
|
if level < 0:
|
|
3266
3251
|
raise ValueError("'level' can not be negative")
|
|
3267
3252
|
|
|
3268
|
-
tank_idx = self.epanet_api.
|
|
3269
|
-
self.epanet_api.
|
|
3253
|
+
tank_idx = self.epanet_api.get_node_idx(tank_id)
|
|
3254
|
+
self.epanet_api.setnodevalue(tank_idx, EpanetConstants.EN_TANKLEVEL, level)
|
|
3270
3255
|
|
|
3271
3256
|
def __warn_if_quality_set(self):
|
|
3272
|
-
|
|
3273
|
-
if
|
|
3257
|
+
qual_code = self.epanet_api.getqualinfo()[0]
|
|
3258
|
+
if qual_code != EpanetConstants.EN_NONE:
|
|
3274
3259
|
warnings.warn("You are overriding current quality settings " +
|
|
3275
|
-
f"'{
|
|
3260
|
+
f"'{qual_code}'")
|
|
3276
3261
|
|
|
3277
3262
|
def enable_waterage_analysis(self) -> None:
|
|
3278
3263
|
"""
|
|
@@ -3285,7 +3270,7 @@ class ScenarioSimulator():
|
|
|
3285
3270
|
self._adapt_to_network_changes()
|
|
3286
3271
|
|
|
3287
3272
|
self.__warn_if_quality_set()
|
|
3288
|
-
self.set_general_parameters(quality_model={"type":
|
|
3273
|
+
self.set_general_parameters(quality_model={"type": EpanetConstants.EN_AGE})
|
|
3289
3274
|
|
|
3290
3275
|
def enable_chemical_analysis(self, chemical_name: str = "Chlorine",
|
|
3291
3276
|
chemical_units: int = MASS_UNIT_MG) -> None:
|
|
@@ -3316,8 +3301,9 @@ class ScenarioSimulator():
|
|
|
3316
3301
|
self._adapt_to_network_changes()
|
|
3317
3302
|
|
|
3318
3303
|
self.__warn_if_quality_set()
|
|
3319
|
-
self.set_general_parameters(quality_model={"type":
|
|
3320
|
-
"
|
|
3304
|
+
self.set_general_parameters(quality_model={"type": EpanetConstants.EN_CHEM,
|
|
3305
|
+
"chem_name": chemical_name,
|
|
3306
|
+
"chem_units": qualityunit_to_str(chemical_units)})
|
|
3321
3307
|
|
|
3322
3308
|
def add_quality_source(self, node_id: str, source_type: int, pattern: np.ndarray = None,
|
|
3323
3309
|
pattern_id: str = None, source_strength: int = 1.) -> None:
|
|
@@ -3330,7 +3316,7 @@ class ScenarioSimulator():
|
|
|
3330
3316
|
ID of the node at which this external water quality source is placed.
|
|
3331
3317
|
source_type : `int`,
|
|
3332
3318
|
Types of the external water quality source -- must be of the following
|
|
3333
|
-
EPANET
|
|
3319
|
+
EPANET constants:
|
|
3334
3320
|
|
|
3335
3321
|
- EN_CONCEN = 0
|
|
3336
3322
|
- EN_MASS = 1
|
|
@@ -3366,7 +3352,7 @@ class ScenarioSimulator():
|
|
|
3366
3352
|
|
|
3367
3353
|
self._adapt_to_network_changes()
|
|
3368
3354
|
|
|
3369
|
-
if self.epanet_api.
|
|
3355
|
+
if self.epanet_api.getqualinfo()[0] != EpanetConstants.EN_CHEM:
|
|
3370
3356
|
raise RuntimeError("Chemical analysis is not enabled -- " +
|
|
3371
3357
|
"call 'enable_chemical_analysis()' before calling this function.")
|
|
3372
3358
|
if node_id not in self._sensor_config.nodes:
|
|
@@ -3382,19 +3368,20 @@ class ScenarioSimulator():
|
|
|
3382
3368
|
if pattern_id is None:
|
|
3383
3369
|
pattern_id = f"qual_src_pat_{node_id}"
|
|
3384
3370
|
|
|
3385
|
-
node_idx = self.epanet_api.
|
|
3371
|
+
node_idx = self.epanet_api.get_node_idx(node_id)
|
|
3386
3372
|
|
|
3387
3373
|
if pattern is None:
|
|
3388
|
-
pattern_idx = self.epanet_api.
|
|
3374
|
+
pattern_idx = self.epanet_api.getpatternindex(pattern_id)
|
|
3389
3375
|
else:
|
|
3390
|
-
|
|
3376
|
+
self.epanet_api.add_pattern(pattern_id, pattern.tolist())
|
|
3377
|
+
pattern_idx = self.epanet_api.getpatternindex(pattern_id)
|
|
3391
3378
|
if pattern_idx == 0:
|
|
3392
3379
|
raise RuntimeError("Failed to add/get pattern! " +
|
|
3393
3380
|
"Maybe pattern name contains invalid characters or is too long?")
|
|
3394
3381
|
|
|
3395
|
-
self.epanet_api.
|
|
3396
|
-
self.epanet_api.
|
|
3397
|
-
self.epanet_api.
|
|
3382
|
+
self.epanet_api.setnodevalue(node_idx, EpanetConstants.EN_SOURCETYPE, source_type)
|
|
3383
|
+
self.epanet_api.setnodevalue(node_idx, EpanetConstants.EN_SOURCEQUAL, source_strength)
|
|
3384
|
+
self.epanet_api.setnodevalue(node_idx, EpanetConstants.EN_SOURCEPAT, pattern_idx)
|
|
3398
3385
|
|
|
3399
3386
|
def set_initial_node_quality(self, node_id: str, initial_quality: float) -> None:
|
|
3400
3387
|
"""
|
|
@@ -3486,12 +3473,8 @@ class ScenarioSimulator():
|
|
|
3486
3473
|
if node_init_qual < 0:
|
|
3487
3474
|
raise ValueError(f"{node_id}: Initial node quality can not be negative")
|
|
3488
3475
|
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
node_idx = self.epanet_api.getNodeIndex(node_id) - 1
|
|
3492
|
-
init_qual[node_idx] = node_init_qual
|
|
3493
|
-
|
|
3494
|
-
self.epanet_api.setNodeInitialQuality(init_qual)
|
|
3476
|
+
for node_idx in self.epanet_api.get_all_nodes_idx():
|
|
3477
|
+
self.epanet_api.set_node_init_quality(node_idx, node_init_qual)
|
|
3495
3478
|
|
|
3496
3479
|
if order_wall is not None:
|
|
3497
3480
|
if not isinstance(order_wall, int):
|
|
@@ -3500,7 +3483,7 @@ class ScenarioSimulator():
|
|
|
3500
3483
|
if order_wall not in [0, 1]:
|
|
3501
3484
|
raise ValueError(f"Invalid value '{order_wall}' for order_wall")
|
|
3502
3485
|
|
|
3503
|
-
self.epanet_api.
|
|
3486
|
+
self.epanet_api.setoption(EpanetConstants.EN_WALLORDER, order_wall)
|
|
3504
3487
|
|
|
3505
3488
|
if order_bulk is not None:
|
|
3506
3489
|
if not isinstance(order_bulk, int):
|
|
@@ -3509,7 +3492,7 @@ class ScenarioSimulator():
|
|
|
3509
3492
|
if order_bulk not in [0, 1]:
|
|
3510
3493
|
raise ValueError(f"Invalid value '{order_bulk}' for order_bulk")
|
|
3511
3494
|
|
|
3512
|
-
self.epanet_api.
|
|
3495
|
+
self.epanet_api.setoption(EpanetConstants.EN_BULKORDER, order_bulk)
|
|
3513
3496
|
|
|
3514
3497
|
if order_tank is not None:
|
|
3515
3498
|
if not isinstance(order_tank, int):
|
|
@@ -3518,29 +3501,25 @@ class ScenarioSimulator():
|
|
|
3518
3501
|
if order_tank not in [0, 1]:
|
|
3519
3502
|
raise ValueError(f"Invalid value '{order_tank}' for order_wall")
|
|
3520
3503
|
|
|
3521
|
-
self.epanet_api.
|
|
3504
|
+
self.epanet_api.setoption(EpanetConstants.EN_TANKORDER, order_tank)
|
|
3522
3505
|
|
|
3523
3506
|
if global_wall_reaction_coefficient is not None:
|
|
3524
3507
|
if not isinstance(global_wall_reaction_coefficient, float):
|
|
3525
3508
|
raise TypeError("'global_wall_reaction_coefficient' must be an instance of " +
|
|
3526
3509
|
f"'float' but not of '{type(global_wall_reaction_coefficient)}'")
|
|
3527
3510
|
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
self.epanet_api.setLinkWallReactionCoeff(wall_reaction_coeff)
|
|
3511
|
+
for link_idx in self.epanet_api.get_all_links_idx():
|
|
3512
|
+
self.epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_KWALL,
|
|
3513
|
+
global_wall_reaction_coefficient)
|
|
3533
3514
|
|
|
3534
3515
|
if global_bulk_reaction_coefficient is not None:
|
|
3535
3516
|
if not isinstance(global_bulk_reaction_coefficient, float):
|
|
3536
3517
|
raise TypeError("'global_bulk_reaction_coefficient' must be an instance of " +
|
|
3537
3518
|
f"'float' but not of '{type(global_bulk_reaction_coefficient)}'")
|
|
3538
3519
|
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
self.epanet_api.setLinkBulkReactionCoeff(bulk_reaction_coeff)
|
|
3520
|
+
for link_idx in self.epanet_api.get_all_links_idx():
|
|
3521
|
+
self.epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_KBULK,
|
|
3522
|
+
global_bulk_reaction_coefficient)
|
|
3544
3523
|
|
|
3545
3524
|
if local_wall_reaction_coefficient is not None:
|
|
3546
3525
|
if not isinstance(local_wall_reaction_coefficient, dict):
|
|
@@ -3556,8 +3535,8 @@ class ScenarioSimulator():
|
|
|
3556
3535
|
raise ValueError(f"Invalid link ID '{link_id}'")
|
|
3557
3536
|
|
|
3558
3537
|
for link_id, link_reaction_coeff in local_wall_reaction_coefficient:
|
|
3559
|
-
link_idx = self.epanet_api.
|
|
3560
|
-
self.epanet_api.
|
|
3538
|
+
link_idx = self.epanet_api.get_link_idx(link_id)
|
|
3539
|
+
self.epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_KWALL, link_reaction_coeff)
|
|
3561
3540
|
|
|
3562
3541
|
if local_bulk_reaction_coefficient is not None:
|
|
3563
3542
|
if not isinstance(local_bulk_reaction_coefficient, dict):
|
|
@@ -3573,8 +3552,8 @@ class ScenarioSimulator():
|
|
|
3573
3552
|
raise ValueError(f"Invalid link ID '{link_id}'")
|
|
3574
3553
|
|
|
3575
3554
|
for link_id, link_reaction_coeff in local_bulk_reaction_coefficient:
|
|
3576
|
-
link_idx = self.epanet_api.
|
|
3577
|
-
self.epanet_api.
|
|
3555
|
+
link_idx = self.epanet_api.get_link_idx(link_id)
|
|
3556
|
+
self.epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_KBULK, link_reaction_coeff)
|
|
3578
3557
|
|
|
3579
3558
|
if local_tank_reaction_coefficient is not None:
|
|
3580
3559
|
if not isinstance(local_tank_reaction_coefficient, dict):
|
|
@@ -3590,8 +3569,9 @@ class ScenarioSimulator():
|
|
|
3590
3569
|
raise ValueError(f"Invalid tank ID '{tank_id}'")
|
|
3591
3570
|
|
|
3592
3571
|
for tank_id, tank_reaction_coeff in local_tank_reaction_coefficient:
|
|
3593
|
-
tank_idx = self.epanet_api.
|
|
3594
|
-
self.epanet_api.
|
|
3572
|
+
tank_idx = self.epanet_api.get_node_idx(tank_id)
|
|
3573
|
+
self.epanet_api.setnodevalue(tank_idx, EpanetConstants.EN_TANK_KBULK,
|
|
3574
|
+
tank_reaction_coeff)
|
|
3595
3575
|
|
|
3596
3576
|
if limiting_potential is not None:
|
|
3597
3577
|
if not isinstance(limiting_potential, float):
|
|
@@ -3600,7 +3580,7 @@ class ScenarioSimulator():
|
|
|
3600
3580
|
if limiting_potential < 0:
|
|
3601
3581
|
raise ValueError("'limiting_potential' can not be negative")
|
|
3602
3582
|
|
|
3603
|
-
self.epanet_api.
|
|
3583
|
+
self.epanet_api.setoption(EpanetConstants.EN_CONCENLIMIT, limiting_potential)
|
|
3604
3584
|
|
|
3605
3585
|
def enable_sourcetracing_analysis(self, trace_node_id: str) -> None:
|
|
3606
3586
|
"""
|
|
@@ -3621,7 +3601,7 @@ class ScenarioSimulator():
|
|
|
3621
3601
|
raise ValueError(f"Invalid node ID '{trace_node_id}'")
|
|
3622
3602
|
|
|
3623
3603
|
self.__warn_if_quality_set()
|
|
3624
|
-
self.set_general_parameters(quality_model={"type":
|
|
3604
|
+
self.set_general_parameters(quality_model={"type": EpanetConstants.EN_TRACE,
|
|
3625
3605
|
"trace_node_id": trace_node_id})
|
|
3626
3606
|
|
|
3627
3607
|
def add_species_injection_source(self, species_id: str, node_id: str, pattern: np.ndarray,
|
|
@@ -3645,7 +3625,7 @@ class ScenarioSimulator():
|
|
|
3645
3625
|
Note that the pattern time step is equivalent to the EPANET pattern time step.
|
|
3646
3626
|
source_type : `int`,
|
|
3647
3627
|
Type of the external (bulk or surface) species injection source -- must be one of
|
|
3648
|
-
the following EPANET
|
|
3628
|
+
the following EPANET constants:
|
|
3649
3629
|
|
|
3650
3630
|
- EN_CONCEN = 0
|
|
3651
3631
|
- EN_MASS = 1
|
|
@@ -3670,25 +3650,18 @@ class ScenarioSimulator():
|
|
|
3670
3650
|
|
|
3671
3651
|
The default is 1.
|
|
3672
3652
|
"""
|
|
3673
|
-
source_type_ = "None"
|
|
3674
|
-
if source_type == ToolkitConstants.EN_CONCEN:
|
|
3675
|
-
source_type_ = "CONCEN"
|
|
3676
|
-
elif source_type == ToolkitConstants.EN_MASS:
|
|
3677
|
-
source_type_ = "MASS"
|
|
3678
|
-
elif source_type == ToolkitConstants.EN_SETPOINT:
|
|
3679
|
-
source_type_ = "SETPOINT"
|
|
3680
|
-
elif source_type == ToolkitConstants.EN_FLOWPACED:
|
|
3681
|
-
source_type_ = "FLOWPACED"
|
|
3682
|
-
|
|
3683
3653
|
if pattern_id is None:
|
|
3684
3654
|
pattern_id = f"{species_id}_{node_id}"
|
|
3685
|
-
if pattern_id in self.epanet_api.
|
|
3655
|
+
if pattern_id in self.epanet_api.get_all_msx_pattern_id():
|
|
3686
3656
|
raise ValueError("Invalid 'pattern_id' -- " +
|
|
3687
3657
|
f"there already exists a pattern with ID '{pattern_id}'")
|
|
3688
3658
|
|
|
3689
|
-
self.epanet_api.
|
|
3690
|
-
self.epanet_api.
|
|
3691
|
-
|
|
3659
|
+
self.epanet_api.MSXaddpattern(pattern_id)
|
|
3660
|
+
pattern_idx = self.epanet_api.MSXgetindex(EpanetConstants.MSX_PATTERN, pattern_id)
|
|
3661
|
+
self.epanet_api.MSXsetpattern(pattern_idx, pattern.tolist(), len(pattern))
|
|
3662
|
+
self.epanet_api.MSXsetsource(self.epanet_api.get_node_idx(node_id),
|
|
3663
|
+
self.epanet_api.get_msx_species_idx(species_id),
|
|
3664
|
+
source_type, source_strength, pattern_idx)
|
|
3692
3665
|
|
|
3693
3666
|
def set_bulk_species_node_initial_concentrations(self,
|
|
3694
3667
|
inital_conc: dict[str, list[tuple[str, float]]]
|
|
@@ -3723,12 +3696,12 @@ class ScenarioSimulator():
|
|
|
3723
3696
|
raise ValueError("Initial node concentration can not be negative")
|
|
3724
3697
|
|
|
3725
3698
|
for species_id, node_initial_conc in inital_conc.items():
|
|
3726
|
-
species_idx
|
|
3699
|
+
species_idx = self.epanet_api.get_msx_species_idx(species_id)
|
|
3727
3700
|
|
|
3728
3701
|
for node_id, initial_conc in node_initial_conc:
|
|
3729
|
-
node_idx = self.epanet_api.
|
|
3730
|
-
self.epanet_api.
|
|
3731
|
-
|
|
3702
|
+
node_idx = self.epanet_api.get_node_idx(node_id)
|
|
3703
|
+
self.epanet_api.MSXsetinitqual(EpanetConstants.MSX_NODE, node_idx, species_idx,
|
|
3704
|
+
initial_conc)
|
|
3732
3705
|
|
|
3733
3706
|
def set_species_link_initial_concentrations(self,
|
|
3734
3707
|
inital_conc: dict[str, list[tuple[str, float]]]
|
|
@@ -3762,9 +3735,9 @@ class ScenarioSimulator():
|
|
|
3762
3735
|
raise ValueError("Initial link concentration can not be negative")
|
|
3763
3736
|
|
|
3764
3737
|
for species_id, link_initial_conc in inital_conc.items():
|
|
3765
|
-
species_idx
|
|
3738
|
+
species_idx = self.epanet_api.get_msx_species_idx(species_id)
|
|
3766
3739
|
|
|
3767
3740
|
for link_id, initial_conc in link_initial_conc:
|
|
3768
|
-
link_idx = self.epanet_api.
|
|
3769
|
-
self.epanet_api.
|
|
3770
|
-
|
|
3741
|
+
link_idx = self.epanet_api.get_link_idx(link_id)
|
|
3742
|
+
self.epanet_api.MSXsetinitqual(EpanetConstants.MSX_LINK, link_idx, species_idx,
|
|
3743
|
+
initial_conc)
|