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
|
@@ -4,8 +4,7 @@ Module provides a class for implementing model uncertainty.
|
|
|
4
4
|
from typing import Optional
|
|
5
5
|
from copy import deepcopy
|
|
6
6
|
import warnings
|
|
7
|
-
import
|
|
8
|
-
from epyt.epanet import ToolkitConstants
|
|
7
|
+
from epanet_plus import EPyT, EpanetConstants
|
|
9
8
|
import numpy as np
|
|
10
9
|
|
|
11
10
|
from ..serialization import serializable, JsonSerializable, MODEL_UNCERTAINTY_ID
|
|
@@ -627,7 +626,7 @@ class ModelUncertainty(JsonSerializable):
|
|
|
627
626
|
f"local_patterns: {self._local_patterns} " + \
|
|
628
627
|
f"local_msx_patterns: {self._local_msx_patterns} + seed: {self.__seed}"
|
|
629
628
|
|
|
630
|
-
def undo(self, epanet_api:
|
|
629
|
+
def undo(self, epanet_api: EPyT) -> None:
|
|
631
630
|
"""
|
|
632
631
|
Undo all applied uncertainties -- i.e, resets the properties to their original value.
|
|
633
632
|
|
|
@@ -636,278 +635,303 @@ class ModelUncertainty(JsonSerializable):
|
|
|
636
635
|
|
|
637
636
|
Parameters
|
|
638
637
|
----------
|
|
639
|
-
epanet_api : `
|
|
638
|
+
epanet_api : `epanet_plus.EPyT <https://epanet-plus.readthedocs.io/en/stable/api.html#epanet_plus.epanet_toolkit.EPyT>`_
|
|
640
639
|
Interface to EPANET and EPANET-MSX
|
|
641
640
|
"""
|
|
642
641
|
if self.__cache_original is False:
|
|
643
642
|
raise ValueError("Caching was disabled by the user")
|
|
644
643
|
|
|
645
644
|
if self._cache_links_length is not None:
|
|
646
|
-
epanet_api.
|
|
645
|
+
for link_idx, link_len in zip(epanet_api.get_all_links_idx(),
|
|
646
|
+
self._cache_links_length):
|
|
647
|
+
epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_LENGTH, link_len)
|
|
647
648
|
|
|
648
649
|
if self._cache_links_diameter is not None:
|
|
649
|
-
epanet_api.
|
|
650
|
+
for link_idx, link_diam in zip(epanet_api.get_all_links_idx(),
|
|
651
|
+
self._cache_links_diameter):
|
|
652
|
+
epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_DIAMETER, link_diam)
|
|
650
653
|
|
|
651
654
|
if self._cache_links_roughness_coeff is not None:
|
|
652
|
-
epanet_api.
|
|
655
|
+
for link_idx, link_roughness in zip(epanet_api.get_all_links_idx(),
|
|
656
|
+
self._cache_links_roughness_coeff):
|
|
657
|
+
epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_ROUGHNESS, link_roughness)
|
|
653
658
|
|
|
654
659
|
if self._cache_nodes_base_demand is not None:
|
|
655
660
|
for node_idx in self._cache_nodes_base_demand.keys():
|
|
656
661
|
for demand_category, base_demand in self._cache_nodes_base_demand[node_idx].items():
|
|
657
|
-
epanet_api.
|
|
662
|
+
epanet_api.setbasedemand(node_idx, demand_category + 1, base_demand)
|
|
658
663
|
|
|
659
664
|
if self._cache_nodes_demand_pattern is not None:
|
|
660
|
-
for
|
|
661
|
-
|
|
662
|
-
epanet_api.setPatternValue(pattern_id, t+1, v)
|
|
665
|
+
for pattern_idx, demand_pattern in self._cache_nodes_demand_pattern.items():
|
|
666
|
+
epanet_api.set_pattern(pattern_idx, demand_pattern.tolist())
|
|
663
667
|
|
|
664
668
|
if self._cache_nodes_elevation is not None:
|
|
665
|
-
epanet_api.
|
|
669
|
+
for node_idx, node_elev in zip(epanet_api.get_all_nodes_idx(),
|
|
670
|
+
self._cache_nodes_elevation):
|
|
671
|
+
epanet_api.setnodevalue(node_idx, EpanetConstants.EN_ELEVATION, node_elev)
|
|
666
672
|
|
|
667
673
|
if self._cache_patterns is not None:
|
|
668
674
|
for pattern_idx, pattern in self._cache_patterns.items():
|
|
669
|
-
epanet_api.
|
|
675
|
+
epanet_api.set_pattern(pattern_idx, pattern.tolist())
|
|
670
676
|
|
|
671
677
|
if self._cache_msx_constants is not None:
|
|
672
|
-
|
|
678
|
+
for constant_idx, constant_value in enumerate(self._cache_msx_constants):
|
|
679
|
+
epanet_api.MSXsetconstant(constant_idx + 1, constant_value)
|
|
673
680
|
|
|
674
681
|
if self._cache_msx_links_parameters is not None:
|
|
675
682
|
for pipe_idx, parameters_pipes_val in self._cache_msx_links_parameters.items():
|
|
676
|
-
|
|
683
|
+
for param_idx, param_value in enumerate(parameters_pipes_val):
|
|
684
|
+
epanet_api.MSXsetparameter(EpanetConstants.MSX_LINK, pipe_idx,
|
|
685
|
+
param_idx + 1, param_value)
|
|
677
686
|
|
|
678
687
|
if self._cache_msx_tanks_parameters is not None:
|
|
679
688
|
for tank_idx, parameters_tanks_val in self._cache_msx_tanks_parameters.items():
|
|
680
|
-
|
|
689
|
+
for param_idx, param_value in enumerate(parameters_tanks_val):
|
|
690
|
+
epanet_api.MSXsetparameter(EpanetConstants.MSX_NODE, tank_idx,
|
|
691
|
+
param_idx + 1, param_value)
|
|
681
692
|
|
|
682
693
|
if self._cache_msx_patterns is not None:
|
|
683
694
|
for pattern_idx, pattern in self._cache_msx_patterns:
|
|
684
|
-
epanet_api.
|
|
695
|
+
epanet_api.MSXsetpattern(pattern_idx, pattern.tolist(), len(pattern))
|
|
685
696
|
|
|
686
|
-
def apply(self, epanet_api:
|
|
697
|
+
def apply(self, epanet_api: EPyT) -> None:
|
|
687
698
|
"""
|
|
688
699
|
Applies the specified model uncertainties to the scenario.
|
|
689
700
|
|
|
690
701
|
Parameters
|
|
691
702
|
----------
|
|
692
|
-
epanet_api : `
|
|
703
|
+
epanet_api : `epanet_plus.EPyT <https://epanet-plus.readthedocs.io/en/stable/api.html#epanet_plus.epanet_toolkit.EPyT>`_
|
|
693
704
|
Interface to EPANET and EPANET-MSX.
|
|
694
705
|
"""
|
|
695
706
|
np_rand_gen = np.random.default_rng(seed=self.__seed)
|
|
696
707
|
|
|
708
|
+
all_links_idx = epanet_api.get_all_links_idx()
|
|
709
|
+
all_nodes_idx = epanet_api.get_all_nodes_idx()
|
|
710
|
+
|
|
697
711
|
if self._global_pipe_length is not None:
|
|
698
712
|
self._global_pipe_length.set_random_generator(np_rand_gen)
|
|
699
713
|
|
|
700
|
-
link_length = epanet_api.
|
|
714
|
+
link_length = np.array([epanet_api.getlinkvalue(link_idx, EpanetConstants.EN_LENGTH)
|
|
715
|
+
for link_idx in all_links_idx])
|
|
701
716
|
self._cache_links_length = np.copy(link_length)
|
|
702
717
|
|
|
703
718
|
link_length = self._global_pipe_length.apply_batch(link_length)
|
|
704
|
-
|
|
719
|
+
for link_idx, link_value in zip(all_links_idx, link_length):
|
|
720
|
+
epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_LENGTH, link_value)
|
|
705
721
|
|
|
706
722
|
if self._local_pipe_length is not None:
|
|
707
723
|
self._local_pipe_length.set_random_generator(np_rand_gen)
|
|
708
|
-
self._cache_links_length = epanet_api.
|
|
724
|
+
self._cache_links_length = np.array([epanet_api.getlinkvalue(link_idx, EpanetConstants.EN_LENGTH)
|
|
725
|
+
for link_idx in all_links_idx])
|
|
709
726
|
|
|
710
727
|
for pipe_id, uncertainty in self._local_pipe_length.items():
|
|
711
|
-
link_idx = epanet_api.
|
|
712
|
-
link_length = epanet_api.
|
|
728
|
+
link_idx = epanet_api.get_link_idx(pipe_id)
|
|
729
|
+
link_length = epanet_api.getlinkvalue(link_idx, EpanetConstants.EN_LENGTH)
|
|
713
730
|
|
|
714
731
|
link_length = uncertainty.apply(link_length)
|
|
715
|
-
epanet_api.
|
|
732
|
+
epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_LENGTH, link_length)
|
|
716
733
|
|
|
717
734
|
if self._global_pipe_diameter is not None:
|
|
718
735
|
self._global_pipe_diameter.set_random_generator(np_rand_gen)
|
|
719
736
|
|
|
720
|
-
link_diameters = epanet_api.
|
|
737
|
+
link_diameters = np.array([epanet_api.getlinkvalue(link_idx, EpanetConstants.EN_DIAMETER)
|
|
738
|
+
for link_idx in all_links_idx])
|
|
721
739
|
self._cache_links_diameter = np.copy(link_diameters)
|
|
722
740
|
|
|
723
741
|
link_diameters = self._global_pipe_diameter.apply_batch(link_diameters)
|
|
724
|
-
|
|
742
|
+
for link_idx, link_value in zip(all_links_idx, link_diameters):
|
|
743
|
+
epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_DIAMETER, link_value)
|
|
725
744
|
|
|
726
745
|
if self._local_pipe_diameter is not None:
|
|
727
746
|
self._local_pipe_diameter.set_random_generator(np_rand_gen)
|
|
728
|
-
self._cache_links_diameter = epanet_api.
|
|
747
|
+
self._cache_links_diameter = np.array([epanet_api.getlinkvalue(link_idx,
|
|
748
|
+
EpanetConstants.EN_DIAMETER)
|
|
749
|
+
for link_idx in all_links_idx])
|
|
729
750
|
|
|
730
751
|
for pipe_id, uncertainty in self._local_pipe_diameter.items():
|
|
731
|
-
link_idx = epanet_api.
|
|
732
|
-
link_diameter = epanet_api.
|
|
752
|
+
link_idx = epanet_api.get_link_idx(pipe_id)
|
|
753
|
+
link_diameter = epanet_api.getlinkvalue(link_idx, EpanetConstants.EN_DIAMETER)
|
|
733
754
|
|
|
734
755
|
link_diameter = uncertainty.apply(link_diameter)
|
|
735
|
-
epanet_api.
|
|
756
|
+
epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_DIAMETER, link_diameter)
|
|
736
757
|
|
|
737
758
|
if self._global_pipe_roughness is not None:
|
|
738
759
|
self._global_pipe_roughness.set_random_generator(np_rand_gen)
|
|
739
760
|
|
|
740
|
-
coeffs = epanet_api.
|
|
761
|
+
coeffs = np.array([epanet_api.getlinkvalue(link_idx, EpanetConstants.EN_ROUGHNESS)
|
|
762
|
+
for link_idx in all_links_idx])
|
|
741
763
|
self._cache_links_roughness_coeff = np.copy(coeffs)
|
|
742
764
|
|
|
743
765
|
coeffs = self._global_pipe_roughness.apply_batch(coeffs)
|
|
744
|
-
|
|
766
|
+
for link_idx, link_value in zip(all_links_idx, coeffs):
|
|
767
|
+
epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_ROUGHNESS, link_value)
|
|
745
768
|
|
|
746
769
|
if self._local_pipe_roughness is not None:
|
|
747
770
|
self._local_pipe_roughness.set_random_generator(np_rand_gen)
|
|
748
|
-
self._cache_links_roughness_coeff =
|
|
771
|
+
self._cache_links_roughness_coeff = \
|
|
772
|
+
np.array([epanet_api.getlinkvalue(link_idx, EpanetConstants.EN_ROUGHNESS)
|
|
773
|
+
for link_idx in all_links_idx])
|
|
749
774
|
|
|
750
775
|
for pipe_id, uncertainty in self._local_pipe_roughness.items():
|
|
751
|
-
link_idx = epanet_api.
|
|
752
|
-
link_roughness_coeff = epanet_api.
|
|
776
|
+
link_idx = epanet_api.get_link_idx(pipe_id)
|
|
777
|
+
link_roughness_coeff = epanet_api.getlinkvalue(link_idx,
|
|
778
|
+
EpanetConstants.EN_ROUGHNESS)
|
|
753
779
|
|
|
754
780
|
link_roughness_coeff = uncertainty.apply(link_roughness_coeff)
|
|
755
|
-
epanet_api.
|
|
781
|
+
epanet_api.setlinkvalue(link_idx, EpanetConstants.EN_ROUGHNESS,
|
|
782
|
+
link_roughness_coeff)
|
|
756
783
|
|
|
757
784
|
if self._global_base_demand is not None:
|
|
758
785
|
self._global_base_demand.set_random_generator(np_rand_gen)
|
|
759
786
|
|
|
760
787
|
self._cache_nodes_base_demand = {}
|
|
761
|
-
all_nodes_idx = epanet_api.getNodeIndex()
|
|
762
788
|
for node_idx in all_nodes_idx:
|
|
763
789
|
self._cache_nodes_base_demand[node_idx] = {}
|
|
764
|
-
n_demand_categories = epanet_api.
|
|
765
|
-
for
|
|
766
|
-
base_demand = epanet_api.
|
|
767
|
-
self._cache_nodes_base_demand[node_idx][
|
|
790
|
+
n_demand_categories = epanet_api.getnumdemands(node_idx)
|
|
791
|
+
for demand_idx in range(n_demand_categories):
|
|
792
|
+
base_demand = epanet_api.getbasedemand(node_idx, demand_idx + 1)
|
|
793
|
+
self._cache_nodes_base_demand[node_idx][demand_idx] = base_demand
|
|
768
794
|
|
|
769
795
|
base_demand = self._global_base_demand.apply(base_demand)
|
|
770
|
-
epanet_api.
|
|
796
|
+
epanet_api.setbasedemand(node_idx, demand_idx + 1, base_demand)
|
|
771
797
|
|
|
772
798
|
if self._local_base_demand is not None:
|
|
773
799
|
self._local_base_demand.set_random_generator(np_rand_gen)
|
|
774
800
|
|
|
775
801
|
self._cache_nodes_base_demand = {}
|
|
776
802
|
for node_id, uncertainty in self._local_base_demand.items():
|
|
777
|
-
node_idx = epanet_api.
|
|
803
|
+
node_idx = epanet_api.get_node_idx(node_id)
|
|
778
804
|
self._cache_nodes_base_demand[node_idx] = {}
|
|
779
|
-
n_demand_categories = epanet_api.
|
|
780
|
-
for
|
|
781
|
-
base_demand = epanet_api.
|
|
782
|
-
self._cache_nodes_base_demand[node_idx][
|
|
805
|
+
n_demand_categories = epanet_api.getnumdemands(node_idx)
|
|
806
|
+
for demand_idx in range(n_demand_categories):
|
|
807
|
+
base_demand = epanet_api.getbasedemand(node_idx, demand_idx + 1)
|
|
808
|
+
self._cache_nodes_base_demand[node_idx][demand_idx] = base_demand
|
|
783
809
|
|
|
784
810
|
base_demand = uncertainty.apply(base_demand)
|
|
785
|
-
epanet_api.
|
|
811
|
+
epanet_api.setbasedemand(node_idx, demand_idx + 1, base_demand)
|
|
786
812
|
|
|
787
813
|
if self._global_demand_pattern is not None:
|
|
788
814
|
self._global_demand_pattern.set_random_generator(np_rand_gen)
|
|
789
815
|
|
|
790
816
|
self._cache_nodes_demand_pattern = {}
|
|
791
|
-
demand_patterns_idx = epanet_api.
|
|
792
|
-
|
|
793
|
-
for
|
|
794
|
-
for pattern_id in demand_patterns_id:
|
|
795
|
-
if pattern_id == 0:
|
|
796
|
-
continue
|
|
797
|
-
|
|
798
|
-
pattern_length = epanet_api.getPatternLengths(pattern_id)
|
|
799
|
-
demand_pattern = np.zeros(pattern_length)
|
|
800
|
-
|
|
801
|
-
for t in range(pattern_length):
|
|
802
|
-
v = epanet_api.getPatternValue(pattern_id, t+1)
|
|
803
|
-
demand_pattern[t] = v
|
|
817
|
+
demand_patterns_idx = np.array([epanet_api.getdemandpattern(node_idx, demand_idx + 1)
|
|
818
|
+
for node_idx in epanet_api.get_all_nodes_idx()
|
|
819
|
+
for demand_idx in range(epanet_api.getnumdemands(node_idx))])
|
|
804
820
|
|
|
805
|
-
|
|
806
|
-
|
|
821
|
+
for pattern_idx in list(set(demand_patterns_idx)):
|
|
822
|
+
demand_pattern = np.array(epanet_api.get_pattern(pattern_idx))
|
|
823
|
+
self._cache_nodes_demand_pattern[pattern_idx] = np.copy(demand_pattern)
|
|
807
824
|
|
|
808
|
-
|
|
825
|
+
demand_pattern = self._global_demand_pattern.apply_batch(demand_pattern)
|
|
826
|
+
epanet_api.set_pattern(pattern_idx, demand_pattern.tolist())
|
|
809
827
|
|
|
810
828
|
if self._local_demand_pattern is not None:
|
|
811
829
|
self._local_demand_pattern.set_random_generator(np_rand_gen)
|
|
812
830
|
|
|
813
831
|
self._cache_nodes_demand_pattern = {}
|
|
814
|
-
patterns_id = epanet_api.getPatternNameID()
|
|
815
|
-
paterns_idx = epanet_api.getPatternIndex()
|
|
816
832
|
|
|
817
833
|
for pattern_id, uncertainty in self._local_demand_pattern.items():
|
|
818
|
-
pattern_idx =
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
for t in range(pattern_length):
|
|
823
|
-
v = epanet_api.getPatternValue(pattern_idx, t+1)
|
|
824
|
-
demand_pattern[t] = v
|
|
825
|
-
|
|
826
|
-
v_ = uncertainty.apply(v)
|
|
827
|
-
epanet_api.setPatternValue(pattern_idx, t+1, v_)
|
|
834
|
+
pattern_idx = epanet_api.getpatternindex(pattern_id)
|
|
835
|
+
demand_pattern = np.array(epanet_api.get_pattern(pattern_idx))
|
|
836
|
+
self._cache_nodes_demand_pattern[pattern_id] = np.copy(demand_pattern)
|
|
828
837
|
|
|
829
|
-
|
|
838
|
+
demand_pattern = uncertainty.apply_batch(demand_pattern)
|
|
839
|
+
epanet_api.set_pattern(pattern_idx, demand_pattern.tolist())
|
|
830
840
|
|
|
831
841
|
if self._global_elevation is not None:
|
|
832
842
|
self._global_elevation.set_random_generator(np_rand_gen)
|
|
833
843
|
|
|
834
|
-
elevations = epanet_api.
|
|
844
|
+
elevations = np.array([epanet_api.get_node_elevation(node_idx)
|
|
845
|
+
for node_idx in epanet_api.get_all_nodes_idx()])
|
|
835
846
|
self._cache_nodes_elevation = np.copy(elevations)
|
|
836
847
|
|
|
837
848
|
elevations = self._global_elevation.apply_batch(elevations)
|
|
838
|
-
|
|
849
|
+
for node_idx, node_elev in enumerate(elevations):
|
|
850
|
+
epanet_api.setnodevalue(node_idx + 1, EpanetConstants.EN_ELEVATION, node_elev)
|
|
839
851
|
|
|
840
852
|
if self._local_elevation is not None:
|
|
841
853
|
self._local_elevation.set_random_generator(np_rand_gen)
|
|
842
|
-
self._cache_nodes_elevation = epanet_api.
|
|
854
|
+
self._cache_nodes_elevation = np.array([epanet_api.get_node_elevation(node_idx)
|
|
855
|
+
for node_idx in epanet_api.get_all_nodes_idx()])
|
|
843
856
|
|
|
844
857
|
for node_id, uncertainty in self._local_elevation.items():
|
|
845
|
-
node_idx = epanet_api.
|
|
846
|
-
elevation = epanet_api.
|
|
858
|
+
node_idx = epanet_api.get_node_idx(node_id)
|
|
859
|
+
elevation = epanet_api.get_node_elevation(node_idx)
|
|
847
860
|
|
|
848
861
|
elevation = uncertainty.apply(elevation)
|
|
849
|
-
epanet_api.
|
|
862
|
+
epanet_api.setnodevalue(node_idx, EpanetConstants.EN_ELEVATION, elevation)
|
|
850
863
|
|
|
851
864
|
if self._local_patterns is not None:
|
|
852
865
|
self._local_patterns.set_random_generator(np_rand_gen)
|
|
853
866
|
self._cache_patterns = {}
|
|
854
867
|
|
|
855
868
|
for pattern_id, uncertainty in self._local_patterns.items():
|
|
856
|
-
pattern_idx = epanet_api.
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
for t in range(pattern_length)])
|
|
860
|
-
self._cache_patterns[pattern_idx] = pattern
|
|
869
|
+
pattern_idx = epanet_api.getpatternindex(pattern_id)
|
|
870
|
+
pattern = np.array(epanet_api.get_pattern(pattern_idx))
|
|
871
|
+
self._cache_patterns[pattern_idx] = np.copy(pattern)
|
|
861
872
|
|
|
862
873
|
pattern = uncertainty.apply_batch(pattern)
|
|
863
|
-
epanet_api.
|
|
874
|
+
epanet_api.set_pattern(pattern_idx, pattern.tolist())
|
|
864
875
|
|
|
865
|
-
if epanet_api.
|
|
876
|
+
if epanet_api.msx_file is not None:
|
|
866
877
|
if self._global_constants is not None:
|
|
867
878
|
self._global_constants.set_random_generator(np_rand_gen)
|
|
868
879
|
|
|
869
|
-
constants = np.array(epanet_api.
|
|
880
|
+
constants = np.array([epanet_api.MSXgetconstant(const_idx + 1)
|
|
881
|
+
for const_idx in range(epanet_api.MSXgetcount(EpanetConstants.MSX_CONSTANT))])
|
|
870
882
|
self._cache_msx_patterns = np.copy(constants)
|
|
871
883
|
|
|
872
884
|
constants = self._global_constants.apply_batch(constants)
|
|
873
|
-
|
|
885
|
+
for const_idx, const_value in enumerate(constants):
|
|
886
|
+
epanet_api.MSXsetconstant(const_idx + 1, const_value)
|
|
874
887
|
|
|
875
888
|
if self._local_constants:
|
|
876
889
|
self._local_constants.set_random_generator(np_rand_gen)
|
|
877
890
|
|
|
878
|
-
self._cache_msx_patterns = np.array(epanet_api.
|
|
891
|
+
self._cache_msx_patterns = np.array([epanet_api.MSXgetconstant(const_idx + 1)
|
|
892
|
+
for const_idx in range(epanet_api.MSXgetcount(EpanetConstants.MSX_CONSTANT))])
|
|
879
893
|
|
|
880
894
|
for constant_id, uncertainty in self._local_constants.items():
|
|
881
|
-
idx = epanet_api.MSXgetindex(
|
|
882
|
-
constant = epanet_api.
|
|
895
|
+
idx = epanet_api.MSXgetindex(EpanetConstants.MSX_CONSTANT, constant_id)
|
|
896
|
+
constant = epanet_api.MSXgetconstant(idx)
|
|
883
897
|
|
|
884
898
|
constant = uncertainty.apply(constant)
|
|
885
|
-
epanet_api.
|
|
899
|
+
epanet_api.MSXsetconstant(idx, constant)
|
|
886
900
|
|
|
887
901
|
if self._global_parameters is not None:
|
|
888
902
|
self._global_parameters.set_random_generator(np_rand_gen)
|
|
889
903
|
|
|
890
904
|
self._cache_msx_links_parameters = {}
|
|
891
|
-
|
|
892
|
-
|
|
905
|
+
num_params = epanet_api.MSXgetcount(EpanetConstants.MSX_PARAMETER)
|
|
906
|
+
parameters_pipes = [np.array([epanet_api.MSXgetparameter(EpanetConstants.MSX_LINK,
|
|
907
|
+
pipe_idx,
|
|
908
|
+
param_idx + 1)
|
|
909
|
+
for param_idx in range(num_params)])
|
|
910
|
+
for pipe_idx in epanet_api.get_all_pipes_idx()]
|
|
911
|
+
for i, pipe_idx in enumerate(epanet_api.get_all_pipes_idx()):
|
|
893
912
|
if len(parameters_pipes[i]) == 0:
|
|
894
913
|
continue
|
|
895
914
|
|
|
896
|
-
self._cache_msx_links_parameters[pipe_idx] =
|
|
897
|
-
parameters_pipes_val = self._global_parameters.apply_batch(
|
|
898
|
-
|
|
899
|
-
|
|
915
|
+
self._cache_msx_links_parameters[pipe_idx] = parameters_pipes[i]
|
|
916
|
+
parameters_pipes_val = self._global_parameters.apply_batch(parameters_pipes[i])
|
|
917
|
+
for param_idx, param_value in enumerate(parameters_pipes_val):
|
|
918
|
+
epanet_api.MSXsetparameter(EpanetConstants.MSX_LINK, pipe_idx,
|
|
919
|
+
param_idx + 1, param_value)
|
|
900
920
|
|
|
901
921
|
self._cache_msx_tanks_parameters = {}
|
|
902
|
-
|
|
903
|
-
|
|
922
|
+
num_params = epanet_api.MSXgetcount(EpanetConstants.MSX_PARAMETER)
|
|
923
|
+
parameters_tanks = [np.array([epanet_api.MSXgetparameter(EpanetConstants.MSX_NODE,
|
|
924
|
+
tank_idx + 1, param_idx + 1)
|
|
925
|
+
for param_idx in range(num_params)])
|
|
926
|
+
for tank_idx in range(epanet_api.get_num_tanks())]
|
|
927
|
+
for i, tank_idx in enumerate(epanet_api.get_all_tanks_idx()):
|
|
904
928
|
if parameters_tanks[i] is None or len(parameters_tanks[i]) == 0:
|
|
905
929
|
continue
|
|
906
930
|
|
|
907
|
-
self._cache_msx_tanks_parameters[tank_idx] =
|
|
908
|
-
parameters_tanks_val = self._global_parameters.apply_batch(
|
|
909
|
-
|
|
910
|
-
|
|
931
|
+
self._cache_msx_tanks_parameters[tank_idx] = parameters_tanks[i]
|
|
932
|
+
parameters_tanks_val = self._global_parameters.apply_batch(parameters_tanks[i])
|
|
933
|
+
for idx, val in enumerate(parameters_tanks_val):
|
|
934
|
+
epanet_api.MSXsetparameter(EpanetConstants.MSX_NODE, tank_idx, idx + 1, val)
|
|
911
935
|
|
|
912
936
|
if self._local_parameters is not None:
|
|
913
937
|
self._local_parameters.set_random_generator(np_rand_gen)
|
|
@@ -915,36 +939,36 @@ class ModelUncertainty(JsonSerializable):
|
|
|
915
939
|
self._cache_msx_tanks_parameters = {}
|
|
916
940
|
|
|
917
941
|
for (param_id, item_type, item_id), uncertainty in self._local_parameters.items():
|
|
918
|
-
idx, = epanet_api.
|
|
942
|
+
idx, = epanet_api.MSXgetindex(EpanetConstants.MSX_PARAMETER, param_id)
|
|
919
943
|
|
|
920
|
-
if item_type ==
|
|
921
|
-
item_idx = epanet_api.
|
|
922
|
-
elif item_type ==
|
|
923
|
-
item_idx = epanet_api.
|
|
944
|
+
if item_type == EpanetConstants.MSX_NODE:
|
|
945
|
+
item_idx = epanet_api.get_node_idx(item_id)
|
|
946
|
+
elif item_type == EpanetConstants.MSX_LINK:
|
|
947
|
+
item_idx = epanet_api.get_link_idx(item_id)
|
|
924
948
|
else:
|
|
925
949
|
raise ValueError(f"Unknown item type '{item_type}' must be either " +
|
|
926
|
-
"
|
|
950
|
+
"EpanetConstants.MSX_NODE or EpanetConstants.MSX_LINK")
|
|
927
951
|
|
|
928
|
-
parameter = epanet_api.
|
|
929
|
-
if item_type ==
|
|
952
|
+
parameter = epanet_api.MSXgetparameter(item_type, item_idx, idx)
|
|
953
|
+
if item_type == EpanetConstants.MSX_NODE:
|
|
930
954
|
self._cache_msx_tanks_parameters[item_idx] = parameter
|
|
931
|
-
elif item_type ==
|
|
955
|
+
elif item_type == EpanetConstants.MSX_LINK:
|
|
932
956
|
self._cache_msx_links_parameters[item_idx] = parameter
|
|
933
957
|
|
|
934
958
|
parameter = uncertainty.apply(parameter)
|
|
935
|
-
epanet_api.
|
|
959
|
+
epanet_api.MSXsetparameter(item_type, item_idx, idx, parameter)
|
|
936
960
|
|
|
937
961
|
if self._local_msx_patterns is not None:
|
|
938
962
|
self._local_msx_patterns.set_random_generator(np_rand_gen)
|
|
939
963
|
self._cache_msx_patterns = {}
|
|
940
964
|
|
|
941
965
|
for pattern_id, uncertainty in self._local_msx_patterns.items():
|
|
942
|
-
pattern_idx
|
|
943
|
-
pattern = epanet_api.
|
|
966
|
+
pattern_idx = epanet_api.MSXgetindex(EpanetConstants.MSX_PATTERN, pattern_id)
|
|
967
|
+
pattern = np.array(epanet_api.get_msx_pattern(pattern_idx))
|
|
944
968
|
self._cache_msx_patterns[pattern_idx] = np.copy(pattern)
|
|
945
969
|
|
|
946
970
|
pattern = uncertainty.apply_batch(pattern)
|
|
947
|
-
epanet_api.
|
|
971
|
+
epanet_api.MSXsetpattern(pattern_idx, pattern.tolist(), len(pattern))
|
|
948
972
|
else:
|
|
949
973
|
if self._local_msx_patterns is not None or self._local_parameters is not None or \
|
|
950
974
|
self._local_constants is not None or self._global_constants is not None or \
|
epyt_flow/utils.py
CHANGED
|
@@ -6,6 +6,7 @@ import math
|
|
|
6
6
|
import tempfile
|
|
7
7
|
import zipfile
|
|
8
8
|
from pathlib import Path
|
|
9
|
+
import re
|
|
9
10
|
import requests
|
|
10
11
|
from tqdm import tqdm
|
|
11
12
|
import numpy as np
|
|
@@ -344,6 +345,71 @@ def plot_timeseries_prediction(y: np.ndarray, y_pred: np.ndarray,
|
|
|
344
345
|
return ax
|
|
345
346
|
|
|
346
347
|
|
|
348
|
+
def download_from_gdrive_if_necessary(download_path: str, url: str, verbose: bool = True) -> None:
|
|
349
|
+
"""
|
|
350
|
+
Downloads a file from a google drive repository if it does not already exist
|
|
351
|
+
in a given path.
|
|
352
|
+
|
|
353
|
+
Note that if the path (folder) does not already exist, it will be created.
|
|
354
|
+
|
|
355
|
+
Parameters
|
|
356
|
+
----------
|
|
357
|
+
download_path : `str`
|
|
358
|
+
Local path to the file -- if this path does not exist, the file will be downloaded from
|
|
359
|
+
the provided 'url' and stored in 'download_dir'.
|
|
360
|
+
url : `str`
|
|
361
|
+
Web-URL of the google drive repository.
|
|
362
|
+
verbose : `bool`, optional
|
|
363
|
+
If True, a progress bar is shown while downloading the file.
|
|
364
|
+
|
|
365
|
+
The default is True.
|
|
366
|
+
"""
|
|
367
|
+
folder_path = str(Path(download_path).parent.absolute())
|
|
368
|
+
create_path_if_not_exist(folder_path)
|
|
369
|
+
|
|
370
|
+
if not os.path.isfile(download_path):
|
|
371
|
+
session = requests.Session()
|
|
372
|
+
|
|
373
|
+
response = session.get(url)
|
|
374
|
+
html = response.text
|
|
375
|
+
|
|
376
|
+
def extract(pattern):
|
|
377
|
+
match = re.search(pattern, html)
|
|
378
|
+
return match.group(1) if match else None
|
|
379
|
+
|
|
380
|
+
file_id = extract(r'name="id" value="([^"]+)"')
|
|
381
|
+
file_confirm = extract(r'name="confirm" value="([^"]+)"')
|
|
382
|
+
file_uuid = extract(r'name="uuid" value="([^"]+)"')
|
|
383
|
+
|
|
384
|
+
if not all([file_id, file_confirm, file_uuid]):
|
|
385
|
+
raise SystemError("Failed to extract download parameters")
|
|
386
|
+
|
|
387
|
+
download_url = (
|
|
388
|
+
f"https://drive.usercontent.google.com/download"
|
|
389
|
+
f"?id={file_id}&export=download&confirm={file_confirm}&uuid={file_uuid}"
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
response = session.get(download_url, stream=True)
|
|
393
|
+
|
|
394
|
+
if response.status_code != 200:
|
|
395
|
+
raise SystemError(f"Failed to download -- {response.status_code}")
|
|
396
|
+
|
|
397
|
+
if verbose is True:
|
|
398
|
+
content_length = int(response.headers.get('content-length', 0))
|
|
399
|
+
with open(download_path, "wb") as file, tqdm(desc=download_path,
|
|
400
|
+
total=content_length,
|
|
401
|
+
ascii=True,
|
|
402
|
+
unit='B',
|
|
403
|
+
unit_scale=True,
|
|
404
|
+
unit_divisor=1024) as progress_bar:
|
|
405
|
+
for data in response.iter_content(chunk_size=1024):
|
|
406
|
+
size = file.write(data)
|
|
407
|
+
progress_bar.update(size)
|
|
408
|
+
else:
|
|
409
|
+
with open(download_path, "wb") as f_out:
|
|
410
|
+
f_out.write(response.content)
|
|
411
|
+
|
|
412
|
+
|
|
347
413
|
def download_if_necessary(download_path: str, url: str, verbose: bool = True,
|
|
348
414
|
backup_urls: list[str] = []) -> None:
|
|
349
415
|
"""
|
|
@@ -188,7 +188,8 @@ class JunctionObject:
|
|
|
188
188
|
|
|
189
189
|
valid_params = {
|
|
190
190
|
key: value for key, value in attributes.items()
|
|
191
|
-
if key in sig.parameters and
|
|
191
|
+
if key in sig.parameters and key not in ['vmin', 'vmax', 'cmap']
|
|
192
|
+
and value is not None
|
|
192
193
|
}
|
|
193
194
|
|
|
194
195
|
return valid_params
|
|
@@ -518,7 +519,8 @@ class EdgeObject:
|
|
|
518
519
|
|
|
519
520
|
valid_params = {
|
|
520
521
|
key: value for key, value in attributes.items()
|
|
521
|
-
if key in sig.parameters and
|
|
522
|
+
if key in sig.parameters and key not in ['vmin', 'vmax', 'cmap']
|
|
523
|
+
and value is not None
|
|
522
524
|
}
|
|
523
525
|
|
|
524
526
|
return valid_params
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: epyt-flow
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.0
|
|
4
4
|
Summary: EPyT-Flow -- EPANET Python Toolkit - Flow
|
|
5
5
|
Author-email: André Artelt <aartelt@techfak.uni-bielefeld.de>, "Marios S. Kyriakou" <kiriakou.marios@ucy.ac.cy>, "Stelios G. Vrachimis" <vrachimis.stelios@ucy.ac.cy>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -17,10 +17,11 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
21
|
Requires-Python: >=3.9
|
|
21
22
|
Description-Content-Type: text/markdown
|
|
22
23
|
License-File: LICENSE
|
|
23
|
-
Requires-Dist:
|
|
24
|
+
Requires-Dist: epanet-plus>=0.1.2
|
|
24
25
|
Requires-Dist: requests>=2.31.0
|
|
25
26
|
Requires-Dist: scipy>=1.11.4
|
|
26
27
|
Requires-Dist: u-msgpack-python>=2.8.0
|
|
@@ -49,11 +50,13 @@ Dynamic: license-file
|
|
|
49
50
|
|
|
50
51
|
<img src="https://github.com/WaterFutures/EPyT-Flow/blob/main/docs/_static/net1_plot.png?raw=true" align="right" height="230px"/>
|
|
51
52
|
|
|
52
|
-
EPyT-Flow is a Python package building on top of [
|
|
53
|
+
EPyT-Flow is a Python package building on top of [EPANET-PLUS](https://github.com/WaterFutures/EPANET-PLUS) --
|
|
54
|
+
an extension and interface of [EPANET](https://github.com/OpenWaterAnalytics/EPANET)
|
|
55
|
+
and [EPANET-MSX](https://github.com/OpenWaterAnalytics/epanet-msx) --
|
|
53
56
|
for providing easy access to water distribution network simulations.
|
|
54
57
|
It aims to provide a high-level interface for the easy generation of hydraulic and water quality scenario data.
|
|
55
|
-
However, it also provides access to
|
|
56
|
-
and
|
|
58
|
+
However, it also provides access to all functions of EPANET
|
|
59
|
+
and EPANET-MSX.
|
|
57
60
|
|
|
58
61
|
EPyT-Flow provides easy access to popular benchmark data sets for event detection and localization.
|
|
59
62
|
Furthermore, it also provides an environment for developing and testing control algorithms.
|
|
@@ -77,21 +80,13 @@ Unique features of EPyT-Flow that make it superior to other (Python) toolboxes a
|
|
|
77
80
|
|
|
78
81
|
## Installation
|
|
79
82
|
|
|
80
|
-
EPyT-Flow supports Python 3.9 - 3.
|
|
83
|
+
EPyT-Flow supports Python 3.9 - 3.14
|
|
81
84
|
|
|
82
|
-
Note that
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
#### Prerequisites for macOS users
|
|
88
|
-
The "true" *gcc* compiler (version 15) is needed which is not the
|
|
89
|
-
*clang* compiler that is shipped with Xcode and is linked to gcc!
|
|
90
|
-
|
|
91
|
-
The correct version of the "true" *gcc* can be installed via [brew](https://brew.sh/):
|
|
92
|
-
```
|
|
93
|
-
brew install gcc@15
|
|
94
|
-
```
|
|
85
|
+
Note that EPyT-Flow builds upon [EPANET-PLUS](https://github.com/WaterFutures/EPANET-PLUS) which
|
|
86
|
+
constitutes a C extension and Python package.
|
|
87
|
+
In the rare case that the pre-build package of EPANET-PLUS does not work on your system,
|
|
88
|
+
you have to build and install it manually -- please follow the instructions provided
|
|
89
|
+
[here](https://epanet-plus.readthedocs.io/en/stable/installation.html).
|
|
95
90
|
|
|
96
91
|
### PyPI
|
|
97
92
|
|