epyt-flow 0.5.0__tar.gz → 0.7.0__tar.gz

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 (152) hide show
  1. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/PKG-INFO +49 -6
  2. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/README.md +47 -4
  3. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/REQUIREMENTS.txt +1 -1
  4. epyt_flow-0.7.0/epyt_flow/VERSION +1 -0
  5. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/data/benchmarks/battledim.py +6 -2
  6. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/data/benchmarks/leakdb.py +11 -9
  7. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/data/networks.py +1 -2
  8. epyt_flow-0.7.0/epyt_flow/gym/scenario_control_env.py +196 -0
  9. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/metrics.py +66 -4
  10. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/serialization.py +46 -3
  11. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/simulation/scada/advanced_control.py +1 -1
  12. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/simulation/scada/scada_data.py +160 -62
  13. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/simulation/scenario_config.py +6 -2
  14. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/simulation/scenario_simulator.py +362 -65
  15. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/simulation/sensor_config.py +186 -15
  16. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/topology.py +93 -5
  17. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/uncertainty/uncertainties.py +8 -0
  18. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/utils.py +69 -2
  19. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow.egg-info/PKG-INFO +49 -6
  20. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow.egg-info/requires.txt +1 -1
  21. epyt_flow-0.7.0/epyt_flow.egg-info/top_level.txt +6 -0
  22. epyt_flow-0.5.0/epyt_flow/VERSION +0 -1
  23. epyt_flow-0.5.0/epyt_flow/gym/scenario_control_env.py +0 -101
  24. epyt_flow-0.5.0/epyt_flow.egg-info/top_level.txt +0 -12
  25. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/CITATION.cff +0 -0
  26. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/CODE_OF_CONDUCT.md +0 -0
  27. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/LICENSE +0 -0
  28. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/MANIFEST.in +0 -0
  29. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/AUTHORS +0 -0
  30. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/LICENSE +0 -0
  31. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/Readme_SRC_Engines.txt +0 -0
  32. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/enumstxt.h +0 -0
  33. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/epanet.c +0 -0
  34. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/epanet2.c +0 -0
  35. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/epanet2.def +0 -0
  36. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/errors.dat +0 -0
  37. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/funcs.h +0 -0
  38. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/genmmd.c +0 -0
  39. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/hash.c +0 -0
  40. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/hash.h +0 -0
  41. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/hydcoeffs.c +0 -0
  42. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/hydraul.c +0 -0
  43. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/hydsolver.c +0 -0
  44. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/hydstatus.c +0 -0
  45. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2.h +0 -0
  46. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_2.h +0 -0
  47. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_enums.h +0 -0
  48. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/inpfile.c +0 -0
  49. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/input1.c +0 -0
  50. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/input2.c +0 -0
  51. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/input3.c +0 -0
  52. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/main.c +0 -0
  53. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/mempool.c +0 -0
  54. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/mempool.h +0 -0
  55. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/output.c +0 -0
  56. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/project.c +0 -0
  57. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/quality.c +0 -0
  58. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/qualreact.c +0 -0
  59. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/qualroute.c +0 -0
  60. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/report.c +0 -0
  61. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/rules.c +0 -0
  62. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/smatrix.c +0 -0
  63. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/text.h +0 -0
  64. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET/SRC_engines/types.h +0 -0
  65. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/MSX_Updates.txt +0 -0
  66. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/dispersion.h +0 -0
  67. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/hash.c +0 -0
  68. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/hash.h +0 -0
  69. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/include/epanetmsx.h +0 -0
  70. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/include/epanetmsx_export.h +0 -0
  71. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/mathexpr.c +0 -0
  72. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/mathexpr.h +0 -0
  73. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/mempool.c +0 -0
  74. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/mempool.h +0 -0
  75. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/msxchem.c +0 -0
  76. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/msxcompiler.c +0 -0
  77. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/msxdict.h +0 -0
  78. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/msxdispersion.c +0 -0
  79. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/msxerr.c +0 -0
  80. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/msxfile.c +0 -0
  81. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/msxfuncs.c +0 -0
  82. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/msxfuncs.h +0 -0
  83. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/msxinp.c +0 -0
  84. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/msxout.c +0 -0
  85. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/msxproj.c +0 -0
  86. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/msxqual.c +0 -0
  87. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/msxrpt.c +0 -0
  88. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/msxtank.c +0 -0
  89. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/msxtoolkit.c +0 -0
  90. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/msxtypes.h +0 -0
  91. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/msxutils.c +0 -0
  92. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/msxutils.h +0 -0
  93. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/newton.c +0 -0
  94. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/newton.h +0 -0
  95. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/rk5.c +0 -0
  96. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/rk5.h +0 -0
  97. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/ros2.c +0 -0
  98. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/ros2.h +0 -0
  99. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/smatrix.c +0 -0
  100. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/Src/smatrix.h +0 -0
  101. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/EPANET-MSX/readme.txt +0 -0
  102. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/compile_linux.sh +0 -0
  103. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/EPANET/compile_macos.sh +0 -0
  104. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/__init__.py +0 -0
  105. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/data/__init__.py +0 -0
  106. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/data/benchmarks/__init__.py +0 -0
  107. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/data/benchmarks/batadal.py +0 -0
  108. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/data/benchmarks/batadal_data.py +0 -0
  109. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/data/benchmarks/battledim_data.py +0 -0
  110. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/data/benchmarks/gecco_water_quality.py +0 -0
  111. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/data/benchmarks/leakdb_data.py +0 -0
  112. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/data/benchmarks/water_usage.py +0 -0
  113. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/gym/__init__.py +0 -0
  114. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/gym/control_gyms.py +0 -0
  115. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/models/__init__.py +0 -0
  116. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/models/event_detector.py +0 -0
  117. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/models/sensor_interpolation_detector.py +0 -0
  118. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/rest_api/__init__.py +0 -0
  119. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/rest_api/base_handler.py +0 -0
  120. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/rest_api/res_manager.py +0 -0
  121. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/rest_api/scada_data/__init__.py +0 -0
  122. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/rest_api/scada_data/data_handlers.py +0 -0
  123. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/rest_api/scada_data/export_handlers.py +0 -0
  124. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/rest_api/scada_data/handlers.py +0 -0
  125. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/rest_api/scenario/__init__.py +0 -0
  126. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/rest_api/scenario/event_handlers.py +0 -0
  127. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/rest_api/scenario/handlers.py +0 -0
  128. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/rest_api/scenario/simulation_handlers.py +0 -0
  129. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/rest_api/scenario/uncertainty_handlers.py +0 -0
  130. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/rest_api/server.py +0 -0
  131. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/simulation/__init__.py +0 -0
  132. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/simulation/events/__init__.py +0 -0
  133. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/simulation/events/actuator_events.py +0 -0
  134. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/simulation/events/event.py +0 -0
  135. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/simulation/events/leakages.py +0 -0
  136. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/simulation/events/sensor_faults.py +0 -0
  137. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/simulation/events/sensor_reading_attack.py +0 -0
  138. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/simulation/events/sensor_reading_event.py +0 -0
  139. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/simulation/events/system_event.py +0 -0
  140. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/simulation/parallel_simulation.py +0 -0
  141. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/simulation/scada/__init__.py +0 -0
  142. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/simulation/scada/scada_data_export.py +0 -0
  143. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/simulation/scenario_visualizer.py +0 -0
  144. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/uncertainty/__init__.py +0 -0
  145. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/uncertainty/model_uncertainty.py +0 -0
  146. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/uncertainty/sensor_noise.py +0 -0
  147. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow/uncertainty/utils.py +0 -0
  148. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow.egg-info/SOURCES.txt +0 -0
  149. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/epyt_flow.egg-info/dependency_links.txt +0 -0
  150. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/pyproject.toml +0 -0
  151. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/setup.cfg +0 -0
  152. {epyt_flow-0.5.0 → epyt_flow-0.7.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: epyt-flow
3
- Version: 0.5.0
3
+ Version: 0.7.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: MIT License
@@ -20,7 +20,7 @@ Classifier: Programming Language :: Python :: 3.12
20
20
  Requires-Python: >=3.9
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
- Requires-Dist: epyt>=1.1.6
23
+ Requires-Dist: epyt>=1.2.0
24
24
  Requires-Dist: requests>=2.31.0
25
25
  Requires-Dist: scipy>=1.11.4
26
26
  Requires-Dist: u-msgpack-python>=2.8.0
@@ -121,7 +121,7 @@ pip install .
121
121
  ```python
122
122
  from epyt_flow.data.benchmarks import load_leakdb_scenarios
123
123
  from epyt_flow.simulation import ScenarioSimulator
124
- from epyt_flow.utils import to_seconds
124
+ from epyt_flow.utils import to_seconds, plot_timeseries_data
125
125
 
126
126
 
127
127
  if __name__ == "__main__":
@@ -142,14 +142,49 @@ if __name__ == "__main__":
142
142
  # Run entire simulation
143
143
  scada_data = sim.run_simulation()
144
144
 
145
- # Show sensor readings over the entire simulation
145
+ # Print & plot sensor readings over the entire simulation
146
146
  print(f"Pressure readings: {scada_data.get_data_pressures()}")
147
+ plot_timeseries_data(scada_data.get_data_pressures().T,
148
+ labels=[f"Node {n_id}" for n_id in
149
+ scada_data.sensor_config.pressure_sensors],
150
+ x_axis_label="Time (30min steps)",
151
+ y_axis_label="Pressure in $m$")
152
+
147
153
  print(f"Flow readings: {scada_data.get_data_flows()}")
154
+ plot_timeseries_data(scada_data.get_data_flows().T,
155
+ x_axis_label="Time (30min steps)",
156
+ y_axis_label="Flow rate in $m^3/h$")
148
157
  ```
158
+ ### Generated plots
159
+
160
+ <div>
161
+ <img src="https://github.com/WaterFutures/EPyT-Flow/blob/dev/docs/_static/examples_basic_usage_pressure.png?raw=true" width="49%"/>
162
+ <img src="https://github.com/WaterFutures/EPyT-Flow/blob/dev/docs/_static/examples_basic_usage_flow.png?raw=true" width="49%"/>
163
+ </div>
149
164
 
150
165
  ## Documentation
151
166
 
152
- Documentation is available on readthedocs:[https://epyt-flow.readthedocs.io/en/latest/](https://epyt-flow.readthedocs.io/en/stable)
167
+ Documentation is available on readthedocs: [https://epyt-flow.readthedocs.io/en/latest/](https://epyt-flow.readthedocs.io/en/stable)
168
+
169
+ ## How to Get Started?
170
+
171
+ EPyT-Flow is accompanied by an extensive documentation
172
+ [https://epyt-flow.readthedocs.io/en/latest/](https://epyt-flow.readthedocs.io/en/stable)
173
+ (including many [examples](https://epyt-flow.readthedocs.io/en/stable/#examples)).
174
+
175
+ If you are new to water distribution networks, we recommend first to read the chapter on
176
+ [Modeling of Water Distribution Networks](https://epyt-flow.readthedocs.io/en/stable/tut.intro.html).
177
+ You might also want to check out some lecture notes on
178
+ [Smart Water Systems](https://github.com/KIOS-Research/ece808-smart-water-systems).
179
+
180
+ If you are already familiar with WDNs (and software such as EPANET), we recommend checking out
181
+ our [WDSA CCWI 2024 tutorial](https://github.com/WaterFutures/EPyT-and-EPyT-Flow-Tutorial) which
182
+ not only teaches you how to use EPyT and EPyT-Flow but also contains some examples of applying
183
+ Machine Learning in WDNs.
184
+ Besides that, you can read in-depth about the different functionalities of EPyT-Flow in the
185
+ [In-depth Tutorial](https://epyt-flow.readthedocs.io/en/stable/tutorial.html) of the documentation --
186
+ we recommend reading the chapters in the order in which they are presented;
187
+ you might decide to skip some of the last chapters if their content is not relevant to you.
153
188
 
154
189
  ## License
155
190
 
@@ -170,6 +205,14 @@ If you use this software, please cite it as follows:
170
205
  }
171
206
  ```
172
207
 
208
+ ## How to get Support?
209
+
210
+ If you come across any bug or need assistance please feel free to open a new
211
+ [issue](https://github.com/WaterFutures/EPyT-Flow/issues/)
212
+ if non of the existing issues answers your questions.
213
+
173
214
  ## How to Contribute?
174
215
 
175
- Contributions (e.g. creating issues, pull-requests, etc.) are welcome -- please make sure to read the [code of conduct](CODE_OF_CONDUCT.md) and follow the [developers' guidelines](DEVELOPERS.md).
216
+ Contributions (e.g. creating issues, pull-requests, etc.) are welcome --
217
+ please make sure to read the [code of conduct](CODE_OF_CONDUCT.md) and
218
+ follow the [developers' guidelines](DEVELOPERS.md).
@@ -86,7 +86,7 @@ pip install .
86
86
  ```python
87
87
  from epyt_flow.data.benchmarks import load_leakdb_scenarios
88
88
  from epyt_flow.simulation import ScenarioSimulator
89
- from epyt_flow.utils import to_seconds
89
+ from epyt_flow.utils import to_seconds, plot_timeseries_data
90
90
 
91
91
 
92
92
  if __name__ == "__main__":
@@ -107,14 +107,49 @@ if __name__ == "__main__":
107
107
  # Run entire simulation
108
108
  scada_data = sim.run_simulation()
109
109
 
110
- # Show sensor readings over the entire simulation
110
+ # Print & plot sensor readings over the entire simulation
111
111
  print(f"Pressure readings: {scada_data.get_data_pressures()}")
112
+ plot_timeseries_data(scada_data.get_data_pressures().T,
113
+ labels=[f"Node {n_id}" for n_id in
114
+ scada_data.sensor_config.pressure_sensors],
115
+ x_axis_label="Time (30min steps)",
116
+ y_axis_label="Pressure in $m$")
117
+
112
118
  print(f"Flow readings: {scada_data.get_data_flows()}")
119
+ plot_timeseries_data(scada_data.get_data_flows().T,
120
+ x_axis_label="Time (30min steps)",
121
+ y_axis_label="Flow rate in $m^3/h$")
113
122
  ```
123
+ ### Generated plots
124
+
125
+ <div>
126
+ <img src="https://github.com/WaterFutures/EPyT-Flow/blob/dev/docs/_static/examples_basic_usage_pressure.png?raw=true" width="49%"/>
127
+ <img src="https://github.com/WaterFutures/EPyT-Flow/blob/dev/docs/_static/examples_basic_usage_flow.png?raw=true" width="49%"/>
128
+ </div>
114
129
 
115
130
  ## Documentation
116
131
 
117
- Documentation is available on readthedocs:[https://epyt-flow.readthedocs.io/en/latest/](https://epyt-flow.readthedocs.io/en/stable)
132
+ Documentation is available on readthedocs: [https://epyt-flow.readthedocs.io/en/latest/](https://epyt-flow.readthedocs.io/en/stable)
133
+
134
+ ## How to Get Started?
135
+
136
+ EPyT-Flow is accompanied by an extensive documentation
137
+ [https://epyt-flow.readthedocs.io/en/latest/](https://epyt-flow.readthedocs.io/en/stable)
138
+ (including many [examples](https://epyt-flow.readthedocs.io/en/stable/#examples)).
139
+
140
+ If you are new to water distribution networks, we recommend first to read the chapter on
141
+ [Modeling of Water Distribution Networks](https://epyt-flow.readthedocs.io/en/stable/tut.intro.html).
142
+ You might also want to check out some lecture notes on
143
+ [Smart Water Systems](https://github.com/KIOS-Research/ece808-smart-water-systems).
144
+
145
+ If you are already familiar with WDNs (and software such as EPANET), we recommend checking out
146
+ our [WDSA CCWI 2024 tutorial](https://github.com/WaterFutures/EPyT-and-EPyT-Flow-Tutorial) which
147
+ not only teaches you how to use EPyT and EPyT-Flow but also contains some examples of applying
148
+ Machine Learning in WDNs.
149
+ Besides that, you can read in-depth about the different functionalities of EPyT-Flow in the
150
+ [In-depth Tutorial](https://epyt-flow.readthedocs.io/en/stable/tutorial.html) of the documentation --
151
+ we recommend reading the chapters in the order in which they are presented;
152
+ you might decide to skip some of the last chapters if their content is not relevant to you.
118
153
 
119
154
  ## License
120
155
 
@@ -135,6 +170,14 @@ If you use this software, please cite it as follows:
135
170
  }
136
171
  ```
137
172
 
173
+ ## How to get Support?
174
+
175
+ If you come across any bug or need assistance please feel free to open a new
176
+ [issue](https://github.com/WaterFutures/EPyT-Flow/issues/)
177
+ if non of the existing issues answers your questions.
178
+
138
179
  ## How to Contribute?
139
180
 
140
- Contributions (e.g. creating issues, pull-requests, etc.) are welcome -- please make sure to read the [code of conduct](CODE_OF_CONDUCT.md) and follow the [developers' guidelines](DEVELOPERS.md).
181
+ Contributions (e.g. creating issues, pull-requests, etc.) are welcome --
182
+ please make sure to read the [code of conduct](CODE_OF_CONDUCT.md) and
183
+ follow the [developers' guidelines](DEVELOPERS.md).
@@ -1,4 +1,4 @@
1
- epyt>=1.1.6
1
+ epyt>=1.2.0
2
2
  requests>=2.31.0
3
3
  scipy>=1.11.4
4
4
  u-msgpack-python>=2.8.0
@@ -0,0 +1 @@
1
+ 0.7.0
@@ -458,10 +458,14 @@ def load_scenario(return_test_scenario: bool, download_dir: str = None,
458
458
  ltown_config = load_ltown(use_realistic_demands=True, include_default_sensor_placement=True,
459
459
  verbose=verbose)
460
460
 
461
- # Set simulation duration
461
+ # Set simulation duration and other general parameters such as the demand model
462
462
  general_params = {"simulation_duration": to_seconds(days=365), # One year
463
463
  "hydraulic_time_step": to_seconds(minutes=5), # 5min time steps
464
- "reporting_time_step": to_seconds(minutes=5)} | ltown_config.general_params
464
+ "reporting_time_step": to_seconds(minutes=5),
465
+ "demand_model": {"type": "PDA", "pressure_min": 0,
466
+ "pressure_required": 0.1,
467
+ "pressure_exponent": 0.5}
468
+ } | ltown_config.general_params
465
469
 
466
470
  # Add events
467
471
  start_time = START_TIME_TEST if return_test_scenario is True else START_TIME_TRAIN
@@ -30,7 +30,7 @@ from .leakdb_data import NET1_LEAKAGES, HANOI_LEAKAGES
30
30
  from ...utils import get_temp_folder, to_seconds, unpack_zip_archive, create_path_if_not_exist, \
31
31
  download_if_necessary
32
32
  from ...metrics import f1_score, true_positive_rate, true_negative_rate
33
- from ...simulation import ScenarioSimulator
33
+ from ...simulation import ScenarioSimulator, ToolkitConstants
34
34
  from ...simulation.events import AbruptLeakage, IncipientLeakage
35
35
  from ...simulation import ScenarioConfig
36
36
  from ...simulation.scada import ScadaData
@@ -424,14 +424,19 @@ def load_scenarios(scenarios_id: list[int], use_net1: bool = True,
424
424
  download_dir = download_dir if download_dir is not None else get_temp_folder()
425
425
  network_config = load_network(download_dir)
426
426
 
427
- # Set simulation duration
427
+ # Set simulation duration and other general parameters such as the demand model and flow units
428
428
  hydraulic_time_step = to_seconds(minutes=30) # 30min time steps
429
429
  general_params = {"simulation_duration": to_seconds(days=365), # One year
430
430
  "hydraulic_time_step": hydraulic_time_step,
431
- "reporting_time_step": hydraulic_time_step} | network_config.general_params
431
+ "reporting_time_step": hydraulic_time_step,
432
+ "flow_units_id": ToolkitConstants.EN_CMH,
433
+ "demand_model": {"type": "PDA", "pressure_min": 0,
434
+ "pressure_required": 0.1,
435
+ "pressure_exponent": 0.5}
436
+ } | network_config.general_params
432
437
 
433
438
  # Add demand patterns
434
- def gen_dem(download_dir, use_net1):
439
+ def gen_dem(download_dir):
435
440
  # Taken from https://github.com/KIOS-Research/LeakDB/blob/master/CCWI-WDSA2018/Dataset_Generator_Py3/demandGenerator.py
436
441
  week_pat = scipy.io.loadmat(os.path.join(download_dir, "weekPat_30min.mat"))
437
442
  a_w = week_pat['Aw']
@@ -503,10 +508,7 @@ def load_scenarios(scenarios_id: list[int], use_net1: bool = True,
503
508
 
504
509
  if not os.path.exists(f_inp_in):
505
510
  with ScenarioSimulator(f_inp_in=network_config.f_inp_in) as wdn:
506
- wdn.epanet_api.setTimeHydraulicStep(general_params["hydraulic_time_step"])
507
- wdn.epanet_api.setTimeSimulationDuration(general_params["simulation_duration"])
508
- wdn.epanet_api.setTimePatternStep(general_params["hydraulic_time_step"])
509
- wdn.epanet_api.setFlowUnitsCMH()
511
+ wdn.set_general_parameters(**general_params)
510
512
 
511
513
  wdn.epanet_api.deletePatternsAll()
512
514
 
@@ -519,7 +521,7 @@ def load_scenarios(scenarios_id: list[int], use_net1: bool = True,
519
521
  node_idx = wdn.epanet_api.getNodeIndex(node_id)
520
522
  base_demand = wdn.epanet_api.getNodeBaseDemands(node_idx)[1][0]
521
523
 
522
- my_demand_pattern = np.array(gen_dem(download_dir, use_net1))
524
+ my_demand_pattern = np.array(gen_dem(download_dir))
523
525
 
524
526
  wdn.set_node_demand_pattern(node_id=node_id, base_demand=base_demand,
525
527
  demand_pattern_id=f"demand_{node_id}",
@@ -56,8 +56,7 @@ def get_default_hydraulic_options(flow_units_id: int = None) -> dict:
56
56
  Dictionary with default hydraulics options that can be passed to
57
57
  :func:`~epyt_flow.simulation.scenario_simulator.ScenarioSimulator.set_general_parameters`.
58
58
  """
59
- params = {"demand_model": {"type": "PDA", "pressure_min": 0, "pressure_required": 0.1,
60
- "pressure_exponent": 0.5}}
59
+ params = {}
61
60
  if flow_units_id is not None:
62
61
  params |= {"flow_units_id": flow_units_id}
63
62
 
@@ -0,0 +1,196 @@
1
+ """
2
+ Module provides a base class for control environments.
3
+ """
4
+ from abc import abstractmethod, ABC
5
+ from typing import Union
6
+ import warnings
7
+ import numpy as np
8
+
9
+ from ..simulation import ScenarioSimulator, ScenarioConfig, ScadaData
10
+
11
+
12
+ class ScenarioControlEnv(ABC):
13
+ """
14
+ Base class for a control environment challenge.
15
+
16
+ Parameters
17
+ ----------
18
+ scenario_config : :class:`~epyt_flow.simulation.scenario_config.ScenarioConfig`
19
+ Scenario configuration.
20
+ autoreset : `bool`, optional
21
+ If True, environment is automatically reset if terminated.
22
+
23
+ The default is False.
24
+ """
25
+ def __init__(self, scenario_config: ScenarioConfig, autoreset: bool = False, **kwds):
26
+ self.__scenario_config = scenario_config
27
+ self._scenario_sim = None
28
+ self._sim_generator = None
29
+ self.__autoreset = autoreset
30
+
31
+ super().__init__(**kwds)
32
+
33
+ @property
34
+ def autoreset(self) -> bool:
35
+ """
36
+ True, if environment automatically resets after it terminated.
37
+
38
+ Returns
39
+ -------
40
+ `bool`
41
+ True, if environment automatically resets after it terminated.
42
+ """
43
+ return self.__autoreset
44
+
45
+ def __enter__(self):
46
+ return self
47
+
48
+ def __exit__(self, *args):
49
+ self.close()
50
+
51
+ def close(self) -> None:
52
+ """
53
+ Frees all resources.
54
+ """
55
+ try:
56
+ if self._sim_generator is not None:
57
+ self._sim_generator.send(True)
58
+ next(self._sim_generator)
59
+ except StopIteration:
60
+ pass
61
+
62
+ if self._scenario_sim is not None:
63
+ self._scenario_sim.close()
64
+
65
+ def reset(self) -> ScadaData:
66
+ """
67
+ Resets the environment (i.e. simulation).
68
+
69
+ Returns
70
+ -------
71
+ :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`
72
+ Current SCADA data (i.e. sensor readings).
73
+ """
74
+ if self._scenario_sim is not None:
75
+ self._scenario_sim.close()
76
+
77
+ self._scenario_sim = ScenarioSimulator(
78
+ scenario_config=self.__scenario_config)
79
+ self._sim_generator = self._scenario_sim.run_simulation_as_generator(support_abort=True)
80
+
81
+ return self._next_sim_itr()
82
+
83
+ def _next_sim_itr(self) -> ScadaData:
84
+ try:
85
+ next(self._sim_generator)
86
+ r = self._sim_generator.send(False)
87
+
88
+ if self.autoreset is True:
89
+ return r
90
+ else:
91
+ return r, False
92
+ except StopIteration:
93
+ if self.__autoreset is True:
94
+ return self.reset()
95
+ else:
96
+ return None, True
97
+
98
+ def set_pump_status(self, pump_id: str, status: int) -> None:
99
+ """
100
+ Sets the status of a pump.
101
+
102
+ Parameters
103
+ ----------
104
+ pump_id : `str`
105
+ ID of the pump for which the status is set.
106
+ status : `int`
107
+ New status of the pump -- either active (i.e. open) or inactive (i.e. closed).
108
+
109
+ Must be one of the following constants defined in
110
+ :class:`~epyt_flow.simulation.events.actuator_events.ActuatorConstants`:
111
+
112
+ - EN_CLOSED = 0
113
+ - EN_OPEN = 1
114
+ """
115
+ pump_idx = self._scenario_sim.epanet_api.getLinkPumpNameID().index(pump_id)
116
+ pump_link_idx = self._scenario_sim.epanet_api.getLinkPumpIndex(pump_idx + 1)
117
+ self._scenario_sim.epanet_api.setLinkStatus(pump_link_idx, status)
118
+
119
+ def set_pump_speed(self, pump_id: str, speed: float) -> None:
120
+ """
121
+ Sets the speed of a pump.
122
+
123
+ Parameters
124
+ ----------
125
+ pump_id : `str`
126
+ ID of the pump for which the pump speed is set.
127
+ speed : `float`
128
+ New pump speed.
129
+ """
130
+ pump_idx = self._scenario_sim.epanet_api.getLinkPumpNameID().index(pump_id)
131
+ pattern_idx = self._scenario_sim.epanet_api.getLinkPumpPatternIndex(pump_idx + 1)
132
+
133
+ if pattern_idx == 0:
134
+ warnings.warn(f"No pattern for pump '{pump_id}' found -- a new pattern is created")
135
+ pattern_idx = self._scenario_sim.epanet_api.addPattern(f"pump_speed_{pump_id}")
136
+ self._scenario_sim.epanet_api.setLinkPumpPatternIndex(pattern_idx)
137
+
138
+ self._scenario_sim.epanet_api.setPattern(pattern_idx, np.array([speed]))
139
+
140
+ def set_valve_status(self, valve_id: str, status: int) -> None:
141
+ """
142
+ Sets the status of a valve.
143
+
144
+ Parameters
145
+ ----------
146
+ valve_id : `str`
147
+ ID of the valve for which the status is set.
148
+ status : `int`
149
+ New status of the valve -- either open or closed.
150
+
151
+ Must be one of the following constants defined in
152
+ :class:`~epyt_flow.simulation.events.actuator_events.ActuatorConstants`:
153
+
154
+ - EN_CLOSED = 0
155
+ - EN_OPEN = 1
156
+ """
157
+ valve_idx = self._scenario_sim.epanet_api.getLinkValveNameID().index(valve_id)
158
+ valve_link_idx = self._scenario_sim.epanet_api.getLinkValveIndex()[valve_idx]
159
+ self._scenario_sim.epanet_api.setLinkStatus(valve_link_idx, status)
160
+
161
+ def set_node_quality_source_value(self, node_id: str, pattern_id: str,
162
+ qual_value: float) -> None:
163
+ """
164
+ Sets the quality source at a particular node to a specific value -- e.g.
165
+ setting the chlorine concentration injection to a specified value.
166
+
167
+ Parameters
168
+ ----------
169
+ node_id : `str`
170
+ ID of the node.
171
+ pattern_id : `str`
172
+ ID of the quality pattern at the specific node.
173
+ qual_value : `float`
174
+ New quality source value.
175
+ """
176
+ node_idx = self._scenario_sim.epanet_api.getNodeIndex(node_id)
177
+ pattern_idx = self._scenario_sim.epanet_api.getPatternIndex(pattern_id)
178
+ self._scenario_sim.epanet_api.setNodeSourceQuality(node_idx, 1)
179
+ self._scenario_sim.epanet_api.setPattern(pattern_idx, np.array([qual_value]))
180
+
181
+ @abstractmethod
182
+ def step(self, *actions) -> Union[tuple[ScadaData, float, bool], tuple[ScadaData, float]]:
183
+ """
184
+ Performs the next step by applying an action and observing
185
+ the consequences (SCADA data, reward, terminated).
186
+
187
+ Note that `terminated` is only returned if `autoreset=False` otherwise
188
+ only the current SCADA data and reward are returned.
189
+
190
+ Returns
191
+ -------
192
+ `(` :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` `, float, bool)` or `(` :class:`~epyt_flow.simulation.scada.scada_data.ScadaData` `, float)`
193
+ Triple or tuple of observations (:class:`~epyt_flow.simulation.scada.scada_data.ScadaData`),
194
+ reward (`float`), and terminated (`bool`).
195
+ """
196
+ raise NotImplementedError()
@@ -3,12 +3,74 @@ This module provides different metrics for evaluation.
3
3
  """
4
4
  import numpy as np
5
5
  from sklearn.metrics import roc_auc_score as skelarn_roc_auc_score, f1_score as skelarn_f1_scpre, \
6
- mean_absolute_error
6
+ mean_absolute_error, root_mean_squared_error, r2_score as sklearn_r2_score
7
7
 
8
8
 
9
- def running_mse(y_pred: np.ndarray, y: np.ndarray):
9
+ def r2_score(y_pred: np.ndarray, y: np.ndarray) -> float:
10
10
  """
11
- Computes the running Mean Squared Error (MSE).
11
+ Computes the R^2 score (also called the coefficient of determination).
12
+
13
+ Parameters
14
+ ----------
15
+ y_pred : `numpy.ndarray`
16
+ Predicted outputs.
17
+ y : `numpy.ndarray`
18
+ Ground truth outputs.
19
+
20
+ Returns
21
+ -------
22
+ `float`
23
+ R^2 score.
24
+ """
25
+ return sklearn_r2_score(y, y_pred)
26
+
27
+
28
+ def running_r2_score(y_pred: np.ndarray, y: np.ndarray) -> list[float]:
29
+ """
30
+ Computes and returns the running R^2 score -- i.e. the R^2 score for every point in time.
31
+
32
+ Parameters
33
+ ----------
34
+ y_pred : `numpy.ndarray`
35
+ Predicted outputs.
36
+ y : `numpy.ndarray`
37
+ Ground truth outputs.
38
+
39
+ Returns
40
+ -------
41
+ `list[float]`
42
+ The running R^2 score.
43
+ """
44
+ r = []
45
+
46
+ for t in range(2, len(y_pred)):
47
+ r.append(r2_score(y_pred[:t], y[:t]))
48
+
49
+ return r
50
+
51
+
52
+ def mean_squared_error(y_pred: np.ndarray, y: np.ndarray) -> float:
53
+ """
54
+ Computes the Mean Squared Error (MSE).
55
+
56
+ Parameters
57
+ ----------
58
+ y_pred : `numpy.ndarray`
59
+ Predicted outputs.
60
+ y : `numpy.ndarray`
61
+ Ground truth outputs.
62
+
63
+ Returns
64
+ -------
65
+ `float`
66
+ MSE.
67
+ """
68
+ return root_mean_squared_error(y, y_pred)**2
69
+
70
+
71
+ def running_mse(y_pred: np.ndarray, y: np.ndarray) -> list[float]:
72
+ """
73
+ Computes the running Mean Squared Error (MSE) -- i.e. the MSE for every point in time.
12
74
 
13
75
  Parameters
14
76
  ----------
@@ -39,7 +101,7 @@ def running_mse(y_pred: np.ndarray, y: np.ndarray):
39
101
  r_mse = list(esq for esq in e_sq)
40
102
 
41
103
  for i in range(1, len(y)):
42
- r_mse[i] = ((i * r_mse[i - 1]) / (i + 1)) + (r_mse[i] / (i + 1))
104
+ r_mse[i] = float((i * r_mse[i - 1]) / (i + 1)) + (r_mse[i] / (i + 1))
43
105
 
44
106
  return r_mse
45
107
 
@@ -4,6 +4,7 @@ Module provides functions and classes for serialization.
4
4
  from typing import Any, Union
5
5
  from abc import abstractmethod, ABC
6
6
  from io import BufferedIOBase
7
+ import pathlib
7
8
  import importlib
8
9
  import json
9
10
  import gzip
@@ -97,7 +98,9 @@ class Serializable(ABC):
97
98
  Base class for a serializable class -- must be used in conjunction with the
98
99
  :func:`~epyt_flow.serialization.serializable` decorator.
99
100
  """
100
- def __init__(self, **kwds):
101
+ def __init__(self, _parent_path: str = "", **kwds):
102
+ self._parent_path = _parent_path
103
+
101
104
  super().__init__(**kwds)
102
105
 
103
106
  @abstractmethod
@@ -306,6 +309,39 @@ class JsonSerializable(Serializable):
306
309
  """
307
310
  return my_load_from_json(data)
308
311
 
312
+ @staticmethod
313
+ def load_from_json_file(f_in: str) -> Any:
314
+ """
315
+ Deserializes an instance of this class from a JSON file.
316
+
317
+ Parameters
318
+ ----------
319
+ f_in : `str`
320
+ Path to the JSON file from which to deserialize the object.
321
+
322
+ Returns
323
+ -------
324
+ `Any`
325
+ Deserialized object.
326
+ """
327
+ with open(f_in, "r", encoding="utf-8") as f:
328
+ return my_load_from_json(f.read())
329
+
330
+ def save_to_json_file(self, f_out: str) -> None:
331
+ """
332
+ Serializes this instance and stores it in a JSON file.
333
+
334
+ Parameters
335
+ ----------
336
+ f_in : `str`
337
+ Path to the JSON file where this serialized object will be stored.
338
+ """
339
+ if not f_out.endswith(self.file_ext()):
340
+ f_out += self.file_ext()
341
+
342
+ with open(f_out, "w", encoding="utf-8") as f:
343
+ f.write(self.to_json())
344
+
309
345
 
310
346
  def load(data: Union[bytes, BufferedIOBase]) -> Any:
311
347
  """
@@ -376,12 +412,19 @@ def load_from_file(f_in: str, use_compression: bool = True) -> Any:
376
412
  `Any`
377
413
  Deserialized data.
378
414
  """
415
+ inst = None
416
+
379
417
  if use_compression is False:
380
418
  with open(f_in, "rb") as f:
381
- return umsgpack.unpack(f, ext_handlers=ext_handler_unpack)
419
+ inst = load(f.read())
382
420
  else:
383
421
  with gzip.open(f_in, "rb") as f:
384
- return load(f.read())
422
+ inst = load(f.read())
423
+
424
+ if isinstance(inst, Serializable):
425
+ inst._parent_path = pathlib.Path(f_in).parent.resolve()
426
+
427
+ return inst
385
428
 
386
429
 
387
430
  def save_to_file(f_out: str, data: Any, use_compression: bool = True) -> None:
@@ -98,7 +98,7 @@ class AdvancedControlModule(ABC):
98
98
  - EN_OPEN = 1
99
99
  """
100
100
  valve_idx = self._epanet_api.getLinkValveNameID().index(valve_id)
101
- valve_link_idx = self._epanet_api.getLinkValveIndex(valve_idx + 1)
101
+ valve_link_idx = self._epanet_api.getLinkValveIndex()[valve_idx]
102
102
  self._epanet_api.setLinkStatus(valve_link_idx, status)
103
103
 
104
104
  def set_node_quality_source_value(self, node_id: str, pattern_id: str,