epyt-flow 0.1.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 (131) hide show
  1. epyt_flow/EPANET/EPANET/SRC_engines/AUTHORS +28 -0
  2. epyt_flow/EPANET/EPANET/SRC_engines/LICENSE +21 -0
  3. epyt_flow/EPANET/EPANET/SRC_engines/Readme_SRC_Engines.txt +18 -0
  4. epyt_flow/EPANET/EPANET/SRC_engines/enumstxt.h +134 -0
  5. epyt_flow/EPANET/EPANET/SRC_engines/epanet.c +5578 -0
  6. epyt_flow/EPANET/EPANET/SRC_engines/epanet2.c +865 -0
  7. epyt_flow/EPANET/EPANET/SRC_engines/epanet2.def +131 -0
  8. epyt_flow/EPANET/EPANET/SRC_engines/errors.dat +73 -0
  9. epyt_flow/EPANET/EPANET/SRC_engines/funcs.h +193 -0
  10. epyt_flow/EPANET/EPANET/SRC_engines/genmmd.c +1000 -0
  11. epyt_flow/EPANET/EPANET/SRC_engines/hash.c +177 -0
  12. epyt_flow/EPANET/EPANET/SRC_engines/hash.h +28 -0
  13. epyt_flow/EPANET/EPANET/SRC_engines/hydcoeffs.c +1151 -0
  14. epyt_flow/EPANET/EPANET/SRC_engines/hydraul.c +1117 -0
  15. epyt_flow/EPANET/EPANET/SRC_engines/hydsolver.c +720 -0
  16. epyt_flow/EPANET/EPANET/SRC_engines/hydstatus.c +476 -0
  17. epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2.h +431 -0
  18. epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_2.h +1786 -0
  19. epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_enums.h +468 -0
  20. epyt_flow/EPANET/EPANET/SRC_engines/inpfile.c +810 -0
  21. epyt_flow/EPANET/EPANET/SRC_engines/input1.c +707 -0
  22. epyt_flow/EPANET/EPANET/SRC_engines/input2.c +864 -0
  23. epyt_flow/EPANET/EPANET/SRC_engines/input3.c +2170 -0
  24. epyt_flow/EPANET/EPANET/SRC_engines/main.c +93 -0
  25. epyt_flow/EPANET/EPANET/SRC_engines/mempool.c +142 -0
  26. epyt_flow/EPANET/EPANET/SRC_engines/mempool.h +24 -0
  27. epyt_flow/EPANET/EPANET/SRC_engines/output.c +852 -0
  28. epyt_flow/EPANET/EPANET/SRC_engines/project.c +1359 -0
  29. epyt_flow/EPANET/EPANET/SRC_engines/quality.c +685 -0
  30. epyt_flow/EPANET/EPANET/SRC_engines/qualreact.c +743 -0
  31. epyt_flow/EPANET/EPANET/SRC_engines/qualroute.c +694 -0
  32. epyt_flow/EPANET/EPANET/SRC_engines/report.c +1489 -0
  33. epyt_flow/EPANET/EPANET/SRC_engines/rules.c +1362 -0
  34. epyt_flow/EPANET/EPANET/SRC_engines/smatrix.c +871 -0
  35. epyt_flow/EPANET/EPANET/SRC_engines/text.h +497 -0
  36. epyt_flow/EPANET/EPANET/SRC_engines/types.h +874 -0
  37. epyt_flow/EPANET/EPANET-MSX/MSX_Updates.txt +53 -0
  38. epyt_flow/EPANET/EPANET-MSX/Src/dispersion.h +27 -0
  39. epyt_flow/EPANET/EPANET-MSX/Src/hash.c +107 -0
  40. epyt_flow/EPANET/EPANET-MSX/Src/hash.h +28 -0
  41. epyt_flow/EPANET/EPANET-MSX/Src/include/epanetmsx.h +102 -0
  42. epyt_flow/EPANET/EPANET-MSX/Src/include/epanetmsx_export.h +42 -0
  43. epyt_flow/EPANET/EPANET-MSX/Src/mathexpr.c +937 -0
  44. epyt_flow/EPANET/EPANET-MSX/Src/mathexpr.h +39 -0
  45. epyt_flow/EPANET/EPANET-MSX/Src/mempool.c +204 -0
  46. epyt_flow/EPANET/EPANET-MSX/Src/mempool.h +24 -0
  47. epyt_flow/EPANET/EPANET-MSX/Src/msxchem.c +1285 -0
  48. epyt_flow/EPANET/EPANET-MSX/Src/msxcompiler.c +368 -0
  49. epyt_flow/EPANET/EPANET-MSX/Src/msxdict.h +42 -0
  50. epyt_flow/EPANET/EPANET-MSX/Src/msxdispersion.c +586 -0
  51. epyt_flow/EPANET/EPANET-MSX/Src/msxerr.c +116 -0
  52. epyt_flow/EPANET/EPANET-MSX/Src/msxfile.c +260 -0
  53. epyt_flow/EPANET/EPANET-MSX/Src/msxfuncs.c +175 -0
  54. epyt_flow/EPANET/EPANET-MSX/Src/msxfuncs.h +35 -0
  55. epyt_flow/EPANET/EPANET-MSX/Src/msxinp.c +1504 -0
  56. epyt_flow/EPANET/EPANET-MSX/Src/msxout.c +401 -0
  57. epyt_flow/EPANET/EPANET-MSX/Src/msxproj.c +791 -0
  58. epyt_flow/EPANET/EPANET-MSX/Src/msxqual.c +2010 -0
  59. epyt_flow/EPANET/EPANET-MSX/Src/msxrpt.c +400 -0
  60. epyt_flow/EPANET/EPANET-MSX/Src/msxtank.c +422 -0
  61. epyt_flow/EPANET/EPANET-MSX/Src/msxtoolkit.c +1164 -0
  62. epyt_flow/EPANET/EPANET-MSX/Src/msxtypes.h +551 -0
  63. epyt_flow/EPANET/EPANET-MSX/Src/msxutils.c +524 -0
  64. epyt_flow/EPANET/EPANET-MSX/Src/msxutils.h +56 -0
  65. epyt_flow/EPANET/EPANET-MSX/Src/newton.c +158 -0
  66. epyt_flow/EPANET/EPANET-MSX/Src/newton.h +34 -0
  67. epyt_flow/EPANET/EPANET-MSX/Src/rk5.c +287 -0
  68. epyt_flow/EPANET/EPANET-MSX/Src/rk5.h +39 -0
  69. epyt_flow/EPANET/EPANET-MSX/Src/ros2.c +293 -0
  70. epyt_flow/EPANET/EPANET-MSX/Src/ros2.h +35 -0
  71. epyt_flow/EPANET/EPANET-MSX/Src/smatrix.c +816 -0
  72. epyt_flow/EPANET/EPANET-MSX/Src/smatrix.h +29 -0
  73. epyt_flow/EPANET/EPANET-MSX/readme.txt +14 -0
  74. epyt_flow/EPANET/compile.sh +4 -0
  75. epyt_flow/VERSION +1 -0
  76. epyt_flow/__init__.py +24 -0
  77. epyt_flow/data/__init__.py +0 -0
  78. epyt_flow/data/benchmarks/__init__.py +11 -0
  79. epyt_flow/data/benchmarks/batadal.py +257 -0
  80. epyt_flow/data/benchmarks/batadal_data.py +28 -0
  81. epyt_flow/data/benchmarks/battledim.py +473 -0
  82. epyt_flow/data/benchmarks/battledim_data.py +51 -0
  83. epyt_flow/data/benchmarks/gecco_water_quality.py +267 -0
  84. epyt_flow/data/benchmarks/leakdb.py +592 -0
  85. epyt_flow/data/benchmarks/leakdb_data.py +18923 -0
  86. epyt_flow/data/benchmarks/water_usage.py +123 -0
  87. epyt_flow/data/networks.py +650 -0
  88. epyt_flow/gym/__init__.py +4 -0
  89. epyt_flow/gym/control_gyms.py +47 -0
  90. epyt_flow/gym/scenario_control_env.py +101 -0
  91. epyt_flow/metrics.py +404 -0
  92. epyt_flow/models/__init__.py +2 -0
  93. epyt_flow/models/event_detector.py +31 -0
  94. epyt_flow/models/sensor_interpolation_detector.py +118 -0
  95. epyt_flow/rest_api/__init__.py +4 -0
  96. epyt_flow/rest_api/base_handler.py +70 -0
  97. epyt_flow/rest_api/res_manager.py +95 -0
  98. epyt_flow/rest_api/scada_data_handler.py +476 -0
  99. epyt_flow/rest_api/scenario_handler.py +352 -0
  100. epyt_flow/rest_api/server.py +106 -0
  101. epyt_flow/serialization.py +438 -0
  102. epyt_flow/simulation/__init__.py +5 -0
  103. epyt_flow/simulation/events/__init__.py +6 -0
  104. epyt_flow/simulation/events/actuator_events.py +259 -0
  105. epyt_flow/simulation/events/event.py +81 -0
  106. epyt_flow/simulation/events/leakages.py +404 -0
  107. epyt_flow/simulation/events/sensor_faults.py +267 -0
  108. epyt_flow/simulation/events/sensor_reading_attack.py +185 -0
  109. epyt_flow/simulation/events/sensor_reading_event.py +170 -0
  110. epyt_flow/simulation/events/system_event.py +88 -0
  111. epyt_flow/simulation/parallel_simulation.py +147 -0
  112. epyt_flow/simulation/scada/__init__.py +3 -0
  113. epyt_flow/simulation/scada/advanced_control.py +134 -0
  114. epyt_flow/simulation/scada/scada_data.py +1589 -0
  115. epyt_flow/simulation/scada/scada_data_export.py +255 -0
  116. epyt_flow/simulation/scenario_config.py +608 -0
  117. epyt_flow/simulation/scenario_simulator.py +1897 -0
  118. epyt_flow/simulation/scenario_visualizer.py +61 -0
  119. epyt_flow/simulation/sensor_config.py +1289 -0
  120. epyt_flow/topology.py +290 -0
  121. epyt_flow/uncertainty/__init__.py +3 -0
  122. epyt_flow/uncertainty/model_uncertainty.py +302 -0
  123. epyt_flow/uncertainty/sensor_noise.py +73 -0
  124. epyt_flow/uncertainty/uncertainties.py +555 -0
  125. epyt_flow/uncertainty/utils.py +206 -0
  126. epyt_flow/utils.py +306 -0
  127. epyt_flow-0.1.0.dist-info/LICENSE +21 -0
  128. epyt_flow-0.1.0.dist-info/METADATA +139 -0
  129. epyt_flow-0.1.0.dist-info/RECORD +131 -0
  130. epyt_flow-0.1.0.dist-info/WHEEL +5 -0
  131. epyt_flow-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,206 @@
1
+ """
2
+ Module provides some helper functions regarding the implementation of uncertainty.
3
+ """
4
+ import numpy as np
5
+ from scipy.ndimage import gaussian_filter1d
6
+
7
+
8
+ def smoothing(pattern: np.ndarray, sigma: float = 10.) -> np.ndarray:
9
+ """
10
+ Smoothes a given pattern by applying a Gaussian filter.
11
+
12
+ Parameters
13
+ ----------
14
+ pattern : `numpy.ndarray`
15
+ The original pattern
16
+ sigma : `float`, optional
17
+ Standard deviation for the Gaussian filter.
18
+
19
+ The default is 10.
20
+
21
+ Returns
22
+ -------
23
+ `numpy.ndarray`
24
+ The smoothed pattern.
25
+ """
26
+ return gaussian_filter1d(pattern, sigma=sigma)
27
+
28
+
29
+ def scale_to_range(pattern: np.ndarray, min_value: float, max_value: float) -> np.ndarray:
30
+ """
31
+ Scales a given pattern to an interval.
32
+
33
+ Parameters
34
+ ----------
35
+ pattern : `numpy.ndarray`
36
+ The pattern to be scaled.
37
+ min_value : `float`
38
+ Lower bound of the pattern.
39
+ max_value : `float`
40
+ Upper bound of the pattern.
41
+
42
+ Returns
43
+ -------
44
+ `numpy.ndarray`
45
+ The scaled pattern.
46
+ """
47
+ if min_value is None or max_value is None:
48
+ return pattern
49
+
50
+ min_pattern_val = np.min(pattern)
51
+ max_pattern_val = np.max(pattern)
52
+
53
+ return [(x - min_pattern_val) / (max_pattern_val - min_pattern_val) * (max_value - min_value)
54
+ + min_value for x in pattern]
55
+
56
+
57
+ def generate_random_gaussian_noise(n_samples: int):
58
+ """
59
+ Generates Gaussian noise using a random mean ([0,1]) and random standard deviation ([0,1]).
60
+
61
+ Parameters
62
+ ----------
63
+ n_samples : `int`
64
+ Number of random samples.
65
+
66
+ Returns
67
+ -------
68
+ `numpy.ndarray`
69
+ Gaussian noise.
70
+ """
71
+ return np.random.normal(np.random.rand(), np.random.rand(), size=n_samples)
72
+
73
+
74
+ def generate_deep_random_gaussian_noise(n_samples: int, mean: float = None):
75
+ """
76
+ Generates random Gaussian noise where the standard deviations (and mean) are changing over time.
77
+
78
+ Parameters
79
+ ----------
80
+ n_samples : `int`
81
+ Number of random samples.
82
+ mean : `float`, optional
83
+ Fixed mean at all points in time.
84
+ If None, random means are generated.
85
+
86
+ The default is None.
87
+
88
+ Returns
89
+ -------
90
+ `numpy.ndarray`
91
+ Random Gaussian noise.
92
+ """
93
+ noise = []
94
+
95
+ if mean is None:
96
+ mean = create_deep_random_pattern(n_samples, min_value=-1., max_value=1.)
97
+ else:
98
+ mean = [mean] * n_samples
99
+ rand_std = create_deep_random_pattern(n_samples)
100
+ noise = np.array([np.random.normal(m, s) for m, s in zip(mean, rand_std)])
101
+
102
+ return noise
103
+
104
+
105
+ def create_deep_random_pattern(n_samples: int, min_value: float = 0., max_value: float = 1.,
106
+ init_value: float = None) -> np.ndarray:
107
+ """
108
+ Generates a random pattern.
109
+
110
+ Parameters
111
+ ----------
112
+ n_samples : `int`
113
+ Number of random samples -- i.e. length of the pattern.
114
+ min_value : `float`, optional
115
+ Lower bound of the pattern.
116
+
117
+ The default is zero.
118
+ max_value : `float`, optional
119
+ Upper bound of the pattern.
120
+
121
+ The default is one.
122
+ init_value : `float`, optional
123
+ Value of the first sample in the pattern.
124
+ If None, a random value is used.
125
+
126
+ The default is None.
127
+
128
+ Returns
129
+ -------
130
+ `numpy.ndarray`
131
+ Random pattern.
132
+ """
133
+ pattern = []
134
+ start_value = init_value
135
+
136
+ while len(pattern) < n_samples:
137
+ if len(pattern) != 0:
138
+ start_value = pattern[-1]
139
+
140
+ pattern += _create_deep_random_pattern(start_value, min_value=min_value,
141
+ max_value=max_value)
142
+
143
+ pattern = pattern[:n_samples]
144
+
145
+ # Scaling to value range
146
+ pattern = scale_to_range(pattern, min_value, max_value)
147
+
148
+ return np.array(pattern)
149
+
150
+
151
+ def _create_deep_random_pattern(start_value: float = None, min_length: int = 2, max_length: int = 5,
152
+ min_value: float = None, max_value: float = None) -> np.ndarray:
153
+ """
154
+ Generates a random pattern of random length.
155
+
156
+ Parameters
157
+ ----------
158
+ start_value : `float`, optional
159
+ First value in the pattern.
160
+ If None, a random value is used.
161
+
162
+ The default is None.
163
+ min_length : `int`, optional
164
+ Minium length of the pattern.
165
+
166
+ The default is 2.
167
+ max_length : `int`
168
+ Maximum length of the pattern.
169
+
170
+ The default is 5.
171
+ min_value : `float`, optional
172
+ Lower bound of the pattern.
173
+
174
+ The default is zero.
175
+ max_value : `float`, optional
176
+ Upper bound of the pattern.
177
+
178
+ The default is one.
179
+
180
+ Returns
181
+ -------
182
+ `numpy.ndarray`
183
+ Random pattern.
184
+ """
185
+ pattern = []
186
+
187
+ # Random parameters of pattern
188
+ if start_value is None:
189
+ start_value = np.random.rand()
190
+ length = np.random.randint(low=min_length, high=max_length)
191
+ vec = np.random.choice([-.1, .1])
192
+
193
+ # Generate pattern
194
+ cur_value = start_value
195
+ pattern.append(start_value)
196
+
197
+ for _ in range(length):
198
+ cur_value = cur_value + np.random.rand() * vec
199
+ pattern.append(cur_value)
200
+ if min_value is not None and max_value is not None:
201
+ if cur_value < min_value:
202
+ vec = .1
203
+ elif cur_value > max_value:
204
+ vec = -.1
205
+
206
+ return pattern
epyt_flow/utils.py ADDED
@@ -0,0 +1,306 @@
1
+ """
2
+ Module provides helper functions.
3
+ """
4
+ import os
5
+ import math
6
+ import tempfile
7
+ import zipfile
8
+ from pathlib import Path
9
+ import requests
10
+ from tqdm import tqdm
11
+ import numpy as np
12
+ import matplotlib.pyplot as plt
13
+
14
+
15
+ def time_points_to_one_hot_encoding(time_points: list[int], total_length: int) -> list[int]:
16
+ """
17
+ Converts a list of time points into a one-hot-encoding.
18
+
19
+ Parameters
20
+ ----------
21
+ time_points : `list[int]`
22
+ Time points to be one-hot-encoded.
23
+ total_length : `int`
24
+ Length of final one-hot-encoding.
25
+
26
+ Returns
27
+ -------
28
+ `list[int]`
29
+ One-hot-encoded time points.
30
+ """
31
+ results = [0] * total_length
32
+
33
+ for t in time_points:
34
+ results[t] = 1
35
+
36
+ return results
37
+
38
+
39
+ def volume_to_level(tank_volume: float, tank_diameter: float) -> float:
40
+ """
41
+ Computes the water level in a tank containing a given volume of water.
42
+
43
+ Parameters
44
+ ----------
45
+ tank_volume : `float`
46
+ Water volume in the tank.
47
+ tank_diameter : `float`
48
+ Diameter of the tank.
49
+
50
+ Returns
51
+ -------
52
+ `float`
53
+ Water level in tank.
54
+ """
55
+ if not isinstance(tank_volume, float):
56
+ raise TypeError("'tank_volume' must be an instace of 'float' " +
57
+ f"but not of '{type(tank_volume)}'")
58
+ if tank_volume < 0:
59
+ raise ValueError("'tank_volume' can not be negative")
60
+ if not isinstance(tank_diameter, float):
61
+ raise TypeError("'tank_diameter' must be an instace of 'float' " +
62
+ f"but not of '{type(tank_diameter)}'")
63
+ if tank_diameter <= 0:
64
+ raise ValueError("'tank_diameter' must be greater than zero")
65
+
66
+ return (4. / (math.pow(tank_diameter, 2) * math.pi)) * tank_volume
67
+
68
+
69
+ def plot_timeseries_data(data: np.ndarray, labels: list[str] = None, x_axis_label: str = None,
70
+ y_axis_label: str = None, show: bool = True) -> None:
71
+ """
72
+ Plots a single or multiple time series.
73
+
74
+ Parameters
75
+ ----------
76
+ data : `numpy.ndarray`
77
+ Time series data -- each row in `data` corresponds to a complete time series.
78
+ labels : `list[str]`, optional
79
+ Labels for each time series in `data`.
80
+ If None, no labels are shown.
81
+
82
+ The default is None.
83
+ x_axis_label : `str`, optional
84
+ X axis label.
85
+
86
+ The default is None.
87
+ y_axis_label : `str`, optional
88
+ Y axis label.
89
+
90
+ The default is None.
91
+ show : `bool`, optional
92
+ If True, the plot/figure is shown in a window.
93
+
94
+ The default is True.
95
+ """
96
+ if not isinstance(data, np.ndarray):
97
+ raise TypeError(f"'data' must be an instance of 'numpy.ndarray' but not of '{type(data)}'")
98
+ if len(data.shape) != 2:
99
+ raise ValueError("'data' must be a 2d array where each row corresponds to a time series " +
100
+ "-- use '.reshape(1, -1)' in case of single time series")
101
+ if labels is not None:
102
+ if not isinstance(labels, list) or not all(isinstance(label, str) for label in labels):
103
+ raise TypeError("'labels' must be a instance of 'list[str]'")
104
+ if x_axis_label is not None:
105
+ if not isinstance(x_axis_label, str):
106
+ raise TypeError("'x_axis_label' must be an instance of 'str' " +
107
+ f"but not of '{type(x_axis_label)}'")
108
+ if y_axis_label is not None:
109
+ if not isinstance(y_axis_label, str):
110
+ raise TypeError("'y_axis_label' must be an instance of 'str' " +
111
+ f"but not of '{type(y_axis_label)}'")
112
+ if not isinstance(show, bool):
113
+ raise TypeError(f"'show' must be an instance of 'bool' but not of '{type(show)}'")
114
+
115
+ plt.figure()
116
+
117
+ labels = labels if labels is not None else [None] * data.shape[0]
118
+
119
+ for i in range(data.shape[0]):
120
+ plt.plot(data[i, :], ".-", label=labels[i])
121
+
122
+ if not any(label is None for label in labels):
123
+ plt.legend()
124
+
125
+ if x_axis_label is not None:
126
+ plt.xlabel(x_axis_label)
127
+ if y_axis_label is not None:
128
+ plt.ylabel(y_axis_label)
129
+
130
+ if show is True:
131
+ plt.show()
132
+
133
+
134
+ def plot_timeseries_prediction(y: np.ndarray, y_pred: np.ndarray,
135
+ confidence_interval: np.ndarray = None, show: bool = True) -> None:
136
+ """
137
+ Plots the prediction (e.g. forecast) of *single* time series together with the
138
+ ground truth time series. In addition, confidence intervals can be plotted as well.
139
+
140
+ Parameters
141
+ ----------
142
+ y : `numpy.ndarray`
143
+ Ground truth values.
144
+ y_pred : `numpy.ndarray`
145
+ Predicted values.
146
+ confidence_interval : `numpy.ndarray`, optional
147
+ Confidence interval (upper and lower value) for each prediction in `y_pred`.
148
+ If not None, the confidence interval is plotted as well.
149
+
150
+ The default is None.
151
+ show : `bool`, optional
152
+ If True, the plot/figure is shown in a window.
153
+
154
+ The default is True.
155
+ """
156
+ if not isinstance(y_pred, np.ndarray):
157
+ raise TypeError("'y_pred' must be an instance of 'numpy.ndarray' " +
158
+ f"but not of '{type(y_pred)}'")
159
+ if not isinstance(y, np.ndarray):
160
+ raise TypeError("'y' must be an instance of 'numpy.ndarray' " +
161
+ f"but not of '{type(y)}'")
162
+ if y_pred.shape != y.shape:
163
+ raise ValueError(f"Shape mismatch: {y_pred.shape} vs. {y.shape}")
164
+ if len(y_pred.shape) != 1:
165
+ raise ValueError("'y_pred' must be a 1d array")
166
+ if len(y.shape) != 1:
167
+ raise ValueError("'y' must be a 1d array")
168
+ if not isinstance(show, bool):
169
+ raise TypeError(f"'show' must be an instance of 'bool' but not of '{type(show)}'")
170
+
171
+ plt.figure()
172
+
173
+ if confidence_interval is not None:
174
+ plt.fill_between(range(len(y_pred)),
175
+ y_pred - confidence_interval[0],
176
+ y_pred + confidence_interval[1],
177
+ alpha=0.5)
178
+ plt.plot(y_pred, ".-", label="Prediction")
179
+ plt.plot(y, ".-", label="Ground truth")
180
+ plt.legend()
181
+
182
+ if show is True:
183
+ plt.show()
184
+
185
+
186
+ def download_if_necessary(download_path: str, url: str, verbose: bool = True) -> None:
187
+ """
188
+ Downloads a file from a given URL if it does not already exist in a given path.
189
+
190
+ Note that if the path (folder) does not already exist, it will be created.
191
+
192
+ Parameters
193
+ ----------
194
+ download_path : `str`
195
+ Local path to the file -- if this path does not exist, the file will be downloaded from
196
+ the provided 'url' and stored in 'download_dir'.
197
+ url : `str`
198
+ Web-URL.
199
+ verbose : `bool`, optional
200
+ If True, a progress bar is shown while downloading the file.
201
+
202
+ The default is True.
203
+ """
204
+ folder_path = str(Path(download_path).parent.absolute())
205
+ create_path_if_not_exist(folder_path)
206
+
207
+ if not os.path.isfile(download_path):
208
+ response = requests.get(url, stream=verbose, allow_redirects=True, timeout=1000)
209
+
210
+ if verbose is True:
211
+ content_length = int(response.headers.get('content-length', 0))
212
+ with open(download_path, "wb") as file, tqdm(desc=download_path,
213
+ total=content_length,
214
+ unit='B',
215
+ unit_scale=True,
216
+ unit_divisor=1024) as progress_bar:
217
+ for data in response.iter_content(chunk_size=1024):
218
+ size = file.write(data)
219
+ progress_bar.update(size)
220
+ else:
221
+ with open(download_path, "wb") as f_out:
222
+ f_out.write(response.content)
223
+
224
+
225
+ def create_path_if_not_exist(path_in: str) -> None:
226
+ """
227
+ Creates a directory and all its parent directories if they do not already exist.
228
+
229
+ Parameters
230
+ ----------
231
+ path_in : `str`
232
+ Path to be created.
233
+ """
234
+ Path(path_in).mkdir(parents=True, exist_ok=True)
235
+
236
+
237
+ def unpack_zip_archive(f_in: str, folder_out: str) -> None:
238
+ """
239
+ Unpacks a .zip archive.
240
+
241
+ Parameters
242
+ ----------
243
+ f_in : `str`
244
+ Path to the .zip file.
245
+ folder_out : `str`
246
+ Path to the folder where the unpacked files will be stored.
247
+ """
248
+ with zipfile.ZipFile(f_in, "r") as f:
249
+ f.extractall(folder_out)
250
+
251
+
252
+ def get_temp_folder() -> str:
253
+ """
254
+ Gets a path to a temporary folder -- i.e. a folder for storing temporary files.
255
+
256
+ Returns
257
+ -------
258
+ `str`
259
+ Path to a temporary folder.
260
+ """
261
+ return tempfile.gettempdir()
262
+
263
+
264
+ def to_seconds(days: int = None, hours: int = None, minutes: int = None) -> int:
265
+ """
266
+ Converts a timestamp (i.e. days, hours, minutes) into seconds.
267
+
268
+ Parameters
269
+ ----------
270
+ days : `int`, optional
271
+ Days.
272
+ hours : `int`, optional
273
+ Hours.
274
+ minutes : `int`, optional
275
+ Minutes.
276
+
277
+ Returns
278
+ -------
279
+ `int`
280
+ Timestamp in seconds.
281
+ """
282
+ sec = 0
283
+
284
+ if days is not None:
285
+ if not isinstance(days, int):
286
+ raise TypeError(f"'days' must be an instance of 'int' but not of {type(days)}")
287
+ if days <= 0:
288
+ raise ValueError("'days' must be positive")
289
+
290
+ sec += 24*60*60 * days
291
+ if hours is not None:
292
+ if not isinstance(hours, int):
293
+ raise TypeError(f"'hours' must be an instance of 'int' but not of {type(hours)}")
294
+ if hours <= 0:
295
+ raise ValueError("'hours' must be positive")
296
+
297
+ sec += 60*60 * hours
298
+ if minutes is not None:
299
+ if not isinstance(minutes, int):
300
+ raise TypeError(f"'minutes' must be an instance of 'int' but not of {type(minutes)}")
301
+ if minutes <= 0:
302
+ raise ValueError("'minutes' must be positive")
303
+
304
+ sec += 60 * minutes
305
+
306
+ return sec
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 EPyT-Flow Developers
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,139 @@
1
+ Metadata-Version: 2.1
2
+ Name: epyt-flow
3
+ Version: 0.1.0
4
+ Summary: EPyT-Flow -- EPANET Python Toolkit - Flow
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
+ License: MIT License
7
+ Project-URL: Homepage, https://github.com/WaterFutures/EPyT-Flow
8
+ Project-URL: Documentation, https://epytflow.readthedocs.io/en/latest/
9
+ Project-URL: Repository, https://github.com/WaterFutures/EPyT-Flow.git
10
+ Project-URL: Issues, https://github.com/WaterFutures/EPyT-Flow/issues
11
+ Keywords: epanet,water,networks,hydraulics,quality,simulations
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: epyt >=1.1.3
24
+ Requires-Dist: requests >=2.31.0
25
+ Requires-Dist: scipy >=1.11.4
26
+ Requires-Dist: u-msgpack-python >=2.8.0
27
+ Requires-Dist: networkx >=3.2.1
28
+ Requires-Dist: scikit-learn >=1.4.0
29
+ Requires-Dist: tqdm >=4.66.2
30
+ Requires-Dist: openpyxl >=3.1.2
31
+ Requires-Dist: falcon >=3.1.3
32
+ Requires-Dist: multiprocess >=0.70.16
33
+ Requires-Dist: psutil
34
+
35
+ # EPyT-Flow -- EPANET Python Toolkit - Flow
36
+
37
+ EPyT-Flow is a Python package building on top of [EPyT](https://github.com/OpenWaterAnalytics/EPyT)
38
+ for providing easy access to water distribution network simulations.
39
+ It aims to provide a high-level interface for the easy generation of hydraulic and water quality scenario data.
40
+ However, it also provides access to low-level functions by [EPANET](https://github.com/USEPA/EPANET2.2)
41
+ and [EPANET-MSX](https://github.com/USEPA/EPANETMSX/).
42
+
43
+ EPyT-Flow provides easy access to popular benchmark data sets for event detection and localization.
44
+ Furthermore, it also provides an environment for developing and testing control algorithms.
45
+
46
+ ![](https://github.com/WaterFutures/EPyT-Flow/blob/main/docs/_static/net1_plot.png?raw=true)
47
+
48
+ ## Installation
49
+
50
+ EPyT-Flow supports Python 3.9 - 3.12
51
+
52
+ Note that [EPANET and EPANET-MSX sources](epyt_flow/EPANET/) are compiled and overrite the binaries
53
+ shipped by EPyT IF EPyT-Flow is installed on a Linux system. By this we not only aim to achieve
54
+ a better performance of the simulations but also avoid any compatibility problems of pre-compiled binaries.
55
+
56
+ ### PyPI
57
+
58
+ ```
59
+ pip install epyt-flow
60
+ ```
61
+
62
+ ### Git
63
+ Download or clone the repository:
64
+ ```
65
+ git clone https://github.com/WaterFutures/EPyT-Flow.git
66
+ cd EPyT-Flow
67
+ ```
68
+
69
+ Install all requirements as listed in [REQUIREMENTS.txt](REQUIREMENTS.txt):
70
+ ```
71
+ pip install -r REQUIREMENTS.txt
72
+ ```
73
+
74
+ Install the toolbox:
75
+ ```
76
+ pip install .
77
+ ```
78
+
79
+ ## Quick Example
80
+
81
+ <a target="_blank" href="https://colab.research.google.com/github/WaterFutures/EPyT-Flow/blob/main/docs/examples/basic_usage.ipynb">
82
+ <img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/>
83
+ </a>
84
+
85
+ ```python
86
+ from epyt_flow.data.benchmarks import load_leakdb_scenarios
87
+ from epyt_flow.simulation import ScenarioSimulator
88
+ from epyt_flow.utils import to_seconds
89
+
90
+
91
+ if __name__ == "__main__":
92
+ # Load first Hanoi scenario from LeakDB
93
+ network_config, = load_leakdb_scenarios(scenarios_id=["1"], use_net1=False)
94
+
95
+ # Create scenario
96
+ with ScenarioSimulator(scenario_config=network_config) as sim:
97
+ # Set simulation duration to two days
98
+ sim.set_general_parameters(simulation_duration=to_seconds(days=2))
99
+
100
+ # Place pressure sensors at nodes "13", "16", "22", and "30"
101
+ sim.set_pressure_sensors(sensor_locations=["13", "16", "22", "30"])
102
+
103
+ # Place a flow sensor at link/pipe "1"
104
+ sim.set_flow_sensors(sensor_locations=["1"])
105
+
106
+ # Run entire simulation
107
+ scada_data = sim.run_simulation()
108
+
109
+ # Show sensor readings over the entire simulation
110
+ print(f"Pressure readings: {scada_data.get_data_pressures()}")
111
+ print(f"Flow readings: {scada_data.get_data_flows()}")
112
+ ```
113
+
114
+ ## Documentation
115
+
116
+ Documentation is available on readthedocs: [https://epytflow.readthedocs.io/en/latest/](https://epytflow.readthedocs.io/en/latest/)
117
+
118
+ ## License
119
+
120
+ MIT license -- see [LICENSE](LICENSE)
121
+
122
+ ## How to Cite?
123
+
124
+ If you use this software, please cite it as follows:
125
+
126
+ ```
127
+ @misc{github:epytflow,
128
+ author = {André Artelt, Marios S. Kyriakou, Stelios G. Vrachimis, Demetrios G. Eliades, Barbara Hammer, Marios M. Polycarpou},
129
+ title = {EPyT-Flow -- EPANET Python Toolkit - Flow},
130
+ year = {2024},
131
+ publisher = {GitHub},
132
+ journal = {GitHub repository},
133
+ howpublished = {\url{https://github.com/WaterFutures/EPyT-Flow}}
134
+ }
135
+ ```
136
+
137
+ ## How to Contribute?
138
+
139
+ 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).