epanet-plus 0.0.1__cp311-cp311-macosx_10_9_x86_64.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.

Potentially problematic release.


This version of epanet-plus might be problematic. Click here for more details.

Files changed (105) hide show
  1. docs/conf.py +67 -0
  2. epanet-msx-src/dispersion.h +27 -0
  3. epanet-msx-src/hash.c +107 -0
  4. epanet-msx-src/hash.h +28 -0
  5. epanet-msx-src/include/epanetmsx.h +104 -0
  6. epanet-msx-src/include/epanetmsx_export.h +42 -0
  7. epanet-msx-src/mathexpr.c +937 -0
  8. epanet-msx-src/mathexpr.h +39 -0
  9. epanet-msx-src/mempool.c +204 -0
  10. epanet-msx-src/mempool.h +24 -0
  11. epanet-msx-src/msxchem.c +1285 -0
  12. epanet-msx-src/msxcompiler.c +368 -0
  13. epanet-msx-src/msxdict.h +42 -0
  14. epanet-msx-src/msxdispersion.c +586 -0
  15. epanet-msx-src/msxerr.c +116 -0
  16. epanet-msx-src/msxfile.c +260 -0
  17. epanet-msx-src/msxfuncs.c +175 -0
  18. epanet-msx-src/msxfuncs.h +35 -0
  19. epanet-msx-src/msxinp.c +1504 -0
  20. epanet-msx-src/msxout.c +398 -0
  21. epanet-msx-src/msxproj.c +791 -0
  22. epanet-msx-src/msxqual.c +2011 -0
  23. epanet-msx-src/msxrpt.c +400 -0
  24. epanet-msx-src/msxtank.c +422 -0
  25. epanet-msx-src/msxtoolkit.c +1164 -0
  26. epanet-msx-src/msxtypes.h +551 -0
  27. epanet-msx-src/msxutils.c +524 -0
  28. epanet-msx-src/msxutils.h +56 -0
  29. epanet-msx-src/newton.c +158 -0
  30. epanet-msx-src/newton.h +34 -0
  31. epanet-msx-src/rk5.c +287 -0
  32. epanet-msx-src/rk5.h +39 -0
  33. epanet-msx-src/ros2.c +293 -0
  34. epanet-msx-src/ros2.h +35 -0
  35. epanet-msx-src/smatrix.c +816 -0
  36. epanet-msx-src/smatrix.h +29 -0
  37. epanet-src/AUTHORS +60 -0
  38. epanet-src/LICENSE +21 -0
  39. epanet-src/enumstxt.h +151 -0
  40. epanet-src/epanet.c +5937 -0
  41. epanet-src/epanet2.c +961 -0
  42. epanet-src/epanet2.def +131 -0
  43. epanet-src/errors.dat +79 -0
  44. epanet-src/flowbalance.c +186 -0
  45. epanet-src/funcs.h +219 -0
  46. epanet-src/genmmd.c +1000 -0
  47. epanet-src/hash.c +177 -0
  48. epanet-src/hash.h +28 -0
  49. epanet-src/hydcoeffs.c +1303 -0
  50. epanet-src/hydraul.c +1164 -0
  51. epanet-src/hydsolver.c +781 -0
  52. epanet-src/hydstatus.c +442 -0
  53. epanet-src/include/epanet2.h +466 -0
  54. epanet-src/include/epanet2_2.h +1962 -0
  55. epanet-src/include/epanet2_enums.h +518 -0
  56. epanet-src/inpfile.c +884 -0
  57. epanet-src/input1.c +672 -0
  58. epanet-src/input2.c +970 -0
  59. epanet-src/input3.c +2265 -0
  60. epanet-src/leakage.c +527 -0
  61. epanet-src/mempool.c +146 -0
  62. epanet-src/mempool.h +24 -0
  63. epanet-src/output.c +853 -0
  64. epanet-src/project.c +1691 -0
  65. epanet-src/quality.c +695 -0
  66. epanet-src/qualreact.c +800 -0
  67. epanet-src/qualroute.c +696 -0
  68. epanet-src/report.c +1559 -0
  69. epanet-src/rules.c +1500 -0
  70. epanet-src/smatrix.c +871 -0
  71. epanet-src/text.h +508 -0
  72. epanet-src/types.h +928 -0
  73. epanet-src/util/cstr_helper.c +59 -0
  74. epanet-src/util/cstr_helper.h +38 -0
  75. epanet-src/util/errormanager.c +92 -0
  76. epanet-src/util/errormanager.h +39 -0
  77. epanet-src/util/filemanager.c +212 -0
  78. epanet-src/util/filemanager.h +81 -0
  79. epanet-src/validate.c +408 -0
  80. epanet.cpython-311-darwin.so +0 -0
  81. epanet_plus/VERSION +1 -0
  82. epanet_plus/__init__.py +8 -0
  83. epanet_plus/epanet_plus.c +118 -0
  84. epanet_plus/epanet_toolkit.py +2730 -0
  85. epanet_plus/epanet_wrapper.py +2414 -0
  86. epanet_plus/include/epanet_plus.h +9 -0
  87. epanet_plus-0.0.1.dist-info/METADATA +152 -0
  88. epanet_plus-0.0.1.dist-info/RECORD +105 -0
  89. epanet_plus-0.0.1.dist-info/WHEEL +6 -0
  90. epanet_plus-0.0.1.dist-info/licenses/LICENSE +21 -0
  91. epanet_plus-0.0.1.dist-info/top_level.txt +11 -0
  92. examples/basic_usage.py +35 -0
  93. python-extension/ext.c +344 -0
  94. python-extension/pyepanet.c +2133 -0
  95. python-extension/pyepanet.h +143 -0
  96. python-extension/pyepanet2.c +1823 -0
  97. python-extension/pyepanet2.h +141 -0
  98. python-extension/pyepanet_plus.c +37 -0
  99. python-extension/pyepanet_plus.h +4 -0
  100. python-extension/pyepanetmsx.c +388 -0
  101. python-extension/pyepanetmsx.h +35 -0
  102. tests/test_epanet.py +16 -0
  103. tests/test_epanetmsx.py +36 -0
  104. tests/test_epyt.py +114 -0
  105. tests/test_load_inp_from_buffer.py +18 -0
epanet-src/hydraul.c ADDED
@@ -0,0 +1,1164 @@
1
+ /*
2
+ ******************************************************************************
3
+ Project: OWA EPANET
4
+ Version: 2.3
5
+ Module: hydraul.c
6
+ Description: implements EPANET's hydraulic engine
7
+ Authors: see AUTHORS
8
+ Copyright: see AUTHORS
9
+ License: see LICENSE
10
+ Last Updated: 04/19/2025
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
+ const double QZERO = 1.e-6; // Equivalent to zero flow in cfs
24
+
25
+ // Imported functions
26
+ extern int validateproject(Project *);
27
+ extern int createsparse(Project *);
28
+ extern void freesparse(Project *);
29
+ extern int hydsolve(Project *, int *, double *);
30
+
31
+ // Local functions
32
+ int allocmatrix(Project *);
33
+ void freematrix(Project *);
34
+ void initlinkflow(Project *, int, char, double);
35
+ void demands(Project *);
36
+ int controls(Project *);
37
+ long timestep(Project *);
38
+ void ruletimestep(Project *, long *);
39
+ void addenergy(Project *, long);
40
+ void tanklevels(Project *, long);
41
+ void resetpumpflow(Project *, int);
42
+ void getallpumpsenergy(Project *);
43
+
44
+
45
+ int openhyd(Project *pr)
46
+ /*
47
+ *--------------------------------------------------------------
48
+ * Input: none
49
+ * Output: returns error code
50
+ * Purpose: opens hydraulics solver system
51
+ *--------------------------------------------------------------
52
+ */
53
+ {
54
+ int i;
55
+ int errcode = 0;
56
+ Slink *link;
57
+
58
+ // Check for valid project data (see VALIDATE.C)
59
+ errcode = validateproject(pr);
60
+ if (errcode > 0) return errcode;
61
+
62
+ // Allocate memory for sparse matrix structures (see SMATRIX.C)
63
+ ERRCODE(createsparse(pr));
64
+
65
+ // Allocate memory for hydraulic variables
66
+ ERRCODE(allocmatrix(pr));
67
+
68
+ // Check for unconnected nodes
69
+ ERRCODE(unlinked(pr));
70
+
71
+ // Initialize link flows
72
+ if (!errcode) for (i = 1; i <= pr->network.Nlinks; i++)
73
+ {
74
+ link = &pr->network.Link[i];
75
+ initlinkflow(pr, i, link->InitStatus, link->Kc);
76
+ }
77
+ else closehyd(pr);
78
+ return errcode;
79
+ }
80
+
81
+ void inithyd(Project *pr, int initflag)
82
+ /*
83
+ **--------------------------------------------------------------
84
+ ** Input: initflag > 0 if link flows should be re-initialized
85
+ ** = 0 if not
86
+ ** Output: none
87
+ ** Purpose: initializes hydraulics solver system
88
+ **--------------------------------------------------------------
89
+ */
90
+ {
91
+ Network *net = &pr->network;
92
+ Hydraul *hyd = &pr->hydraul;
93
+ Outfile *out = &pr->outfile;
94
+ Times *time = &pr->times;
95
+
96
+ int i;
97
+ Stank *tank;
98
+ Slink *link;
99
+ Spump *pump;
100
+
101
+ // Initialize tanks
102
+ for (i = 1; i <= net->Ntanks; i++)
103
+ {
104
+ tank = &net->Tank[i];
105
+ tank->V = tank->V0;
106
+ hyd->NodeHead[tank->Node] = tank->H0;
107
+ hyd->NodeDemand[tank->Node] = 0.0;
108
+ hyd->OldStatus[net->Nlinks+i] = TEMPCLOSED;
109
+ }
110
+
111
+ // Initialize node outflows
112
+ memset(hyd->DemandFlow,0,(net->Nnodes+1)*sizeof(double));
113
+ memset(hyd->EmitterFlow,0,(net->Nnodes+1)*sizeof(double));
114
+ memset(hyd->LeakageFlow,0,(net->Nnodes+1)*sizeof(double));
115
+ for (i = 1; i <= net->Nnodes; i++)
116
+ {
117
+ net->Node[i].ResultIndex = i;
118
+ if (net->Node[i].Ke > 0.0) hyd->EmitterFlow[i] = 1.0;
119
+ }
120
+
121
+ // Initialize links
122
+ for (i = 1; i <= net->Nlinks; i++)
123
+ {
124
+ link = &net->Link[i];
125
+ link->ResultIndex = i;
126
+
127
+ // Initialize status and setting
128
+ hyd->LinkStatus[i] = link->InitStatus;
129
+ hyd->LinkSetting[i] = link->InitSetting;
130
+ if (link->Type > PUMP && link->Type != GPV && link->InitStatus != ACTIVE)
131
+ {
132
+ hyd->LinkSetting[i] = MISSING;
133
+ }
134
+
135
+ // Compute flow resistance
136
+ resistcoeff(pr, i);
137
+
138
+ // Start active control valves in ACTIVE position
139
+ if (
140
+ (link->Type == PRV || link->Type == PSV
141
+ || link->Type == FCV) && (hyd->LinkSetting[i] != MISSING)
142
+ ) hyd->LinkStatus[i] = ACTIVE;
143
+
144
+ // Initialize flows if necessary
145
+ if (hyd->LinkStatus[i] <= CLOSED)
146
+ {
147
+ hyd->LinkFlow[i] = QZERO;
148
+ }
149
+ else if (ABS(hyd->LinkFlow[i]) <= QZERO || initflag > 0)
150
+ {
151
+ initlinkflow(pr, i, hyd->LinkStatus[i], hyd->LinkSetting[i]);
152
+ }
153
+
154
+ // Save initial status
155
+ hyd->OldStatus[i] = hyd->LinkStatus[i];
156
+ }
157
+
158
+ // Initialize pump energy usage
159
+ for (i = 1; i <= net->Npumps; i++)
160
+ {
161
+ pump = &net->Pump[i];
162
+ pump->Energy.Efficiency = 0.0;
163
+ pump->Energy.TimeOnLine = 0.0;
164
+ pump->Energy.KwHrs = 0.0;
165
+ pump->Energy.KwHrsPerFlow = 0.0;
166
+ pump->Energy.MaxKwatts = 0.0;
167
+ pump->Energy.TotalCost = 0.0;
168
+ pump->Energy.CurrentPower = 0.0;
169
+ pump->Energy.CurrentEffic = 0.0;
170
+ }
171
+
172
+ // Initialize flow balance
173
+ startflowbalance(pr);
174
+
175
+ // Re-position hydraulics file
176
+ if (pr->outfile.Saveflag)
177
+ {
178
+ fseek(out->HydFile,out->HydOffset,SEEK_SET);
179
+ }
180
+
181
+ // Initialize current time
182
+ hyd->Haltflag = 0;
183
+ time->Htime = 0;
184
+ time->Hydstep = 0;
185
+ time->Rtime = time->Rstep;
186
+ }
187
+
188
+
189
+ int runhyd(Project *pr, long *t)
190
+ /*
191
+ **--------------------------------------------------------------
192
+ ** Input: none
193
+ ** Output: t = pointer to current time (in seconds)
194
+ ** Returns: error code
195
+ ** Purpose: solves network hydraulics in a single time period
196
+ **--------------------------------------------------------------
197
+ */
198
+ {
199
+ Hydraul *hyd = &pr->hydraul;
200
+ Times *time = &pr->times;
201
+ Report *rpt = &pr->report;
202
+
203
+ int iter; // Iteration count
204
+ int errcode; // Error code
205
+ double relerr; // Solution accuracy
206
+
207
+ // Find new demands & control actions
208
+ *t = time->Htime;
209
+ demands(pr);
210
+ controls(pr);
211
+
212
+ // Solve network hydraulic equations
213
+ errcode = hydsolve(pr,&iter,&relerr);
214
+ if (!errcode)
215
+ {
216
+ // Report new status & save results
217
+ if (rpt->Statflag) writehydstat(pr,iter,relerr);
218
+
219
+ // If system unbalanced and no extra trials
220
+ // allowed, then activate the Haltflag
221
+ if (relerr > hyd->Hacc && hyd->ExtraIter == -1)
222
+ {
223
+ hyd->Haltflag = 1;
224
+ }
225
+
226
+ // Report any warning conditions
227
+ if (!errcode) errcode = writehydwarn(pr,iter,relerr);
228
+ }
229
+ return errcode;
230
+ }
231
+
232
+ int nexthyd(Project *pr, long *tstep)
233
+ /*
234
+ **--------------------------------------------------------------
235
+ ** Input: none
236
+ ** Output: tstep = pointer to time step (in seconds)
237
+ ** Returns: error code
238
+ ** Purpose: finds length of next time step & updates tank
239
+ ** levels and rule-based contol actions
240
+ **--------------------------------------------------------------
241
+ */
242
+ {
243
+ Hydraul *hyd = &pr->hydraul;
244
+ Times *time = &pr->times;
245
+
246
+ long hydstep; // Actual time step
247
+ int errcode = 0; // Error code
248
+
249
+ // Compute current power and efficiency of all pumps
250
+ getallpumpsenergy(pr);
251
+
252
+ // Save current results to hydraulics file and
253
+ // force end of simulation if Haltflag is active
254
+ if (pr->outfile.Saveflag) errcode = savehyd(pr, &time->Htime);
255
+ if (hyd->Haltflag) time->Htime = time->Dur;
256
+
257
+ // Compute next time step & update tank levels
258
+ *tstep = 0;
259
+ hydstep = 0;
260
+ if (time->Htime < time->Dur) hydstep = timestep(pr);
261
+ if (pr->outfile.Saveflag) errcode = savehydstep(pr,&hydstep);
262
+
263
+ // Accumulate pumping energy
264
+ if (time->Dur == 0) addenergy(pr,0);
265
+ else if (time->Htime < time->Dur) addenergy(pr,hydstep);
266
+
267
+ // Update flow balance
268
+ updateflowbalance(pr, hydstep);
269
+
270
+ // More time remains - update current time
271
+ if (time->Htime < time->Dur)
272
+ {
273
+ time->Htime += hydstep;
274
+ if (!pr->quality.OpenQflag)
275
+ {
276
+ if (time->Htime >= time->Rtime) time->Rtime += time->Rstep;
277
+ }
278
+ }
279
+
280
+ // No more time remains - force completion of analysis
281
+ else
282
+ {
283
+ endflowbalance(pr);
284
+ if (pr->report.Statflag) writeflowbalance(pr);
285
+ time->Htime++;
286
+ if (pr->quality.OpenQflag) time->Qtime++;
287
+ }
288
+ *tstep = hydstep;
289
+ return errcode;
290
+ }
291
+
292
+
293
+ void closehyd(Project *pr)
294
+ /*
295
+ **--------------------------------------------------------------
296
+ ** Input: none
297
+ ** Output: returns error code
298
+ ** Purpose: closes hydraulics solver system
299
+ **--------------------------------------------------------------
300
+ */
301
+ {
302
+ freesparse(pr);
303
+ freematrix(pr);
304
+ freeadjlists(&pr->network);
305
+ }
306
+
307
+
308
+ int allocmatrix(Project *pr)
309
+ /*
310
+ **--------------------------------------------------------------
311
+ ** Input: none
312
+ ** Output: returns error code
313
+ ** Purpose: allocates memory used for solution matrix coeffs.
314
+ **--------------------------------------------------------------
315
+ */
316
+ {
317
+ Network *net = &pr->network;
318
+ Hydraul *hyd = &pr->hydraul;
319
+
320
+ int errcode = 0;
321
+
322
+ hyd->P = (double *) calloc(net->Nlinks+1,sizeof(double));
323
+ hyd->Y = (double *) calloc(net->Nlinks+1,sizeof(double));
324
+ hyd->Xflow = (double *) calloc(MAX((net->Nnodes+1), (net->Nlinks+1)),
325
+ sizeof(double));
326
+ hyd->OldStatus = (StatusType *) calloc(net->Nlinks+net->Ntanks+1,
327
+ sizeof(StatusType));
328
+ ERRCODE(MEMCHECK(hyd->P));
329
+ ERRCODE(MEMCHECK(hyd->Y));
330
+ ERRCODE(MEMCHECK(hyd->Xflow));
331
+ ERRCODE(MEMCHECK(hyd->OldStatus));
332
+ return errcode;
333
+ }
334
+
335
+
336
+ void freematrix(Project *pr)
337
+ /*
338
+ **--------------------------------------------------------------
339
+ ** Input: none
340
+ ** Output: none
341
+ ** Purpose: frees memory used for solution matrix coeffs.
342
+ **--------------------------------------------------------------
343
+ */
344
+ {
345
+ Hydraul *hyd = &pr->hydraul;
346
+
347
+ free(hyd->P);
348
+ free(hyd->Y);
349
+ free(hyd->Xflow);
350
+ free(hyd->OldStatus);
351
+ }
352
+
353
+
354
+ void initlinkflow(Project *pr, int i, char s, double k)
355
+ /*
356
+ **--------------------------------------------------------------------
357
+ ** Input: i = link index
358
+ ** s = link status
359
+ ** k = link setting (i.e., pump speed)
360
+ ** Output: none
361
+ ** Purpose: sets initial flow in link to QZERO if link is closed,
362
+ ** to design flow for a pump, or to flow at velocity of
363
+ ** 1 fps for other links.
364
+ **--------------------------------------------------------------------
365
+ */
366
+ {
367
+ Hydraul *hyd = &pr->hydraul;
368
+ Network *n = &pr->network;
369
+
370
+ Slink *link = &n->Link[i];
371
+
372
+ if (s == CLOSED)
373
+ {
374
+ hyd->LinkFlow[i] = QZERO;
375
+ }
376
+ else if (link->Type == PUMP)
377
+ {
378
+ hyd->LinkFlow[i] = k * n->Pump[findpump(n,i)].Q0;
379
+ }
380
+ else
381
+ {
382
+ hyd->LinkFlow[i] = PI * SQR(link->Diam)/4.0;
383
+ }
384
+ }
385
+
386
+
387
+ void setlinkstatus(Project *pr, int index, char value, StatusType *s, double *k)
388
+ /*----------------------------------------------------------------
389
+ ** Input: index = link index
390
+ ** value = 0 (CLOSED) or 1 (OPEN)
391
+ ** s = pointer to link status
392
+ ** k = pointer to link setting
393
+ ** Output: none
394
+ ** Purpose: sets link status to OPEN or CLOSED
395
+ **----------------------------------------------------------------
396
+ */
397
+ {
398
+ Network *net = &pr->network;
399
+
400
+ Slink *link = &net->Link[index];
401
+ LinkType t = link->Type;
402
+
403
+ // Status set to open
404
+ if (value == 1)
405
+ {
406
+ // Adjust link setting for pumps & valves
407
+ if (t == PUMP)
408
+ {
409
+ *k = 1.0;
410
+ // Check if a re-opened pump needs its flow reset
411
+ if (*s == CLOSED) resetpumpflow(pr, index);
412
+ }
413
+ if (t > PUMP && t != GPV) *k = MISSING;
414
+ *s = OPEN;
415
+ }
416
+
417
+ // Status set to closed
418
+ else if (value == 0)
419
+ {
420
+ // Adjust link setting for pumps & valves
421
+ if (t == PUMP) *k = 0.0;
422
+ if (t > PUMP && t != GPV) *k = MISSING;
423
+ *s = CLOSED;
424
+ }
425
+ }
426
+
427
+
428
+ void setlinksetting(Project *pr, int index, double value, StatusType *s,
429
+ double *k)
430
+ /*----------------------------------------------------------------
431
+ ** Input: index = link index
432
+ ** value = pump speed or valve setting
433
+ ** s = pointer to link status
434
+ ** k = pointer to link setting
435
+ ** Output: none
436
+ ** Purpose: sets pump speed or valve setting, adjusting link
437
+ ** status and flow when necessary
438
+ **----------------------------------------------------------------
439
+ */
440
+ {
441
+ Network *net = &pr->network;
442
+
443
+ Slink *link = &net->Link[index];
444
+ LinkType t = link->Type;
445
+
446
+ // For a pump, status is OPEN if speed > 0, CLOSED otherwise
447
+ if (t == PUMP)
448
+ {
449
+ *k = value;
450
+ if (value > 0 && *s <= CLOSED)
451
+ {
452
+ // Check if a re-opened pump needs its flow reset
453
+ resetpumpflow(pr, index);
454
+ *s = OPEN;
455
+ }
456
+ if (value == 0 && *s > CLOSED) *s = CLOSED;
457
+ }
458
+
459
+ // For FCV, activate it
460
+ else if (t == FCV)
461
+ {
462
+ *k = value;
463
+ *s = ACTIVE;
464
+ }
465
+
466
+ // Open closed control valve with fixed status (setting = MISSING)
467
+ else
468
+ {
469
+ if (*k == MISSING && *s <= CLOSED) *s = OPEN;
470
+ if (t == PCV) link->R = pcvlosscoeff(pr, index, value);
471
+ *k = value;
472
+ }
473
+ }
474
+
475
+
476
+ void demands(Project *pr)
477
+ /*
478
+ **--------------------------------------------------------------------
479
+ ** Input: none
480
+ ** Output: none
481
+ ** Purpose: computes demands at nodes during current time period
482
+ **--------------------------------------------------------------------
483
+ */
484
+ {
485
+ Network *net = &pr->network;
486
+ Hydraul *hyd = &pr->hydraul;
487
+ Times *time = &pr->times;
488
+
489
+ int i ,j, n;
490
+ long k, p;
491
+ double djunc, sum;
492
+ Pdemand demand;
493
+
494
+ // Determine total elapsed number of pattern periods
495
+ p = (time->Htime + time->Pstart) / time->Pstep;
496
+
497
+ // Update demand at each node according to its assigned pattern
498
+ hyd->Dsystem = 0.0; // System-wide demand
499
+ for (i = 1; i <= net->Njuncs; i++)
500
+ {
501
+ sum = 0.0;
502
+ for (demand = net->Node[i].D; demand != NULL; demand = demand->next)
503
+ {
504
+ // pattern period (k) = (elapsed periods) modulus (periods per pattern)
505
+ j = demand->Pat;
506
+ if (j == 0)
507
+ j = hyd->DefPat;
508
+ k = p % (long)net->Pattern[j].Length;
509
+ djunc = (demand->Base) * net->Pattern[j].F[k] * hyd->Dmult;
510
+ if (djunc > 0.0) hyd->Dsystem += djunc;
511
+ sum += djunc;
512
+ }
513
+ hyd->FullDemand[i] = sum;
514
+
515
+ // Initialize pressure dependent demand
516
+ hyd->DemandFlow[i] = sum;
517
+ }
518
+
519
+ // Update head at fixed grade nodes with time patterns
520
+ for (n = 1; n <= net->Ntanks; n++)
521
+ {
522
+ Stank *tank = &net->Tank[n];
523
+ if (tank->A == 0.0)
524
+ {
525
+ j = tank->Pat;
526
+ if (j > 0)
527
+ {
528
+ k = p % (long) net->Pattern[j].Length;
529
+ i = tank->Node;
530
+ hyd->NodeHead[i] = net->Node[i].El * net->Pattern[j].F[k];
531
+ }
532
+ }
533
+ }
534
+
535
+ // Update status of pumps with utilization patterns
536
+ for (n = 1; n <= net->Npumps; n++)
537
+ {
538
+ Spump *pump = &net->Pump[n];
539
+ j = pump->Upat;
540
+ if (j > 0)
541
+ {
542
+ i = pump->Link;
543
+ k = p % (long) net->Pattern[j].Length;
544
+ setlinksetting(pr, i, net->Pattern[j].F[k], &hyd->LinkStatus[i],
545
+ &hyd->LinkSetting[i]);
546
+ }
547
+ }
548
+ }
549
+
550
+
551
+ int controls(Project *pr)
552
+ /*
553
+ **---------------------------------------------------------------------
554
+ ** Input: none
555
+ ** Output: number of links whose setting changes
556
+ ** Purpose: implements simple controls based on time or tank levels
557
+ **---------------------------------------------------------------------
558
+ */
559
+ {
560
+ Network *net = &pr->network;
561
+ Hydraul *hyd = &pr->hydraul;
562
+ Times *time = &pr->times;
563
+
564
+ int i, k, n, reset, setsum;
565
+ double h, vplus;
566
+ double v1, v2;
567
+ double k1, k2;
568
+ char s1, s2;
569
+ Slink *link;
570
+ Scontrol *control;
571
+
572
+ // Examine each control statement
573
+ setsum = 0;
574
+ for (i=1; i <= net->Ncontrols; i++)
575
+ {
576
+ // Make sure that link is defined
577
+ control = &net->Control[i];
578
+ if (!control->isEnabled)
579
+ {
580
+ continue;
581
+ }
582
+ reset = 0;
583
+ if ( (k = control->Link) <= 0) continue;
584
+ link = &net->Link[k];
585
+
586
+ // Link is controlled by tank level
587
+ if ((n = control->Node) > 0 && n > net->Njuncs)
588
+ {
589
+ h = hyd->NodeHead[n];
590
+ vplus = ABS(hyd->NodeDemand[n]);
591
+ v1 = tankvolume(pr,n - net->Njuncs,h);
592
+ v2 = tankvolume(pr,n - net->Njuncs, control->Grade);
593
+ if (control->Type == LOWLEVEL && v1 <= v2 + vplus) reset = 1;
594
+ if (control->Type == HILEVEL && v1 >= v2 - vplus) reset = 1;
595
+ }
596
+
597
+ // Link is time-controlled
598
+ if (control->Type == TIMER)
599
+ {
600
+ if (control->Time == time->Htime) reset = 1;
601
+ }
602
+
603
+ //* Link is time-of-day controlled
604
+ if (control->Type == TIMEOFDAY)
605
+ {
606
+ if ((time->Htime + time->Tstart) % SECperDAY == control->Time)
607
+ {
608
+ reset = 1;
609
+ }
610
+ }
611
+
612
+ // Update link status & pump speed or valve setting
613
+ if (reset == 1)
614
+ {
615
+ if (hyd->LinkStatus[k] <= CLOSED) s1 = CLOSED;
616
+ else s1 = OPEN;
617
+ s2 = control->Status;
618
+ k1 = hyd->LinkSetting[k];
619
+ k2 = k1;
620
+ if (link->Type > PIPE) k2 = control->Setting;
621
+
622
+ // Check if a re-opened pump needs its flow reset
623
+ if (link->Type == PUMP && s1 == CLOSED && s2 == OPEN)
624
+ resetpumpflow(pr, k);
625
+
626
+ if (s1 != s2 || k1 != k2)
627
+ {
628
+ hyd->LinkStatus[k] = s2;
629
+ hyd->LinkSetting[k] = k2;
630
+ if (link->Type == PCV) link->R = pcvlosscoeff(pr, k, k2);
631
+ if (pr->report.Statflag) writecontrolaction(pr,k,i);
632
+ setsum++;
633
+ }
634
+ }
635
+ }
636
+ return setsum;
637
+ }
638
+
639
+
640
+ long timestep(Project *pr)
641
+ /*
642
+ **----------------------------------------------------------------
643
+ ** Input: none
644
+ ** Output: returns time step until next change in hydraulics
645
+ ** Purpose: computes time step to advance hydraulic simulation
646
+ **----------------------------------------------------------------
647
+ */
648
+ {
649
+ Network *net = &pr->network;
650
+ Times *time = &pr->times;
651
+
652
+ long n, t, tstep;
653
+
654
+ // Normal time step is hydraulic time step
655
+ tstep = time->Hstep;
656
+
657
+ // Revise time step based on time until next demand period
658
+ // (n = next pattern period, t = time till next period)
659
+ n = ((time->Htime + time->Pstart) / time->Pstep) + 1;
660
+ t = n * time->Pstep - time->Htime;
661
+ if (t > 0 && t < tstep) tstep = t;
662
+
663
+ // Revise time step based on time until next reporting period
664
+ t = time->Rtime - time->Htime;
665
+ if (t > 0 && t < tstep) tstep = t;
666
+
667
+ // Revise time step based on smallest time to fill or drain a tank
668
+ tanktimestep(pr, &tstep);
669
+
670
+ // Revise time step based on smallest time to activate a control
671
+ controltimestep(pr, &tstep);
672
+
673
+ // Evaluate rule-based controls (which will also update tank levels)
674
+ if (net->Nrules > 0) ruletimestep(pr, &tstep);
675
+ else tanklevels(pr, tstep);
676
+ return tstep;
677
+ }
678
+
679
+
680
+ int tanktimestep(Project *pr, long *tstep)
681
+ /*
682
+ **-----------------------------------------------------------------
683
+ ** Input: *tstep = current time step
684
+ ** Output: *tstep = modified current time step
685
+ ** Purpose: revises time step based on shortest time to fill or
686
+ ** drain a tank
687
+ **-----------------------------------------------------------------
688
+ */
689
+ {
690
+ Network *net = &pr->network;
691
+ Hydraul *hyd = &pr->hydraul;
692
+
693
+ int i, n, tankIdx = 0;
694
+ double h, q, v, xt;
695
+ long t;
696
+ Stank *tank;
697
+
698
+ // Examine each tank
699
+ for (i = 1; i <= net->Ntanks; i++)
700
+ {
701
+ // Skip reservoirs
702
+ tank = &net->Tank[i];
703
+ if (tank->A == 0.0) continue;
704
+
705
+ // Get current tank grade (h) & inflow (q)
706
+ n = tank->Node;
707
+ h = hyd->NodeHead[n];
708
+ q = hyd->NodeDemand[n];
709
+ if (ABS(q) <= QZERO) continue;
710
+
711
+ // Find volume to fill/drain tank
712
+ if (q > 0.0 && h < tank->Hmax) v = tank->Vmax - tank->V;
713
+ else if (q < 0.0 && h > tank->Hmin) v = tank->Vmin - tank->V;
714
+ else continue;
715
+
716
+ // Find time to fill/drain tank
717
+ xt = v / q;
718
+ if (ABS(xt) > *tstep + 1) continue;
719
+ t = (long)ROUND(xt);
720
+ if (t > 0 && t < *tstep)
721
+ {
722
+ *tstep = t;
723
+ tankIdx = n;
724
+ }
725
+ }
726
+ return tankIdx;
727
+ }
728
+
729
+
730
+ int controltimestep(Project *pr, long *tstep)
731
+ /*
732
+ **------------------------------------------------------------------
733
+ ** Input: *tstep = current time step
734
+ ** Output: *tstep = modified current time step
735
+ ** Purpose: revises time step based on shortest time to activate
736
+ ** a simple control
737
+ **------------------------------------------------------------------
738
+ */
739
+ {
740
+ Network *net = &pr->network;
741
+ Hydraul *hyd = &pr->hydraul;
742
+
743
+ int i, j, k, n, controlIndex = 0;
744
+ double h, q, v;
745
+ long t, t1, t2;
746
+ Slink *link;
747
+ Scontrol *control;
748
+
749
+ // Examine each control
750
+ for (i = 1; i <= net->Ncontrols; i++)
751
+ {
752
+ t = 0;
753
+ control = &net->Control[i];
754
+ if (!control->isEnabled)
755
+ {
756
+ continue;
757
+ }
758
+ // Control depends on a tank level
759
+ if ( (n = control->Node) > 0)
760
+ {
761
+ // Skip node if not a tank or reservoir
762
+ if ((j = n - net->Njuncs) <= 0) continue;
763
+
764
+ // Find current head and flow into tank
765
+ h = hyd->NodeHead[n];
766
+ q = hyd->NodeDemand[n];
767
+ if (ABS(q) <= QZERO) continue;
768
+
769
+ // Find time to reach upper or lower control level
770
+ if ( (h < control->Grade && control->Type == HILEVEL && q > 0.0)
771
+ || (h > control->Grade && control->Type == LOWLEVEL && q < 0.0) )
772
+ {
773
+ v = tankvolume(pr, j, control->Grade) - net->Tank[j].V;
774
+ t = (long)ROUND(v/q);
775
+ }
776
+ }
777
+
778
+ // Control is based on elapsed time
779
+ if (control->Type == TIMER)
780
+ {
781
+ if (control->Time > pr->times.Htime)
782
+ {
783
+ t = control->Time - pr->times.Htime;
784
+ }
785
+ }
786
+
787
+ // Control is based on time of day
788
+ if (control->Type == TIMEOFDAY)
789
+ {
790
+ t1 = (pr->times.Htime + pr->times.Tstart) % SECperDAY;
791
+ t2 = control->Time;
792
+ if (t2 >= t1) t = t2 - t1;
793
+ else t = SECperDAY - t1 + t2;
794
+ }
795
+
796
+ // Revise the current estimated next time step
797
+ if (t > 0 && t < *tstep)
798
+ {
799
+ // Check if rule actually changes link status or setting
800
+ k = control->Link;
801
+ link = &net->Link[k];
802
+ if ( (link->Type > PIPE && hyd->LinkSetting[k] != control->Setting)
803
+ || (hyd->LinkStatus[k] != control->Status) )
804
+ {
805
+ *tstep = t;
806
+ controlIndex = i;
807
+ }
808
+ }
809
+ }
810
+ return controlIndex;
811
+ }
812
+
813
+
814
+ void ruletimestep(Project *pr, long *tstep)
815
+ /*
816
+ **--------------------------------------------------------------
817
+ ** Input: *tstep = current time step (sec)
818
+ ** Output: *tstep = modified time step
819
+ ** Purpose: updates next time step by checking if any rules
820
+ ** will fire before then; also updates tank levels.
821
+ **--------------------------------------------------------------
822
+ */
823
+ {
824
+ Network *net = &pr->network;
825
+ Times *time = &pr->times;
826
+
827
+ long tnow, // Start of time interval for rule evaluation
828
+ tmax, // End of time interval for rule evaluation
829
+ dt, // Normal time increment for rule evaluation
830
+ dt1; // Actual time increment for rule evaluation
831
+
832
+ // Find interval of time for rule evaluation
833
+ tnow = time->Htime;
834
+ tmax = tnow + *tstep;
835
+
836
+ // If no rules, then time increment equals current time step
837
+ if (net->Nrules == 0)
838
+ {
839
+ dt = *tstep;
840
+ dt1 = dt;
841
+ }
842
+
843
+ // Otherwise, time increment equals rule evaluation time step and
844
+ // first actual increment equals time until next even multiple of
845
+ // Rulestep occurs.
846
+ else
847
+ {
848
+ dt = time->Rulestep;
849
+ dt1 = time->Rulestep - (tnow % time->Rulestep);
850
+ }
851
+
852
+ // Make sure time increment is no larger than current time step
853
+ dt = MIN(dt, *tstep);
854
+ dt1 = MIN(dt1, *tstep);
855
+ if (dt1 == 0) dt1 = dt;
856
+
857
+ // Step through time, updating tank levels, until either
858
+ // a rule fires or we reach the end of evaluation period.
859
+ //
860
+ // Note: we are updating the global simulation time (Htime)
861
+ // here because it is used by functions in RULES.C
862
+ // to evaluate rules when checkrules() is called.
863
+ // It is restored to its original value after the
864
+ // rule evaluation process is completed (see below).
865
+ // Also note that dt1 will equal dt after the first
866
+ // time increment is taken.
867
+ //
868
+ do
869
+ {
870
+ time->Htime += dt1; // Update simulation clock
871
+ tanklevels(pr, dt1); // Find new tank levels
872
+ if (checkrules(pr, dt1)) break; // Stop if any rule fires
873
+ dt = MIN(dt, tmax - time->Htime); // Update time increment
874
+ dt1 = dt; // Update actual increment
875
+ } while (dt > 0); // Stop if no time left
876
+
877
+ // Compute an updated simulation time step (*tstep)
878
+ // and return simulation time to its original value
879
+ *tstep = time->Htime - tnow;
880
+ time->Htime = tnow;
881
+ }
882
+
883
+
884
+ void addenergy(Project *pr, long hstep)
885
+ /*
886
+ **-------------------------------------------------------------
887
+ ** Input: hstep = time step (sec)
888
+ ** Output: none
889
+ ** Purpose: accumulates pump energy usage
890
+ **-------------------------------------------------------------
891
+ */
892
+ {
893
+ Network *net = &pr->network;
894
+ Hydraul *hyd = &pr->hydraul;
895
+ Times *time = &pr->times;
896
+
897
+ int i, j, k;
898
+ long m, n;
899
+ double c0, c, // Energy cost (cost/kwh)
900
+ f0, // Energy cost factor
901
+ dt, // Time interval (hr)
902
+ e, // Pump efficiency (fraction)
903
+ q, // Pump flow (cfs)
904
+ p, // Pump energy (kw)
905
+ psum = 0.0; // Total energy (kw)
906
+ Spump *pump;
907
+
908
+ // Determine current time interval in hours
909
+ if (time->Dur == 0) dt = 1.0;
910
+ else if (time->Htime < time->Dur)
911
+ {
912
+ dt = (double) hstep / 3600.0;
913
+ }
914
+ else dt = 0.0;
915
+ if (dt == 0.0) return;
916
+ n = (time->Htime + time->Pstart) / time->Pstep;
917
+
918
+ // Compute default energy cost at current time
919
+ c0 = hyd->Ecost;
920
+ f0 = 1.0;
921
+ if (hyd->Epat > 0)
922
+ {
923
+ m = n % (long)net->Pattern[hyd->Epat].Length;
924
+ f0 = net->Pattern[hyd->Epat].F[m];
925
+ }
926
+
927
+ // Examine each pump
928
+ for (j = 1; j <= net->Npumps; j++)
929
+ {
930
+ // Skip closed pumps
931
+ pump = &net->Pump[j];
932
+ k = pump->Link;
933
+ if (pump->Energy.CurrentEffic == 0.0) continue;
934
+ q = MAX(QZERO, ABS(hyd->LinkFlow[k]));
935
+
936
+ // Find pump-specific energy cost
937
+ if (pump->Ecost > 0.0) c = pump->Ecost;
938
+ else c = c0;
939
+ if ( (i = pump->Epat) > 0)
940
+ {
941
+ m = n % (long)net->Pattern[i].Length;
942
+ c *= net->Pattern[i].F[m];
943
+ }
944
+ else c *= f0;
945
+
946
+ // Update pump's cumulative statistics
947
+ p = pump->Energy.CurrentPower;
948
+ e = pump->Energy.CurrentEffic;
949
+ psum += p;
950
+ pump->Energy.TimeOnLine += dt;
951
+ pump->Energy.Efficiency += e * dt;
952
+ pump->Energy.KwHrsPerFlow += p / q * dt;
953
+ pump->Energy.KwHrs += p * dt;
954
+ pump->Energy.MaxKwatts = MAX(pump->Energy.MaxKwatts, p);
955
+ pump->Energy.TotalCost += c * p * dt;
956
+ }
957
+
958
+ // Update maximum kw value
959
+ hyd->Emax = MAX(hyd->Emax, psum);
960
+ }
961
+
962
+
963
+ void getenergy(Project *pr, int k, double *kw, double *eff)
964
+ /*
965
+ **----------------------------------------------------------------
966
+ ** Input: k = link index
967
+ ** Output: *kw = kwatt energy used
968
+ ** *eff = efficiency (pumps only)
969
+ ** Purpose: computes flow energy associated with link k
970
+ **----------------------------------------------------------------
971
+ */
972
+ {
973
+ Network *net = &pr->network;
974
+ Hydraul *hyd = &pr->hydraul;
975
+
976
+ int i, // efficiency curve index
977
+ j; // pump index
978
+ double dh, // head across pump (ft)
979
+ q, // flow through pump (cfs)
980
+ e; // pump efficiency
981
+ double q4eff; // flow at nominal pump speed of 1.0
982
+ double speed; // current speed setting
983
+ Scurve *curve;
984
+ Slink *link = &net->Link[k];
985
+
986
+ // No energy if link is closed
987
+ if (hyd->LinkStatus[k] <= CLOSED)
988
+ {
989
+ *kw = 0.0;
990
+ *eff = 0.0;
991
+ return;
992
+ }
993
+
994
+ // Determine flow and head difference
995
+ q = ABS(hyd->LinkFlow[k]);
996
+ dh = ABS(hyd->NodeHead[link->N1] - hyd->NodeHead[link->N2]);
997
+
998
+ // For pumps, find effic. at current flow
999
+ if (link->Type == PUMP)
1000
+ {
1001
+ j = findpump(net, k);
1002
+ e = hyd->Epump;
1003
+ speed = hyd->LinkSetting[k];
1004
+ if ((i = net->Pump[j].Ecurve) > 0)
1005
+ {
1006
+ q4eff = q / speed * pr->Ucf[FLOW];
1007
+ curve = &net->Curve[i];
1008
+ e = interp(curve->Npts,curve->X, curve->Y, q4eff);
1009
+
1010
+ // Sarbu and Borza pump speed adjustment
1011
+ e = 100.0 - ((100.0-e) * pow(1.0/speed, 0.1));
1012
+ }
1013
+ e = MIN(e, 100.0);
1014
+ e = MAX(e, 1.0);
1015
+ e /= 100.0;
1016
+ }
1017
+ else e = 1.0;
1018
+
1019
+ // Compute energy
1020
+ *kw = dh * q * hyd->SpGrav / 8.814 / e * KWperHP;
1021
+ *eff = e;
1022
+ }
1023
+
1024
+
1025
+ void getallpumpsenergy(Project *pr)
1026
+ /*
1027
+ **-------------------------------------------------------------
1028
+ ** Input: none
1029
+ ** Output: none
1030
+ ** Purpose: finds the current power and efficiency for each pump.
1031
+ **-------------------------------------------------------------
1032
+ */
1033
+ {
1034
+ int j;
1035
+ Spump *pump;
1036
+
1037
+ for (j = 1; j <= pr->network.Npumps; j++)
1038
+ {
1039
+ pump = &(pr->network.Pump[j]);
1040
+ getenergy(pr, pump->Link, &(pump->Energy.CurrentPower),
1041
+ &(pump->Energy.CurrentEffic));
1042
+ }
1043
+ }
1044
+
1045
+
1046
+ void tanklevels(Project *pr, long tstep)
1047
+ /*
1048
+ **----------------------------------------------------------------
1049
+ ** Input: tstep = current time step
1050
+ ** Output: none
1051
+ ** Purpose: computes new water levels in tanks after current
1052
+ ** time step
1053
+ **----------------------------------------------------------------
1054
+ */
1055
+ {
1056
+ Network *net = &pr->network;
1057
+ Hydraul *hyd = &pr->hydraul;
1058
+
1059
+ int i, n;
1060
+ double dv;
1061
+
1062
+ for (i = 1; i <= net->Ntanks; i++)
1063
+ {
1064
+ Stank *tank = &net->Tank[i];
1065
+ if (tank->A == 0.0) continue; // Skip reservoirs
1066
+
1067
+ // Update the tank's volume & water elevation
1068
+ n = tank->Node;
1069
+ dv = hyd->NodeDemand[n] * tstep;
1070
+ tank->V += dv;
1071
+
1072
+ // Check if tank full/empty within next second
1073
+ if (tank->V + hyd->NodeDemand[n] >= tank->Vmax)
1074
+ {
1075
+ tank->V = tank->Vmax;
1076
+ }
1077
+ else if (tank->V - hyd->NodeDemand[n] <= tank->Vmin)
1078
+ {
1079
+ tank->V = tank->Vmin;
1080
+ }
1081
+ hyd->NodeHead[n] = tankgrade(pr, i, tank->V);
1082
+ }
1083
+ }
1084
+
1085
+
1086
+ double tankvolume(Project *pr, int i, double h)
1087
+ /*
1088
+ **--------------------------------------------------------------------
1089
+ ** Input: i = tank index
1090
+ ** h = water elevation in tank
1091
+ ** Output: returns water volume in tank
1092
+ ** Purpose: finds water volume in tank i corresponding to elev. h.
1093
+ **--------------------------------------------------------------------
1094
+ */
1095
+ {
1096
+ Network *net = &pr->network;
1097
+
1098
+ int j;
1099
+ double y, v;
1100
+ Stank *tank = &net->Tank[i];
1101
+ Scurve *curve;
1102
+
1103
+ // Use level*area if no volume curve
1104
+ j = tank->Vcurve;
1105
+ if (j == 0) return(tank->Vmin + (h - tank->Hmin) * tank->A);
1106
+
1107
+ // If curve exists, interpolate on h to find volume v
1108
+ // remembering that volume curve is in original units.
1109
+ else
1110
+ {
1111
+ curve = &net->Curve[j];
1112
+ y = (h - net->Node[tank->Node].El) * pr->Ucf[HEAD];
1113
+ v = interp(curve->Npts, curve->X, curve->Y, y) / pr->Ucf[VOLUME];
1114
+ return v;
1115
+ }
1116
+ }
1117
+
1118
+
1119
+ double tankgrade(Project *pr, int i, double v)
1120
+ /*
1121
+ **-------------------------------------------------------------------
1122
+ ** Input: i = tank index
1123
+ ** v = volume in tank
1124
+ ** Output: returns water level in tank
1125
+ ** Purpose: finds water level in tank i corresponding to volume v.
1126
+ **-------------------------------------------------------------------
1127
+ */
1128
+ {
1129
+ Network *net = &pr->network;
1130
+
1131
+ int j;
1132
+ double y, h;
1133
+ Stank *tank = &net->Tank[i];
1134
+
1135
+ // Use area if no volume curve
1136
+ j = tank->Vcurve;
1137
+ if (j == 0) return(tank->Hmin + (v - tank->Vmin) / tank->A);
1138
+
1139
+ // If curve exists, interpolate on volume (originally the Y-variable
1140
+ // but used here as the X-variable) to find new level above bottom.
1141
+ // Remember that volume curve is stored in original units.
1142
+ else
1143
+ {
1144
+ Scurve *curve = &net->Curve[j];
1145
+ y = interp(curve->Npts, curve->Y, curve->X, v * pr->Ucf[VOLUME]);
1146
+ h = net->Node[tank->Node].El + y / pr->Ucf[HEAD];
1147
+ return h;
1148
+ }
1149
+ }
1150
+
1151
+ void resetpumpflow(Project *pr, int i)
1152
+ /*
1153
+ **-------------------------------------------------------------------
1154
+ ** Input: i = link index
1155
+ ** Output: none
1156
+ ** Purpose: resets flow in a constant HP pump to its initial value.
1157
+ **-------------------------------------------------------------------
1158
+ */
1159
+ {
1160
+ Network *net = &pr->network;
1161
+ Spump *pump = &net->Pump[findpump(net, i)];
1162
+ if (pump->Ptype == CONST_HP)
1163
+ pr->hydraul.LinkFlow[i] = pump->Q0;
1164
+ }