epyt-flow 0.14.0__py3-none-any.whl → 0.14.1__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 (60) hide show
  1. epyt_flow/EPANET/EPANET/SRC_engines/AUTHORS +40 -8
  2. epyt_flow/EPANET/EPANET/SRC_engines/LICENSE +3 -3
  3. epyt_flow/EPANET/EPANET/SRC_engines/enumstxt.h +24 -7
  4. epyt_flow/EPANET/EPANET/SRC_engines/epanet.c +726 -374
  5. epyt_flow/EPANET/EPANET/SRC_engines/epanet2.c +128 -32
  6. epyt_flow/EPANET/EPANET/SRC_engines/errors.dat +7 -1
  7. epyt_flow/EPANET/EPANET/SRC_engines/flowbalance.c +186 -0
  8. epyt_flow/EPANET/EPANET/SRC_engines/funcs.h +40 -14
  9. epyt_flow/EPANET/EPANET/SRC_engines/hash.c +177 -177
  10. epyt_flow/EPANET/EPANET/SRC_engines/hash.h +28 -28
  11. epyt_flow/EPANET/EPANET/SRC_engines/hydcoeffs.c +192 -40
  12. epyt_flow/EPANET/EPANET/SRC_engines/hydraul.c +101 -46
  13. epyt_flow/EPANET/EPANET/SRC_engines/hydsolver.c +85 -24
  14. epyt_flow/EPANET/EPANET/SRC_engines/hydstatus.c +29 -63
  15. epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2.h +70 -37
  16. epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_2.h +408 -234
  17. epyt_flow/EPANET/EPANET/SRC_engines/include/epanet2_enums.h +87 -37
  18. epyt_flow/EPANET/EPANET/SRC_engines/inpfile.c +153 -79
  19. epyt_flow/EPANET/EPANET/SRC_engines/input1.c +59 -94
  20. epyt_flow/EPANET/EPANET/SRC_engines/input2.c +73 -202
  21. epyt_flow/EPANET/EPANET/SRC_engines/input3.c +446 -351
  22. epyt_flow/EPANET/EPANET/SRC_engines/leakage.c +527 -0
  23. epyt_flow/EPANET/EPANET/SRC_engines/mempool.c +8 -4
  24. epyt_flow/EPANET/EPANET/SRC_engines/mempool.h +23 -23
  25. epyt_flow/EPANET/EPANET/SRC_engines/output.c +5 -4
  26. epyt_flow/EPANET/EPANET/SRC_engines/project.c +407 -75
  27. epyt_flow/EPANET/EPANET/SRC_engines/quality.c +12 -2
  28. epyt_flow/EPANET/EPANET/SRC_engines/qualreact.c +70 -13
  29. epyt_flow/EPANET/EPANET/SRC_engines/qualroute.c +7 -5
  30. epyt_flow/EPANET/EPANET/SRC_engines/report.c +88 -20
  31. epyt_flow/EPANET/EPANET/SRC_engines/rules.c +144 -6
  32. epyt_flow/EPANET/EPANET/SRC_engines/smatrix.c +19 -19
  33. epyt_flow/EPANET/EPANET/SRC_engines/text.h +16 -5
  34. epyt_flow/EPANET/EPANET/SRC_engines/types.h +73 -19
  35. epyt_flow/EPANET/EPANET/SRC_engines/util/cstr_helper.c +59 -0
  36. epyt_flow/EPANET/EPANET/SRC_engines/util/cstr_helper.h +38 -0
  37. epyt_flow/EPANET/EPANET/SRC_engines/util/errormanager.c +92 -0
  38. epyt_flow/EPANET/EPANET/SRC_engines/util/errormanager.h +39 -0
  39. epyt_flow/EPANET/EPANET/SRC_engines/util/filemanager.c +212 -0
  40. epyt_flow/EPANET/EPANET/SRC_engines/util/filemanager.h +81 -0
  41. epyt_flow/EPANET/EPANET/SRC_engines/validate.c +408 -0
  42. epyt_flow/EPANET/compile_linux.sh +1 -1
  43. epyt_flow/EPANET/compile_macos.sh +1 -1
  44. epyt_flow/VERSION +1 -1
  45. epyt_flow/gym/scenario_control_env.py +26 -3
  46. epyt_flow/simulation/events/quality_events.py +6 -6
  47. epyt_flow/simulation/events/sensor_faults.py +24 -24
  48. epyt_flow/simulation/events/system_event.py +3 -3
  49. epyt_flow/simulation/scada/scada_data.py +1 -1
  50. epyt_flow/simulation/scenario_simulator.py +14 -11
  51. epyt_flow/topology.py +8 -1
  52. epyt_flow/uncertainty/model_uncertainty.py +292 -150
  53. {epyt_flow-0.14.0.dist-info → epyt_flow-0.14.1.dist-info}/METADATA +2 -2
  54. {epyt_flow-0.14.0.dist-info → epyt_flow-0.14.1.dist-info}/RECORD +57 -51
  55. epyt_flow/EPANET/EPANET/SRC_engines/Readme_SRC_Engines.txt +0 -18
  56. epyt_flow/EPANET/EPANET/SRC_engines/epanet2.def +0 -131
  57. epyt_flow/EPANET/EPANET/SRC_engines/main.c +0 -93
  58. {epyt_flow-0.14.0.dist-info → epyt_flow-0.14.1.dist-info}/WHEEL +0 -0
  59. {epyt_flow-0.14.0.dist-info → epyt_flow-0.14.1.dist-info}/licenses/LICENSE +0 -0
  60. {epyt_flow-0.14.0.dist-info → epyt_flow-0.14.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,408 @@
1
+ /*
2
+ ******************************************************************************
3
+ Project: OWA EPANET
4
+ Version: 2.3
5
+ Module: validate.c
6
+ Description: validates project data
7
+ Authors: see AUTHORS
8
+ Copyright: see AUTHORS
9
+ License: see LICENSE
10
+ Last Updated: 03/18/2024
11
+ ******************************************************************************
12
+ */
13
+
14
+ #include <stdlib.h>
15
+ #include <stdio.h>
16
+ #include <string.h>
17
+ #include <math.h>
18
+
19
+ #include "types.h"
20
+ #include "funcs.h"
21
+ #include "text.h"
22
+
23
+ // Exported functions
24
+ int validateproject(Project *);
25
+ void reindextanks(Project *);
26
+
27
+ int validatetanks(Project *pr)
28
+ /*
29
+ **-------------------------------------------------------------------
30
+ ** Input: none
31
+ ** Output: returns 1 if successful, 0 if not
32
+ ** Purpose: checks for valid tank levels.
33
+ **-------------------------------------------------------------------
34
+ */
35
+ {
36
+ Network *net = &pr->network;
37
+ int i, j, n, result = 1, levelerr;
38
+ char errmsg[MAXMSG+1] = "";
39
+ Stank *tank;
40
+ Scurve *curve;
41
+ double elev;
42
+
43
+ for (j = 1; j <= net->Ntanks; j++)
44
+ {
45
+ tank = &net->Tank[j];
46
+ if (tank->A == 0.0) continue; // Skip reservoirs
47
+
48
+ // Check for valid lower/upper tank levels
49
+ levelerr = 0;
50
+ if (tank->H0 > tank->Hmax ||
51
+ tank->Hmin > tank->Hmax ||
52
+ tank->H0 < tank->Hmin
53
+ ) levelerr = 1;
54
+
55
+ // Check that tank heights are within volume curve
56
+ elev = net->Node[tank->Node].El;
57
+ i = tank->Vcurve;
58
+ if (i > 0)
59
+ {
60
+ curve = &net->Curve[i];
61
+ n = curve->Npts - 1;
62
+ if ((tank->Hmin - elev) * pr->Ucf[ELEV] < curve->X[0] - TINY ||
63
+ (tank->Hmax - elev) * pr->Ucf[ELEV] > curve->X[n] + TINY)
64
+ {
65
+ levelerr = 1;
66
+ }
67
+ }
68
+
69
+ // Report error in levels if found
70
+ if (levelerr)
71
+ {
72
+ sprintf(pr->Msg, "Error 225: %s node %s", geterrmsg(225, errmsg),
73
+ net->Node[tank->Node].ID);
74
+ writeline(pr, pr->Msg);
75
+ result = 0;
76
+ }
77
+ }
78
+ return result;
79
+ }
80
+
81
+ int validatepatterns(Project *pr)
82
+ /*
83
+ **-------------------------------------------------------------------
84
+ ** Input: none
85
+ ** Output: returns 1 if successful, 0 if not
86
+ ** Purpose: checks if time patterns have data.
87
+ **-------------------------------------------------------------------
88
+ */
89
+ {
90
+ Network *net = &pr->network;
91
+ int j, result = 1;
92
+ char errmsg[MAXMSG+1] = "";
93
+
94
+ if (pr->network.Pattern != NULL)
95
+ {
96
+ for (j = 0; j <= pr->network.Npats; j++)
97
+ {
98
+ if (pr->network.Pattern[j].Length == 0)
99
+ {
100
+ sprintf(pr->Msg, "Error 232: %s %s", geterrmsg(232, errmsg),
101
+ pr->network.Pattern[j].ID);
102
+ writeline(pr, pr->Msg);
103
+ result = 0;
104
+ }
105
+ }
106
+ }
107
+ return result;
108
+ }
109
+
110
+ int validatecurves(Project *pr)
111
+ /*
112
+ **-------------------------------------------------------------------
113
+ ** Input: none
114
+ ** Output: returns 1 if successful, 0 if not
115
+ ** Purpose: checks if data curves have data.
116
+ **-------------------------------------------------------------------
117
+ */
118
+ {
119
+ int i, j, npts, result = 1;
120
+ char errmsg[MAXMSG+1] = "";
121
+ Scurve *curve;
122
+
123
+ if (pr->network.Curve != NULL)
124
+ {
125
+ for (j = 1; j <= pr->network.Ncurves; j++)
126
+ {
127
+ // Check that curve has data
128
+ curve = &pr->network.Curve[j];
129
+ npts = curve->Npts;
130
+ if (npts == 0)
131
+ {
132
+ sprintf(pr->Msg, "Error 231: %s %s", geterrmsg(231, errmsg),
133
+ curve->ID);
134
+ writeline(pr, pr->Msg);
135
+ result = 0;
136
+ }
137
+
138
+ // Check that x values are increasing
139
+ for (i = 1; i < npts; i++)
140
+ {
141
+ if (curve->X[i-1] >= curve->X[i])
142
+ {
143
+ sprintf(pr->Msg, "Error 230: %s %s", geterrmsg(230, errmsg),
144
+ curve->ID);
145
+ writeline(pr, pr->Msg);
146
+ result = 0;
147
+ break;
148
+ }
149
+ }
150
+ }
151
+ }
152
+ return result;
153
+ }
154
+
155
+ int powerfuncpump(double h0, double h1, double h2, double q1, double q2,
156
+ double *a, double *b, double *c)
157
+ /*
158
+ **---------------------------------------------------------
159
+ ** Input: h0 = shutoff head
160
+ ** h1 = design head
161
+ ** h2 = head at max. flow
162
+ ** q1 = design flow
163
+ ** q2 = max. flow
164
+ ** Output: *a, *b, *c = pump curve coeffs. (H = a-bQ^c),
165
+ ** Returns 1 if successful, 0 otherwise.
166
+ ** Purpose: computes coeffs. for a power function pump curve
167
+ **----------------------------------------------------------
168
+ */
169
+ {
170
+ double h4, h5;
171
+
172
+ if (h0 < TINY || h0 - h1 < TINY || h1 - h2 < TINY ||
173
+ q1 < TINY || q2 - q1 < TINY
174
+ ) return 0;
175
+ *a = h0;
176
+ h4 = h0 - h1;
177
+ h5 = h0 - h2;
178
+ *c = log(h5 / h4) / log(q2 / q1);
179
+ if (*c <= 0.0 || *c > 20.0) return 0;
180
+ *b = -h4 / pow(q1, *c);
181
+ if (*b >= 0.0) return 0;
182
+ return 1;
183
+ }
184
+
185
+ int customcurvepump(Project *pr, Spump *pump, Scurve *curve)
186
+ /*
187
+ **-------------------------------------------------------------------
188
+ ** Input: pump = a pump object
189
+ ** curve = a data curve object
190
+ ** Output: returns an error code
191
+ ** Purpose: computes properties for a pump with a custom pump curve.
192
+ **-------------------------------------------------------------------
193
+ */
194
+ {
195
+ int m, npts = curve->Npts;
196
+ pump->Ptype = CUSTOM;
197
+ for (m = 1; m < npts; m++)
198
+ {
199
+ // Curve must have continuously decreasing head (the Y value)
200
+ if (curve->Y[m] >= curve->Y[m - 1])
201
+ {
202
+ pump->Ptype = NOCURVE;
203
+ return 227;
204
+ }
205
+ }
206
+ pump->Qmax = curve->X[npts - 1];
207
+ pump->Q0 = (curve->X[0] + pump->Qmax) / 2.0 / pr->Ucf[FLOW];
208
+ pump->Qmax /= pr->Ucf[FLOW];
209
+ pump->Hmax = curve->Y[0] / pr->Ucf[HEAD];
210
+ return 0;
211
+ }
212
+
213
+ int pumpcurvepump(Project *pr, Spump *pump, Scurve *curve)
214
+ /*
215
+ **-------------------------------------------------------------------
216
+ ** Input: pump = a pump object
217
+ ** curve = a data curve object
218
+ ** Output: returns an error code
219
+ ** Purpose: computes properties for a pump assigned a pump curve.
220
+ **-------------------------------------------------------------------
221
+ */
222
+ {
223
+ double a, b, c, h0 = 0.0, h1 = 0.0, h2 = 0.0, q1 = 0.0, q2 = 0.0;
224
+ int npts = curve->Npts;
225
+
226
+ curve->Type = PUMP_CURVE;
227
+
228
+ // Generic power function curve
229
+ if (npts == 1)
230
+ {
231
+ pump->Ptype = POWER_FUNC;
232
+ q1 = curve->X[0];
233
+ h1 = curve->Y[0];
234
+ h0 = 1.33334 * h1;
235
+ q2 = 2.0 * q1;
236
+ h2 = 0.0;
237
+ }
238
+
239
+ // 3 point curve with shutoff head
240
+ else if (npts == 3 && curve->X[0] == 0.0)
241
+ {
242
+ pump->Ptype = POWER_FUNC;
243
+ h0 = curve->Y[0];
244
+ q1 = curve->X[1];
245
+ h1 = curve->Y[1];
246
+ q2 = curve->X[2];
247
+ h2 = curve->Y[2];
248
+ }
249
+ else return customcurvepump(pr, pump, curve);
250
+
251
+ // Compute shape factors & limits of power function curves
252
+ if (!powerfuncpump(h0, h1, h2, q1, q2, &a, &b, &c))
253
+ {
254
+ pump->Ptype = NOCURVE;
255
+ return 227;
256
+ }
257
+ else
258
+ {
259
+ pump->H0 = -a / pr->Ucf[HEAD];
260
+ pump->R = -b * (pow(pr->Ucf[FLOW], c) / pr->Ucf[HEAD]);
261
+ pump->N = c;
262
+ pump->Q0 = q1 / pr->Ucf[FLOW];
263
+ pump->Qmax = pow((-a / b), (1.0 / c)) / pr->Ucf[FLOW];
264
+ pump->Hmax = h0 / pr->Ucf[HEAD];
265
+ }
266
+ return 0;
267
+ }
268
+
269
+ int constpowerpump(Project *pr, Spump *pump)
270
+ /*
271
+ **-------------------------------------------------------------------
272
+ ** Input: pump = a pump object
273
+ ** Output: returns an error code
274
+ ** Purpose: computes properties for a constant power pump.
275
+ **-------------------------------------------------------------------
276
+ */
277
+ {
278
+ pump->Ptype = CONST_HP;
279
+ pump->H0 = 0.0;
280
+ pump->R = -8.814 * pr->network.Link[pump->Link].Km / pr->Ucf[POWER];
281
+ pump->N = -1.0;
282
+ pump->Hmax = BIG; // No head limit
283
+ pump->Qmax = BIG; // No flow limit
284
+ pump->Q0 = 1.0; // Init. flow = 1 cfs
285
+ return 0;
286
+ }
287
+
288
+ int validatepumps(Project *pr)
289
+ /*
290
+ **-------------------------------------------------------------------
291
+ ** Input: none
292
+ ** Output: returns 1 if successful, 0 if not
293
+ ** Purpose: checks if pumps are assigned valid pump curve data.
294
+ **-------------------------------------------------------------------
295
+ */
296
+ {
297
+ Network *net = &pr->network;
298
+ int i, errcode, result = 1;
299
+ char errmsg[MAXMSG+1] = "";
300
+ Spump *pump;
301
+
302
+ for (i = 1; i <= net->Npumps; i++)
303
+ {
304
+ // Pump has a designated pump curve
305
+ pump = &net->Pump[i];
306
+ if (pump->Hcurve > 0)
307
+ errcode = pumpcurvepump(pr, pump, &net->Curve[pump->Hcurve]);
308
+
309
+ // Pump has a constant power setting
310
+ else if (net->Link[pump->Link].Km > 0.0)
311
+ errcode = constpowerpump(pr, pump);
312
+
313
+ // Pump has no pump curve info assigned
314
+ else
315
+ {
316
+ pump->Ptype = NOCURVE;
317
+ errcode = 226;
318
+ }
319
+
320
+ if (errcode)
321
+ {
322
+ sprintf(pr->Msg, "Error %d: %s %s",
323
+ errcode, geterrmsg(errcode, errmsg), net->Link[pump->Link].ID);
324
+ writeline(pr, pr->Msg);
325
+ result = 0;
326
+ }
327
+ }
328
+ return result;
329
+ }
330
+
331
+ int validateproject(Project *pr)
332
+ /*
333
+ *--------------------------------------------------------------
334
+ * Input: none
335
+ * Output: returns error code
336
+ * Purpose: checks for valid network data.
337
+ *--------------------------------------------------------------
338
+ */
339
+ {
340
+ int errcode = 0;
341
+ if (pr->network.Nnodes < 2) return 223;
342
+ if (pr->network.Ntanks == 0) return 224;
343
+ if (!validatetanks(pr)) errcode = 110;
344
+ if (!validatepumps(pr)) errcode = 110;
345
+ if (!validatepatterns(pr)) errcode = 110;
346
+ if (!validatecurves(pr)) errcode = 110;
347
+ return errcode;
348
+ }
349
+
350
+ void reindextanks(Project *pr)
351
+ /*
352
+ *--------------------------------------------------------------
353
+ * Input: none
354
+ * Output: none
355
+ * Purpose: adjusts tank node indexes when the number of
356
+ * junctions created from an input file is less than
357
+ * the total number of junction lines in the file.
358
+ *--------------------------------------------------------------
359
+ */
360
+ {
361
+ Network *net = &pr->network;
362
+ Parser *parser = &pr->parser;
363
+ Quality *qual = &pr->quality;
364
+ Scontrol *control;
365
+ int i, j, ndiff, n1, n2, size;
366
+
367
+ // ndiff = # unused entries in Node array before first tank node
368
+ ndiff = parser->MaxJuncs - net->Njuncs;
369
+ if (ndiff > 0)
370
+ {
371
+ for (i = 1; i <= net->Ntanks; ++i)
372
+ {
373
+ // n1 is current tank index in Node array, n2 is adjusted index
374
+ n1 = net->Tank[i].Node;
375
+ n2 = n1 - ndiff;
376
+
377
+ // Update the tank node's hash table entry
378
+ hashtable_update(net->NodeHashTable, net->Node[n1].ID, n2);
379
+
380
+ // Update the tank's node index
381
+ net->Tank[i].Node = n2;
382
+
383
+ // Re-position tank node in Node array
384
+ net->Node[n2] = net->Node[n1];
385
+
386
+ // Replace all references to old tank node index with new one
387
+ for (j = 1; j <= net->Nlinks; ++j)
388
+ {
389
+ if (net->Link[j].N1 == n1) net->Link[j].N1 = n2;
390
+ if (net->Link[j].N2 == n1) net->Link[j].N2 = n2;
391
+ }
392
+ for (j = 1; j <= net->Ncontrols; ++j)
393
+ {
394
+ control = &net->Control[j];
395
+ if (control->Node == n1) control->Node = n2;
396
+ }
397
+ adjusttankrules(pr, -ndiff);
398
+ if (qual->TraceNode == n1) qual->TraceNode = n2;
399
+ }
400
+
401
+ // Reallocate the Node array (shouldn't fail as new size < old size)
402
+ parser->MaxJuncs = net->Njuncs;
403
+ parser->MaxNodes = net->Njuncs + net->Ntanks;
404
+ size = (net->Nnodes + 2) * sizeof(Snode);
405
+ net->Node = (Snode *)realloc(net->Node, size);
406
+ }
407
+ }
408
+
@@ -1,4 +1,4 @@
1
1
  #!/bin/bash
2
2
  mkdir -p "../customlibs/"
3
- gcc -w -O3 -march=native -shared -Wl,-soname,libepanet2_2.so -fPIC -o "../customlibs/libepanet2_2.so" EPANET/SRC_engines/*.c -IEPANET/SRC_engines/include -lc -lm -pthread
3
+ gcc -w -O3 -march=native -shared -Wl,-soname,libepanet2_2.so -fPIC -o "../customlibs/libepanet2_2.so" EPANET/SRC_engines/*.c -IEPANET/SRC_engines/include EPANET/SRC_engines/util/*.c -IEPANET/SRC_engines/util/ -lc -lm -pthread
4
4
  gcc -w -O3 -march=native -fPIC -shared -Wl,-soname,libepanetmsx2_2_0.so -o "../customlibs/libepanetmsx2_2_0.so" -fopenmp -Depanetmsx_EXPORTS -IEPANET-MSX/Src/include -IEPANET/SRC_engines/include EPANET-MSX/Src/*.c -Wl,-rpath=. "../customlibs/libepanet2_2.so" -lm -lgomp -lpthread
@@ -1,4 +1,4 @@
1
1
  #!/bin/bash
2
2
  mkdir -p "../customlibs/"
3
- gcc-15 -w -O3 -march=native -dynamiclib -fPIC -install_name libepanet2_2.dylib -o "../customlibs/libepanet2_2.dylib" EPANET/SRC_engines/*.c -IEPANET/SRC_engines/include -lc -lm -pthread
3
+ gcc-15 -w -O3 -march=native -dynamiclib -fPIC -install_name libepanet2_2.dylib -o "../customlibs/libepanet2_2.dylib" EPANET/SRC_engines/*.c -IEPANET/SRC_engines/include EPANET/SRC_engines/util/*.c -IEPANET/SRC_engines/util/ -lc -lm -pthread
4
4
  gcc-15 -w -O3 -march=native -dynamiclib -fPIC -install_name libepanetmsx2_2_0.dylib -o "../customlibs/libepanetmsx2_2_0.dylib" -fopenmp -Depanetmsx_EXPORTS -IEPANET-MSX/Src/include -IEPANET/SRC_engines/include EPANET-MSX/Src/*.c -L'../customlibs' -lepanet2_2 -lm -lgomp -lpthread
epyt_flow/VERSION CHANGED
@@ -1 +1 @@
1
- 0.14.0
1
+ 0.14.1
@@ -23,6 +23,10 @@ class ScenarioControlEnv(ABC):
23
23
  autoreset : `bool`, optional
24
24
  If True, environment is automatically reset if terminated.
25
25
 
26
+ The default is False.
27
+ reapply_uncertainties_at_reset : `bool`, optional
28
+ If True, the uncertainties are re-applied to the original properties at each reset.
29
+
26
30
  The default is False.
27
31
 
28
32
  Attributes
@@ -36,7 +40,9 @@ class ScenarioControlEnv(ABC):
36
40
  _hydraulic_scada_data : :class:`~epyt_flow.simulation.scada.scada_data.ScadaData`, protected
37
41
  SCADA data from the hydraulic simulation -- only used if EPANET-MSX is used in the control scenario.
38
42
  """
39
- def __init__(self, scenario_config: ScenarioConfig, autoreset: bool = False, **kwds):
43
+ def __init__(self, scenario_config: ScenarioConfig, autoreset: bool = False,
44
+ reapply_uncertainties_at_reset: bool = False,
45
+ **kwds):
40
46
  if not isinstance(scenario_config, ScenarioConfig):
41
47
  raise TypeError("'scenario_config' must be an instance of " +
42
48
  "'epyt_flow.simulation.ScenarioConfig' " +
@@ -44,12 +50,15 @@ class ScenarioControlEnv(ABC):
44
50
  if not isinstance(autoreset, bool):
45
51
  raise TypeError("'autoreset' must be an instance of 'bool' " +
46
52
  f"but not of '{type(autoreset)}'")
53
+ if not isinstance(reapply_uncertainties_at_reset, bool):
54
+ raise TypeError("")
47
55
 
48
56
  self._scenario_config = scenario_config
49
57
  self._scenario_sim = None
50
58
  self._sim_generator = None
51
59
  self.__autoreset = autoreset
52
60
  self._hydraulic_scada_data = None
61
+ self.__reapply_uncertainties_at_reset = reapply_uncertainties_at_reset
53
62
 
54
63
  super().__init__(**kwds)
55
64
 
@@ -65,6 +74,18 @@ class ScenarioControlEnv(ABC):
65
74
  """
66
75
  return self.__autoreset
67
76
 
77
+ @property
78
+ def reapply_uncertainties_at_reset(self) -> bool:
79
+ """
80
+ True, if the uncertainties are re-applied to the original properties at each reset.
81
+
82
+ Returns
83
+ -------
84
+ `bool`
85
+ True, if the uncertainties are re-applied to the original properties at each reset.
86
+ """
87
+ return self.__reapply_uncertainties_at_reset
88
+
68
89
  def __enter__(self):
69
90
  return self
70
91
 
@@ -117,14 +138,16 @@ class ScenarioControlEnv(ABC):
117
138
  # Run hydraulic simulation first
118
139
  hyd_export = os.path.join(get_temp_folder(), f"epytflow_env_MSX_{uuid.uuid4()}.hyd")
119
140
  sim = self._scenario_sim.run_hydraulic_simulation
120
- self._hydraulic_scada_data = sim(hyd_export=hyd_export)
141
+ self._hydraulic_scada_data = sim(hyd_export=hyd_export,
142
+ reapply_uncertainties=self.__reapply_uncertainties_at_reset)
121
143
 
122
144
  # Run advanced quality analysis (EPANET-MSX) on top of the computed hydraulics
123
145
  gen = self._scenario_sim.run_advanced_quality_simulation_as_generator
124
146
  self._sim_generator = gen(hyd_export, support_abort=True)
125
147
  else:
126
148
  gen = self._scenario_sim.run_hydraulic_simulation_as_generator
127
- self._sim_generator = gen(support_abort=True)
149
+ self._sim_generator = gen(support_abort=True,
150
+ reapply_uncertainties=self.__reapply_uncertainties_at_reset)
128
151
 
129
152
  return self._next_sim_itr()
130
153
 
@@ -64,7 +64,7 @@ class SpeciesInjectionEvent(SystemEvent, JsonSerializable):
64
64
 
65
65
  self.__species_id = species_id
66
66
  self.__node_id = node_id
67
- self.__profile = profile
67
+ self._profile = profile
68
68
  self.__source_type = source_type
69
69
 
70
70
  super().__init__(**kwds)
@@ -103,7 +103,7 @@ class SpeciesInjectionEvent(SystemEvent, JsonSerializable):
103
103
  `numpy.ndarray`
104
104
  Pattern of the injection.
105
105
  """
106
- return deepcopy(self.__profile)
106
+ return deepcopy(self._profile)
107
107
 
108
108
  @property
109
109
  def source_type(self) -> int:
@@ -125,17 +125,17 @@ class SpeciesInjectionEvent(SystemEvent, JsonSerializable):
125
125
 
126
126
  def get_attributes(self) -> dict:
127
127
  return super().get_attributes() | {"species_id": self.__species_id,
128
- "node_id": self.__node_id, "profile": self.__profile,
128
+ "node_id": self.__node_id, "profile": self._profile,
129
129
  "source_type": self.__source_type}
130
130
 
131
131
  def __eq__(self, other) -> bool:
132
132
  return super().__eq__(other) and self.__species_id == other.species_id and \
133
- self.__node_id == other.node_id and np.all(self.__profile == other.profile) and \
133
+ self.__node_id == other.node_id and np.all(self._profile == other.profile) and \
134
134
  self.__source_type == other.source_type
135
135
 
136
136
  def __str__(self) -> str:
137
137
  return f"{super().__str__()} species_id: {self.__species_id} " +\
138
- f"node_id: {self.__node_id} profile: {self.__profile} source_type: {self.__source_type}"
138
+ f"node_id: {self.__node_id} profile: {self._profile} source_type: {self.__source_type}"
139
139
 
140
140
  def _get_pattern_id(self) -> str:
141
141
  return f"{self.__species_id}_{self.__node_id}"
@@ -160,7 +160,7 @@ class SpeciesInjectionEvent(SystemEvent, JsonSerializable):
160
160
  injection_time_start_idx = int(self.start_time / time_step)
161
161
 
162
162
  injection_pattern = None
163
- if len(self.__profile) == injection_pattern_length:
163
+ if len(self._profile) == injection_pattern_length:
164
164
  injection_pattern = self.profile
165
165
  else:
166
166
  injection_pattern = np.tile(self.profile,
@@ -93,7 +93,7 @@ class SensorFaultConstant(SensorFault, JsonSerializable):
93
93
  raise TypeError("'constant_shift' must be an instance of 'float' but no of " +
94
94
  f"'{type(constant_shift)}'")
95
95
 
96
- self.__constant_shift = constant_shift
96
+ self._constant_shift = constant_shift
97
97
 
98
98
  super().__init__(**kwds)
99
99
 
@@ -107,20 +107,20 @@ class SensorFaultConstant(SensorFault, JsonSerializable):
107
107
  `float`
108
108
  Constant that is added to the sensor reading.
109
109
  """
110
- return self.__constant_shift
110
+ return self._constant_shift
111
111
 
112
112
  def get_attributes(self) -> dict:
113
- return super().get_attributes() | {"constant_shift": self.__constant_shift}
113
+ return super().get_attributes() | {"constant_shift": self._constant_shift}
114
114
 
115
115
  def __eq__(self, other) -> bool:
116
- return super().__eq__(other) and self.__constant_shift == other.constant_shift
116
+ return super().__eq__(other) and self._constant_shift == other.constant_shift
117
117
 
118
118
  def __str__(self) -> str:
119
- return f"{type(self).__name__} {super().__str__()} constant: {self.__constant_shift}"
119
+ return f"{type(self).__name__} {super().__str__()} constant: {self._constant_shift}"
120
120
 
121
121
  def apply_sensor_fault(self, cur_multiplier: float, sensor_reading: float,
122
122
  cur_time: int) -> float:
123
- return sensor_reading + cur_multiplier * self.__constant_shift
123
+ return sensor_reading + cur_multiplier * self._constant_shift
124
124
 
125
125
 
126
126
  @serializable(SENSOR_FAULT_DRIFT_ID, ".epytflow_sensorfault_drift")
@@ -134,7 +134,7 @@ class SensorFaultDrift(SensorFault, JsonSerializable):
134
134
  Coefficient of the drift.
135
135
  """
136
136
  def __init__(self, coef: float, **kwds):
137
- self.__coef = coef
137
+ self._coef = coef
138
138
 
139
139
  super().__init__(**kwds)
140
140
 
@@ -148,20 +148,20 @@ class SensorFaultDrift(SensorFault, JsonSerializable):
148
148
  `float`
149
149
  Coefficient of the drift.
150
150
  """
151
- return self.__coef
151
+ return self._coef
152
152
 
153
153
  def get_attributes(self) -> dict:
154
- return super().get_attributes() | {"coef": self.__coef}
154
+ return super().get_attributes() | {"coef": self._coef}
155
155
 
156
156
  def __eq__(self, other) -> bool:
157
- return super().__eq__(other) and self.__coef == other.coef
157
+ return super().__eq__(other) and self._coef == other.coef
158
158
 
159
159
  def __str__(self) -> str:
160
- return f"{type(self).__name__} {super().__str__()} coef: {self.__coef}"
160
+ return f"{type(self).__name__} {super().__str__()} coef: {self._coef}"
161
161
 
162
162
  def apply_sensor_fault(self, cur_multiplier: float, sensor_reading: float,
163
163
  cur_time: int) -> float:
164
- return sensor_reading + cur_multiplier * (self.__coef * (cur_time - self.start_time))
164
+ return sensor_reading + cur_multiplier * (self._coef * (cur_time - self.start_time))
165
165
 
166
166
 
167
167
  @serializable(SENSOR_FAULT_GAUSSIAN_ID, ".epytflow_sensorfault_gaussian")
@@ -179,7 +179,7 @@ class SensorFaultGaussian(SensorFault, JsonSerializable):
179
179
  if not isinstance(std, float) or not std > 0:
180
180
  raise ValueError("'std' must be an instance of 'float' and be greater than 0")
181
181
 
182
- self.__std = std
182
+ self._std = std
183
183
 
184
184
  super().__init__(**kwds)
185
185
 
@@ -193,20 +193,20 @@ class SensorFaultGaussian(SensorFault, JsonSerializable):
193
193
  `float`
194
194
  Standard deviation of the Gaussian noise.
195
195
  """
196
- return self.__std
196
+ return self._std
197
197
 
198
198
  def get_attributes(self) -> dict:
199
- return super().get_attributes() | {"std": self.__std}
199
+ return super().get_attributes() | {"std": self._std}
200
200
 
201
201
  def __eq__(self, other) -> bool:
202
- return super().__eq__(other) and self.__std == other.std
202
+ return super().__eq__(other) and self._std == other.std
203
203
 
204
204
  def __str__(self) -> str:
205
- return f"{type(self).__name__} {super().__str__()} std: {self.__std}"
205
+ return f"{type(self).__name__} {super().__str__()} std: {self._std}"
206
206
 
207
207
  def apply_sensor_fault(self, cur_multiplier: float, sensor_reading: float,
208
208
  cur_time: int) -> float:
209
- return sensor_reading + cur_multiplier * np.random.normal(loc=0, scale=self.__std)
209
+ return sensor_reading + cur_multiplier * np.random.normal(loc=0, scale=self._std)
210
210
 
211
211
 
212
212
  @serializable(SENSOR_FAULT_PERCENTAGE_ID, ".epytflow_sensorfault_percentage",)
@@ -223,7 +223,7 @@ class SensorFaultPercentage(SensorFault, JsonSerializable):
223
223
  if not isinstance(coef, float) or not coef > 0:
224
224
  raise ValueError("'coef' must be an instance of 'float' and be greater than zero.")
225
225
 
226
- self.__coef = coef
226
+ self._coef = coef
227
227
 
228
228
  super().__init__(**kwds)
229
229
 
@@ -237,20 +237,20 @@ class SensorFaultPercentage(SensorFault, JsonSerializable):
237
237
  `float`
238
238
  Coefficient (percentage) of the shift.
239
239
  """
240
- return self.__coef
240
+ return self._coef
241
241
 
242
242
  def get_attributes(self) -> dict:
243
- return super().get_attributes() | {"coef": self.__coef}
243
+ return super().get_attributes() | {"coef": self._coef}
244
244
 
245
245
  def __eq__(self, other) -> bool:
246
- return super().__eq__(other) and self.__coef == other.coef
246
+ return super().__eq__(other) and self._coef == other.coef
247
247
 
248
248
  def __str__(self) -> str:
249
- return f"{type(self).__name__} {super().__str__()} coef: {self.__coef}"
249
+ return f"{type(self).__name__} {super().__str__()} coef: {self._coef}"
250
250
 
251
251
  def apply_sensor_fault(self, cur_multiplier: float, sensor_reading: float,
252
252
  cur_time: int) -> float:
253
- return sensor_reading + cur_multiplier * self.__coef * sensor_reading
253
+ return sensor_reading + cur_multiplier * self._coef * sensor_reading
254
254
 
255
255
 
256
256
  @serializable(SENSOR_FAULT_STUCKATZERO_ID, ".epytflow_sensorfault_zero")