epanet-plus 0.2.1__cp311-cp311-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.
Files changed (106) 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 +2833 -0
  85. epanet_plus/epanet_wrapper.py +2434 -0
  86. epanet_plus/include/epanet_plus.h +9 -0
  87. epanet_plus-0.2.1.dist-info/METADATA +157 -0
  88. epanet_plus-0.2.1.dist-info/RECORD +106 -0
  89. epanet_plus-0.2.1.dist-info/WHEEL +6 -0
  90. epanet_plus-0.2.1.dist-info/licenses/LICENSE +21 -0
  91. epanet_plus-0.2.1.dist-info/top_level.txt +11 -0
  92. examples/basic_usage.py +35 -0
  93. examples/epanet_msx.py +35 -0
  94. python-extension/ext.c +344 -0
  95. python-extension/pyepanet.c +2771 -0
  96. python-extension/pyepanet.h +144 -0
  97. python-extension/pyepanet2.c +2453 -0
  98. python-extension/pyepanet2.h +142 -0
  99. python-extension/pyepanet_plus.c +43 -0
  100. python-extension/pyepanet_plus.h +4 -0
  101. python-extension/pyepanetmsx.c +524 -0
  102. python-extension/pyepanetmsx.h +35 -0
  103. tests/test_epanet.py +18 -0
  104. tests/test_epanetmsx.py +38 -0
  105. tests/test_epyt.py +115 -0
  106. tests/test_load_inp_from_buffer.py +59 -0
epanet-src/qualreact.c ADDED
@@ -0,0 +1,800 @@
1
+ /*
2
+ ******************************************************************************
3
+ Project: OWA EPANET
4
+ Version: 2.3
5
+ Module: qualreact.c
6
+ Description: computes water quality reactions within pipes and tanks
7
+ Authors: see AUTHORS
8
+ Copyright: see AUTHORS
9
+ License: see LICENSE
10
+ Last Updated: 12/16/2024
11
+ ******************************************************************************
12
+ */
13
+
14
+ #include <stdlib.h>
15
+ #include <stdio.h>
16
+ #include <math.h>
17
+ #include "types.h"
18
+
19
+ // Exported functions
20
+ char setreactflag(Project *);
21
+ double getucf(double);
22
+ void ratecoeffs(Project *);
23
+ void reactpipes(Project *, long);
24
+ void reacttanks(Project *, long);
25
+ double mixtank(Project *, int, double, double ,double);
26
+
27
+ // Imported functions
28
+ extern void addseg(Project *, int, double, double);
29
+ extern void reversesegs(Project *, int);
30
+
31
+ // Local functions
32
+ static double piperate(Project *, int);
33
+ static double pipereact(Project *, int, double, double, long);
34
+ static double tankreact(Project *, double, double, double, long);
35
+ static double bulkrate(Project *, double, double, double);
36
+ static double wallrate(Project *, double, double, double, double);
37
+
38
+ static void tankmix1(Project *, int, double, double, double);
39
+ static void tankmix2(Project *, int, double, double, double);
40
+ static void tankmix3(Project *, int, double, double, double);
41
+ static void tankmix4(Project *, int, double, double, double);
42
+
43
+
44
+ char setreactflag(Project *pr)
45
+ /*
46
+ **-----------------------------------------------------------
47
+ ** Input: none
48
+ ** Output: returns 1 for reactive WQ constituent, 0 otherwise
49
+ ** Purpose: checks if reactive chemical being simulated
50
+ **-----------------------------------------------------------
51
+ */
52
+ {
53
+ Network *net = &pr->network;
54
+ int i;
55
+
56
+ if (pr->quality.Qualflag == TRACE) return 0;
57
+ else if (pr->quality.Qualflag == AGE) return 1;
58
+ else
59
+ {
60
+ for (i = 1; i <= net->Nlinks; i++)
61
+ {
62
+ if (net->Link[i].Type <= PIPE)
63
+ {
64
+ if (net->Link[i].Kb != 0.0 || net->Link[i].Kw != 0.0) return 1;
65
+ }
66
+ }
67
+ for (i = 1; i <= net->Ntanks; i++)
68
+ {
69
+ if (net->Tank[i].Kb != 0.0) return 1;
70
+ }
71
+ }
72
+ return 0;
73
+ }
74
+
75
+
76
+ double getucf(double order)
77
+ /*
78
+ **--------------------------------------------------------------
79
+ ** Input: order = bulk reaction order
80
+ ** Output: returns a unit conversion factor
81
+ ** Purpose: converts bulk reaction rates from per Liter to
82
+ ** per FT3 basis
83
+ **--------------------------------------------------------------
84
+ */
85
+ {
86
+ if (order < 0.0) order = 0.0;
87
+ if (order == 1.0) return (1.0);
88
+ else return (1. / pow(LperFT3, (order - 1.0)));
89
+ }
90
+
91
+
92
+ void ratecoeffs(Project *pr)
93
+ /*
94
+ **--------------------------------------------------------------
95
+ ** Input: none
96
+ ** Output: none
97
+ ** Purpose: determines wall reaction coeff. for each pipe
98
+ **--------------------------------------------------------------
99
+ */
100
+ {
101
+ Network *net = &pr->network;
102
+ Quality *qual = &pr->quality;
103
+
104
+ int k;
105
+ double kw;
106
+
107
+ for (k = 1; k <= net->Nlinks; k++)
108
+ {
109
+ kw = net->Link[k].Kw;
110
+ if (kw != 0.0) kw = piperate(pr, k);
111
+ net->Link[k].Rc = kw;
112
+ qual->PipeRateCoeff[k] = 0.0;
113
+ }
114
+ }
115
+
116
+
117
+ void reactpipes(Project *pr, long dt)
118
+ /*
119
+ **--------------------------------------------------------------
120
+ ** Input: dt = time step
121
+ ** Output: none
122
+ ** Purpose: reacts water within each pipe over a time step.
123
+ **--------------------------------------------------------------
124
+ */
125
+ {
126
+ Network *net = &pr->network;
127
+ Quality *qual = &pr->quality;
128
+
129
+ int k;
130
+ Pseg seg;
131
+ double cseg, rsum, vsum;
132
+
133
+ // Examine each link in network
134
+ for (k = 1; k <= net->Nlinks; k++)
135
+ {
136
+ // Skip non-pipe links (pumps & valves)
137
+ if (net->Link[k].Type != PIPE) continue;
138
+ rsum = 0.0;
139
+ vsum = 0.0;
140
+
141
+ // Examine each segment of the pipe
142
+ seg = qual->FirstSeg[k];
143
+ while (seg != NULL)
144
+ {
145
+ // React segment over time dt
146
+ cseg = seg->c;
147
+ seg->c = pipereact(pr, k, seg->c, seg->v, dt);
148
+
149
+ // Update reaction component of mass balance
150
+ qual->MassBalance.reacted += (cseg - seg->c) * seg->v;
151
+
152
+ // Accumulate volume-weighted reaction rate
153
+ if (qual->Qualflag == CHEM)
154
+ {
155
+ rsum += fabs(seg->c - cseg) * seg->v;
156
+ vsum += seg->v;
157
+ }
158
+ seg = seg->prev;
159
+ }
160
+
161
+ // Normalize volume-weighted reaction rate
162
+ if (vsum > 0.0) qual->PipeRateCoeff[k] = rsum / vsum / dt * SECperDAY;
163
+ else qual->PipeRateCoeff[k] = 0.0;
164
+ }
165
+ }
166
+
167
+
168
+ void reacttanks(Project *pr, long dt)
169
+ /*
170
+ **--------------------------------------------------------------
171
+ ** Input: dt = time step
172
+ ** Output: none
173
+ ** Purpose: reacts water within each tank over a time step.
174
+ **--------------------------------------------------------------
175
+ */
176
+ {
177
+ Network *net = &pr->network;
178
+ Quality *qual = &pr->quality;
179
+
180
+ int i, k;
181
+ double c;
182
+ Pseg seg;
183
+ Stank *tank;
184
+
185
+ // Examine each tank in network
186
+ for (i = 1; i <= net->Ntanks; i++)
187
+ {
188
+ // Skip reservoirs
189
+ tank = &net->Tank[i];
190
+ if (tank->A == 0.0) continue;
191
+
192
+ // k is segment chain belonging to tank i
193
+ k = net->Nlinks + i;
194
+
195
+ // React each volume segment in the chain
196
+ seg = qual->FirstSeg[k];
197
+ while (seg != NULL)
198
+ {
199
+ c = seg->c;
200
+ seg->c = tankreact(pr, seg->c, seg->v, tank->Kb, dt);
201
+ qual->MassBalance.reacted += (c - seg->c) * seg->v;
202
+ seg = seg->prev;
203
+ }
204
+ }
205
+ }
206
+
207
+
208
+ double piperate(Project *pr, int k)
209
+ /*
210
+ **--------------------------------------------------------------
211
+ ** Input: k = link index
212
+ ** Output: returns reaction rate coeff. for 1st-order wall
213
+ ** reactions or mass transfer rate coeff. for 0-order
214
+ ** reactions
215
+ ** Purpose: finds wall reaction rate coeffs.
216
+ **--------------------------------------------------------------
217
+ */
218
+ {
219
+ Network *net = &pr->network;
220
+ Hydraul *hyd = &pr->hydraul;
221
+ Quality *qual = &pr->quality;
222
+
223
+ double a, d, u, q, kf, kw, y, Re, Sh;
224
+
225
+ d = net->Link[k].Diam; // Pipe diameter, ft
226
+
227
+ // Ignore mass transfer if Schmidt No. is 0
228
+ if (qual->Sc == 0.0)
229
+ {
230
+ if (qual->WallOrder == 0.0) return BIG;
231
+ else return (net->Link[k].Kw * (4.0 / d) / pr->Ucf[ELEV]);
232
+ }
233
+
234
+ // Compute Reynolds No.
235
+ // Flow rate made consistent with how it's saved to hydraulics file
236
+ q = (hyd->LinkStatus[k] <= CLOSED) ? 0.0 : hyd->LinkFlow[k];
237
+ a = PI * d * d / 4.0; // pipe area
238
+ u = fabs(q) / a; // flow velocity
239
+ Re = u * d / hyd->Viscos; // Reynolds number
240
+
241
+ // Compute Sherwood No. for stagnant flow
242
+ // (mass transfer coeff. = Diffus./radius)
243
+ if (Re < 1.0) Sh = 2.0;
244
+
245
+ // Compute Sherwood No. for turbulent flow using the Notter-Sleicher formula.
246
+ else if (Re >= 2300.0) Sh = 0.0149 * pow(Re, 0.88) * pow(qual->Sc, 0.333);
247
+
248
+ // Compute Sherwood No. for laminar flow using Graetz solution formula.
249
+ else
250
+ {
251
+ y = d / net->Link[k].Len * Re * qual->Sc;
252
+ Sh = 3.65 + 0.0668 * y / (1.0 + 0.04 * pow(y, 0.667));
253
+ }
254
+
255
+ // Compute mass transfer coeff. (in ft/sec)
256
+ kf = Sh * qual->Diffus / d;
257
+
258
+ // For zero-order reaction, return mass transfer coeff.
259
+ if (qual->WallOrder == 0.0) return kf;
260
+
261
+ // For first-order reaction, return apparent wall coeff.
262
+ kw = net->Link[k].Kw / pr->Ucf[ELEV]; // Wall coeff, ft/sec
263
+ kw = (4.0 / d) * kw * kf / (kf + fabs(kw)); // Wall coeff, 1/sec
264
+ return kw;
265
+ }
266
+
267
+
268
+ double pipereact(Project *pr, int k, double c, double v, long dt)
269
+ /*
270
+ **------------------------------------------------------------
271
+ ** Input: k = link index
272
+ ** c = current quality in segment
273
+ ** v = segment volume
274
+ ** dt = time step
275
+ ** Output: returns new WQ value
276
+ ** Purpose: computes new quality in a pipe segment after
277
+ ** reaction occurs
278
+ **------------------------------------------------------------
279
+ */
280
+ {
281
+ Network *net = &pr->network;
282
+ Quality *qual = &pr->quality;
283
+
284
+ double cnew, dc, dcbulk, dcwall, rbulk, rwall;
285
+
286
+ // For water age (hrs), update concentration by timestep
287
+ if (qual->Qualflag == AGE)
288
+ {
289
+ dc = (double)dt / 3600.0;
290
+ cnew = c + dc;
291
+ cnew = MAX(0.0, cnew);
292
+ return cnew;
293
+ }
294
+
295
+ // Otherwise find bulk & wall reaction rates
296
+ rbulk = bulkrate(pr, c, net->Link[k].Kb, qual->BulkOrder) * qual->Bucf;
297
+ rwall = wallrate(pr, c, net->Link[k].Diam, net->Link[k].Kw, net->Link[k].Rc);
298
+
299
+ // Find change in concentration over timestep
300
+ dcbulk = rbulk * (double)dt;
301
+ dcwall = rwall * (double)dt;
302
+
303
+ // Update cumulative mass reacted
304
+ if (pr->times.Htime >= pr->times.Rstart)
305
+ {
306
+ qual->Wbulk += fabs(dcbulk) * v;
307
+ qual->Wwall += fabs(dcwall) * v;
308
+ }
309
+
310
+ // Update concentration
311
+ dc = dcbulk + dcwall;
312
+ cnew = c + dc;
313
+ cnew = MAX(0.0, cnew);
314
+ return cnew;
315
+ }
316
+
317
+
318
+ double tankreact(Project *pr, double c, double v, double kb, long dt)
319
+ /*
320
+ **-------------------------------------------------------
321
+ ** Input: c = current quality in tank
322
+ ** v = tank volume
323
+ ** kb = reaction coeff.
324
+ ** dt = time step
325
+ ** Output: returns new WQ value
326
+ ** Purpose: computes new quality in a tank after
327
+ ** reaction occurs
328
+ **-------------------------------------------------------
329
+ */
330
+ {
331
+ Quality *qual = &pr->quality;
332
+
333
+ double cnew, dc, rbulk;
334
+
335
+ // For water age, update concentration by timestep
336
+ if (qual->Qualflag == AGE)
337
+ {
338
+ dc = (double)dt / 3600.0;
339
+ }
340
+
341
+ // For chemical analysis apply bulk reaction rate
342
+ else
343
+ {
344
+ // Find bulk reaction rate
345
+ rbulk = bulkrate(pr, c, kb, qual->TankOrder) * qual->Tucf;
346
+
347
+ // Find concentration change & update quality
348
+ dc = rbulk * (double)dt;
349
+ if (pr->times.Htime >= pr->times.Rstart)
350
+ {
351
+ qual->Wtank += fabs(dc) * v;
352
+ }
353
+ }
354
+ cnew = c + dc;
355
+ cnew = MAX(0.0, cnew);
356
+ return cnew;
357
+ }
358
+
359
+
360
+ double bulkrate(Project *pr, double c, double kb, double order)
361
+ /*
362
+ **-----------------------------------------------------------
363
+ ** Input: c = current WQ concentration
364
+ ** kb = bulk reaction coeff.
365
+ ** order = bulk reaction order
366
+ ** Output: returns bulk reaction rate
367
+ ** Purpose: computes bulk reaction rate (mass/volume/time)
368
+ **-----------------------------------------------------------
369
+ */
370
+ {
371
+ Quality *qual = &pr->quality;
372
+
373
+ double c1;
374
+
375
+ // Find bulk reaction potential taking into account
376
+ // limiting potential & reaction order.
377
+
378
+ // Zero-order kinetics:
379
+ if (order == 0.0) c = 1.0;
380
+
381
+ // Michaelis-Menton kinetics:
382
+ else if (order < 0.0)
383
+ {
384
+ c1 = qual->Climit + SGN(kb) * c;
385
+ if (fabs(c1) < TINY) c1 = SGN(c1) * TINY;
386
+ c = c / c1;
387
+ }
388
+
389
+ // N-th order kinetics:
390
+ else
391
+ {
392
+ // Account for limiting potential
393
+ if (qual->Climit == 0.0) c1 = c;
394
+ else c1 = MAX(0.0, SGN(kb) * (qual->Climit - c));
395
+
396
+ // Compute concentration potential
397
+ if (order == 1.0) c = c1;
398
+ else if (order == 2.0) c = c1 * c;
399
+ else c = c1 * pow(MAX(0.0, c), order - 1.0);
400
+ }
401
+
402
+ // Reaction rate = bulk coeff. * potential
403
+ if (c < 0) c = 0;
404
+ return kb * c;
405
+ }
406
+
407
+
408
+ double wallrate(Project *pr, double c, double d, double kw, double kf)
409
+ /*
410
+ **------------------------------------------------------------
411
+ ** Input: c = current WQ concentration
412
+ ** d = pipe diameter
413
+ ** kw = intrinsic wall reaction coeff.
414
+ ** kf = mass transfer coeff. for 0-order reaction
415
+ ** (ft/sec) or apparent wall reaction coeff.
416
+ ** for 1-st order reaction (1/sec)
417
+ ** Output: returns wall reaction rate in mass/ft3/sec
418
+ ** Purpose: computes wall reaction rate
419
+ **------------------------------------------------------------
420
+ */
421
+ {
422
+ Quality *qual = &pr->quality;
423
+
424
+ if (kw == 0.0 || d == 0.0) return (0.0);
425
+
426
+ if (qual->WallOrder == 0.0) // 0-order reaction */
427
+ {
428
+ kf = SGN(kw) * c * kf; //* Mass transfer rate (mass/ft2/sec)
429
+ kw = kw * SQR(pr->Ucf[ELEV]); // Reaction rate (mass/ft2/sec)
430
+ if (fabs(kf) < fabs(kw)) kw = kf; // Reaction mass transfer limited
431
+ return (kw * 4.0 / d); // Reaction rate (mass/ft3/sec)
432
+ }
433
+ else return (c * kf); // 1st-order reaction
434
+ }
435
+
436
+
437
+ double mixtank(Project *pr, int n, double volin, double massin, double volout)
438
+ /*
439
+ **------------------------------------------------------------
440
+ ** Input: n = node index
441
+ ** volin = inflow volume to tank over time step
442
+ ** massin = mass inflow to tank over time step
443
+ ** volout = outflow volume from tank over time step
444
+ ** Output: returns new quality for tank
445
+ ** Purpose: mixes inflow with tank's contents to update its quality.
446
+ **------------------------------------------------------------
447
+ */
448
+ {
449
+ Network *net = &pr->network;
450
+
451
+ int i;
452
+ double vnet;
453
+ i = n - net->Njuncs;
454
+ vnet = volin - volout;
455
+ switch (net->Tank[i].MixModel)
456
+ {
457
+ case MIX1: tankmix1(pr, i, volin, massin, vnet); break;
458
+ case MIX2: tankmix2(pr, i, volin, massin, vnet); break;
459
+ case FIFO: tankmix3(pr, i, volin, massin, vnet); break;
460
+ case LIFO: tankmix4(pr, i, volin, massin, vnet); break;
461
+ }
462
+ return net->Tank[i].C;
463
+ }
464
+
465
+
466
+ void tankmix1(Project *pr, int i, double vin, double win, double vnet)
467
+ /*
468
+ **---------------------------------------------
469
+ ** Input: i = tank index
470
+ ** vin = inflow volume
471
+ ** win = mass inflow
472
+ ** vnet = inflow - outflow
473
+ ** Output: none
474
+ ** Purpose: updates quality in a complete mix tank model
475
+ **---------------------------------------------
476
+ */
477
+ {
478
+ Network *net = &pr->network;
479
+ Quality *qual = &pr->quality;
480
+
481
+ int k;
482
+ double vnew;
483
+ Pseg seg;
484
+ Stank *tank = &net->Tank[i];
485
+
486
+ k = net->Nlinks + i;
487
+ seg = qual->FirstSeg[k];
488
+ if (seg)
489
+ {
490
+ vnew = seg->v + vin;
491
+ if (vnew > 0.0) seg->c = (seg->c * seg->v + win) / vnew;
492
+ seg->v += vnet;
493
+ seg->v = MAX(0.0, seg->v);
494
+ tank->C = seg->c;
495
+
496
+ // Account for mass lost in tank overflow
497
+ if (seg->v > tank->Vmax)
498
+ {
499
+ qual->MassBalance.outflow += ((seg->v) - tank->Vmax) * tank->C;
500
+ seg->v = tank->Vmax;
501
+ }
502
+ }
503
+ }
504
+
505
+
506
+ void tankmix2(Project *pr, int i, double vin, double win, double vnet)
507
+ /*
508
+ **------------------------------------------------
509
+ ** Input: i = tank index
510
+ ** vin = inflow volume
511
+ ** win = mass inflow
512
+ ** vnet = inflow - outflow
513
+ ** Output: none
514
+ ** Purpose: updates quality in a 2-compartment tank model
515
+ **------------------------------------------------
516
+ */
517
+ {
518
+ Network *net = &pr->network;
519
+ Quality *qual = &pr->quality;
520
+
521
+ int k;
522
+ double vt, // Transferred volume
523
+ vmz, // Full mixing zone volume
524
+ vsz; // Full stagnant zone volume
525
+ Pseg mixzone, // Mixing zone segment
526
+ stagzone; // Stagnant zone segment
527
+ Stank *tank = &pr->network.Tank[i];
528
+
529
+ // Identify segments for each compartment
530
+ k = net->Nlinks + i;
531
+ mixzone = qual->LastSeg[k];
532
+ stagzone = qual->FirstSeg[k];
533
+ if (mixzone == NULL || stagzone == NULL) return;
534
+
535
+ // Full mixing zone volume
536
+ vmz = tank->V1frac * tank->Vmax;
537
+
538
+ // Tank is filling
539
+ vt = 0.0;
540
+ if (vnet > 0.0)
541
+ {
542
+ vt = MAX(0.0, (mixzone->v + vnet - vmz));
543
+ if (vin > 0.0)
544
+ {
545
+ mixzone->c = ((mixzone->c) * (mixzone->v) + win) /
546
+ (mixzone->v + vin);
547
+ }
548
+ if (vt > 0.0)
549
+ {
550
+ stagzone->c = ((stagzone->c) * (stagzone->v) +
551
+ (mixzone->c) * vt) / (stagzone->v + vt);
552
+ }
553
+ }
554
+
555
+ // Tank is emptying
556
+ else if (vnet < 0.0)
557
+ {
558
+ if (stagzone->v > 0.0) vt = MIN(stagzone->v, (-vnet));
559
+ if (vin + vt > 0.0)
560
+ {
561
+ mixzone->c = ((mixzone->c) * (mixzone->v) + win +
562
+ (stagzone->c) * vt) / (mixzone->v + vin + vt);
563
+ }
564
+ }
565
+
566
+ // Update segment volumes
567
+ if (vt > 0.0)
568
+ {
569
+ if (vnet > 0.0)
570
+ {
571
+ mixzone->v = vmz;
572
+ stagzone->v += vt;
573
+
574
+ // Account for mass lost in overflow from stagnant zone
575
+ vsz = (tank->Vmax) - vmz;
576
+ if (stagzone->v > vsz)
577
+ {
578
+ qual->MassBalance.outflow += ((stagzone->v) - vsz) * stagzone->c;
579
+ stagzone->v = vsz;
580
+ }
581
+ }
582
+ else
583
+ {
584
+ stagzone->v = MAX(0.0, ((stagzone->v) - vt));
585
+ mixzone->v = vmz + vt + vnet;
586
+ }
587
+ }
588
+ else
589
+ {
590
+ mixzone->v += vnet;
591
+ mixzone->v = MIN(mixzone->v, vmz);
592
+ mixzone->v = MAX(0.0, mixzone->v);
593
+ if (vmz - mixzone->v > 0.0) stagzone->v = 0.0;
594
+ }
595
+
596
+ // Use quality of mixing zone to represent quality of
597
+ // tank since this is where outflow begins to flow from
598
+ tank->C = mixzone->c;
599
+ }
600
+
601
+
602
+ void tankmix3(Project *pr, int i, double vin, double win, double vnet)
603
+ /*
604
+ **----------------------------------------------------------
605
+ ** Input: i = tank index
606
+ ** vin = inflow volume
607
+ ** win = mass inflow
608
+ ** vnet = inflow - outflow
609
+ ** Output: none
610
+ ** Purpose: Updates quality in a First-In-First-Out (FIFO) tank model.
611
+ **----------------------------------------------------------
612
+ */
613
+ {
614
+ Network *net = &pr->network;
615
+ Quality *qual = &pr->quality;
616
+
617
+ int k;
618
+ double vout, vseg;
619
+ double cin, vsum, wsum;
620
+ Pseg seg;
621
+ Stank *tank = &pr->network.Tank[i];
622
+
623
+ k = net->Nlinks + i;
624
+ if (qual->LastSeg[k] == NULL || qual->FirstSeg[k] == NULL) return;
625
+
626
+ // Add new last segment for flow entering the tank
627
+ if (vin > 0.0)
628
+ {
629
+ // ... increase segment volume if inflow has same quality as segment
630
+ cin = win / vin;
631
+ seg = qual->LastSeg[k];
632
+ if (fabs(seg->c - cin) < qual->Ctol) seg->v += vin;
633
+
634
+ // ... otherwise add a new last segment to the tank
635
+ else addseg(pr, k, vin, cin);
636
+ }
637
+
638
+ // Find volume leaving tank, adjusted so its volume doesn't exceed Vmax
639
+ vout = vin - vnet;
640
+ if (tank->V >= tank->Vmax && vnet > 0.0) vout = vin;
641
+
642
+ // Withdraw outflow from first segment
643
+ vsum = 0.0;
644
+ wsum = 0.0;
645
+ while (vout > 0.0)
646
+ {
647
+ seg = qual->FirstSeg[k];
648
+ if (seg == NULL) break;
649
+ vseg = seg->v; // Flow volume from leading seg
650
+ vseg = MIN(vseg, vout);
651
+ if (seg == qual->LastSeg[k]) vseg = vout;
652
+ vsum += vseg;
653
+ wsum += (seg->c) * vseg;
654
+ vout -= vseg; // Remaining flow volume
655
+ if (vout >= 0.0 && vseg >= seg->v) // Seg used up
656
+ {
657
+ if (seg->prev)
658
+ {
659
+ qual->FirstSeg[k] = seg->prev;
660
+ seg->prev = qual->FreeSeg;
661
+ qual->FreeSeg = seg;
662
+ }
663
+ }
664
+ else seg->v -= vseg; // Remaining volume in segment
665
+ }
666
+
667
+ // Use quality withdrawn from 1st segment
668
+ // to represent overall quality of tank
669
+ if (vsum > 0.0) tank->C = wsum / vsum;
670
+ else if (qual->FirstSeg[k] == NULL) tank->C = 0.0;
671
+ else tank->C = qual->FirstSeg[k]->c;
672
+
673
+ // Account for mass lost in overflow from 1st segment
674
+ if (tank->V >= tank->Vmax && vnet > 0.0)
675
+ qual->MassBalance.outflow += vnet * tank->C;
676
+ }
677
+
678
+
679
+ void tankmix4(Project *pr, int i, double vin, double win, double vnet)
680
+ /*
681
+ **----------------------------------------------------------
682
+ ** Input: i = tank index
683
+ ** vin = inflow volume
684
+ ** win = mass inflow
685
+ ** vnet = inflow - outflow
686
+ ** Output: none
687
+ ** Purpose: Updates quality in a Last In-First Out (LIFO) tank model.
688
+ **----------------------------------------------------------
689
+ */
690
+ {
691
+ Network *net = &pr->network;
692
+ Quality *qual = &pr->quality;
693
+
694
+ int k;
695
+ double cin, vsum, wsum, vseg;
696
+ Pseg seg;
697
+ Stank *tank = &pr->network.Tank[i];
698
+
699
+ k = net->Nlinks + i;
700
+ if (qual->LastSeg[k] == NULL || qual->FirstSeg[k] == NULL) return;
701
+
702
+ // Find inflow concentration
703
+ if (vin > 0.0) cin = win / vin;
704
+ else cin = 0.0;
705
+
706
+ // If tank filling, then create new last seg
707
+ tank->C = qual->LastSeg[k]->c;
708
+ seg = qual->LastSeg[k];
709
+ if (vnet > 0.0)
710
+ {
711
+ // ... inflow quality is same as last segment's quality,
712
+ // so just add inflow volume to last segment
713
+ if (fabs(seg->c - cin) < qual->Ctol) seg->v += vnet;
714
+
715
+ // ... otherwise add a new last segment with inflow quality
716
+ else addseg(pr, k, vnet, cin);
717
+
718
+ // Update reported tank quality
719
+ tank->C = qual->LastSeg[k]->c;
720
+
721
+ // If tank full then remove vnet from leading segments
722
+ if (tank->V >= tank->Vmax)
723
+ {
724
+ wsum = 0.0;
725
+ while (vnet > 0.0)
726
+ {
727
+ seg = qual->FirstSeg[k];
728
+ if (seg == NULL) break;
729
+ vseg = seg->v; // Flow volume from leading seg
730
+ vseg = MIN(vseg, vnet);
731
+ if (seg == qual->LastSeg[k]) vseg = vnet;
732
+ wsum += (seg->c) * vseg;
733
+ vnet -= vseg; // Remaining flow volume
734
+ if (vnet >= 0.0 && vseg >= seg->v) // Seg used up
735
+ {
736
+ if (seg->prev)
737
+ {
738
+ qual->FirstSeg[k] = seg->prev;
739
+ seg->prev = qual->FreeSeg;
740
+ qual->FreeSeg = seg;
741
+ }
742
+ }
743
+ else seg->v -= vseg; // Remaining volume in segment
744
+ }
745
+ qual->MassBalance.outflow += wsum;
746
+ }
747
+ }
748
+
749
+ // If tank emptying then remove last segments until vnet consumed
750
+ else if (vnet < 0.0)
751
+ {
752
+ vsum = 0.0;
753
+ wsum = 0.0;
754
+ vnet = -vnet;
755
+
756
+ // Reverse segment chain so segments are processed from last to first
757
+ reversesegs(pr, k);
758
+
759
+ // While there is still volume to remove
760
+ while (vnet > 0.0)
761
+ {
762
+ // ... start with reversed first segment
763
+ seg = qual->FirstSeg[k];
764
+ if (seg == NULL) break;
765
+
766
+ // ... find volume to remove from it
767
+ vseg = seg->v;
768
+ vseg = MIN(vseg, vnet);
769
+ if (seg == qual->LastSeg[k]) vseg = vnet;
770
+
771
+ // ... update total volume & mass removed
772
+ vsum += vseg;
773
+ wsum += (seg->c) * vseg;
774
+
775
+ // ... update remaining volume to remove
776
+ vnet -= vseg;
777
+
778
+ // ... if no more volume left in current segment
779
+ if (vnet >= 0.0 && vseg >= seg->v)
780
+ {
781
+ // ... replace current segment with previous one
782
+ if (seg->prev)
783
+ {
784
+ qual->FirstSeg[k] = seg->prev;
785
+ seg->prev = qual->FreeSeg;
786
+ qual->FreeSeg = seg;
787
+ }
788
+ }
789
+
790
+ // ... otherwise reduce volume of current segment
791
+ else seg->v -= vseg;
792
+ }
793
+
794
+ // Restore original orientation of segment chain
795
+ reversesegs(pr, k);
796
+
797
+ // Reported tank quality is mixture of flow released and any inflow
798
+ tank->C = (wsum + win) / (vsum + vin);
799
+ }
800
+ }