epanet-plus 0.0.1__cp313-cp313-macosx_11_0_arm64.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-313-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/hydsolver.c ADDED
@@ -0,0 +1,781 @@
1
+ /*
2
+ ******************************************************************************
3
+ Project: OWA EPANET
4
+ Version: 2.3
5
+ Module: hydsolver.c
6
+ Description: computes flows and pressures throughout a pipe network using
7
+ Todini's Global Gradient Algorithm
8
+ Authors: see AUTHORS
9
+ Copyright: see AUTHORS
10
+ License: see LICENSE
11
+ Last Updated: 06/26/2024
12
+ ******************************************************************************
13
+ */
14
+
15
+ #include <stdlib.h>
16
+ #include <stdio.h>
17
+ #include <string.h>
18
+ #include <math.h>
19
+
20
+ #include "types.h"
21
+ #include "funcs.h"
22
+ #include "text.h"
23
+
24
+ // Hydraulic balance error for network being analyzed
25
+ typedef struct {
26
+ double maxheaderror;
27
+ double maxflowerror;
28
+ double maxflowchange;
29
+ int maxheadlink;
30
+ int maxflownode;
31
+ int maxflowlink;
32
+ } Hydbalance;
33
+
34
+ // Exported functions
35
+ int hydsolve(Project *, int *, double *);
36
+
37
+ // Imported functions
38
+ extern int linsolve(Smatrix *, int); //(see SMATRIX.C)
39
+ extern int valvestatus(Project *); //(see HYDSTATUS.C)
40
+ extern int linkstatus(Project *); //(see HYDSTATUS.C)
41
+
42
+ // Local functions
43
+ static int badvalve(Project *, int);
44
+ static int pswitch(Project *);
45
+
46
+ static double newflows(Project *, Hydbalance *);
47
+ static void newlinkflows(Project *, Hydbalance *, double *, double *);
48
+ static void newemitterflows(Project *, Hydbalance *, double *, double *);
49
+ static void newdemandflows(Project *, Hydbalance *, double *, double *);
50
+ static void newleakageflows(Project *, Hydbalance *, double *, double *);
51
+
52
+ static void checkhydbalance(Project *, Hydbalance *);
53
+ static int hasconverged(Project *, double *, Hydbalance *);
54
+ static int pdaconverged(Project *);
55
+ static void reporthydbal(Project *, Hydbalance *);
56
+
57
+
58
+ int hydsolve(Project *pr, int *iter, double *relerr)
59
+ /*
60
+ **-------------------------------------------------------------------
61
+ ** Input: none
62
+ ** Output: *iter = # of iterations to reach solution
63
+ ** *relerr = convergence error in solution
64
+ ** returns error code
65
+ ** Purpose: solves network nodal equations for heads and flows
66
+ ** using Todini's Gradient algorithm
67
+ **
68
+ ** Notes: Status checks on CVs, pumps and pipes to tanks are made
69
+ ** every CheckFreq iteration, up until MaxCheck iterations
70
+ ** are reached. Status checks on control valves are made
71
+ ** every iteration if DampLimit = 0 or only when the
72
+ ** convergence error is at or below DampLimit. If DampLimit
73
+ ** is > 0 then future computed flow changes are only 60% of
74
+ ** their full value. A complete status check on all links
75
+ ** is made when convergence is achieved. If convergence is
76
+ ** not achieved in MaxIter trials and ExtraIter > 0 then
77
+ ** another ExtraIter trials are made with no status changes
78
+ ** made to any links and a warning message is generated.
79
+ **
80
+ ** This procedure calls linsolve() which appears in SMATRIX.C.
81
+ **-------------------------------------------------------------------
82
+ */
83
+ {
84
+ Network *net = &pr->network;
85
+ Hydraul *hyd = &pr->hydraul;
86
+ Smatrix *sm = &hyd->smatrix;
87
+ Report *rpt = &pr->report;
88
+
89
+ int i; // Node index
90
+ int errcode = 0; // Node causing solution error
91
+ int nextcheck; // Next status check trial
92
+ int maxtrials; // Max. trials for convergence
93
+ double newerr; // New convergence error
94
+ int valveChange; // Valve status change flag
95
+ int statChange; // Non-valve status change flag
96
+ Hydbalance hydbal; // Hydraulic balance errors
97
+
98
+ // Initialize status checking & relaxation factor
99
+ nextcheck = hyd->CheckFreq;
100
+ hyd->RelaxFactor = 1.0;
101
+
102
+ // Initialize convergence criteria and PDA results
103
+ hydbal.maxheaderror = 0.0;
104
+ hydbal.maxflowchange = 0.0;
105
+ hyd->DeficientNodes = 0;
106
+ hyd->DemandReduction = 0.0;
107
+
108
+ // Repeat iterations until convergence or trial limit is exceeded.
109
+ // (ExtraIter used to increase trials in case of status cycling.)
110
+ if (rpt->Statflag == FULL) writerelerr(pr, 0, 0);
111
+ maxtrials = hyd->MaxIter;
112
+ if (hyd->ExtraIter > 0) maxtrials += hyd->ExtraIter;
113
+ *iter = 1;
114
+ while (*iter <= maxtrials)
115
+ {
116
+ // Compute coefficient matrices A & F and solve A*H = F
117
+ // where H = heads, A = Jacobian coeffs. derived from
118
+ // head loss gradients, & F = flow correction terms.
119
+ // Solution for H is returned in F from call to linsolve().
120
+
121
+ headlosscoeffs(pr);
122
+ matrixcoeffs(pr);
123
+ errcode = linsolve(sm, net->Njuncs);
124
+
125
+ // Matrix ill-conditioning problem - if control valve causing problem,
126
+ // fix its status & continue, otherwise quit with no solution.
127
+ if (errcode > 0)
128
+ {
129
+ if (badvalve(pr, sm->Order[errcode])) continue;
130
+ else break;
131
+ }
132
+
133
+ // Update current solution.
134
+ // (Row[i] = row of solution matrix corresponding to node i)
135
+ for (i = 1; i <= net->Njuncs; i++)
136
+ {
137
+ hyd->NodeHead[i] = sm->F[sm->Row[i]]; // Update heads
138
+ }
139
+ newerr = newflows(pr, &hydbal); // Update flows
140
+ *relerr = newerr;
141
+
142
+ // Write convergence error to status report if called for
143
+ if (rpt->Statflag == FULL)
144
+ {
145
+ writerelerr(pr, *iter, *relerr);
146
+ }
147
+
148
+ // Apply solution damping & check for change in valve status
149
+ hyd->RelaxFactor = 1.0;
150
+ valveChange = FALSE;
151
+ if (hyd->DampLimit > 0.0)
152
+ {
153
+ if (*relerr <= hyd->DampLimit)
154
+ {
155
+ hyd->RelaxFactor = 0.6;
156
+ valveChange = valvestatus(pr);
157
+ }
158
+ }
159
+ else
160
+ {
161
+ valveChange = valvestatus(pr);
162
+ }
163
+
164
+ // Check for convergence
165
+ if (hasconverged(pr, relerr, &hydbal))
166
+ {
167
+ // We have convergence - quit if we are into extra iterations
168
+ if (*iter > hyd->MaxIter) break;
169
+
170
+ // Quit if no status changes occur
171
+ statChange = FALSE;
172
+ if (valveChange) statChange = TRUE;
173
+ if (linkstatus(pr)) statChange = TRUE;
174
+ if (pswitch(pr)) statChange = TRUE;
175
+ if (!statChange) break;
176
+
177
+ // We have a status change so continue the iterations
178
+ nextcheck = *iter + hyd->CheckFreq;
179
+ }
180
+
181
+ // No convergence yet - see if it's time for a periodic status
182
+ // check on pumps, CV's, and pipes connected to tank
183
+ else if (*iter <= hyd->MaxCheck && *iter == nextcheck)
184
+ {
185
+ linkstatus(pr);
186
+ nextcheck += hyd->CheckFreq;
187
+ }
188
+ (*iter)++;
189
+ }
190
+
191
+ // Iterations ended - report any errors.
192
+ if (errcode > 0)
193
+ {
194
+ writehyderr(pr, sm->Order[errcode]); // Ill-conditioned matrix error
195
+ errcode = 110;
196
+ }
197
+
198
+ // Save total outflow (NodeDemand) at each junction
199
+ for (i = 1; i <= net->Njuncs; i++)
200
+ {
201
+ hyd->NodeDemand[i] = hyd->DemandFlow[i] +
202
+ hyd->EmitterFlow[i] +
203
+ hyd->LeakageFlow[i];
204
+ }
205
+
206
+ // Save convergence info
207
+ hyd->RelativeError = *relerr;
208
+ hyd->MaxHeadError = hydbal.maxheaderror;
209
+ hyd->MaxFlowChange = hydbal.maxflowchange;
210
+ hyd->Iterations = *iter;
211
+ return errcode;
212
+ }
213
+
214
+
215
+ int badvalve(Project *pr, int n)
216
+ /*
217
+ **-----------------------------------------------------------------
218
+ ** Input: n = node index
219
+ ** Output: returns 1 if node n belongs to an active control valve,
220
+ ** 0 otherwise
221
+ ** Purpose: determines if a node belongs to an active control valve
222
+ ** whose setting causes an inconsistent set of eqns. If so,
223
+ ** the valve status is fixed open and a warning condition
224
+ ** is generated.
225
+ **-----------------------------------------------------------------
226
+ */
227
+ {
228
+ Network *net = &pr->network;
229
+ Hydraul *hyd = &pr->hydraul;
230
+ Report *rpt = &pr->report;
231
+ Times *time = &pr->times;
232
+
233
+ int i, k, n1, n2;
234
+ Slink *link;
235
+ LinkType t;
236
+
237
+ for (i = 1; i <= net->Nvalves; i++)
238
+ {
239
+ k = net->Valve[i].Link;
240
+ link = &net->Link[k];
241
+ n1 = link->N1;
242
+ n2 = link->N2;
243
+ if (n == n1 || n == n2)
244
+ {
245
+ t = link->Type;
246
+ if (t == PRV || t == PSV || t == FCV)
247
+ {
248
+ if (hyd->LinkStatus[k] == ACTIVE)
249
+ {
250
+ if (rpt->Statflag == FULL)
251
+ {
252
+ sprintf(pr->Msg, FMT61,
253
+ clocktime(rpt->Atime, time->Htime), link->ID);
254
+ writeline(pr, pr->Msg);
255
+ }
256
+ if (link->Type == FCV) hyd->LinkStatus[k] = XFCV;
257
+ else hyd->LinkStatus[k] = XPRESSURE;
258
+ return 1;
259
+ }
260
+ }
261
+ return 0;
262
+ }
263
+ }
264
+ return 0;
265
+ }
266
+
267
+
268
+ int pswitch(Project *pr)
269
+ /*
270
+ **--------------------------------------------------------------
271
+ ** Input: none
272
+ ** Output: returns 1 if status of any link changes, 0 if not
273
+ ** Purpose: adjusts settings of links controlled by junction
274
+ ** pressures after a hydraulic solution is found
275
+ **--------------------------------------------------------------
276
+ */
277
+ {
278
+ Network *net = &pr->network;
279
+ Hydraul *hyd = &pr->hydraul;
280
+ Report *rpt = &pr->report;
281
+
282
+ int i, // Control statement index
283
+ k, // Index of link being controlled
284
+ n, // Node controlling link k
285
+ reset, // Flag on control conditions
286
+ change, // Flag for status or setting change
287
+ anychange = 0; // Flag for 1 or more control actions
288
+ char s; // Current link status
289
+ Slink *link;
290
+
291
+ // Check each control statement
292
+ for (i = 1; i <= net->Ncontrols; i++)
293
+ {
294
+ reset = 0;
295
+ k = net->Control[i].Link;
296
+ if (k <= 0) continue;
297
+
298
+ // Determine if control based on a junction, not a tank
299
+ n = net->Control[i].Node;
300
+ if (n > 0 && n <= net->Njuncs)
301
+ {
302
+ // Determine if control conditions are satisfied
303
+ if (net->Control[i].Type == LOWLEVEL &&
304
+ hyd->NodeHead[n] <= net->Control[i].Grade + hyd->Htol)
305
+ {
306
+ reset = 1;
307
+ }
308
+ if (net->Control[i].Type == HILEVEL &&
309
+ hyd->NodeHead[n] >= net->Control[i].Grade - hyd->Htol)
310
+ {
311
+ reset = 1;
312
+ }
313
+ }
314
+
315
+ // Determine if control forces a status or setting change
316
+ if (reset == 1)
317
+ {
318
+ link = &net->Link[k];
319
+ change = 0;
320
+ s = hyd->LinkStatus[k];
321
+ if (link->Type == PIPE)
322
+ {
323
+ if (s != net->Control[i].Status) change = 1;
324
+ }
325
+ if (link->Type == PUMP)
326
+ {
327
+ if (hyd->LinkSetting[k] != net->Control[i].Setting) change = 1;
328
+ }
329
+ if (link->Type >= PRV)
330
+ {
331
+ if (hyd->LinkSetting[k] != net->Control[i].Setting) change = 1;
332
+ else if (hyd->LinkSetting[k] == MISSING && s != net->Control[i].Status)
333
+ {
334
+ change = 1;
335
+ }
336
+ }
337
+
338
+ // If a change occurs, update status & setting
339
+ if (change)
340
+ {
341
+ hyd->LinkStatus[k] = net->Control[i].Status;
342
+ if (link->Type > PIPE)
343
+ {
344
+ hyd->LinkSetting[k] = net->Control[i].Setting;
345
+ }
346
+ if (rpt->Statflag == FULL)
347
+ {
348
+ writestatchange(pr, k, s, hyd->LinkStatus[k]);
349
+ }
350
+ anychange = 1;
351
+ }
352
+ }
353
+ }
354
+ return anychange;
355
+ }
356
+
357
+
358
+ double newflows(Project *pr, Hydbalance *hbal)
359
+ /*
360
+ **----------------------------------------------------------------
361
+ ** Input: hbal = ptr. to hydraulic balance information
362
+ ** Output: returns solution convergence error
363
+ ** Purpose: updates link, emitter & demand flows after new
364
+ ** nodal heads are computed.
365
+ **----------------------------------------------------------------
366
+ */
367
+ {
368
+ Hydraul *hyd = &pr->hydraul;
369
+
370
+ double dqsum, // Network flow change
371
+ qsum; // Network total flow
372
+
373
+ // Initialize sum of flows & corrections
374
+ qsum = 0.0;
375
+ dqsum = 0.0;
376
+ hbal->maxflowchange = 0.0;
377
+ hbal->maxflowlink = 1;
378
+ hbal->maxflownode = -1;
379
+
380
+ // Update flows in all real and virtual links
381
+ newlinkflows(pr, hbal, &qsum, &dqsum);
382
+ newemitterflows(pr, hbal, &qsum, &dqsum);
383
+ newdemandflows(pr, hbal, &qsum, &dqsum);
384
+ if (hyd->HasLeakage) newleakageflows(pr, hbal, &qsum, &dqsum);
385
+
386
+ // Return ratio of total flow corrections to total flow
387
+ if (qsum > hyd->Hacc) return (dqsum / qsum);
388
+ else return dqsum;
389
+ }
390
+
391
+
392
+ void newlinkflows(Project *pr, Hydbalance *hbal, double *qsum, double *dqsum)
393
+ /*
394
+ **----------------------------------------------------------------
395
+ ** Input: hbal = ptr. to hydraulic balance information
396
+ ** qsum = sum of current system flows
397
+ ** dqsum = sum of system flow changes
398
+ ** Output: updates hbal, qsum and dqsum
399
+ ** Purpose: updates link flows after new nodal heads computed
400
+ **----------------------------------------------------------------
401
+ */
402
+ {
403
+ Network *net = &pr->network;
404
+ Hydraul *hyd = &pr->hydraul;
405
+
406
+ double dh, /* Link head loss */
407
+ dq; /* Link flow change */
408
+ int k, n, n1, n2;
409
+ Slink *link;
410
+
411
+ // Initialize net inflows (i.e., demands) at fixed grade nodes
412
+ for (n = net->Njuncs + 1; n <= net->Nnodes; n++)
413
+ {
414
+ hyd->NodeDemand[n] = 0.0;
415
+ }
416
+
417
+ // Examine each link
418
+ for (k = 1; k <= net->Nlinks; k++)
419
+ {
420
+ // Get link and its end nodes
421
+ link = &net->Link[k];
422
+ n1 = link->N1;
423
+ n2 = link->N2;
424
+
425
+ // Apply flow update formula:
426
+ // dq = Y - P * (new head loss)
427
+ // P = 1 / (previous head loss gradient)
428
+ // Y = P * (previous head loss)
429
+ // where P & Y were computed in hlosscoeff() in hydcoeffs.c
430
+
431
+ dh = hyd->NodeHead[n1] - hyd->NodeHead[n2];
432
+ dq = hyd->Y[k] - hyd->P[k] * dh;
433
+
434
+ // Adjust flow change by the relaxation factor
435
+ dq *= hyd->RelaxFactor;
436
+
437
+ // Prevent flow in constant HP pumps from going negative
438
+ if (link->Type == PUMP)
439
+ {
440
+ n = findpump(net, k);
441
+ if (net->Pump[n].Ptype == CONST_HP && dq > hyd->LinkFlow[k])
442
+ {
443
+ dq = hyd->LinkFlow[k] / 2.0;
444
+ }
445
+ }
446
+
447
+ // Update link flow and system flow summation
448
+ hyd->LinkFlow[k] -= dq;
449
+ *qsum += ABS(hyd->LinkFlow[k]);
450
+ *dqsum += ABS(dq);
451
+
452
+ // Update identity of element with max. flow change
453
+ if (ABS(dq) > hbal->maxflowchange)
454
+ {
455
+ hbal->maxflowchange = ABS(dq);
456
+ hbal->maxflowlink = k;
457
+ hbal->maxflownode = -1;
458
+ }
459
+
460
+ // Update net flows to fixed grade nodes
461
+ if (hyd->LinkStatus[k] > CLOSED)
462
+ {
463
+ if (n1 > net->Njuncs) hyd->NodeDemand[n1] -= hyd->LinkFlow[k];
464
+ if (n2 > net->Njuncs) hyd->NodeDemand[n2] += hyd->LinkFlow[k];
465
+ }
466
+ }
467
+ }
468
+
469
+
470
+ void newemitterflows(Project *pr, Hydbalance *hbal, double *qsum,
471
+ double *dqsum)
472
+ /*
473
+ **----------------------------------------------------------------
474
+ ** Input: hbal = ptr. to hydraulic balance information
475
+ ** qsum = sum of current system flows
476
+ ** dqsum = sum of system flow changes
477
+ ** Output: updates hbal, qsum and dqsum
478
+ ** Purpose: updates nodal emitter flows after new nodal heads computed
479
+ **----------------------------------------------------------------
480
+ */
481
+ {
482
+ Network *net = &pr->network;
483
+ Hydraul *hyd = &pr->hydraul;
484
+
485
+ int i;
486
+ double hloss, hgrad, dh, dq;
487
+
488
+ // Examine each network junction
489
+ for (i = 1; i <= net->Njuncs; i++)
490
+ {
491
+ // Skip junction if it does not have an emitter
492
+ if (net->Node[i].Ke == 0.0) continue;
493
+
494
+ // Find emitter head loss and gradient
495
+ emitterheadloss(pr, i, &hloss, &hgrad);
496
+
497
+ // Find emitter flow change
498
+ dh = hyd->NodeHead[i] - net->Node[i].El;
499
+ dq = (hloss - dh) / hgrad;
500
+ dq *= hyd->RelaxFactor;
501
+ hyd->EmitterFlow[i] -= dq;
502
+
503
+ // Update system flow summation
504
+ *qsum += ABS(hyd->EmitterFlow[i]);
505
+ *dqsum += ABS(dq);
506
+
507
+ // Update identity of element with max. flow change
508
+ if (ABS(dq) > hbal->maxflowchange)
509
+ {
510
+ hbal->maxflowchange = ABS(dq);
511
+ hbal->maxflownode = i;
512
+ hbal->maxflowlink = -1;
513
+ }
514
+ }
515
+ }
516
+
517
+
518
+ void newleakageflows(Project *pr, Hydbalance *hbal, double *qsum,
519
+ double *dqsum)
520
+ /*
521
+ **----------------------------------------------------------------
522
+ ** Input: hbal = ptr. to hydraulic balance information
523
+ ** qsum = sum of current system flows
524
+ ** dqsum = sum of system flow changes
525
+ ** Output: updates hbal, qsum and dqsum
526
+ ** Purpose: updates nodal leakage flows after new nodal heads computed
527
+ **----------------------------------------------------------------
528
+ */
529
+ {
530
+ Network *net = &pr->network;
531
+ Hydraul *hyd = &pr->hydraul;
532
+
533
+ int i;
534
+ double dq;
535
+
536
+ for (i = 1; i <= net->Njuncs; i++)
537
+ {
538
+ // Update leakage flow at node i
539
+ dq = leakageflowchange(pr, i);
540
+ if (dq == 0.0) continue;
541
+
542
+ // Update system flow summation
543
+ *qsum += ABS(hyd->LeakageFlow[i]);
544
+ *dqsum += ABS(dq);
545
+
546
+ // Update identity of element with max. flow change
547
+ if (ABS(dq) > hbal->maxflowchange)
548
+ {
549
+ hbal->maxflowchange = ABS(dq);
550
+ hbal->maxflownode = i;
551
+ hbal->maxflowlink = -1;
552
+ }
553
+ }
554
+ }
555
+
556
+
557
+ void newdemandflows(Project *pr, Hydbalance *hbal, double *qsum, double *dqsum)
558
+ /*
559
+ **----------------------------------------------------------------
560
+ ** Input: hbal = ptr. to hydraulic balance information
561
+ ** qsum = sum of current system flows
562
+ ** dqsum = sum of system flow changes
563
+ ** Output: updates hbal, qsum and dqsum
564
+ ** Purpose: updates nodal pressure dependent demand flows after
565
+ ** new nodal heads computed
566
+ **----------------------------------------------------------------
567
+ */
568
+ {
569
+ Network *net = &pr->network;
570
+ Hydraul *hyd = &pr->hydraul;
571
+
572
+ double dp, // pressure range over which demand can vary (ft)
573
+ dq, // change in demand flow (cfs)
574
+ n, // exponent in head loss v. demand function
575
+ hloss, // current head loss through outflow junction (ft)
576
+ hgrad, // head loss gradient with respect to flow (ft/cfs)
577
+ dh; // new head loss through outflow junction (ft)
578
+ int i;
579
+
580
+ // Get demand function parameters
581
+ if (hyd->DemandModel == DDA) return;
582
+ dp = MAX((hyd->Preq - hyd->Pmin), MINPDIFF);
583
+ n = 1.0 / hyd->Pexp;
584
+
585
+ // Examine each junction
586
+ for (i = 1; i <= net->Njuncs; i++)
587
+ {
588
+ // Skip junctions with no positive demand
589
+ if (hyd->FullDemand[i] <= 0.0) continue;
590
+
591
+ // Find change in demand flow (see hydcoeffs.c)
592
+ demandheadloss(pr, i, dp, n, &hloss, &hgrad);
593
+ dh = hyd->NodeHead[i] - net->Node[i].El - hyd->Pmin;
594
+ dq = (hloss - dh) / hgrad;
595
+ dq *= hyd->RelaxFactor;
596
+
597
+ // Prevent a flow change greater than full demand
598
+ if (fabs(dq) > 0.4 * hyd->FullDemand[i])
599
+ dq = 0.4 * SGN(dq) * hyd->FullDemand[i];
600
+ hyd->DemandFlow[i] -= dq;
601
+
602
+ // Update system flow summation
603
+ *qsum += ABS(hyd->DemandFlow[i]);
604
+ *dqsum += ABS(dq);
605
+
606
+ // Update identity of element with max. flow change
607
+ if (ABS(dq) > hbal->maxflowchange)
608
+ {
609
+ hbal->maxflowchange = ABS(dq);
610
+ hbal->maxflownode = i;
611
+ hbal->maxflowlink = -1;
612
+ }
613
+ }
614
+ }
615
+
616
+
617
+ void checkhydbalance(Project *pr, Hydbalance *hbal)
618
+ /*
619
+ **--------------------------------------------------------------
620
+ ** Input: hbal = hydraulic balance errors
621
+ ** Output: none
622
+ ** Purpose: finds the link with the largest head imbalance
623
+ **--------------------------------------------------------------
624
+ */
625
+ {
626
+ Network *net = &pr->network;
627
+ Hydraul *hyd = &pr->hydraul;
628
+
629
+ int k, n1, n2;
630
+ double dh, headerror, headloss;
631
+ Slink *link;
632
+
633
+ hbal->maxheaderror = 0.0;
634
+ hbal->maxheadlink = 1;
635
+ headlosscoeffs(pr);
636
+ for (k = 1; k <= net->Nlinks; k++)
637
+ {
638
+ if (hyd->LinkStatus[k] <= CLOSED) continue;
639
+ if (hyd->P[k] == 0.0) continue;
640
+ link = &net->Link[k];
641
+ n1 = link->N1;
642
+ n2 = link->N2;
643
+ dh = hyd->NodeHead[n1] - hyd->NodeHead[n2];
644
+ headloss = hyd->Y[k] / hyd->P[k];
645
+ headerror = ABS(dh - headloss);
646
+ if (headerror > hbal->maxheaderror)
647
+ {
648
+ hbal->maxheaderror = headerror;
649
+ hbal->maxheadlink = k;
650
+ }
651
+ }
652
+ }
653
+
654
+
655
+ int hasconverged(Project *pr, double *relerr, Hydbalance *hbal)
656
+ /*
657
+ **--------------------------------------------------------------
658
+ ** Input: relerr = current total relative flow change
659
+ ** hbal = current hydraulic balance errors
660
+ ** Output: returns 1 if system has converged or 0 if not
661
+ ** Purpose: checks various criteria to see if system has
662
+ ** become hydraulically balanced
663
+ **--------------------------------------------------------------
664
+ */
665
+ {
666
+ Hydraul *hyd = &pr->hydraul;
667
+
668
+ // Check that total relative flow change is small enough
669
+ if (*relerr > hyd->Hacc) return 0;
670
+
671
+ // Find largest head loss error and absolute flow change
672
+ checkhydbalance(pr, hbal);
673
+ if (pr->report.Statflag == FULL)
674
+ {
675
+ reporthydbal(pr, hbal);
676
+ }
677
+
678
+ // Check that head loss error and flow change criteria are met
679
+ if (hyd->HeadErrorLimit > 0.0 &&
680
+ hbal->maxheaderror > hyd->HeadErrorLimit) return 0;
681
+ if (hyd->FlowChangeLimit > 0.0 &&
682
+ hbal->maxflowchange > hyd->FlowChangeLimit) return 0;
683
+
684
+ // Check for node leakage convergence
685
+ if (hyd->HasLeakage && !leakagehasconverged(pr)) return 0;
686
+
687
+ // Check for pressure driven analysis convergence
688
+ if (hyd->DemandModel == PDA) return pdaconverged(pr);
689
+ return 1;
690
+ }
691
+
692
+
693
+ int pdaconverged(Project *pr)
694
+ /*
695
+ **--------------------------------------------------------------
696
+ ** Input: none
697
+ ** Output: returns 1 if PDA converged, 0 if not
698
+ ** Purpose: checks if pressure driven analysis has converged
699
+ ** and updates total demand deficit
700
+ **--------------------------------------------------------------
701
+ */
702
+ {
703
+ Hydraul *hyd = &pr->hydraul;
704
+
705
+ const double QTOL = 0.0001; // 0.0001 cfs ~= 0.05 gpm ~= 0.2 lpm)
706
+ int i, converged = 1;
707
+
708
+ double totalDemand = 0.0, totalReduction = 0.0;
709
+ double dp = hyd->Preq - hyd->Pmin;
710
+ double p, q, r;
711
+
712
+ hyd->DeficientNodes = 0;
713
+ hyd->DemandReduction = 0.0;
714
+
715
+ // Examine each network junction
716
+ for (i = 1; i <= pr->network.Njuncs; i++)
717
+ {
718
+ // Skip nodes whose required demand is non-positive
719
+ if (hyd->FullDemand[i] <= 0.0) continue;
720
+
721
+ // Evaluate demand equation at current pressure solution
722
+ p = hyd->NodeHead[i] - pr->network.Node[i].El;
723
+ if (p <= hyd->Pmin)
724
+ q = 0.0;
725
+ else if (p >= hyd->Preq)
726
+ q = hyd->FullDemand[i];
727
+ else
728
+ {
729
+ r = (p - hyd->Pmin) / dp;
730
+ q = hyd->FullDemand[i] * pow(r, hyd->Pexp);
731
+ }
732
+
733
+ // Check if demand has not converged
734
+ if (fabs(q - hyd->DemandFlow[i]) > QTOL)
735
+ converged = 0;
736
+
737
+ // Accumulate demand deficient node count and demand deficit
738
+ if (hyd->DemandFlow[i] + QTOL < hyd->FullDemand[i])
739
+ {
740
+ hyd->DeficientNodes++;
741
+ totalDemand += hyd->FullDemand[i];
742
+ totalReduction += hyd->FullDemand[i] - hyd->DemandFlow[i];
743
+ }
744
+ }
745
+ if (totalDemand > 0.0)
746
+ hyd->DemandReduction = totalReduction / totalDemand * 100.0;
747
+ return converged;
748
+ }
749
+
750
+
751
+ void reporthydbal(Project *pr, Hydbalance *hbal)
752
+ /*
753
+ **--------------------------------------------------------------
754
+ ** Input: hbal = current hydraulic balance errors
755
+ ** Output: none
756
+ ** Purpose: identifies links with largest flow change and
757
+ ** largest head loss error.
758
+ **--------------------------------------------------------------
759
+ */
760
+ {
761
+ double qchange = hbal->maxflowchange * pr->Ucf[FLOW];
762
+ double herror = hbal->maxheaderror * pr->Ucf[HEAD];
763
+ int qlink = hbal->maxflowlink;
764
+ int qnode = hbal->maxflownode;
765
+ int hlink = hbal->maxheadlink;
766
+ if (qlink >= 1)
767
+ {
768
+ sprintf(pr->Msg, FMT66, qchange, pr->network.Link[qlink].ID);
769
+ writeline(pr, pr->Msg);
770
+ }
771
+ else if (qnode >= 1)
772
+ {
773
+ sprintf(pr->Msg, FMT67, qchange, pr->network.Node[qnode].ID);
774
+ writeline(pr, pr->Msg);
775
+ }
776
+ if (hlink >= 1)
777
+ {
778
+ sprintf(pr->Msg, FMT68, herror, pr->network.Link[hlink].ID);
779
+ writeline(pr, pr->Msg);
780
+ }
781
+ }