epanet-plus 0.0.1__cp310-cp310-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-310-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/quality.c ADDED
@@ -0,0 +1,695 @@
1
+ /*
2
+ ******************************************************************************
3
+ Project: OWA EPANET
4
+ Version: 2.3
5
+ Module: quality.c
6
+ Description: implements EPANET's water quality engine
7
+ Authors: see AUTHORS
8
+ Copyright: see AUTHORS
9
+ License: see LICENSE
10
+ Last Updated: 02/14/2025
11
+ ******************************************************************************
12
+ */
13
+
14
+ #include <stdlib.h>
15
+ #include <stdio.h>
16
+ #include <string.h>
17
+ #include <math.h>
18
+
19
+ #include "mempool.h"
20
+ #include "types.h"
21
+ #include "funcs.h"
22
+
23
+ // Stagnant flow tolerance
24
+ const double Q_STAGNANT = 0.005 / GPMperCFS; // 0.005 gpm = 1.114e-5 cfs
25
+
26
+ // Exported functions
27
+ double findsourcequal(Project *, int, double, long);
28
+
29
+ // Imported functions
30
+ extern char setreactflag(Project *);
31
+ extern double getucf(double);
32
+ extern void ratecoeffs(Project *);
33
+ extern void initsegs(Project *);
34
+ extern void reversesegs(Project *, int);
35
+ extern int sortnodes(Project *);
36
+ extern void transport(Project *, long);
37
+
38
+ // Local functions
39
+ static double sourcequal(Project *, Psource);
40
+ static void evalmassbalance(Project *);
41
+ static double findstoredmass(Project *);
42
+ static int flowdirchanged(Project *);
43
+
44
+
45
+ int openqual(Project *pr)
46
+ /*
47
+ **--------------------------------------------------------------
48
+ ** Input: none
49
+ ** Output: returns error code
50
+ ** Purpose: opens water quality solver
51
+ **--------------------------------------------------------------
52
+ */
53
+ {
54
+ Network *net = &pr->network;
55
+ Quality *qual = &pr->quality;
56
+
57
+ int errcode = 0;
58
+ int n;
59
+
60
+ // Return if no quality analysis requested
61
+ if (qual->Qualflag == NONE) return errcode;
62
+
63
+ // Build nodal adjacency lists if they don't already exist
64
+ if (net->Adjlist == NULL)
65
+ {
66
+ // Check for too few nodes & no fixed grade nodes
67
+ if (net->Nnodes < 2) return 223;
68
+ if (net->Ntanks == 0) return 224;
69
+
70
+ // Build adjacency lists
71
+ errcode = buildadjlists(net);
72
+ if (errcode ) return errcode;
73
+
74
+ // Check for unconnected nodes
75
+ if (errcode = unlinked(pr)) return errcode;
76
+ }
77
+
78
+ // Create a memory pool for water quality segments
79
+ qual->OutOfMemory = FALSE;
80
+ qual->SegPool = mempool_create();
81
+ if (qual->SegPool == NULL) errcode = 101;
82
+
83
+ // Allocate arrays for link flow direction & reaction rates
84
+ n = net->Nlinks + 1;
85
+ qual->FlowDir = (FlowDirection *)calloc(n, sizeof(FlowDirection));
86
+ qual->PipeRateCoeff = (double *)calloc(n, sizeof(double));
87
+
88
+ // Allocate arrays used for volume segments in links & tanks
89
+ n = net->Nlinks + net->Ntanks + 1;
90
+ qual->FirstSeg = (Pseg *)calloc(n, sizeof(Pseg));
91
+ qual->LastSeg = (Pseg *)calloc(n, sizeof(Pseg));
92
+
93
+ // Allocate memory for topologically sorted nodes
94
+ qual->SortedNodes = (int *)calloc(n, sizeof(int));
95
+
96
+ ERRCODE(MEMCHECK(qual->FlowDir));
97
+ ERRCODE(MEMCHECK(qual->PipeRateCoeff));
98
+ ERRCODE(MEMCHECK(qual->FirstSeg));
99
+ ERRCODE(MEMCHECK(qual->LastSeg));
100
+ ERRCODE(MEMCHECK(qual->SortedNodes));
101
+ return errcode;
102
+ }
103
+
104
+
105
+ int initqual(Project *pr)
106
+ /*
107
+ **--------------------------------------------------------------
108
+ ** Input: none
109
+ ** Output: none
110
+ ** Purpose: re-initializes water quality solver
111
+ **--------------------------------------------------------------
112
+ */
113
+ {
114
+ Network *net = &pr->network;
115
+ Hydraul *hyd = &pr->hydraul;
116
+ Quality *qual = &pr->quality;
117
+ Times *time = &pr->times;
118
+
119
+ int i;
120
+ int errcode = 0;
121
+
122
+ // Re-position hydraulics file
123
+ if (!hyd->OpenHflag)
124
+ {
125
+ fseek(pr->outfile.HydFile, pr->outfile.HydOffset, SEEK_SET);
126
+ }
127
+
128
+ // Set elapsed times to zero
129
+ time->Qtime = 0;
130
+ time->Htime = 0;
131
+ time->Rtime = time->Rstart;
132
+ pr->report.Nperiods = 0;
133
+
134
+ // Initialize node quality
135
+ for (i = 1; i <= net->Nnodes; i++)
136
+ {
137
+ if (qual->Qualflag == TRACE) qual->NodeQual[i] = 0.0;
138
+ else qual->NodeQual[i] = net->Node[i].C0;
139
+ if (net->Node[i].S != NULL) net->Node[i].S->Smass = 0.0;
140
+ }
141
+ if (qual->Qualflag == NONE) return errcode;
142
+
143
+ // Initialize tank quality
144
+ for (i = 1; i <= net->Ntanks; i++)
145
+ {
146
+ net->Tank[i].C = qual->NodeQual[net->Tank[i].Node];
147
+ }
148
+
149
+ // Initialize quality at trace node (if applicable)
150
+ if (qual->Qualflag == TRACE) qual->NodeQual[qual->TraceNode] = 100.0;
151
+
152
+ // Compute Schmidt number
153
+ if (qual->Diffus > 0.0) qual->Sc = hyd->Viscos / qual->Diffus;
154
+ else qual->Sc = 0.0;
155
+
156
+ // Compute unit conversion factor for bulk react. coeff.
157
+ qual->Bucf = getucf(qual->BulkOrder);
158
+ qual->Tucf = getucf(qual->TankOrder);
159
+
160
+ // Check if modeling a reactive substance
161
+ qual->Reactflag = setreactflag(pr);
162
+
163
+ // Reset memory pool used for pipe & tank segments
164
+ qual->FreeSeg = NULL;
165
+ mempool_reset(qual->SegPool);
166
+
167
+ // Create initial set of pipe & tank segments
168
+ initsegs(pr);
169
+
170
+ // Initialize link flow direction indicator
171
+ for (i = 1; i <= net->Nlinks; i++) qual->FlowDir[i] = ZERO_FLOW;
172
+
173
+ // Initialize avg. reaction rates
174
+ qual->Wbulk = 0.0;
175
+ qual->Wwall = 0.0;
176
+ qual->Wtank = 0.0;
177
+ qual->Wsource = 0.0;
178
+
179
+ // Initialize mass balance components
180
+ qual->MassBalance.initial = findstoredmass(pr);
181
+ qual->MassBalance.inflow = 0.0;
182
+ qual->MassBalance.outflow = 0.0;
183
+ qual->MassBalance.reacted = 0.0;
184
+ qual->MassBalance.final = 0.0;
185
+ qual->MassBalance.ratio = 0.0;
186
+ qual->MassBalance.segCount = 0;
187
+ return errcode;
188
+ }
189
+
190
+
191
+ int runqual(Project *pr, long *t)
192
+ /*
193
+ **--------------------------------------------------------------
194
+ ** Input: none
195
+ ** Output: t = current simulation time (sec)
196
+ ** Returns: error code
197
+ ** Purpose: retrieves hydraulics for next hydraulic time step
198
+ ** (at time t) and saves current results to file
199
+ **--------------------------------------------------------------
200
+ */
201
+ {
202
+ Hydraul *hyd = &pr->hydraul;
203
+ Quality *qual = &pr->quality;
204
+ Times *time = &pr->times;
205
+
206
+ long hydtime = 0; // Hydraulic solution time
207
+ long hydstep = 0; // Hydraulic time step
208
+ int errcode = 0;
209
+
210
+ // Update reported simulation time
211
+ *t = time->Qtime;
212
+
213
+ // Read hydraulic solution from hydraulics file
214
+ if (time->Qtime == time->Htime)
215
+ {
216
+ // Read hydraulic results from file
217
+ if (!hyd->OpenHflag)
218
+ {
219
+ if (!readhyd(pr, &hydtime)) return 307;
220
+ if (!readhydstep(pr, &hydstep)) return 307;
221
+ time->Htime = hydtime;
222
+ }
223
+
224
+ // Save current results to output file
225
+ if (time->Htime >= time->Rtime)
226
+ {
227
+ if (pr->outfile.Saveflag)
228
+ {
229
+ errcode = saveoutput(pr);
230
+ pr->report.Nperiods++;
231
+ }
232
+ time->Rtime += time->Rstep;
233
+ }
234
+ if (errcode) return errcode;
235
+
236
+ // If simulating water quality
237
+ if (qual->Qualflag != NONE && time->Qtime < time->Dur)
238
+ {
239
+ // ... compute reaction rate coeffs.
240
+ if (qual->Reactflag && qual->Qualflag != AGE) ratecoeffs(pr);
241
+
242
+ // ... topologically sort network nodes if flow directions change
243
+ if (flowdirchanged(pr) == TRUE)
244
+ {
245
+ errcode = sortnodes(pr);
246
+ }
247
+ }
248
+ if (!hyd->OpenHflag) time->Htime = hydtime + hydstep;
249
+ }
250
+ return errcode;
251
+ }
252
+
253
+
254
+ int nextqual(Project *pr, long *tstep)
255
+ /*
256
+ **--------------------------------------------------------------
257
+ ** Input: none
258
+ ** Output: tstep = time step (sec) over which quality was updated
259
+ ** Returns: error code
260
+ ** Purpose: updates water quality in network until next hydraulic
261
+ ** event occurs (after tstep secs.)
262
+ **--------------------------------------------------------------
263
+ */
264
+ {
265
+ Quality *qual = &pr->quality;
266
+ Times *time = &pr->times;
267
+
268
+ long hydstep; // Time step until next hydraulic event
269
+ long dt, qtime;
270
+ int errcode = 0;
271
+
272
+ // Find time step till next hydraulic event
273
+ *tstep = 0;
274
+ hydstep = 0;
275
+ if (time->Htime <= time->Dur) hydstep = time->Htime - time->Qtime;
276
+
277
+ // Perform water quality routing over this time step
278
+ if (qual->Qualflag != NONE && hydstep > 0)
279
+ {
280
+ // Repeat over each quality time step until tstep is reached
281
+ qtime = 0;
282
+ while (!qual->OutOfMemory && qtime < hydstep)
283
+ {
284
+ dt = MIN(time->Qstep, hydstep - qtime);
285
+ qtime += dt;
286
+ transport(pr, dt);
287
+ }
288
+ if (qual->OutOfMemory) errcode = 101;
289
+ }
290
+
291
+ // Update mass balance ratio
292
+ evalmassbalance(pr);
293
+
294
+ // Update current time
295
+ if (!errcode) *tstep = hydstep;
296
+ time->Qtime += hydstep;
297
+
298
+ // If no more time steps remain
299
+ if (!errcode && *tstep == 0)
300
+ {
301
+ // ... report overall mass balance
302
+ if (qual->Qualflag != NONE && pr->report.Statflag)
303
+ {
304
+ writemassbalance(pr);
305
+ }
306
+
307
+ // ... write the final portion of the binary output file
308
+ if (pr->outfile.Saveflag) errcode = savefinaloutput(pr);
309
+ }
310
+ return errcode;
311
+ }
312
+
313
+
314
+ int stepqual(Project *pr, long *tleft)
315
+ /*
316
+ **--------------------------------------------------------------
317
+ ** Input: none
318
+ ** Output: tleft = time left in simulation
319
+ ** Returns: error code
320
+ ** Purpose: updates quality conditions over a single
321
+ ** quality time step
322
+ **--------------------------------------------------------------
323
+ */
324
+ {
325
+ Quality *qual = &pr->quality;
326
+ Times *time = &pr->times;
327
+
328
+ long dt, hstep, t, tstep;
329
+ int errcode = 0;
330
+
331
+ tstep = time->Qstep;
332
+ do
333
+ {
334
+ // Set local time step to quality time step
335
+ dt = tstep;
336
+
337
+ // Find time step until next hydraulic event
338
+ hstep = time->Htime - time->Qtime;
339
+
340
+ // If next hydraulic event occurs before end of local time step
341
+ if (hstep < dt)
342
+ {
343
+ // ... adjust local time step to next hydraulic event
344
+ dt = hstep;
345
+
346
+ // ... transport quality over local time step
347
+ if (qual->Qualflag != NONE) transport(pr, dt);
348
+ time->Qtime += dt;
349
+
350
+ // ... quit if running quality concurrently with hydraulics
351
+ if (pr->hydraul.OpenHflag) break;
352
+
353
+ // ... otherwise call runqual() to update hydraulics
354
+ errcode = runqual(pr, &t);
355
+ time->Qtime = t;
356
+ }
357
+
358
+ // Otherwise transport quality over current local time step
359
+ else
360
+ {
361
+ if (qual->Qualflag != NONE) transport(pr, dt);
362
+ time->Qtime += dt;
363
+ }
364
+
365
+ // Reduce quality time step by local time step
366
+ tstep -= dt;
367
+ if (qual->OutOfMemory) errcode = 101;
368
+
369
+ } while (!errcode && tstep > 0);
370
+
371
+ // Update mass balance ratio
372
+ evalmassbalance(pr);
373
+
374
+ // Update total simulation time left
375
+ *tleft = time->Dur - time->Qtime;
376
+
377
+ // If no more time steps remain
378
+ if (!errcode && *tleft == 0)
379
+ {
380
+ // ... report overall mass balance
381
+ if (qual->Qualflag != NONE && pr->report.Statflag)
382
+ {
383
+ writemassbalance(pr);
384
+ }
385
+
386
+ // ... write the final portion of the binary output file
387
+ if (pr->outfile.Saveflag) errcode = savefinaloutput(pr);
388
+ }
389
+ return errcode;
390
+ }
391
+
392
+
393
+ int closequal(Project *pr)
394
+ /*
395
+ **--------------------------------------------------------------
396
+ ** Input: none
397
+ ** Output: returns error code
398
+ ** Purpose: closes water quality solver
399
+ **--------------------------------------------------------------
400
+ */
401
+ {
402
+ Quality *qual = &pr->quality;
403
+ int errcode = 0;
404
+
405
+ if (qual->Qualflag != NONE)
406
+ {
407
+ if (qual->SegPool) mempool_delete(qual->SegPool);
408
+ FREE(qual->FirstSeg);
409
+ FREE(qual->LastSeg);
410
+ FREE(qual->PipeRateCoeff);
411
+ FREE(qual->FlowDir);
412
+ FREE(qual->SortedNodes);
413
+ }
414
+ freeadjlists(&pr->network);
415
+ return errcode;
416
+ }
417
+
418
+
419
+ double avgqual(Project *pr, int k)
420
+ /*
421
+ **--------------------------------------------------------------
422
+ ** Input: k = link index
423
+ ** Output: returns quality concentration
424
+ ** Purpose: computes current average quality in link k
425
+ **--------------------------------------------------------------
426
+ */
427
+ {
428
+ Network *net = &pr->network;
429
+ Quality *qual = &pr->quality;
430
+
431
+ double vsum = 0.0, msum = 0.0;
432
+ Pseg seg;
433
+
434
+ if (qual->Qualflag == NONE) return 0.0;
435
+
436
+ // Sum up the quality and volume in each segment of the link
437
+ if (qual->FirstSeg != NULL)
438
+ {
439
+ seg = qual->FirstSeg[k];
440
+ while (seg != NULL)
441
+ {
442
+ vsum += seg->v;
443
+ msum += (seg->c) * (seg->v);
444
+ seg = seg->prev;
445
+ }
446
+ }
447
+
448
+ // Compute average quality if link has volume
449
+ if (vsum > 0.0) return (msum / vsum);
450
+
451
+ // Otherwise use the average quality of the link's end nodes
452
+ else
453
+ {
454
+ return ((qual->NodeQual[net->Link[k].N1] +
455
+ qual->NodeQual[net->Link[k].N2]) / 2.);
456
+ }
457
+ }
458
+
459
+
460
+ double findsourcequal(Project *pr, int n, double volout, long tstep)
461
+ /*
462
+ **---------------------------------------------------------------------
463
+ ** Input: n = node index
464
+ ** volout = volume of node outflow over time step
465
+ ** tstep = current quality time step
466
+ ** Output: returns concentration added by an external quality source.
467
+ ** Purpose: computes contribution (if any) of mass addition from an
468
+ ** external quality source at a node.
469
+ **---------------------------------------------------------------------
470
+ */
471
+ {
472
+ Network *net = &pr->network;
473
+ Hydraul *hyd = &pr->hydraul;
474
+ Quality *qual = &pr->quality;
475
+ Times *time = &pr->times;
476
+
477
+ double massadded = 0.0, c;
478
+ Psource source;
479
+
480
+ // Sources only apply to CHEMICAL analyses
481
+ if (qual->Qualflag != CHEM) return 0.0;
482
+
483
+ // Return 0 if node is not a quality source or has no outflow
484
+ source = net->Node[n].S;
485
+ if (source == NULL) return 0.0;
486
+ if (source->C0 == 0.0) return 0.0;
487
+ if (volout / tstep <= Q_STAGNANT) return 0.0;
488
+
489
+ // Added source concentration depends on source type
490
+ c = sourcequal(pr, source);
491
+ switch (source->Type)
492
+ {
493
+ // Concentration Source:
494
+ case CONCEN:
495
+ if (net->Node[n].Type == JUNCTION)
496
+ {
497
+ // ... source requires a negative demand at the node
498
+ if (hyd->NodeDemand[n] < 0.0)
499
+ {
500
+ c = -c * hyd->NodeDemand[n] * tstep / volout;
501
+ }
502
+ else c = 0.0;
503
+ }
504
+ break;
505
+
506
+ // Mass Inflow Booster Source:
507
+ case MASS:
508
+ // ... convert source input from mass/sec to concentration
509
+ c = c * tstep / volout;
510
+ break;
511
+
512
+ // Setpoint Booster Source:
513
+ // Source quality is difference between source strength
514
+ // & node quality
515
+ case SETPOINT:
516
+ c = MAX(c - qual->NodeQual[n], 0.0);
517
+ break;
518
+
519
+ // Flow-Paced Booster Source:
520
+ // Source quality equals source strength
521
+ case FLOWPACED:
522
+ break;
523
+ }
524
+
525
+ // Source mass added over time step = source concen. * outflow volume
526
+ massadded = c * volout;
527
+
528
+ // Update source's total mass added
529
+ source->Smass += massadded;
530
+
531
+ // Update Wsource
532
+ if (time->Htime >= time->Rstart)
533
+ {
534
+ qual->Wsource += massadded;
535
+ }
536
+ return c;
537
+ }
538
+
539
+
540
+ double sourcequal(Project *pr, Psource source)
541
+ /*
542
+ **--------------------------------------------------------------
543
+ ** Input: source = a water quality source object
544
+ ** Output: returns strength of quality source
545
+ ** Purpose: determines source strength in current time period
546
+ **--------------------------------------------------------------
547
+ */
548
+ {
549
+ Network *net = &pr->network;
550
+ Times *time = &pr->times;
551
+
552
+ int i;
553
+ long k;
554
+ double c;
555
+
556
+ // Get source concentration (or mass flow) in original units
557
+ c = source->C0;
558
+
559
+ // Convert mass flow rate from min. to sec.
560
+ // and convert concen. from liters to cubic feet
561
+ if (source->Type == MASS) c /= 60.0;
562
+ else c /= pr->Ucf[QUALITY];
563
+
564
+ // Apply time pattern if assigned
565
+ i = source->Pat;
566
+ if (i == 0) return c;
567
+ k = ((time->Qtime + time->Pstart) / time->Pstep) %
568
+ (long)net->Pattern[i].Length;
569
+ return (c * net->Pattern[i].F[k]);
570
+ }
571
+
572
+
573
+ void evalmassbalance(Project *pr)
574
+ /*
575
+ **--------------------------------------------------------------
576
+ ** Input: none
577
+ ** Output: none
578
+ ** Purpose: computes the overall mass balance ratio of a
579
+ ** quality constituent.
580
+ **--------------------------------------------------------------
581
+ */
582
+ {
583
+ Quality *qual = &pr->quality;
584
+
585
+ double massin;
586
+ double massout;
587
+ double massreacted;
588
+
589
+ if (qual->Qualflag == NONE) qual->MassBalance.ratio = 1.0;
590
+ else
591
+ {
592
+ qual->MassBalance.final = findstoredmass(pr);
593
+ massin = qual->MassBalance.initial + qual->MassBalance.inflow;
594
+ massout = qual->MassBalance.outflow + qual->MassBalance.final;
595
+ massreacted = qual->MassBalance.reacted;
596
+ if (massreacted > 0.0) massout += massreacted;
597
+ else massin -= massreacted;
598
+ if (massin == 0.0) qual->MassBalance.ratio = 1.0;
599
+ else qual->MassBalance.ratio = massout / massin;
600
+ }
601
+ }
602
+
603
+
604
+ double findstoredmass(Project *pr)
605
+ /*
606
+ **--------------------------------------------------------------
607
+ ** Input: none
608
+ ** Output: returns total constituent mass stored in the network
609
+ ** Purpose: finds the current mass of a constituent stored in
610
+ ** all pipes and tanks.
611
+ **--------------------------------------------------------------
612
+ */
613
+ {
614
+ Network *net = &pr->network;
615
+ Quality *qual = &pr->quality;
616
+
617
+ int i, k;
618
+ double totalmass = 0.0;
619
+ Pseg seg;
620
+
621
+ // Mass residing in each pipe
622
+ for (k = 1; k <= net->Nlinks; k++)
623
+ {
624
+ // Sum up the quality and volume in each segment of the link
625
+ seg = qual->FirstSeg[k];
626
+ while (seg != NULL)
627
+ {
628
+ totalmass += (seg->c) * (seg->v);
629
+ seg = seg->prev;
630
+ }
631
+ }
632
+
633
+ // Mass residing in each tank
634
+ for (i = 1; i <= net->Ntanks; i++)
635
+ {
636
+ // ... skip reservoirs
637
+ if (net->Tank[i].A == 0.0) continue;
638
+
639
+ // ... add up mass in each volume segment
640
+ else
641
+ {
642
+ k = net->Nlinks + i;
643
+ seg = qual->FirstSeg[k];
644
+ while (seg != NULL)
645
+ {
646
+ totalmass += seg->c * seg->v;
647
+ seg = seg->prev;
648
+ }
649
+ }
650
+ }
651
+ return totalmass;
652
+ }
653
+
654
+ int flowdirchanged(Project *pr)
655
+ /*
656
+ **--------------------------------------------------------------
657
+ ** Input: none
658
+ ** Output: returns TRUE if flow direction changes in any link
659
+ ** Purpose: finds new flow directions for each network link.
660
+ **--------------------------------------------------------------
661
+ */
662
+ {
663
+ Hydraul *hyd = &pr->hydraul;
664
+ Quality *qual = &pr->quality;
665
+
666
+ int k;
667
+ int result = FALSE;
668
+ int newdir;
669
+ int olddir;
670
+ double q;
671
+
672
+ // Examine each network link
673
+ for (k = 1; k <= pr->network.Nlinks; k++)
674
+ {
675
+ // Determine sign (+1 or -1) of new flow rate
676
+ olddir = qual->FlowDir[k];
677
+ q = (hyd->LinkStatus[k] <= CLOSED) ? 0.0 : hyd->LinkFlow[k];
678
+ newdir = SGN(q);
679
+
680
+ // Indicate if flow is negligible
681
+ if (fabs(q) < Q_STAGNANT) newdir = 0;
682
+
683
+ // Reverse link's volume segments if flow direction changes sign
684
+ if (newdir * olddir < 0) reversesegs(pr, k);
685
+
686
+ // If flow direction changes either sign or magnitude then set
687
+ // result to true (e.g., if a link's positive flow becomes
688
+ // negligible then the network still needs to be re-sorted)
689
+ if (newdir != olddir) result = TRUE;
690
+
691
+ // ... replace old flow direction with the new direction
692
+ qual->FlowDir[k] = newdir;
693
+ }
694
+ return result;
695
+ }