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.
Files changed (108) hide show
  1. epyt_flow/VERSION +1 -1
  2. epyt_flow/__init__.py +0 -37
  3. epyt_flow/data/benchmarks/battledim.py +2 -2
  4. epyt_flow/data/benchmarks/leakdb.py +12 -9
  5. epyt_flow/gym/scenario_control_env.py +32 -33
  6. epyt_flow/simulation/events/actuator_events.py +24 -18
  7. epyt_flow/simulation/events/leakages.py +59 -57
  8. epyt_flow/simulation/events/quality_events.py +21 -30
  9. epyt_flow/simulation/events/system_event.py +3 -3
  10. epyt_flow/simulation/scada/complex_control.py +14 -12
  11. epyt_flow/simulation/scada/custom_control.py +22 -21
  12. epyt_flow/simulation/scada/scada_data.py +108 -105
  13. epyt_flow/simulation/scada/simple_control.py +38 -31
  14. epyt_flow/simulation/scenario_simulator.py +368 -395
  15. epyt_flow/simulation/sensor_config.py +31 -32
  16. epyt_flow/topology.py +11 -10
  17. epyt_flow/uncertainty/model_uncertainty.py +146 -122
  18. epyt_flow/utils.py +66 -0
  19. epyt_flow/visualization/visualization_utils.py +4 -2
  20. {epyt_flow-0.14.1.dist-info → epyt_flow-0.15.0.dist-info}/METADATA +14 -19
  21. epyt_flow-0.15.0.dist-info/RECORD +65 -0
  22. epyt_flow/EPANET/EPANET/SRC_engines/AUTHORS +0 -60
  23. epyt_flow/EPANET/EPANET/SRC_engines/LICENSE +0 -21
  24. epyt_flow/EPANET/EPANET/SRC_engines/enumstxt.h +0 -151
  25. epyt_flow/EPANET/EPANET/SRC_engines/epanet.c +0 -5930
  26. epyt_flow/EPANET/EPANET/SRC_engines/epanet2.c +0 -961
  27. epyt_flow/EPANET/EPANET/SRC_engines/errors.dat +0 -79
  28. epyt_flow/EPANET/EPANET/SRC_engines/flowbalance.c +0 -186
  29. epyt_flow/EPANET/EPANET/SRC_engines/funcs.h +0 -219
  30. epyt_flow/EPANET/EPANET/SRC_engines/genmmd.c +0 -1000
  31. epyt_flow/EPANET/EPANET/SRC_engines/hash.c +0 -177
  32. epyt_flow/EPANET/EPANET/SRC_engines/hash.h +0 -28
  33. epyt_flow/EPANET/EPANET/SRC_engines/hydcoeffs.c +0 -1303
  34. epyt_flow/EPANET/EPANET/SRC_engines/hydraul.c +0 -1172
  35. epyt_flow/EPANET/EPANET/SRC_engines/hydsolver.c +0 -781
  36. epyt_flow/EPANET/EPANET/SRC_engines/hydstatus.c +0 -442
  37. epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2.h +0 -464
  38. epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_2.h +0 -1960
  39. epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_enums.h +0 -518
  40. epyt_flow/EPANET/EPANET/SRC_engines/inpfile.c +0 -884
  41. epyt_flow/EPANET/EPANET/SRC_engines/input1.c +0 -672
  42. epyt_flow/EPANET/EPANET/SRC_engines/input2.c +0 -735
  43. epyt_flow/EPANET/EPANET/SRC_engines/input3.c +0 -2265
  44. epyt_flow/EPANET/EPANET/SRC_engines/leakage.c +0 -527
  45. epyt_flow/EPANET/EPANET/SRC_engines/mempool.c +0 -146
  46. epyt_flow/EPANET/EPANET/SRC_engines/mempool.h +0 -24
  47. epyt_flow/EPANET/EPANET/SRC_engines/output.c +0 -853
  48. epyt_flow/EPANET/EPANET/SRC_engines/project.c +0 -1691
  49. epyt_flow/EPANET/EPANET/SRC_engines/quality.c +0 -695
  50. epyt_flow/EPANET/EPANET/SRC_engines/qualreact.c +0 -800
  51. epyt_flow/EPANET/EPANET/SRC_engines/qualroute.c +0 -696
  52. epyt_flow/EPANET/EPANET/SRC_engines/report.c +0 -1557
  53. epyt_flow/EPANET/EPANET/SRC_engines/rules.c +0 -1500
  54. epyt_flow/EPANET/EPANET/SRC_engines/smatrix.c +0 -871
  55. epyt_flow/EPANET/EPANET/SRC_engines/text.h +0 -508
  56. epyt_flow/EPANET/EPANET/SRC_engines/types.h +0 -928
  57. epyt_flow/EPANET/EPANET/SRC_engines/util/cstr_helper.c +0 -59
  58. epyt_flow/EPANET/EPANET/SRC_engines/util/cstr_helper.h +0 -38
  59. epyt_flow/EPANET/EPANET/SRC_engines/util/errormanager.c +0 -92
  60. epyt_flow/EPANET/EPANET/SRC_engines/util/errormanager.h +0 -39
  61. epyt_flow/EPANET/EPANET/SRC_engines/util/filemanager.c +0 -212
  62. epyt_flow/EPANET/EPANET/SRC_engines/util/filemanager.h +0 -81
  63. epyt_flow/EPANET/EPANET/SRC_engines/validate.c +0 -408
  64. epyt_flow/EPANET/EPANET-MSX/MSX_Updates.txt +0 -53
  65. epyt_flow/EPANET/EPANET-MSX/Src/dispersion.h +0 -27
  66. epyt_flow/EPANET/EPANET-MSX/Src/hash.c +0 -107
  67. epyt_flow/EPANET/EPANET-MSX/Src/hash.h +0 -28
  68. epyt_flow/EPANET/EPANET-MSX/Src/include/epanetmsx.h +0 -102
  69. epyt_flow/EPANET/EPANET-MSX/Src/include/epanetmsx_export.h +0 -42
  70. epyt_flow/EPANET/EPANET-MSX/Src/mathexpr.c +0 -937
  71. epyt_flow/EPANET/EPANET-MSX/Src/mathexpr.h +0 -39
  72. epyt_flow/EPANET/EPANET-MSX/Src/mempool.c +0 -204
  73. epyt_flow/EPANET/EPANET-MSX/Src/mempool.h +0 -24
  74. epyt_flow/EPANET/EPANET-MSX/Src/msxchem.c +0 -1285
  75. epyt_flow/EPANET/EPANET-MSX/Src/msxcompiler.c +0 -368
  76. epyt_flow/EPANET/EPANET-MSX/Src/msxdict.h +0 -42
  77. epyt_flow/EPANET/EPANET-MSX/Src/msxdispersion.c +0 -586
  78. epyt_flow/EPANET/EPANET-MSX/Src/msxerr.c +0 -116
  79. epyt_flow/EPANET/EPANET-MSX/Src/msxfile.c +0 -260
  80. epyt_flow/EPANET/EPANET-MSX/Src/msxfuncs.c +0 -175
  81. epyt_flow/EPANET/EPANET-MSX/Src/msxfuncs.h +0 -35
  82. epyt_flow/EPANET/EPANET-MSX/Src/msxinp.c +0 -1504
  83. epyt_flow/EPANET/EPANET-MSX/Src/msxout.c +0 -401
  84. epyt_flow/EPANET/EPANET-MSX/Src/msxproj.c +0 -791
  85. epyt_flow/EPANET/EPANET-MSX/Src/msxqual.c +0 -2010
  86. epyt_flow/EPANET/EPANET-MSX/Src/msxrpt.c +0 -400
  87. epyt_flow/EPANET/EPANET-MSX/Src/msxtank.c +0 -422
  88. epyt_flow/EPANET/EPANET-MSX/Src/msxtoolkit.c +0 -1164
  89. epyt_flow/EPANET/EPANET-MSX/Src/msxtypes.h +0 -551
  90. epyt_flow/EPANET/EPANET-MSX/Src/msxutils.c +0 -524
  91. epyt_flow/EPANET/EPANET-MSX/Src/msxutils.h +0 -56
  92. epyt_flow/EPANET/EPANET-MSX/Src/newton.c +0 -158
  93. epyt_flow/EPANET/EPANET-MSX/Src/newton.h +0 -34
  94. epyt_flow/EPANET/EPANET-MSX/Src/rk5.c +0 -287
  95. epyt_flow/EPANET/EPANET-MSX/Src/rk5.h +0 -39
  96. epyt_flow/EPANET/EPANET-MSX/Src/ros2.c +0 -293
  97. epyt_flow/EPANET/EPANET-MSX/Src/ros2.h +0 -35
  98. epyt_flow/EPANET/EPANET-MSX/Src/smatrix.c +0 -816
  99. epyt_flow/EPANET/EPANET-MSX/Src/smatrix.h +0 -29
  100. epyt_flow/EPANET/EPANET-MSX/readme.txt +0 -14
  101. epyt_flow/EPANET/compile_linux.sh +0 -4
  102. epyt_flow/EPANET/compile_macos.sh +0 -4
  103. epyt_flow/simulation/backend/__init__.py +0 -1
  104. epyt_flow/simulation/backend/my_epyt.py +0 -1101
  105. epyt_flow-0.14.1.dist-info/RECORD +0 -148
  106. {epyt_flow-0.14.1.dist-info → epyt_flow-0.15.0.dist-info}/WHEEL +0 -0
  107. {epyt_flow-0.14.1.dist-info → epyt_flow-0.15.0.dist-info}/licenses/LICENSE +0 -0
  108. {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 epyt
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: epyt.epanet) -> None:
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 : `epyt.epanet <https://epanet-python-toolkit-epyt.readthedocs.io/en/stable/api.html#epyt.epanet.epanet>`_
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.setLinkLength(self._cache_links_length)
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.setLinkDiameter(self._cache_links_diameter)
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.setLinkRoughnessCoeff(self._cache_links_roughness_coeff)
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.setNodeBaseDemands(node_idx, demand_category + 1, base_demand)
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 pattern_id, demand_pattern in self._cache_nodes_demand_pattern.items():
661
- for t, v in enumerate(demand_pattern):
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.setNodeElevations(self._cache_nodes_elevation)
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.setPattern(pattern_idx, pattern)
675
+ epanet_api.set_pattern(pattern_idx, pattern.tolist())
670
676
 
671
677
  if self._cache_msx_constants is not None:
672
- epanet_api.setMSXConstantsValue(self._cache_msx_constants)
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
- epanet_api.setMSXParametersPipesValue(pipe_idx, parameters_pipes_val)
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
- epanet_api.setMSXParametersTanksValue(tank_idx, parameters_tanks_val)
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.setMSXPattern(pattern_idx, pattern)
695
+ epanet_api.MSXsetpattern(pattern_idx, pattern.tolist(), len(pattern))
685
696
 
686
- def apply(self, epanet_api: epyt.epanet) -> None:
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 : `epyt.epanet <https://epanet-python-toolkit-epyt.readthedocs.io/en/stable/api.html#epyt.epanet.epanet>`_
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.getLinkLength()
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
- epanet_api.setLinkLength(link_length)
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.getLinkLength()
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.getLinkIndex(pipe_id)
712
- link_length = epanet_api.getLinkLength(link_idx)
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.setLinkLength(link_idx, link_length)
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.getLinkDiameter()
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
- epanet_api.setLinkDiameter(link_diameters)
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.getLinkDiameter()
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.getLinkIndex(pipe_id)
732
- link_diameter = epanet_api.getLinkDiameter(link_idx)
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.setLinkDiameter(link_idx, link_diameter)
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.getLinkRoughnessCoeff()
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
- epanet_api.setLinkRoughnessCoeff(coeffs)
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 = epanet_api.getLinkRoughnessCoeff()
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.getLinkIndex(pipe_id)
752
- link_roughness_coeff = epanet_api.getLinkRoughnessCoeff(link_idx)
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.setLinkRoughnessCoeff(link_idx, link_roughness_coeff)
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.getNodeDemandCategoriesNumber(node_idx)
765
- for demand_category in range(n_demand_categories):
766
- base_demand = epanet_api.getNodeBaseDemands(node_idx)[demand_category + 1]
767
- self._cache_nodes_base_demand[node_idx][demand_category] = base_demand
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.setNodeBaseDemands(node_idx, demand_category + 1, base_demand)
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.getNodeIndex(node_id)
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.getNodeDemandCategoriesNumber(node_idx)
780
- for demand_category in range(n_demand_categories):
781
- base_demand = epanet_api.getNodeBaseDemands(node_idx)[demand_category + 1]
782
- self._cache_nodes_base_demand[node_idx][demand_category] = base_demand
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.setNodeBaseDemands(node_idx, demand_category + 1, base_demand)
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.getNodeDemandPatternIndex()
792
- demand_patterns_id = np.unique([demand_patterns_idx[k]
793
- for k in demand_patterns_idx.keys()])
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
- v_ = self._global_demand_pattern.apply(v)
806
- epanet_api.setPatternValue(pattern_id, t+1, v_)
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
- self._cache_nodes_demand_pattern[pattern_id] = demand_pattern
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 = paterns_idx[patterns_id.index(pattern_id)]
819
- pattern_length, = epanet_api.getPatternLengths(pattern_id)
820
- demand_pattern = np.zeros(pattern_length)
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
- self._cache_nodes_demand_pattern[pattern_id] = demand_pattern
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.getNodeElevations()
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
- epanet_api.setNodeElevations(elevations)
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.getNodeElevations()
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.getNodeIndex(node_id)
846
- elevation = epanet_api.getNodeElevations(node_idx)
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.setNodeElevations(node_idx, elevation)
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.getPatternIndex(pattern_id)
857
- pattern_length = epanet_api.getPatternLengths(pattern_idx)
858
- pattern = np.array([epanet_api.getPatternValue(pattern_idx, t+1)
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.setPattern(pattern_idx, pattern)
874
+ epanet_api.set_pattern(pattern_idx, pattern.tolist())
864
875
 
865
- if epanet_api.MSXFile is not None:
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.getMSXConstantsValue())
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
- epanet_api.setMSXConstantsValue(constants)
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.getMSXConstantsValue())
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(ToolkitConstants.MSX_CONSTANT, constant_id)
882
- constant = epanet_api.msx.MSXgetconstant(idx)
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.msx.MSXsetconstant(idx, constant)
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
- parameters_pipes = epanet_api.getMSXParametersPipesValue()
892
- for i, pipe_idx in enumerate(epanet_api.getLinkPipeIndex()):
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] = np.array(parameters_pipes[i])
897
- parameters_pipes_val = self._global_parameters.apply_batch(
898
- np.array(parameters_pipes[i]))
899
- epanet_api.setMSXParametersPipesValue(pipe_idx, parameters_pipes_val)
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
- parameters_tanks = epanet_api.getMSXParametersTanksValue()
903
- for i, tank_idx in enumerate(epanet_api.getNodeTankIndex()):
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] = np.array(parameters_tanks[i])
908
- parameters_tanks_val = self._global_parameters.apply_batch(
909
- np.array(parameters_tanks[i]))
910
- epanet_api.setMSXParametersTanksValue(tank_idx, parameters_tanks_val)
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.getMSXParametersIndex([param_id])
942
+ idx, = epanet_api.MSXgetindex(EpanetConstants.MSX_PARAMETER, param_id)
919
943
 
920
- if item_type == ToolkitConstants.MSX_NODE:
921
- item_idx = epanet_api.getNodeIndex(item_id)
922
- elif item_type == ToolkitConstants.MSX_LINK:
923
- item_idx = epanet_api.getLinkIndex(item_id)
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
- "ToolkitConstants.MSX_NODE or ToolkitConstants.MSX_LINK")
950
+ "EpanetConstants.MSX_NODE or EpanetConstants.MSX_LINK")
927
951
 
928
- parameter = epanet_api.msx.MSXgetparameter(item_type, item_idx, idx)
929
- if item_type == ToolkitConstants.MSX_NODE:
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 == ToolkitConstants.MSX_LINK:
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.msx.MSXsetparameter(item_type, item_idx, idx, parameter)
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, = epanet_api.getMSXPatternsIndex([pattern_id])
943
- pattern = epanet_api.getMSXConstantsValue([pattern_idx])
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.setMSXPattern(pattern_idx, pattern)
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 value is not None
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 value is not None
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.14.1
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: epyt>=1.2.2
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 [EPyT](https://github.com/OpenWaterAnalytics/EPyT)
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 low-level functions by [EPANET](https://github.com/USEPA/EPANET2.2)
56
- and [EPANET-MSX](https://github.com/USEPA/EPANETMSX/).
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.13
83
+ EPyT-Flow supports Python 3.9 - 3.14
81
84
 
82
- Note that [EPANET and EPANET-MSX sources](epyt_flow/EPANET/) are compiled and overwrite the binaries
83
- shipped by EPyT **IF** EPyT-Flow is installed on a Unix system and the *gcc* compiler is available.
84
- By this, we not only aim to achieve a better performance of the simulations but also avoid any
85
- compatibility issues of pre-compiled binaries.
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