epyt-flow 0.14.2__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 +107 -104
- 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/utils.py +0 -66
- epyt_flow/visualization/visualization_utils.py +2 -4
- {epyt_flow-0.14.2.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 -28
- epyt_flow/EPANET/EPANET/SRC_engines/LICENSE +0 -21
- epyt_flow/EPANET/EPANET/SRC_engines/Readme_SRC_Engines.txt +0 -18
- epyt_flow/EPANET/EPANET/SRC_engines/enumstxt.h +0 -134
- epyt_flow/EPANET/EPANET/SRC_engines/epanet.c +0 -5578
- epyt_flow/EPANET/EPANET/SRC_engines/epanet2.c +0 -865
- epyt_flow/EPANET/EPANET/SRC_engines/epanet2.def +0 -131
- epyt_flow/EPANET/EPANET/SRC_engines/errors.dat +0 -73
- epyt_flow/EPANET/EPANET/SRC_engines/funcs.h +0 -193
- 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 -1151
- epyt_flow/EPANET/EPANET/SRC_engines/hydraul.c +0 -1117
- epyt_flow/EPANET/EPANET/SRC_engines/hydsolver.c +0 -720
- epyt_flow/EPANET/EPANET/SRC_engines/hydstatus.c +0 -476
- epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2.h +0 -431
- epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_2.h +0 -1786
- epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_enums.h +0 -468
- epyt_flow/EPANET/EPANET/SRC_engines/inpfile.c +0 -810
- epyt_flow/EPANET/EPANET/SRC_engines/input1.c +0 -707
- epyt_flow/EPANET/EPANET/SRC_engines/input2.c +0 -864
- epyt_flow/EPANET/EPANET/SRC_engines/input3.c +0 -2170
- epyt_flow/EPANET/EPANET/SRC_engines/main.c +0 -93
- epyt_flow/EPANET/EPANET/SRC_engines/mempool.c +0 -142
- epyt_flow/EPANET/EPANET/SRC_engines/mempool.h +0 -24
- epyt_flow/EPANET/EPANET/SRC_engines/output.c +0 -852
- epyt_flow/EPANET/EPANET/SRC_engines/project.c +0 -1359
- epyt_flow/EPANET/EPANET/SRC_engines/quality.c +0 -685
- epyt_flow/EPANET/EPANET/SRC_engines/qualreact.c +0 -743
- epyt_flow/EPANET/EPANET/SRC_engines/qualroute.c +0 -694
- epyt_flow/EPANET/EPANET/SRC_engines/report.c +0 -1489
- epyt_flow/EPANET/EPANET/SRC_engines/rules.c +0 -1362
- epyt_flow/EPANET/EPANET/SRC_engines/smatrix.c +0 -871
- epyt_flow/EPANET/EPANET/SRC_engines/text.h +0 -497
- epyt_flow/EPANET/EPANET/SRC_engines/types.h +0 -874
- 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.2.dist-info/RECORD +0 -142
- {epyt_flow-0.14.2.dist-info → epyt_flow-0.15.0b1.dist-info}/WHEEL +0 -0
- {epyt_flow-0.14.2.dist-info → epyt_flow-0.15.0b1.dist-info}/licenses/LICENSE +0 -0
- {epyt_flow-0.14.2.dist-info → epyt_flow-0.15.0b1.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,14 @@ class ScenarioSimulator():
|
|
|
1068
1036
|
"coord": node_coord,
|
|
1069
1037
|
"comment": node_comment,
|
|
1070
1038
|
"type": node_type}
|
|
1071
|
-
if node_type ==
|
|
1072
|
-
node_tank_idx =
|
|
1073
|
-
node_info["diameter"] = float(self.epanet_api.
|
|
1074
|
-
node_info["volume"] = float(self.epanet_api.
|
|
1075
|
-
node_info["max_level"] = float(self.epanet_api.
|
|
1076
|
-
node_info["min_level"] = float(self.epanet_api.
|
|
1077
|
-
node_info["mixing_fraction"] = float(self.epanet_api.
|
|
1078
|
-
|
|
1039
|
+
if node_type == EpanetConstants.EN_TANK:
|
|
1040
|
+
node_tank_idx = self.epanet_api.get_node_idx(node_id)
|
|
1041
|
+
node_info["diameter"] = float(self.epanet_api.get_tank_diameter(node_tank_idx))
|
|
1042
|
+
node_info["volume"] = float(self.epanet_api.get_tank_volume(node_tank_idx))
|
|
1043
|
+
node_info["max_level"] = float(self.epanet_api.get_tank_max_level(node_tank_idx))
|
|
1044
|
+
node_info["min_level"] = float(self.epanet_api.get_tank_min_level(node_tank_idx))
|
|
1045
|
+
node_info["mixing_fraction"] = float(self.epanet_api.get_tank_mix_fraction(node_tank_idx))
|
|
1046
|
+
node_info["mixing_model"] = int(self.epanet_api.get_tank_mix_model(node_tank_idx))
|
|
1079
1047
|
|
|
1080
1048
|
nodes.append((node_id, node_info))
|
|
1081
1049
|
|
|
@@ -1132,23 +1100,21 @@ class ScenarioSimulator():
|
|
|
1132
1100
|
self._adapt_to_network_changes()
|
|
1133
1101
|
|
|
1134
1102
|
# Get all demand patterns
|
|
1135
|
-
demand_patterns_idx = self.epanet_api.
|
|
1136
|
-
|
|
1103
|
+
demand_patterns_idx = [self.epanet_api.get_node_demand_patterns_idx(node_idx)
|
|
1104
|
+
for node_idx in self.epanet_api.get_all_nodes_idx()]
|
|
1105
|
+
demand_patterns_idx = list(set(itertools.chain.from_iterable(demand_patterns_idx)))
|
|
1137
1106
|
|
|
1138
1107
|
# Process each pattern separately
|
|
1139
|
-
for
|
|
1140
|
-
|
|
1141
|
-
continue
|
|
1142
|
-
|
|
1143
|
-
pattern_length = self.epanet_api.getPatternLengths(pattern_id)
|
|
1108
|
+
for pattern_idx in demand_patterns_idx:
|
|
1109
|
+
pattern_length = self.epanet_api.getpatternlen(pattern_idx)
|
|
1144
1110
|
pattern = []
|
|
1145
1111
|
for t in range(pattern_length): # Get pattern
|
|
1146
|
-
pattern.append(self.epanet_api.
|
|
1112
|
+
pattern.append(self.epanet_api.getpatternvalue(pattern_idx, t + 1))
|
|
1147
1113
|
|
|
1148
1114
|
random.shuffle(pattern) # Shuffle pattern
|
|
1149
1115
|
|
|
1150
1116
|
for t in range(pattern_length): # Set shuffled/randomized pattern
|
|
1151
|
-
self.epanet_api.
|
|
1117
|
+
self.epanet_api.setpatternvalue(pattern_idx, t + 1, pattern[t])
|
|
1152
1118
|
|
|
1153
1119
|
def get_pattern(self, pattern_id: str) -> np.ndarray:
|
|
1154
1120
|
"""
|
|
@@ -1168,13 +1134,11 @@ class ScenarioSimulator():
|
|
|
1168
1134
|
raise TypeError("'pattern_id' must be an instance of 'str' " +
|
|
1169
1135
|
f"but not of '{type(pattern_id)}'")
|
|
1170
1136
|
|
|
1171
|
-
pattern_idx = self.epanet_api.
|
|
1137
|
+
pattern_idx = self.epanet_api.getpatternindex(pattern_id)
|
|
1172
1138
|
if pattern_idx == 0:
|
|
1173
1139
|
raise ValueError(f"Unknown pattern '{pattern_id}'")
|
|
1174
1140
|
|
|
1175
|
-
|
|
1176
|
-
return np.array([self.epanet_api.getPatternValue(pattern_idx, t+1)
|
|
1177
|
-
for t in range(pattern_length)])
|
|
1141
|
+
return np.array(self.epanet_api.get_pattern(pattern_idx))
|
|
1178
1142
|
|
|
1179
1143
|
def add_pattern(self, pattern_id: str, pattern: np.ndarray) -> None:
|
|
1180
1144
|
"""
|
|
@@ -1199,7 +1163,7 @@ class ScenarioSimulator():
|
|
|
1199
1163
|
raise ValueError(f"Inconsistent pattern shape '{pattern.shape}' " +
|
|
1200
1164
|
"detected. Expected a one dimensional array!")
|
|
1201
1165
|
|
|
1202
|
-
pattern_idx = self.epanet_api.
|
|
1166
|
+
pattern_idx = self.epanet_api.add_pattern(pattern_id, pattern.tolist())
|
|
1203
1167
|
if pattern_idx == 0:
|
|
1204
1168
|
raise RuntimeError("Failed to add pattern! " +
|
|
1205
1169
|
"Maybe pattern name contains invalid characters or is too long?")
|
|
@@ -1223,21 +1187,21 @@ class ScenarioSimulator():
|
|
|
1223
1187
|
if node_id not in self._sensor_config.nodes:
|
|
1224
1188
|
raise ValueError(f"Unknown node '{node_id}'")
|
|
1225
1189
|
|
|
1226
|
-
node_idx = self.epanet_api.
|
|
1227
|
-
n_demand_categories = self.epanet_api.
|
|
1190
|
+
node_idx = self.epanet_api.get_node_idx(node_id)
|
|
1191
|
+
n_demand_categories = self.epanet_api.getnumdemands(node_idx)
|
|
1228
1192
|
|
|
1229
1193
|
if n_demand_categories == 0:
|
|
1230
1194
|
return None
|
|
1231
1195
|
else:
|
|
1232
1196
|
base_demand = 0
|
|
1233
|
-
for
|
|
1234
|
-
base_demand += self.epanet_api.
|
|
1197
|
+
for demand_idx in range(n_demand_categories):
|
|
1198
|
+
base_demand += self.epanet_api.getbasedemand(node_idx)[demand_idx + 1]
|
|
1235
1199
|
|
|
1236
1200
|
return base_demand
|
|
1237
1201
|
|
|
1238
1202
|
def get_node_demand_pattern(self, node_id: str) -> np.ndarray:
|
|
1239
1203
|
"""
|
|
1240
|
-
Returns the values of the demand pattern of a given node --
|
|
1204
|
+
Returns the values of the primary demand pattern of a given node --
|
|
1241
1205
|
i.e. multiplier factors that are applied to the base demand.
|
|
1242
1206
|
|
|
1243
1207
|
Parameters
|
|
@@ -1256,10 +1220,14 @@ class ScenarioSimulator():
|
|
|
1256
1220
|
if node_id not in self._sensor_config.nodes:
|
|
1257
1221
|
raise ValueError(f"Unknown node '{node_id}'")
|
|
1258
1222
|
|
|
1259
|
-
node_idx = self.epanet_api.
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1223
|
+
node_idx = self.epanet_api.get_node_idx(node_id)
|
|
1224
|
+
|
|
1225
|
+
if self.epanet_api.getnodetype(node_idx) != EpanetConstants.EN_RESERVOIR:
|
|
1226
|
+
demand_pattern_idx = self.epanet_api.getdemandpattern(node_idx)
|
|
1227
|
+
else:
|
|
1228
|
+
demand_pattern_idx = self.epanet_api.getnodevalue(node_idx, EpanetConstants.EN_PATTERN)
|
|
1229
|
+
|
|
1230
|
+
return self.get_pattern(self.epanet_api.getpatternid(demand_pattern_idx))
|
|
1263
1231
|
|
|
1264
1232
|
def set_node_demand_pattern(self, node_id: str, base_demand: float, demand_pattern_id: str,
|
|
1265
1233
|
demand_pattern: np.ndarray = None) -> None:
|
|
@@ -1298,20 +1266,20 @@ class ScenarioSimulator():
|
|
|
1298
1266
|
raise ValueError(f"Inconsistent demand pattern shape '{demand_pattern.shape}' " +
|
|
1299
1267
|
"detected. Expected a one dimensional array!")
|
|
1300
1268
|
|
|
1301
|
-
node_idx = self.epanet_api.
|
|
1269
|
+
node_idx = self.epanet_api.get_node_idx(node_id)
|
|
1302
1270
|
|
|
1303
|
-
if demand_pattern_id not in self.epanet_api.
|
|
1271
|
+
if demand_pattern_id not in self.epanet_api.get_all_patterns_id():
|
|
1304
1272
|
if demand_pattern is None:
|
|
1305
1273
|
raise ValueError("'demand_pattern' can not be None if " +
|
|
1306
1274
|
"'demand_pattern_id' does not already exist.")
|
|
1307
|
-
self.epanet_api.
|
|
1275
|
+
self.epanet_api.add_pattern(demand_pattern_id, demand_pattern.tolist())
|
|
1308
1276
|
else:
|
|
1309
1277
|
if demand_pattern is not None:
|
|
1310
|
-
pattern_idx = self.epanet_api.
|
|
1311
|
-
self.epanet_api.
|
|
1278
|
+
pattern_idx = self.epanet_api.get_node_pattern_idx(demand_pattern_id)
|
|
1279
|
+
self.epanet_api.set_pattern(pattern_idx, demand_pattern.tolist())
|
|
1312
1280
|
|
|
1313
|
-
self.epanet_api.
|
|
1314
|
-
|
|
1281
|
+
self.epanet_api.setjuncdata(node_idx, self.epanet_api.get_node_elevation(node_idx),
|
|
1282
|
+
base_demand, demand_pattern_id)
|
|
1315
1283
|
|
|
1316
1284
|
def add_custom_control(self, control: CustomControlModule) -> None:
|
|
1317
1285
|
"""
|
|
@@ -1349,13 +1317,20 @@ class ScenarioSimulator():
|
|
|
1349
1317
|
|
|
1350
1318
|
if not any(c == control for c in self._simple_controls):
|
|
1351
1319
|
self._simple_controls.append(control)
|
|
1352
|
-
|
|
1320
|
+
|
|
1321
|
+
link_idx = self.epanet_api.get_link_idx(control.link_id)
|
|
1322
|
+
cond_var_value = control.cond_var_value
|
|
1323
|
+
if control.cond_type == EpanetConstants.EN_LOWLEVEL or \
|
|
1324
|
+
control.cond_type == EpanetConstants.EN_HILEVEL:
|
|
1325
|
+
cond_var_value = self.epanet_api.get_node_idx(cond_var_value)
|
|
1326
|
+
self.epanet_api.addcontrol(control.cond_type, link_idx, control.link_status,
|
|
1327
|
+
cond_var_value, control.cond_comp_value)
|
|
1353
1328
|
|
|
1354
1329
|
def remove_all_simple_controls(self) -> None:
|
|
1355
1330
|
"""
|
|
1356
1331
|
Removes all simple EPANET controls from the scenario.
|
|
1357
1332
|
"""
|
|
1358
|
-
self.epanet_api.
|
|
1333
|
+
self.epanet_api.remove_all_controls()
|
|
1359
1334
|
self._simple_controls = []
|
|
1360
1335
|
|
|
1361
1336
|
def remove_simple_control(self, control: SimpleControlModule) -> None:
|
|
@@ -1382,7 +1357,7 @@ class ScenarioSimulator():
|
|
|
1382
1357
|
if control_idx is None:
|
|
1383
1358
|
raise ValueError("Invalid/Unknown control module.")
|
|
1384
1359
|
|
|
1385
|
-
self.epanet_api.
|
|
1360
|
+
self.epanet_api.deletecontrol(control_idx)
|
|
1386
1361
|
self._simple_controls.remove(control)
|
|
1387
1362
|
|
|
1388
1363
|
def add_complex_control(self, control: ComplexControlModule) -> None:
|
|
@@ -1403,13 +1378,13 @@ class ScenarioSimulator():
|
|
|
1403
1378
|
|
|
1404
1379
|
if not any(c == control for c in self._complex_controls):
|
|
1405
1380
|
self._complex_controls.append(control)
|
|
1406
|
-
self.epanet_api.
|
|
1381
|
+
self.epanet_api.addrule(str(control))
|
|
1407
1382
|
|
|
1408
1383
|
def remove_all_complex_controls(self) -> None:
|
|
1409
1384
|
"""
|
|
1410
1385
|
Removes all complex EPANET controls from the scenario.
|
|
1411
1386
|
"""
|
|
1412
|
-
self.epanet_api.
|
|
1387
|
+
self.epanet_api.remove_all_rules()
|
|
1413
1388
|
self._complex_controls = []
|
|
1414
1389
|
|
|
1415
1390
|
def remove_complex_control(self, control: ComplexControlModule) -> None:
|
|
@@ -1428,12 +1403,13 @@ class ScenarioSimulator():
|
|
|
1428
1403
|
"'epyt_flow.simulation.scada.ComplexControlModule' not of " +
|
|
1429
1404
|
f"'{type(control)}'")
|
|
1430
1405
|
|
|
1431
|
-
|
|
1406
|
+
all_rules_id = self.epanet_api.get_all_rules_id()
|
|
1407
|
+
if control.rule_id not in all_rules_id:
|
|
1432
1408
|
raise ValueError("Invalid/Unknown control module. " +
|
|
1433
1409
|
f"Can not find rule ID '{control.rule_id}'")
|
|
1434
1410
|
|
|
1435
|
-
rule_idx =
|
|
1436
|
-
self.epanet_api.
|
|
1411
|
+
rule_idx = all_rules_id.index(control.rule_id) + 1
|
|
1412
|
+
self.epanet_api.deleterule(rule_idx)
|
|
1437
1413
|
self._complex_controls.remove(control)
|
|
1438
1414
|
|
|
1439
1415
|
def add_leakage(self, leakage_event: Leakage) -> None:
|
|
@@ -1643,7 +1619,7 @@ class ScenarioSimulator():
|
|
|
1643
1619
|
The default is False.
|
|
1644
1620
|
"""
|
|
1645
1621
|
if junctions_only is True:
|
|
1646
|
-
self.set_pressure_sensors(self.epanet_api.
|
|
1622
|
+
self.set_pressure_sensors(self.epanet_api.get_all_junctions_id())
|
|
1647
1623
|
else:
|
|
1648
1624
|
self.set_pressure_sensors(self._sensor_config.nodes)
|
|
1649
1625
|
|
|
@@ -2112,42 +2088,43 @@ class ScenarioSimulator():
|
|
|
2112
2088
|
self._prepare_simulation(reapply_uncertainties)
|
|
2113
2089
|
|
|
2114
2090
|
# Load pre-computed hydraulics
|
|
2115
|
-
self.epanet_api.
|
|
2091
|
+
self.epanet_api.MSXusehydfile(hyd_file_in)
|
|
2116
2092
|
|
|
2117
2093
|
# Initialize simulation
|
|
2118
|
-
n_nodes = self.epanet_api.
|
|
2119
|
-
n_links = self.epanet_api.
|
|
2094
|
+
n_nodes = self.epanet_api.get_num_nodes()
|
|
2095
|
+
n_links = self.epanet_api.get_num_links()
|
|
2120
2096
|
|
|
2121
|
-
reporting_time_start = self.epanet_api.
|
|
2122
|
-
reporting_time_step = self.epanet_api.
|
|
2123
|
-
hyd_time_step = self.epanet_api.
|
|
2097
|
+
reporting_time_start = self.epanet_api.get_reporting_start_time()
|
|
2098
|
+
reporting_time_step = self.epanet_api.get_reporting_time_step()
|
|
2099
|
+
hyd_time_step = self.epanet_api.get_hydraulic_time_step()
|
|
2124
2100
|
|
|
2125
2101
|
network_topo = self.get_topology()
|
|
2126
2102
|
|
|
2127
2103
|
if use_quality_time_step_as_reporting_time_step is True:
|
|
2128
|
-
quality_time_step = self.epanet_api.
|
|
2104
|
+
quality_time_step = self.epanet_api.get_msx_time_step()
|
|
2129
2105
|
reporting_time_step = quality_time_step
|
|
2130
2106
|
hyd_time_step = quality_time_step
|
|
2131
2107
|
|
|
2132
|
-
self.epanet_api.
|
|
2108
|
+
self.epanet_api.MSXinit(EpanetConstants.EN_NOSAVE)
|
|
2133
2109
|
|
|
2134
2110
|
self.__running_simulation = True
|
|
2135
2111
|
|
|
2136
|
-
bulk_species_idx = self.epanet_api.
|
|
2137
|
-
|
|
2138
|
-
|
|
2112
|
+
bulk_species_idx = [self.epanet_api.get_msx_species_idx(species_id)
|
|
2113
|
+
for species_id in self._sensor_config.bulk_species]
|
|
2114
|
+
surface_species_idx = [self.epanet_api.get_msx_species_idx(species_id)
|
|
2115
|
+
for species_id in self._sensor_config.surface_species]
|
|
2139
2116
|
|
|
2140
2117
|
if verbose is True:
|
|
2141
2118
|
print("Running EPANET-MSX ...")
|
|
2142
|
-
n_iterations = math.ceil(self.epanet_api.
|
|
2119
|
+
n_iterations = math.ceil(self.epanet_api.get_simulation_duration() /
|
|
2143
2120
|
hyd_time_step)
|
|
2144
2121
|
progress_bar = iter(tqdm(range(n_iterations + 1), ascii=True, desc="Time steps"))
|
|
2145
2122
|
|
|
2146
2123
|
def __get_concentrations(init_qual=False):
|
|
2147
2124
|
if init_qual is True:
|
|
2148
|
-
msx_get_cur_value = self.epanet_api.
|
|
2125
|
+
msx_get_cur_value = self.epanet_api.MSXgetinitqual
|
|
2149
2126
|
else:
|
|
2150
|
-
msx_get_cur_value = self.epanet_api.
|
|
2127
|
+
msx_get_cur_value = self.epanet_api.get_msx_species_concentration
|
|
2151
2128
|
|
|
2152
2129
|
# Bulk species
|
|
2153
2130
|
bulk_species_node_concentrations = []
|
|
@@ -2155,13 +2132,13 @@ class ScenarioSimulator():
|
|
|
2155
2132
|
for species_idx in bulk_species_idx:
|
|
2156
2133
|
cur_species_concentrations = []
|
|
2157
2134
|
for node_idx in range(1, n_nodes + 1):
|
|
2158
|
-
concen = msx_get_cur_value(
|
|
2135
|
+
concen = msx_get_cur_value(EpanetConstants.MSX_NODE, node_idx, species_idx)
|
|
2159
2136
|
cur_species_concentrations.append(concen)
|
|
2160
2137
|
bulk_species_node_concentrations.append(cur_species_concentrations)
|
|
2161
2138
|
|
|
2162
2139
|
cur_species_concentrations = []
|
|
2163
2140
|
for link_idx in range(1, n_links + 1):
|
|
2164
|
-
concen = msx_get_cur_value(
|
|
2141
|
+
concen = msx_get_cur_value(EpanetConstants.MSX_LINK, link_idx, species_idx)
|
|
2165
2142
|
cur_species_concentrations.append(concen)
|
|
2166
2143
|
bulk_species_link_concentrations.append(cur_species_concentrations)
|
|
2167
2144
|
|
|
@@ -2183,7 +2160,7 @@ class ScenarioSimulator():
|
|
|
2183
2160
|
cur_species_concentrations = []
|
|
2184
2161
|
|
|
2185
2162
|
for link_idx in range(1, n_links + 1):
|
|
2186
|
-
concen = msx_get_cur_value(
|
|
2163
|
+
concen = msx_get_cur_value(EpanetConstants.MSX_LINK, link_idx, species_idx)
|
|
2187
2164
|
cur_species_concentrations.append(concen)
|
|
2188
2165
|
|
|
2189
2166
|
surface_species_concentrations.append(cur_species_concentrations)
|
|
@@ -2208,7 +2185,7 @@ class ScenarioSimulator():
|
|
|
2208
2185
|
pass
|
|
2209
2186
|
|
|
2210
2187
|
if reporting_time_start == 0:
|
|
2211
|
-
msx_error_code = self.epanet_api.
|
|
2188
|
+
msx_error_code = self.epanet_api.get_last_error_code()
|
|
2212
2189
|
|
|
2213
2190
|
if return_as_dict is True:
|
|
2214
2191
|
data = {"bulk_species_node_concentration_raw": bulk_species_node_concentrations,
|
|
@@ -2241,8 +2218,8 @@ class ScenarioSimulator():
|
|
|
2241
2218
|
last_msx_error_code = 0
|
|
2242
2219
|
while tleft > 0:
|
|
2243
2220
|
# Compute current time step
|
|
2244
|
-
total_time, tleft = self.epanet_api.
|
|
2245
|
-
msx_error_code = self.epanet_api.
|
|
2221
|
+
total_time, tleft = self.epanet_api.MSXstep()
|
|
2222
|
+
msx_error_code = self.epanet_api.get_last_error_code()
|
|
2246
2223
|
if last_msx_error_code == 0:
|
|
2247
2224
|
last_msx_error_code = msx_error_code
|
|
2248
2225
|
|
|
@@ -2404,26 +2381,26 @@ class ScenarioSimulator():
|
|
|
2404
2381
|
if self.__running_simulation is True:
|
|
2405
2382
|
raise RuntimeError("A simulation is already running.")
|
|
2406
2383
|
|
|
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.
|
|
2384
|
+
requested_total_time = self.epanet_api.get_simulation_duration()
|
|
2385
|
+
requested_time_step = self.epanet_api.get_hydraulic_time_step()
|
|
2386
|
+
reporting_time_start = self.epanet_api.get_reporting_start_time()
|
|
2387
|
+
reporting_time_step = self.epanet_api.get_reporting_time_step()
|
|
2411
2388
|
|
|
2412
2389
|
if use_quality_time_step_as_reporting_time_step is True:
|
|
2413
|
-
quality_time_step = self.epanet_api.
|
|
2390
|
+
quality_time_step = self.epanet_api.get_quality_time_step()
|
|
2414
2391
|
requested_time_step = quality_time_step
|
|
2415
2392
|
reporting_time_step = quality_time_step
|
|
2416
2393
|
|
|
2417
2394
|
network_topo = self.get_topology()
|
|
2418
2395
|
|
|
2419
|
-
self.epanet_api.
|
|
2396
|
+
self.epanet_api.usehydfile(hyd_file_in)
|
|
2420
2397
|
|
|
2421
|
-
self.epanet_api.
|
|
2422
|
-
self.epanet_api.
|
|
2398
|
+
self.epanet_api.openQ()
|
|
2399
|
+
self.epanet_api.initQ(EpanetConstants.EN_NOSAVE)
|
|
2423
2400
|
|
|
2424
2401
|
if verbose is True:
|
|
2425
2402
|
print("Running basic quality analysis using EPANET ...")
|
|
2426
|
-
n_iterations = math.ceil(self.epanet_api.
|
|
2403
|
+
n_iterations = math.ceil(self.epanet_api.get_simulation_duration() /
|
|
2427
2404
|
requested_time_step)
|
|
2428
2405
|
progress_bar = iter(tqdm(range(n_iterations + 1), ascii=True, desc="Time steps"))
|
|
2429
2406
|
|
|
@@ -2445,15 +2422,15 @@ class ScenarioSimulator():
|
|
|
2445
2422
|
pass
|
|
2446
2423
|
|
|
2447
2424
|
# Compute current time step
|
|
2448
|
-
t = self.epanet_api.
|
|
2425
|
+
t = self.epanet_api.runQ()
|
|
2449
2426
|
total_time = t
|
|
2450
2427
|
|
|
2451
2428
|
# Fetch data
|
|
2452
2429
|
error_code = self.epanet_api.get_last_error_code()
|
|
2453
2430
|
if last_error_code == 0:
|
|
2454
2431
|
last_error_code = error_code
|
|
2455
|
-
quality_node_data = self.epanet_api.
|
|
2456
|
-
quality_link_data = self.epanet_api.
|
|
2432
|
+
quality_node_data = np.array(self.epanet_api.getnodevalues(EpanetConstants.EN_QUALITY)).reshape(1, -1)
|
|
2433
|
+
quality_link_data = np.array(self.epanet_api.getlinkvalues(EpanetConstants.EN_QUALITY)).reshape(1, -1)
|
|
2457
2434
|
|
|
2458
2435
|
# Yield results in a regular time interval only!
|
|
2459
2436
|
if total_time % reporting_time_step == 0 and total_time >= reporting_time_start:
|
|
@@ -2481,12 +2458,12 @@ class ScenarioSimulator():
|
|
|
2481
2458
|
yield (data, total_time >= requested_total_time)
|
|
2482
2459
|
|
|
2483
2460
|
# Next
|
|
2484
|
-
tstep = self.epanet_api.
|
|
2461
|
+
tstep = self.epanet_api.stepQ()
|
|
2485
2462
|
error_code = self.epanet_api.get_last_error_code()
|
|
2486
2463
|
if last_error_code == 0:
|
|
2487
2464
|
last_error_code = error_code
|
|
2488
2465
|
|
|
2489
|
-
self.epanet_api.
|
|
2466
|
+
self.epanet_api.closeQ()
|
|
2490
2467
|
|
|
2491
2468
|
def run_hydraulic_simulation(self, hyd_export: str = None, verbose: bool = False,
|
|
2492
2469
|
frozen_sensor_config: bool = False,
|
|
@@ -2541,13 +2518,18 @@ class ScenarioSimulator():
|
|
|
2541
2518
|
if result is None:
|
|
2542
2519
|
result = {}
|
|
2543
2520
|
for data_type, data in scada_data.items():
|
|
2544
|
-
|
|
2521
|
+
if data is None:
|
|
2522
|
+
result[data_type] = None
|
|
2523
|
+
else:
|
|
2524
|
+
result[data_type] = [data]
|
|
2545
2525
|
else:
|
|
2546
2526
|
for data_type, data in scada_data.items():
|
|
2547
|
-
result[data_type]
|
|
2527
|
+
if result[data_type] is not None:
|
|
2528
|
+
result[data_type].append(data)
|
|
2548
2529
|
|
|
2549
2530
|
for data_type in result:
|
|
2550
|
-
result[data_type]
|
|
2531
|
+
if result[data_type] is not None:
|
|
2532
|
+
result[data_type] = np.concatenate(result[data_type], axis=0)
|
|
2551
2533
|
|
|
2552
2534
|
result = ScadaData(**result,
|
|
2553
2535
|
network_topo=self.get_topology(),
|
|
@@ -2619,21 +2601,21 @@ class ScenarioSimulator():
|
|
|
2619
2601
|
|
|
2620
2602
|
self.__running_simulation = True
|
|
2621
2603
|
|
|
2622
|
-
self.epanet_api.
|
|
2623
|
-
self.epanet_api.
|
|
2624
|
-
self.epanet_api.
|
|
2625
|
-
self.epanet_api.
|
|
2604
|
+
self.epanet_api.openH()
|
|
2605
|
+
self.epanet_api.openQ()
|
|
2606
|
+
self.epanet_api.initH(EpanetConstants.EN_SAVE)
|
|
2607
|
+
self.epanet_api.initQ(EpanetConstants.EN_SAVE)
|
|
2626
2608
|
|
|
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.
|
|
2609
|
+
requested_total_time = self.epanet_api.get_simulation_duration()
|
|
2610
|
+
requested_time_step = self.epanet_api.get_hydraulic_time_step()
|
|
2611
|
+
reporting_time_start = self.epanet_api.get_reporting_start_time()
|
|
2612
|
+
reporting_time_step = self.epanet_api.get_reporting_time_step()
|
|
2631
2613
|
|
|
2632
2614
|
network_topo = self.get_topology()
|
|
2633
2615
|
|
|
2634
2616
|
if verbose is True:
|
|
2635
2617
|
print("Running EPANET ...")
|
|
2636
|
-
n_iterations = math.ceil(self.epanet_api.
|
|
2618
|
+
n_iterations = math.ceil(self.epanet_api.get_simulation_duration() /
|
|
2637
2619
|
requested_time_step)
|
|
2638
2620
|
progress_bar = iter(tqdm(range(n_iterations + 1), ascii=True, desc="Time steps"))
|
|
2639
2621
|
|
|
@@ -2661,9 +2643,9 @@ class ScenarioSimulator():
|
|
|
2661
2643
|
event.step(total_time + tstep)
|
|
2662
2644
|
|
|
2663
2645
|
# Compute current time step
|
|
2664
|
-
t = self.epanet_api.
|
|
2646
|
+
t = self.epanet_api.runH()
|
|
2665
2647
|
error_code = self.epanet_api.get_last_error_code()
|
|
2666
|
-
self.epanet_api.
|
|
2648
|
+
self.epanet_api.runQ()
|
|
2667
2649
|
if error_code == 0:
|
|
2668
2650
|
error_code = self.epanet_api.get_last_error_code()
|
|
2669
2651
|
total_time = t
|
|
@@ -2671,20 +2653,32 @@ class ScenarioSimulator():
|
|
|
2671
2653
|
last_error_code = error_code
|
|
2672
2654
|
|
|
2673
2655
|
# 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
|
-
|
|
2656
|
+
pressure_data = np.array(self.epanet_api.getnodevalues(EpanetConstants.EN_PRESSURE)).reshape(1, -1)
|
|
2657
|
+
flow_data = np.array(self.epanet_api.getlinkvalues(EpanetConstants.EN_FLOW)).reshape(1, -1)
|
|
2658
|
+
demand_data = np.array(self.epanet_api.getnodevalues(EpanetConstants.EN_DEMAND)).reshape(1, -1)
|
|
2659
|
+
quality_node_data = np.array(self.epanet_api.getnodevalues(EpanetConstants.EN_QUALITY)).reshape(1, -1)
|
|
2660
|
+
quality_link_data = np.array(self.epanet_api.getlinkvalues(EpanetConstants.EN_QUALITY)).reshape(1, -1)
|
|
2661
|
+
|
|
2662
|
+
tanks_volume_data = None
|
|
2663
|
+
if len(self.epanet_api.get_all_tanks_idx()) > 0:
|
|
2664
|
+
tanks_volume_data = np.array([self.epanet_api.get_tank_volume(tank_idx)
|
|
2665
|
+
for tank_idx in self.epanet_api.get_all_tanks_idx()]).reshape(1, -1)
|
|
2666
|
+
|
|
2667
|
+
pumps_state_data = None
|
|
2668
|
+
pumps_energy_usage_data = None
|
|
2669
|
+
pumps_efficiency_data = None
|
|
2670
|
+
if len(self.epanet_api.get_all_pumps_idx()) > 0:
|
|
2671
|
+
pumps_state_data = np.array([self.epanet_api.getlinkvalue(link_idx, EpanetConstants.EN_PUMP_STATE)
|
|
2672
|
+
for link_idx in self.epanet_api.get_all_pumps_idx()]).reshape(1, -1)
|
|
2673
|
+
pumps_energy_usage_data = np.array([self.epanet_api.get_pump_energy_usage(pump_idx)
|
|
2674
|
+
for pump_idx in self.epanet_api.get_all_pumps_idx()]).reshape(1, -1)
|
|
2675
|
+
pumps_efficiency_data = np.array([self.epanet_api.get_pump_efficiency(pump_idx)
|
|
2676
|
+
for pump_idx in self.epanet_api.get_all_pumps_idx()]).reshape(1, -1)
|
|
2677
|
+
|
|
2678
|
+
valves_state_data = None
|
|
2679
|
+
if len(self.epanet_api.get_all_valves_idx()) > 0:
|
|
2680
|
+
valves_state_data = np.array([self.epanet_api.getlinkvalue(link_valve_idx, EpanetConstants.EN_STATUS)
|
|
2681
|
+
for link_valve_idx in self.epanet_api.get_all_valves_idx()]).reshape(1, -1)
|
|
2688
2682
|
|
|
2689
2683
|
scada_data = ScadaData(network_topo=network_topo,
|
|
2690
2684
|
sensor_config=self._sensor_config,
|
|
@@ -2736,23 +2730,23 @@ class ScenarioSimulator():
|
|
|
2736
2730
|
control.step(scada_data)
|
|
2737
2731
|
|
|
2738
2732
|
# Next
|
|
2739
|
-
tstep = self.epanet_api.
|
|
2733
|
+
tstep = self.epanet_api.nextH()
|
|
2740
2734
|
error_code = self.epanet_api.get_last_error_code()
|
|
2741
2735
|
if last_error_code == 0:
|
|
2742
2736
|
last_error_code = error_code
|
|
2743
2737
|
|
|
2744
|
-
self.epanet_api.
|
|
2738
|
+
self.epanet_api.nextQ()
|
|
2745
2739
|
error_code = self.epanet_api.get_last_error_code()
|
|
2746
2740
|
if last_error_code == 0:
|
|
2747
2741
|
last_error_code = error_code
|
|
2748
2742
|
|
|
2749
|
-
self.epanet_api.
|
|
2750
|
-
self.epanet_api.
|
|
2743
|
+
self.epanet_api.closeQ()
|
|
2744
|
+
self.epanet_api.closeH()
|
|
2751
2745
|
|
|
2752
2746
|
self.__running_simulation = False
|
|
2753
2747
|
|
|
2754
2748
|
if hyd_export is not None:
|
|
2755
|
-
self.epanet_api.
|
|
2749
|
+
self.epanet_api.savehydfile(hyd_export)
|
|
2756
2750
|
except Exception as ex:
|
|
2757
2751
|
self.__running_simulation = False
|
|
2758
2752
|
raise ex
|
|
@@ -2929,7 +2923,7 @@ class ScenarioSimulator():
|
|
|
2929
2923
|
Specifies the flow units -- i.e. all flows will be reported in these units.
|
|
2930
2924
|
If None, the units from the .inp file will be used.
|
|
2931
2925
|
|
|
2932
|
-
Must be one of the following EPANET
|
|
2926
|
+
Must be one of the following EPANET constants:
|
|
2933
2927
|
|
|
2934
2928
|
- EN_CFS = 0 (cubic foot/sec)
|
|
2935
2929
|
- EN_GPM = 1 (gal/min)
|
|
@@ -2956,39 +2950,39 @@ class ScenarioSimulator():
|
|
|
2956
2950
|
self._adapt_to_network_changes()
|
|
2957
2951
|
|
|
2958
2952
|
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.
|
|
2953
|
+
if flow_units_id == EpanetConstants.EN_CFS:
|
|
2954
|
+
self.epanet_api.setflowunits(flow_units_id)
|
|
2955
|
+
elif flow_units_id == EpanetConstants.EN_GPM:
|
|
2956
|
+
self.epanet_api.setflowunits(flow_units_id)
|
|
2957
|
+
elif flow_units_id == EpanetConstants.EN_MGD:
|
|
2958
|
+
self.epanet_api.setflowunits(flow_units_id)
|
|
2959
|
+
elif flow_units_id == EpanetConstants.EN_IMGD:
|
|
2960
|
+
self.epanet_api.setflowunits(flow_units_id)
|
|
2961
|
+
elif flow_units_id == EpanetConstants.EN_AFD:
|
|
2962
|
+
self.epanet_api.setflowunits(flow_units_id)
|
|
2963
|
+
elif flow_units_id == EpanetConstants.EN_LPS:
|
|
2964
|
+
self.epanet_api.setflowunits(flow_units_id)
|
|
2965
|
+
elif flow_units_id == EpanetConstants.EN_LPM:
|
|
2966
|
+
self.epanet_api.setflowunits(flow_units_id)
|
|
2967
|
+
elif flow_units_id == EpanetConstants.EN_MLD:
|
|
2968
|
+
self.epanet_api.setflowunits(flow_units_id)
|
|
2969
|
+
elif flow_units_id == EpanetConstants.EN_CMH:
|
|
2970
|
+
self.epanet_api.setflowunits(flow_units_id)
|
|
2971
|
+
elif flow_units_id == EpanetConstants.EN_CMD:
|
|
2972
|
+
self.epanet_api.setflowunits(flow_units_id)
|
|
2979
2973
|
else:
|
|
2980
2974
|
raise ValueError(f"Unknown flow units '{flow_units_id}'")
|
|
2981
2975
|
|
|
2982
2976
|
if demand_model is not None:
|
|
2983
|
-
self.epanet_api.
|
|
2984
|
-
|
|
2985
|
-
|
|
2977
|
+
self.epanet_api.set_demand_model(demand_model["type"], demand_model["pressure_min"],
|
|
2978
|
+
demand_model["pressure_required"],
|
|
2979
|
+
demand_model["pressure_exponent"])
|
|
2986
2980
|
|
|
2987
2981
|
if simulation_duration is not None:
|
|
2988
2982
|
if not isinstance(simulation_duration, int) or simulation_duration <= 0:
|
|
2989
2983
|
raise ValueError("'simulation_duration' must be a positive integer specifying " +
|
|
2990
2984
|
"the number of seconds to simulate")
|
|
2991
|
-
self.epanet_api.
|
|
2985
|
+
self.epanet_api.set_simulation_duration(simulation_duration)
|
|
2992
2986
|
|
|
2993
2987
|
if hydraulic_time_step is not None:
|
|
2994
2988
|
if not isinstance(hydraulic_time_step, int) or hydraulic_time_step <= 0:
|
|
@@ -2997,52 +2991,47 @@ class ScenarioSimulator():
|
|
|
2997
2991
|
if len(self._system_events) != 0:
|
|
2998
2992
|
raise RuntimeError("Hydraulic time step cannot be changed after system events " +
|
|
2999
2993
|
"such as leakages have been added to the scenario")
|
|
3000
|
-
self.epanet_api.
|
|
2994
|
+
self.epanet_api.set_hydraulic_time_step(hydraulic_time_step)
|
|
3001
2995
|
if reporting_time_step is None:
|
|
3002
2996
|
warnings.warn("No report time steps specified -- using 'hydraulic_time_step'")
|
|
3003
|
-
self.epanet_api.
|
|
2997
|
+
self.epanet_api.set_reporting_time_step(hydraulic_time_step)
|
|
3004
2998
|
|
|
3005
2999
|
if reporting_time_step is not None:
|
|
3006
|
-
hydraulic_time_step = self.epanet_api.
|
|
3000
|
+
hydraulic_time_step = self.epanet_api.get_hydraulic_time_step()
|
|
3007
3001
|
if not isinstance(reporting_time_step, int) or \
|
|
3008
3002
|
reporting_time_step % hydraulic_time_step != 0:
|
|
3009
3003
|
raise ValueError("'reporting_time_step' must be a positive integer " +
|
|
3010
3004
|
"and a multiple of 'hydraulic_time_step'")
|
|
3011
|
-
self.epanet_api.
|
|
3005
|
+
self.epanet_api.set_reporting_time_step(reporting_time_step)
|
|
3012
3006
|
|
|
3013
3007
|
if reporting_time_start is not None:
|
|
3014
3008
|
if not isinstance(reporting_time_start, int) or reporting_time_start <= 0:
|
|
3015
3009
|
raise ValueError("'reporting_time_start' must be a positive integer specifying " +
|
|
3016
3010
|
"the time at which reporting starts")
|
|
3017
|
-
self.epanet_api.
|
|
3011
|
+
self.epanet_api.set_reporting_start_time(reporting_time_start)
|
|
3018
3012
|
|
|
3019
3013
|
if quality_time_step is not None:
|
|
3020
3014
|
if not isinstance(quality_time_step, int) or quality_time_step <= 0 or \
|
|
3021
|
-
quality_time_step > self.epanet_api.
|
|
3015
|
+
quality_time_step > self.epanet_api.get_hydraulic_time_step():
|
|
3022
3016
|
raise ValueError("'quality_time_step' must be a positive integer that is not " +
|
|
3023
3017
|
"greater than the hydraulic time step")
|
|
3024
|
-
self.epanet_api.
|
|
3018
|
+
self.epanet_api.set_quality_time_step(quality_time_step)
|
|
3025
3019
|
|
|
3026
3020
|
if advanced_quality_time_step is not None:
|
|
3027
3021
|
if not isinstance(advanced_quality_time_step, int) or \
|
|
3028
3022
|
advanced_quality_time_step <= 0 or \
|
|
3029
|
-
advanced_quality_time_step > self.epanet_api.
|
|
3023
|
+
advanced_quality_time_step > self.epanet_api.get_hydraulic_time_step():
|
|
3030
3024
|
raise ValueError("'advanced_quality_time_step' must be a positive integer " +
|
|
3031
3025
|
"that is not greater than the hydraulic time step")
|
|
3032
|
-
self.epanet_api.
|
|
3026
|
+
self.epanet_api.set_msx_time_step(advanced_quality_time_step)
|
|
3033
3027
|
|
|
3034
3028
|
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']}")
|
|
3029
|
+
chem_name = quality_model["chem_name"] if "chem_name" in quality_model else ""
|
|
3030
|
+
chem_units = quality_model["chem_units"] if "chem_units" in quality_model else ""
|
|
3031
|
+
trace_node_id = quality_model["trace_node_id"] \
|
|
3032
|
+
if "trace_node_id" in quality_model else ""
|
|
3033
|
+
self.epanet_api.set_quality_type(quality_model["type"], chem_name, chem_units,
|
|
3034
|
+
trace_node_id)
|
|
3046
3035
|
|
|
3047
3036
|
def get_events_active_time_points(self) -> list[int]:
|
|
3048
3037
|
"""
|
|
@@ -3056,7 +3045,7 @@ class ScenarioSimulator():
|
|
|
3056
3045
|
"""
|
|
3057
3046
|
events_times = []
|
|
3058
3047
|
|
|
3059
|
-
hyd_time_step = self.epanet_api.
|
|
3048
|
+
hyd_time_step = self.epanet_api.get_hydraulic_time_step()
|
|
3060
3049
|
|
|
3061
3050
|
def __process_event(event) -> None:
|
|
3062
3051
|
cur_time = event.start_time
|
|
@@ -3107,18 +3096,18 @@ class ScenarioSimulator():
|
|
|
3107
3096
|
else:
|
|
3108
3097
|
pattern_id = f"energy_price_{pump_id}"
|
|
3109
3098
|
|
|
3110
|
-
pattern_idx = self.epanet_api.
|
|
3099
|
+
pattern_idx = self.epanet_api.getpatternindex(pattern_id)
|
|
3111
3100
|
if pattern_idx != 0:
|
|
3112
3101
|
warnings.warn(f"Overwriting existing pattern '{pattern_id}'")
|
|
3113
3102
|
|
|
3114
|
-
pump_idx = self.epanet_api.
|
|
3115
|
-
pattern_idx = self.epanet_api.
|
|
3103
|
+
pump_idx = self.epanet_api.get_link_idx(pump_id)
|
|
3104
|
+
pattern_idx = self.epanet_api.getlinkvalue(pump_idx, EpanetConstants.EN_PUMP_EPAT)
|
|
3116
3105
|
if pattern_idx != 0:
|
|
3117
3106
|
warnings.warn(f"Overwriting existing energy price pattern of pump '{pump_id}'")
|
|
3118
3107
|
|
|
3119
|
-
self.add_pattern(pattern_id, pattern)
|
|
3120
|
-
pattern_idx = self.epanet_api.
|
|
3121
|
-
self.epanet_api.
|
|
3108
|
+
self.add_pattern(pattern_id, pattern.tolist())
|
|
3109
|
+
pattern_idx = self.epanet_api.getpatternindex(pattern_id)
|
|
3110
|
+
self.epanet_api.setlinkvalue(pump_idx, EpanetConstants.PUMP_EPAT, pattern_idx)
|
|
3122
3111
|
|
|
3123
3112
|
def get_pump_energy_price_pattern(self, pump_id: str) -> np.ndarray:
|
|
3124
3113
|
"""
|
|
@@ -3139,14 +3128,12 @@ class ScenarioSimulator():
|
|
|
3139
3128
|
if pump_id not in self._sensor_config.pumps:
|
|
3140
3129
|
raise ValueError(f"Unknown pump '{pump_id}'")
|
|
3141
3130
|
|
|
3142
|
-
pump_idx = self.epanet_api.
|
|
3143
|
-
pattern_idx = self.epanet_api.
|
|
3131
|
+
pump_idx = self.epanet_api.get_link_idx(pump_id)
|
|
3132
|
+
pattern_idx = self.epanet_api.getlinkvalue(pump_idx, EpanetConstants.EN_PUMP_EPAT)
|
|
3144
3133
|
if pattern_idx == 0:
|
|
3145
3134
|
return None
|
|
3146
3135
|
else:
|
|
3147
|
-
|
|
3148
|
-
return np.array([self.epanet_api.getPatternValue(pattern_idx, t+1)
|
|
3149
|
-
for t in range(pattern_length)])
|
|
3136
|
+
return np.array(self.epanet_api.get_pattern(pattern_idx))
|
|
3150
3137
|
|
|
3151
3138
|
def get_pump_energy_price(self, pump_id: str) -> float:
|
|
3152
3139
|
"""
|
|
@@ -3167,8 +3154,8 @@ class ScenarioSimulator():
|
|
|
3167
3154
|
if pump_id not in self._sensor_config.pumps:
|
|
3168
3155
|
raise ValueError(f"Unknown pump '{pump_id}'")
|
|
3169
3156
|
|
|
3170
|
-
pump_idx = self.epanet_api.
|
|
3171
|
-
return self.epanet_api.
|
|
3157
|
+
pump_idx = self.epanet_api.get_link_idx(pump_id)
|
|
3158
|
+
return self.epanet_api.get_pump_avg_energy_price(pump_idx)
|
|
3172
3159
|
|
|
3173
3160
|
def set_pump_energy_price(self, pump_id, price: float) -> None:
|
|
3174
3161
|
"""
|
|
@@ -3190,11 +3177,8 @@ class ScenarioSimulator():
|
|
|
3190
3177
|
if price <= 0:
|
|
3191
3178
|
raise ValueError("'price' must be positive")
|
|
3192
3179
|
|
|
3193
|
-
pump_idx = self.
|
|
3194
|
-
|
|
3195
|
-
pumps_energy_price[pump_idx - 1] = price
|
|
3196
|
-
|
|
3197
|
-
self.epanet_api.setLinkPumpECost(pumps_energy_price)
|
|
3180
|
+
pump_idx = self.epanet_api.get_link_idx(pump_id)
|
|
3181
|
+
self.epanet_api.setlinkvalue(pump_idx, EpanetConstants.EN_PUMP_ECOST, price)
|
|
3198
3182
|
|
|
3199
3183
|
def set_initial_link_status(self, link_id: str, status: int) -> None:
|
|
3200
3184
|
"""
|
|
@@ -3219,8 +3203,8 @@ class ScenarioSimulator():
|
|
|
3219
3203
|
if status not in [ActuatorConstants.EN_CLOSED, ActuatorConstants.EN_OPEN]:
|
|
3220
3204
|
raise ValueError(f"Invalid link status '{status}'")
|
|
3221
3205
|
|
|
3222
|
-
link_idx = self.epanet_api.
|
|
3223
|
-
self.epanet_api.
|
|
3206
|
+
link_idx = self.epanet_api.get_link_idx(link_id)
|
|
3207
|
+
self.epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_INITSTATUS, status)
|
|
3224
3208
|
|
|
3225
3209
|
def set_initial_pump_speed(self, pump_id: str, speed: float) -> None:
|
|
3226
3210
|
"""
|
|
@@ -3242,8 +3226,8 @@ class ScenarioSimulator():
|
|
|
3242
3226
|
if speed < 0:
|
|
3243
3227
|
raise ValueError("'speed' can not be negative")
|
|
3244
3228
|
|
|
3245
|
-
pump_idx = self.epanet_api.
|
|
3246
|
-
self.epanet_api.
|
|
3229
|
+
pump_idx = self.epanet_api.get_link_idx(pump_id)
|
|
3230
|
+
self.epanet_api.setlinkvalue(pump_idx, EpanetConstants.EN_INITSETTING, speed)
|
|
3247
3231
|
|
|
3248
3232
|
def set_initial_tank_level(self, tank_id, level: int) -> None:
|
|
3249
3233
|
"""
|
|
@@ -3265,14 +3249,14 @@ class ScenarioSimulator():
|
|
|
3265
3249
|
if level < 0:
|
|
3266
3250
|
raise ValueError("'level' can not be negative")
|
|
3267
3251
|
|
|
3268
|
-
tank_idx = self.epanet_api.
|
|
3269
|
-
self.epanet_api.
|
|
3252
|
+
tank_idx = self.epanet_api.get_node_idx(tank_id)
|
|
3253
|
+
self.epanet_api.setnodevalue(tank_idx, EpanetConstants.EN_TANKLEVEL, level)
|
|
3270
3254
|
|
|
3271
3255
|
def __warn_if_quality_set(self):
|
|
3272
|
-
|
|
3273
|
-
if
|
|
3256
|
+
qual_code = self.epanet_api.getqualinfo()[0]
|
|
3257
|
+
if qual_code != EpanetConstants.EN_NONE:
|
|
3274
3258
|
warnings.warn("You are overriding current quality settings " +
|
|
3275
|
-
f"'{
|
|
3259
|
+
f"'{qual_code}'")
|
|
3276
3260
|
|
|
3277
3261
|
def enable_waterage_analysis(self) -> None:
|
|
3278
3262
|
"""
|
|
@@ -3285,7 +3269,7 @@ class ScenarioSimulator():
|
|
|
3285
3269
|
self._adapt_to_network_changes()
|
|
3286
3270
|
|
|
3287
3271
|
self.__warn_if_quality_set()
|
|
3288
|
-
self.set_general_parameters(quality_model={"type":
|
|
3272
|
+
self.set_general_parameters(quality_model={"type": EpanetConstants.EN_AGE})
|
|
3289
3273
|
|
|
3290
3274
|
def enable_chemical_analysis(self, chemical_name: str = "Chlorine",
|
|
3291
3275
|
chemical_units: int = MASS_UNIT_MG) -> None:
|
|
@@ -3316,8 +3300,9 @@ class ScenarioSimulator():
|
|
|
3316
3300
|
self._adapt_to_network_changes()
|
|
3317
3301
|
|
|
3318
3302
|
self.__warn_if_quality_set()
|
|
3319
|
-
self.set_general_parameters(quality_model={"type":
|
|
3320
|
-
"
|
|
3303
|
+
self.set_general_parameters(quality_model={"type": EpanetConstants.EN_CHEM,
|
|
3304
|
+
"chem_name": chemical_name,
|
|
3305
|
+
"chem_units": qualityunit_to_str(chemical_units)})
|
|
3321
3306
|
|
|
3322
3307
|
def add_quality_source(self, node_id: str, source_type: int, pattern: np.ndarray = None,
|
|
3323
3308
|
pattern_id: str = None, source_strength: int = 1.) -> None:
|
|
@@ -3330,7 +3315,7 @@ class ScenarioSimulator():
|
|
|
3330
3315
|
ID of the node at which this external water quality source is placed.
|
|
3331
3316
|
source_type : `int`,
|
|
3332
3317
|
Types of the external water quality source -- must be of the following
|
|
3333
|
-
EPANET
|
|
3318
|
+
EPANET constants:
|
|
3334
3319
|
|
|
3335
3320
|
- EN_CONCEN = 0
|
|
3336
3321
|
- EN_MASS = 1
|
|
@@ -3366,7 +3351,7 @@ class ScenarioSimulator():
|
|
|
3366
3351
|
|
|
3367
3352
|
self._adapt_to_network_changes()
|
|
3368
3353
|
|
|
3369
|
-
if self.epanet_api.
|
|
3354
|
+
if self.epanet_api.getqualinfo()[0] != EpanetConstants.EN_CHEM:
|
|
3370
3355
|
raise RuntimeError("Chemical analysis is not enabled -- " +
|
|
3371
3356
|
"call 'enable_chemical_analysis()' before calling this function.")
|
|
3372
3357
|
if node_id not in self._sensor_config.nodes:
|
|
@@ -3382,19 +3367,20 @@ class ScenarioSimulator():
|
|
|
3382
3367
|
if pattern_id is None:
|
|
3383
3368
|
pattern_id = f"qual_src_pat_{node_id}"
|
|
3384
3369
|
|
|
3385
|
-
node_idx = self.epanet_api.
|
|
3370
|
+
node_idx = self.epanet_api.get_node_idx(node_id)
|
|
3386
3371
|
|
|
3387
3372
|
if pattern is None:
|
|
3388
|
-
pattern_idx = self.epanet_api.
|
|
3373
|
+
pattern_idx = self.epanet_api.getpatternindex(pattern_id)
|
|
3389
3374
|
else:
|
|
3390
|
-
|
|
3375
|
+
self.epanet_api.add_pattern(pattern_id, pattern.tolist())
|
|
3376
|
+
pattern_idx = self.epanet_api.getpatternindex(pattern_id)
|
|
3391
3377
|
if pattern_idx == 0:
|
|
3392
3378
|
raise RuntimeError("Failed to add/get pattern! " +
|
|
3393
3379
|
"Maybe pattern name contains invalid characters or is too long?")
|
|
3394
3380
|
|
|
3395
|
-
self.epanet_api.
|
|
3396
|
-
self.epanet_api.
|
|
3397
|
-
self.epanet_api.
|
|
3381
|
+
self.epanet_api.setnodevalue(node_idx, EpanetConstants.EN_SOURCETYPE, source_type)
|
|
3382
|
+
self.epanet_api.setnodevalue(node_idx, EpanetConstants.EN_SOURCEQUAL, source_strength)
|
|
3383
|
+
self.epanet_api.setnodevalue(node_idx, EpanetConstants.EN_SOURCEPAT, pattern_idx)
|
|
3398
3384
|
|
|
3399
3385
|
def set_initial_node_quality(self, node_id: str, initial_quality: float) -> None:
|
|
3400
3386
|
"""
|
|
@@ -3486,12 +3472,8 @@ class ScenarioSimulator():
|
|
|
3486
3472
|
if node_init_qual < 0:
|
|
3487
3473
|
raise ValueError(f"{node_id}: Initial node quality can not be negative")
|
|
3488
3474
|
|
|
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)
|
|
3475
|
+
for node_idx in self.epanet_api.get_all_nodes_idx():
|
|
3476
|
+
self.epanet_api.set_node_init_quality(node_idx, node_init_qual)
|
|
3495
3477
|
|
|
3496
3478
|
if order_wall is not None:
|
|
3497
3479
|
if not isinstance(order_wall, int):
|
|
@@ -3500,7 +3482,7 @@ class ScenarioSimulator():
|
|
|
3500
3482
|
if order_wall not in [0, 1]:
|
|
3501
3483
|
raise ValueError(f"Invalid value '{order_wall}' for order_wall")
|
|
3502
3484
|
|
|
3503
|
-
self.epanet_api.
|
|
3485
|
+
self.epanet_api.setoption(EpanetConstants.EN_WALLORDER, order_wall)
|
|
3504
3486
|
|
|
3505
3487
|
if order_bulk is not None:
|
|
3506
3488
|
if not isinstance(order_bulk, int):
|
|
@@ -3509,7 +3491,7 @@ class ScenarioSimulator():
|
|
|
3509
3491
|
if order_bulk not in [0, 1]:
|
|
3510
3492
|
raise ValueError(f"Invalid value '{order_bulk}' for order_bulk")
|
|
3511
3493
|
|
|
3512
|
-
self.epanet_api.
|
|
3494
|
+
self.epanet_api.setoption(EpanetConstants.EN_BULKORDER, order_bulk)
|
|
3513
3495
|
|
|
3514
3496
|
if order_tank is not None:
|
|
3515
3497
|
if not isinstance(order_tank, int):
|
|
@@ -3518,29 +3500,25 @@ class ScenarioSimulator():
|
|
|
3518
3500
|
if order_tank not in [0, 1]:
|
|
3519
3501
|
raise ValueError(f"Invalid value '{order_tank}' for order_wall")
|
|
3520
3502
|
|
|
3521
|
-
self.epanet_api.
|
|
3503
|
+
self.epanet_api.setoption(EpanetConstants.EN_TANKORDER, order_tank)
|
|
3522
3504
|
|
|
3523
3505
|
if global_wall_reaction_coefficient is not None:
|
|
3524
3506
|
if not isinstance(global_wall_reaction_coefficient, float):
|
|
3525
3507
|
raise TypeError("'global_wall_reaction_coefficient' must be an instance of " +
|
|
3526
3508
|
f"'float' but not of '{type(global_wall_reaction_coefficient)}'")
|
|
3527
3509
|
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
self.epanet_api.setLinkWallReactionCoeff(wall_reaction_coeff)
|
|
3510
|
+
for link_idx in self.epanet_api.get_all_links_idx():
|
|
3511
|
+
self.epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_KWALL,
|
|
3512
|
+
global_wall_reaction_coefficient)
|
|
3533
3513
|
|
|
3534
3514
|
if global_bulk_reaction_coefficient is not None:
|
|
3535
3515
|
if not isinstance(global_bulk_reaction_coefficient, float):
|
|
3536
3516
|
raise TypeError("'global_bulk_reaction_coefficient' must be an instance of " +
|
|
3537
3517
|
f"'float' but not of '{type(global_bulk_reaction_coefficient)}'")
|
|
3538
3518
|
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
self.epanet_api.setLinkBulkReactionCoeff(bulk_reaction_coeff)
|
|
3519
|
+
for link_idx in self.epanet_api.get_all_links_idx():
|
|
3520
|
+
self.epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_KBULK,
|
|
3521
|
+
global_bulk_reaction_coefficient)
|
|
3544
3522
|
|
|
3545
3523
|
if local_wall_reaction_coefficient is not None:
|
|
3546
3524
|
if not isinstance(local_wall_reaction_coefficient, dict):
|
|
@@ -3556,8 +3534,8 @@ class ScenarioSimulator():
|
|
|
3556
3534
|
raise ValueError(f"Invalid link ID '{link_id}'")
|
|
3557
3535
|
|
|
3558
3536
|
for link_id, link_reaction_coeff in local_wall_reaction_coefficient:
|
|
3559
|
-
link_idx = self.epanet_api.
|
|
3560
|
-
self.epanet_api.
|
|
3537
|
+
link_idx = self.epanet_api.get_link_idx(link_id)
|
|
3538
|
+
self.epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_KWALL, link_reaction_coeff)
|
|
3561
3539
|
|
|
3562
3540
|
if local_bulk_reaction_coefficient is not None:
|
|
3563
3541
|
if not isinstance(local_bulk_reaction_coefficient, dict):
|
|
@@ -3573,8 +3551,8 @@ class ScenarioSimulator():
|
|
|
3573
3551
|
raise ValueError(f"Invalid link ID '{link_id}'")
|
|
3574
3552
|
|
|
3575
3553
|
for link_id, link_reaction_coeff in local_bulk_reaction_coefficient:
|
|
3576
|
-
link_idx = self.epanet_api.
|
|
3577
|
-
self.epanet_api.
|
|
3554
|
+
link_idx = self.epanet_api.get_link_idx(link_id)
|
|
3555
|
+
self.epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_KBULK, link_reaction_coeff)
|
|
3578
3556
|
|
|
3579
3557
|
if local_tank_reaction_coefficient is not None:
|
|
3580
3558
|
if not isinstance(local_tank_reaction_coefficient, dict):
|
|
@@ -3590,8 +3568,9 @@ class ScenarioSimulator():
|
|
|
3590
3568
|
raise ValueError(f"Invalid tank ID '{tank_id}'")
|
|
3591
3569
|
|
|
3592
3570
|
for tank_id, tank_reaction_coeff in local_tank_reaction_coefficient:
|
|
3593
|
-
tank_idx = self.epanet_api.
|
|
3594
|
-
self.epanet_api.
|
|
3571
|
+
tank_idx = self.epanet_api.get_node_idx(tank_id)
|
|
3572
|
+
self.epanet_api.setnodevalue(tank_idx, EpanetConstants.EN_TANK_KBULK,
|
|
3573
|
+
tank_reaction_coeff)
|
|
3595
3574
|
|
|
3596
3575
|
if limiting_potential is not None:
|
|
3597
3576
|
if not isinstance(limiting_potential, float):
|
|
@@ -3600,7 +3579,7 @@ class ScenarioSimulator():
|
|
|
3600
3579
|
if limiting_potential < 0:
|
|
3601
3580
|
raise ValueError("'limiting_potential' can not be negative")
|
|
3602
3581
|
|
|
3603
|
-
self.epanet_api.
|
|
3582
|
+
self.epanet_api.setoption(EpanetConstants.EN_CONCENLIMIT, limiting_potential)
|
|
3604
3583
|
|
|
3605
3584
|
def enable_sourcetracing_analysis(self, trace_node_id: str) -> None:
|
|
3606
3585
|
"""
|
|
@@ -3621,7 +3600,7 @@ class ScenarioSimulator():
|
|
|
3621
3600
|
raise ValueError(f"Invalid node ID '{trace_node_id}'")
|
|
3622
3601
|
|
|
3623
3602
|
self.__warn_if_quality_set()
|
|
3624
|
-
self.set_general_parameters(quality_model={"type":
|
|
3603
|
+
self.set_general_parameters(quality_model={"type": EpanetConstants.EN_TRACE,
|
|
3625
3604
|
"trace_node_id": trace_node_id})
|
|
3626
3605
|
|
|
3627
3606
|
def add_species_injection_source(self, species_id: str, node_id: str, pattern: np.ndarray,
|
|
@@ -3645,7 +3624,7 @@ class ScenarioSimulator():
|
|
|
3645
3624
|
Note that the pattern time step is equivalent to the EPANET pattern time step.
|
|
3646
3625
|
source_type : `int`,
|
|
3647
3626
|
Type of the external (bulk or surface) species injection source -- must be one of
|
|
3648
|
-
the following EPANET
|
|
3627
|
+
the following EPANET constants:
|
|
3649
3628
|
|
|
3650
3629
|
- EN_CONCEN = 0
|
|
3651
3630
|
- EN_MASS = 1
|
|
@@ -3670,25 +3649,18 @@ class ScenarioSimulator():
|
|
|
3670
3649
|
|
|
3671
3650
|
The default is 1.
|
|
3672
3651
|
"""
|
|
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
3652
|
if pattern_id is None:
|
|
3684
3653
|
pattern_id = f"{species_id}_{node_id}"
|
|
3685
|
-
if pattern_id in self.epanet_api.
|
|
3654
|
+
if pattern_id in self.epanet_api.get_all_msx_pattern_id():
|
|
3686
3655
|
raise ValueError("Invalid 'pattern_id' -- " +
|
|
3687
3656
|
f"there already exists a pattern with ID '{pattern_id}'")
|
|
3688
3657
|
|
|
3689
|
-
self.epanet_api.
|
|
3690
|
-
self.epanet_api.
|
|
3691
|
-
|
|
3658
|
+
self.epanet_api.MSXaddpattern(pattern_id)
|
|
3659
|
+
pattern_idx = self.epanet_api.MSXgetindex(EpanetConstants.MSX_PATTERN, pattern_id)
|
|
3660
|
+
self.epanet_api.MSXsetpattern(pattern_idx, pattern.tolist(), len(pattern))
|
|
3661
|
+
self.epanet_api.MSXsetsource(self.epanet_api.get_node_idx(node_id),
|
|
3662
|
+
self.epanet_api.get_msx_species_idx(species_id),
|
|
3663
|
+
source_type, source_strength, pattern_idx)
|
|
3692
3664
|
|
|
3693
3665
|
def set_bulk_species_node_initial_concentrations(self,
|
|
3694
3666
|
inital_conc: dict[str, list[tuple[str, float]]]
|
|
@@ -3723,12 +3695,12 @@ class ScenarioSimulator():
|
|
|
3723
3695
|
raise ValueError("Initial node concentration can not be negative")
|
|
3724
3696
|
|
|
3725
3697
|
for species_id, node_initial_conc in inital_conc.items():
|
|
3726
|
-
species_idx
|
|
3698
|
+
species_idx = self.epanet_api.get_msx_species_idx(species_id)
|
|
3727
3699
|
|
|
3728
3700
|
for node_id, initial_conc in node_initial_conc:
|
|
3729
|
-
node_idx = self.epanet_api.
|
|
3730
|
-
self.epanet_api.
|
|
3731
|
-
|
|
3701
|
+
node_idx = self.epanet_api.get_node_idx(node_id)
|
|
3702
|
+
self.epanet_api.MSXsetinitqual(EpanetConstants.MSX_NODE, node_idx, species_idx,
|
|
3703
|
+
initial_conc)
|
|
3732
3704
|
|
|
3733
3705
|
def set_species_link_initial_concentrations(self,
|
|
3734
3706
|
inital_conc: dict[str, list[tuple[str, float]]]
|
|
@@ -3762,9 +3734,9 @@ class ScenarioSimulator():
|
|
|
3762
3734
|
raise ValueError("Initial link concentration can not be negative")
|
|
3763
3735
|
|
|
3764
3736
|
for species_id, link_initial_conc in inital_conc.items():
|
|
3765
|
-
species_idx
|
|
3737
|
+
species_idx = self.epanet_api.get_msx_species_idx(species_id)
|
|
3766
3738
|
|
|
3767
3739
|
for link_id, initial_conc in link_initial_conc:
|
|
3768
|
-
link_idx = self.epanet_api.
|
|
3769
|
-
self.epanet_api.
|
|
3770
|
-
|
|
3740
|
+
link_idx = self.epanet_api.get_link_idx(link_id)
|
|
3741
|
+
self.epanet_api.MSXsetinitqual(EpanetConstants.MSX_LINK, link_idx, species_idx,
|
|
3742
|
+
initial_conc)
|