epanet-plus 0.0.1__cp311-cp311-musllinux_1_2_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-x86_64-linux-musl.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 +5 -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
@@ -0,0 +1,2011 @@
1
+ /******************************************************************************
2
+ ** MODULE: MSXQUAL.C
3
+ ** PROJECT: EPANET-MSX
4
+ ** DESCRIPTION: Water quality routing routines.
5
+ ** COPYRIGHT: Copyright (C) 2007 Feng Shang, Lewis Rossman, and James Uber.
6
+ ** All Rights Reserved. See license information in LICENSE.TXT.
7
+ ** AUTHORS: See AUTHORS
8
+ ** VERSION: 2.0.00
9
+ ** LAST UPDATE: 08/30/2022
10
+ ******************************************************************************/
11
+
12
+ #include <stdio.h>
13
+ #include <string.h>
14
+ #include <math.h>
15
+ #include <stdlib.h>
16
+
17
+ #include "msxtypes.h"
18
+ //#include "mempool.h"
19
+ #include "msxutils.h"
20
+ #include "dispersion.h"
21
+
22
+ // Macros to identify upstream & downstream nodes of a link
23
+ // under the current flow and to compute link volume
24
+ //
25
+ #define UP_NODE(x) ( (MSX.FlowDir[(x)]==POSITIVE) ? MSX.Link[(x)].n1 : MSX.Link[(x)].n2 )
26
+ #define DOWN_NODE(x) ( (MSX.FlowDir[(x)]==POSITIVE) ? MSX.Link[(x)].n2 : MSX.Link[(x)].n1 )
27
+ #define LINKVOL(k) ( 0.785398*MSX.Link[(k)].len*SQR(MSX.Link[(k)].diam) )
28
+
29
+ // External variables
30
+ //--------------------
31
+ extern MSXproject MSX; // MSX project data
32
+
33
+ // Local variables
34
+ //-----------------
35
+ //static Pseg FreeSeg; // pointer to unused pipe segment
36
+ //static Pseg *NewSeg; // new segment added to each pipe
37
+ //static char *FlowDir; // flow direction for each pipe
38
+ //static double *VolIn; // inflow flow volume to each node
39
+ //static double **MassIn; // mass inflow of each species to each node
40
+ //static double **X; // work matrix
41
+ //static char HasWallSpecies; // wall species indicator
42
+ //static char OutOfMemory; // out of memory indicator
43
+ //static alloc_handle_t *QualPool; // memory pool
44
+
45
+ // Stagnant flow tolerance
46
+ //const double Q_STAGNANT = 0.005 / GPMperCFS; // 0.005 gpm = 1.114e-5 cfs
47
+ extern double Q_STAGNANT; // Already defined in quality.c (EPANET)
48
+
49
+ // Imported functions
50
+ //--------------------
51
+ int MSXchem_open(void);
52
+ void MSXchem_close(void);
53
+ extern int MSXchem_react(double dt);
54
+ extern int MSXchem_equil(int zone, int k, double *c);
55
+
56
+ extern void MSXtank_mix1(int i, double vin, double *massin, double vnet);
57
+ extern void MSXtank_mix2(int i, double vin, double *massin, double vnet);
58
+ extern void MSXtank_mix3(int i, double vin, double *massin, double vnet);
59
+ extern void MSXtank_mix4(int i, double vIn, double *massin, double vnet);
60
+
61
+
62
+
63
+ int MSXout_open(void);
64
+ int MSXout_saveResults(void);
65
+ int MSXout_saveFinalResults(void);
66
+
67
+ void MSXerr_clearMathError(void);
68
+ int MSXerr_mathError(void);
69
+ char* MSXerr_writeMathErrorMsg(void);
70
+
71
+ // Exported functions
72
+ //--------------------
73
+ int MSXqual_open(void);
74
+ int MSXqual_init(void);
75
+ int MSXqual_step(double *t, double *tleft);
76
+ int MSXqual_close(void);
77
+ double MSXqual_getNodeQual(int j, int m);
78
+ double MSXqual_getLinkQual(int k, int m);
79
+ int MSXqual_isSame(double c1[], double c2[]);
80
+ void MSXqual_removeSeg(Pseg seg);
81
+ Pseg MSXqual_getFreeSeg(double v, double c[]);
82
+ void MSXqual_addSeg(int k, Pseg seg);
83
+ void MSXqual_reversesegs(int k);
84
+
85
+ // Local functions
86
+ //-----------------
87
+ static int getHydVars(void);
88
+ static int transport(int64_t tstep);
89
+ static void initSegs(void);
90
+ static int flowdirchanged(void);
91
+ static void advectSegs(double dt);
92
+ static void getNewSegWallQual(int k, double dt, Pseg seg);
93
+ static void shiftSegWallQual(int k, double dt);
94
+ static void sourceInput(int n, double vout, double dt);
95
+ static void addSource(int n, Psource source, double v, double dt);
96
+ static double getSourceQual(Psource source);
97
+ static void removeAllSegs(int k);
98
+
99
+ static void topological_transport(double dt);
100
+ static void findnodequal(int n, double volin, double* massin, double volout, double tstep);
101
+ static void noflowqual(int n);
102
+ static void evalnodeinflow(int, double, double*, double*);
103
+ static void evalnodeoutflow(int k, double* upnodequal, double tstep);
104
+ static int sortNodes();
105
+ static int selectnonstacknode(int numsorted, int* indegree);
106
+ static void findstoredmass(double* mass);
107
+
108
+ static void evalHydVariables(int k);
109
+ //=============================================================================
110
+
111
+ int MSXqual_open()
112
+ /*
113
+ ** Purpose:
114
+ ** opens the WQ routing system.
115
+ **
116
+ ** Returns:
117
+ ** an error code (0 if no errors).
118
+ */
119
+ {
120
+ int errcode = 0;
121
+ int n;
122
+
123
+ // --- set flags
124
+
125
+ MSX.QualityOpened = FALSE;
126
+ MSX.Saveflag = 0;
127
+ MSX.OutOfMemory = FALSE;
128
+ MSX.HasWallSpecies = FALSE;
129
+
130
+ // --- initialize array pointers to null
131
+
132
+ MSX.C1 = NULL;
133
+ MSX.FirstSeg = NULL;
134
+ MSX.LastSeg = NULL;
135
+ MSX.NewSeg = NULL;
136
+ MSX.FlowDir = NULL;
137
+ MSX.MassIn = NULL;
138
+
139
+
140
+ // --- open the chemistry system
141
+
142
+ errcode = MSXchem_open();
143
+ if (errcode > 0) return errcode;
144
+
145
+ // --- allocate a memory pool for pipe segments
146
+
147
+ MSX.QualPool = AllocInit();
148
+ if (MSX.QualPool == NULL) return ERR_MEMORY;
149
+
150
+ // --- allocate memory used for species concentrations
151
+
152
+ MSX.C1 = (double *) calloc(MSX.Nobjects[SPECIES]+1, sizeof(double));
153
+
154
+ MSX.MassBalance.initial = (double*)calloc(MSX.Nobjects[SPECIES] + 1, sizeof(double));
155
+ MSX.MassBalance.inflow = (double*)calloc(MSX.Nobjects[SPECIES] + 1, sizeof(double));
156
+ MSX.MassBalance.indisperse = (double*)calloc(MSX.Nobjects[SPECIES] + 1, sizeof(double));
157
+ MSX.MassBalance.outflow = (double*)calloc(MSX.Nobjects[SPECIES] + 1, sizeof(double));
158
+ MSX.MassBalance.reacted = (double*)calloc(MSX.Nobjects[SPECIES] + 1, sizeof(double));
159
+ MSX.MassBalance.final = (double*)calloc(MSX.Nobjects[SPECIES] + 1, sizeof(double));
160
+ MSX.MassBalance.ratio = (double*)calloc(MSX.Nobjects[SPECIES] + 1, sizeof(double));
161
+
162
+ // --- allocate memory used for pointers to the first, last,
163
+ // and new WQ segments in each link and tank
164
+
165
+ n = MSX.Nobjects[LINK] + MSX.Nobjects[TANK] + 1;
166
+ MSX.FirstSeg = (Pseg *) calloc(n, sizeof(Pseg));
167
+ MSX.LastSeg = (Pseg *) calloc(n, sizeof(Pseg));
168
+ MSX.NewSeg = (Pseg *) calloc(n, sizeof(Pseg));
169
+
170
+ // --- allocate memory used for flow direction in each link
171
+
172
+ MSX.FlowDir = (FlowDirection *) calloc(n, sizeof(FlowDirection));
173
+
174
+ // --- allocate memory used to accumulate mass and volume
175
+ // inflows to each node
176
+
177
+ n = MSX.Nobjects[NODE] + 1;
178
+ MSX.MassIn = (double *) calloc(MSX.Nobjects[SPECIES]+1, sizeof(double));
179
+ MSX.SourceIn = (double*)calloc(MSX.Nobjects[SPECIES] + 1, sizeof(double));
180
+
181
+ // --- allocate memory for topologically sorted nodes
182
+
183
+ MSX.SortedNodes = (int*)calloc(n, sizeof(int));
184
+
185
+ // --- check for successful memory allocation
186
+
187
+ CALL(errcode, MEMCHECK(MSX.C1));
188
+ CALL(errcode, MEMCHECK(MSX.FirstSeg));
189
+ CALL(errcode, MEMCHECK(MSX.LastSeg));
190
+ CALL(errcode, MEMCHECK(MSX.NewSeg));
191
+ CALL(errcode, MEMCHECK(MSX.FlowDir));
192
+ CALL(errcode, MEMCHECK(MSX.MassIn));
193
+ CALL(errcode, MEMCHECK(MSX.SourceIn));
194
+ CALL(errcode, MEMCHECK(MSX.SortedNodes));
195
+ CALL(errcode, MEMCHECK(MSX.MassBalance.initial));
196
+ CALL(errcode, MEMCHECK(MSX.MassBalance.inflow));
197
+ CALL(errcode, MEMCHECK(MSX.MassBalance.indisperse));
198
+ CALL(errcode, MEMCHECK(MSX.MassBalance.outflow));
199
+ CALL(errcode, MEMCHECK(MSX.MassBalance.reacted));
200
+ CALL(errcode, MEMCHECK(MSX.MassBalance.final));
201
+ CALL(errcode, MEMCHECK(MSX.MassBalance.ratio));
202
+
203
+ // --- check if wall species are present
204
+
205
+ for (n=1; n<=MSX.Nobjects[SPECIES]; n++)
206
+ {
207
+ if ( MSX.Species[n].type == WALL ) MSX.HasWallSpecies = TRUE;
208
+ }
209
+ if ( !errcode ) MSX.QualityOpened = TRUE;
210
+ return(errcode);
211
+ }
212
+
213
+ //=============================================================================
214
+
215
+ int MSXqual_init()
216
+ /*
217
+ ** Purpose:
218
+ ** re-initializes the WQ routing system.
219
+ **
220
+ ** Input:
221
+ ** none.
222
+ **
223
+ ** Returns:
224
+ ** an error code (or 0 if no errors).
225
+ */
226
+ {
227
+ int i, n, m;
228
+ int errcode = 0;
229
+
230
+ // --- initialize node concentrations, tank volumes, & source mass flows
231
+
232
+ for (i=1; i<=MSX.Nobjects[NODE]; i++)
233
+ {
234
+ for (m=1; m<=MSX.Nobjects[SPECIES]; m++)
235
+ MSX.Node[i].c[m] = MSX.Node[i].c0[m];
236
+ }
237
+ for (i = 1; i <= MSX.Nobjects[LINK]; i++)
238
+ {
239
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
240
+ MSX.Link[i].reacted[m] = 0.0;
241
+ }
242
+
243
+ for (i=1; i<=MSX.Nobjects[TANK]; i++)
244
+ {
245
+ MSX.Tank[i].hstep = 0.0;
246
+ MSX.Tank[i].v = MSX.Tank[i].v0;
247
+ n = MSX.Tank[i].node;
248
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
249
+ {
250
+ MSX.Tank[i].c[m] = MSX.Node[n].c0[m];
251
+ MSX.Tank[i].reacted[m] = 0.0;
252
+ }
253
+ }
254
+
255
+ for (i=1; i<=MSX.Nobjects[PATTERN]; i++)
256
+ {
257
+ MSX.Pattern[i].interval = 0;
258
+ MSX.Pattern[i].current = MSX.Pattern[i].first;
259
+ }
260
+
261
+ // --- copy expression constants to vector MSX.K[]
262
+
263
+ for (i=1; i<=MSX.Nobjects[CONSTANT]; i++)
264
+ {
265
+ MSX.K[i] = MSX.Const[i].value;
266
+ }
267
+
268
+ // --- check if a separate WQ report is required
269
+
270
+ MSX.Rptflag = 0;
271
+ n = 0;
272
+ for (i=1; i<=MSX.Nobjects[NODE]; i++) n += MSX.Node[i].rpt;
273
+ for (i=1; i<=MSX.Nobjects[LINK]; i++) n += MSX.Link[i].rpt;
274
+ if ( n > 0 )
275
+ {
276
+ n = 0;
277
+ for (m=1; m<=MSX.Nobjects[SPECIES]; m++) n += MSX.Species[m].rpt;
278
+ }
279
+ if ( n > 0 ) MSX.Rptflag = 1;
280
+ if ( MSX.Rptflag ) MSX.Saveflag = 1;
281
+
282
+ // --- reset memory pool
283
+
284
+ AllocSetPool(MSX.QualPool);
285
+ MSX.FreeSeg = NULL;
286
+ AllocReset();
287
+
288
+ // --- re-position hydraulics file
289
+
290
+ fseek(MSX.HydFile.file, MSX.HydOffset, SEEK_SET);
291
+
292
+ // --- set elapsed times to zero
293
+
294
+ MSX.Htime = 0; //Hydraulic solution time
295
+ MSX.Qtime = 0; //Quality routing time
296
+ MSX.Rtime = MSX.Rstart * 1000; //Reporting time
297
+ MSX.Nperiods = 0; //Number fo reporting periods
298
+
299
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
300
+ {
301
+ MSX.MassBalance.initial[m] = 0.0;
302
+ MSX.MassBalance.final[m] = 0.0;
303
+ MSX.MassBalance.inflow[m] = 0.0;
304
+ MSX.MassBalance.indisperse[m] = 0.0;
305
+ MSX.MassBalance.outflow[m] = 0.0;
306
+ MSX.MassBalance.reacted[m] = 0.0;
307
+ MSX.MassBalance.ratio[m] = 0.0;
308
+ }
309
+
310
+ // --- open binary output file if results are to be saved
311
+
312
+ if ( MSX.Saveflag ) errcode = MSXout_open();
313
+ return errcode;
314
+ }
315
+
316
+ //=============================================================================
317
+
318
+ int MSXqual_step(double *t, double *tleft)
319
+ /*
320
+ ** Purpose:
321
+ ** updates WQ conditions over a single WQ time step.
322
+ **
323
+ ** Input:
324
+ ** none.
325
+ **
326
+ ** Output:
327
+ ** *t = current simulation time (sec)
328
+ ** *tleft = time left in simulation (sec)
329
+ **
330
+ ** Returns:
331
+ ** an error code:
332
+ ** 0 = no error
333
+ ** 501 = memory error
334
+ ** 307 = can't read hydraulics file
335
+ ** 513 = can't integrate reaction rates
336
+ */
337
+ {
338
+ int k, errcode = 0, flowchanged;
339
+ int m;
340
+ double smassin, smassout, sreacted;
341
+ int64_t hstep, tstep, dt;
342
+
343
+ // --- set the shared memory pool to the water quality pool
344
+ // and the overall time step to nominal WQ time step
345
+
346
+ AllocSetPool(MSX.QualPool);
347
+ tstep = MSX.Qstep;
348
+ if (MSX.Qtime + tstep > MSX.Dur) tstep = MSX.Dur - MSX.Qtime;
349
+
350
+ // --- repeat until the end of the time step
351
+
352
+ do
353
+ {
354
+ // --- find the time until the next hydraulic event occurs
355
+ dt = tstep;
356
+ hstep = MSX.Htime - MSX.Qtime;
357
+
358
+ // --- check if next hydraulic event occurs within the current time step
359
+
360
+ if (hstep <= dt)
361
+ {
362
+
363
+ // --- reduce current time step to end at next hydraulic event
364
+ dt = hstep;
365
+
366
+ // --- route WQ over this time step
367
+ if ( dt > 0 ) CALL(errcode, transport(dt));
368
+ MSX.Qtime += dt;
369
+
370
+ // --- retrieve new hydraulic solution
371
+ if (MSX.Qtime == MSX.Htime)
372
+ {
373
+ CALL(errcode, getHydVars());
374
+
375
+ for (int kl = 1; kl <= MSX.Nobjects[LINK]; kl++)
376
+ {
377
+ // --- skip non-pipe links
378
+
379
+ if (MSX.Link[kl].len == 0.0) continue;
380
+
381
+ // --- evaluate hydraulic variables
382
+
383
+ evalHydVariables(kl);
384
+ }
385
+
386
+ if (MSX.Qtime < MSX.Dur)
387
+ {
388
+ // --- initialize pipe segments (at time 0) or else re-orient segments
389
+ // to accommodate any flow reversals
390
+ if (MSX.Qtime == 0)
391
+ {
392
+ flowchanged = 1;
393
+ initSegs();
394
+ }
395
+ else
396
+ flowchanged = flowdirchanged();
397
+
398
+ if (flowchanged)
399
+ {
400
+ CALL(errcode, sortNodes());
401
+ }
402
+ }
403
+ }
404
+
405
+ // --- report results if its time to do so
406
+ if (MSX.Saveflag && MSX.Qtime == MSX.Rtime)
407
+ {
408
+ CALL(errcode, MSXout_saveResults());
409
+ MSX.Rtime += MSX.Rstep * 1000;
410
+ MSX.Nperiods++;
411
+ }
412
+ }
413
+
414
+ // --- otherwise just route WQ over the current time step
415
+
416
+ else
417
+ {
418
+ CALL(errcode, transport(dt));
419
+ MSX.Qtime += dt;
420
+ }
421
+
422
+ // --- reduce overall time step by the size of the current time step
423
+
424
+ tstep -= dt;
425
+ if (MSX.OutOfMemory) errcode = ERR_MEMORY;
426
+ } while (!errcode && tstep > 0);
427
+
428
+ // --- update the current time into the simulation and the amount remaining
429
+
430
+ *t = MSX.Qtime / 1000.;
431
+ *tleft = (MSX.Dur - MSX.Qtime) / 1000.;
432
+
433
+ // --- if there's no time remaining, then save the final records to output file
434
+
435
+ if ( *tleft <= 0 && MSX.Saveflag )
436
+ {
437
+ findstoredmass(MSX.MassBalance.final);
438
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
439
+ {
440
+ sreacted = 0.0;
441
+ for (k = 1; k <= MSX.Nobjects[LINK]; k++)
442
+ sreacted += MSX.Link[k].reacted[m];
443
+ for (k = 1; k <= MSX.Nobjects[TANK]; k++)
444
+ sreacted += MSX.Tank[k].reacted[m];
445
+
446
+ MSX.MassBalance.reacted[m] = sreacted;
447
+ smassin = MSX.MassBalance.initial[m] + MSX.MassBalance.inflow[m] + MSX.MassBalance.indisperse[m];
448
+ smassout = MSX.MassBalance.outflow[m]+MSX.MassBalance.final[m];
449
+ if (sreacted < 0) //loss
450
+ smassout -= sreacted;
451
+ else
452
+ smassin += sreacted;
453
+
454
+ if (smassin == 0)
455
+ MSX.MassBalance.ratio[m] = 1.0;
456
+ else
457
+ MSX.MassBalance.ratio[m] = smassout / smassin;
458
+ }
459
+ CALL(errcode, MSXout_saveFinalResults());
460
+ }
461
+ return errcode;
462
+ }
463
+
464
+ //=============================================================================
465
+
466
+ double MSXqual_getNodeQual(int j, int m)
467
+ /*
468
+ ** Purpose:
469
+ ** retrieves WQ for species m at node n.
470
+ **
471
+ ** Input:
472
+ ** j = node index
473
+ ** m = species index.
474
+ **
475
+ ** Returns:
476
+ ** WQ value of node.
477
+ */
478
+ {
479
+ int k;
480
+
481
+ // --- return 0 for WALL species
482
+
483
+ if ( MSX.Species[m].type == WALL ) return 0.0;
484
+
485
+ // --- if node is a tank, return its internal concentration
486
+
487
+ k = MSX.Node[j].tank;
488
+ if (k > 0 && MSX.Tank[k].a > 0.0)
489
+ {
490
+ return MSX.Tank[k].c[m];
491
+ }
492
+
493
+ // --- otherwise return node's concentration (which includes
494
+ // any contribution from external sources)
495
+
496
+ return MSX.Node[j].c[m];
497
+ }
498
+
499
+ //=============================================================================
500
+
501
+ double MSXqual_getLinkQual(int k, int m)
502
+ /*
503
+ ** Purpose:
504
+ ** computes average quality in link k.
505
+ **
506
+ ** Input:
507
+ ** k = link index
508
+ ** m = species index.
509
+ **
510
+ ** Returns:
511
+ ** WQ value of link.
512
+ */
513
+ {
514
+ double vsum = 0.0,
515
+ msum = 0.0;
516
+ Pseg seg;
517
+
518
+ seg = MSX.FirstSeg[k];
519
+ while (seg != NULL)
520
+ {
521
+ vsum += seg->v;
522
+ msum += (seg->c[m])*(seg->v);
523
+ seg = seg->prev;
524
+ }
525
+ if (vsum > 0.0) return(msum/vsum);
526
+ else
527
+ {
528
+ return (MSXqual_getNodeQual(MSX.Link[k].n1, m) +
529
+ MSXqual_getNodeQual(MSX.Link[k].n2, m)) / 2.0;
530
+ }
531
+ }
532
+
533
+ //=============================================================================
534
+
535
+ int MSXqual_close()
536
+ /*
537
+ ** Purpose:
538
+ ** closes the WQ routing system.
539
+ **
540
+ ** Input:
541
+ ** none.
542
+ **
543
+ ** Returns:
544
+ ** error code (0 if no error).
545
+ */
546
+ {
547
+ int errcode = 0;
548
+ if (!MSX.ProjectOpened) return 0;
549
+ MSXchem_close();
550
+
551
+ FREE(MSX.C1);
552
+ FREE(MSX.FirstSeg);
553
+ FREE(MSX.LastSeg);
554
+ FREE(MSX.NewSeg);
555
+ FREE(MSX.FlowDir);
556
+ FREE(MSX.SortedNodes);
557
+ FREE(MSX.MassIn);
558
+ FREE(MSX.SourceIn);
559
+ if ( MSX.QualPool)
560
+ {
561
+ AllocSetPool(MSX.QualPool);
562
+ AllocFreePool();
563
+ }
564
+ FREE(MSX.MassBalance.initial);
565
+ FREE(MSX.MassBalance.inflow);
566
+ FREE(MSX.MassBalance.indisperse);
567
+ FREE(MSX.MassBalance.outflow);
568
+ FREE(MSX.MassBalance.reacted);
569
+ FREE(MSX.MassBalance.final);
570
+ FREE(MSX.MassBalance.ratio);
571
+
572
+ MSX.QualityOpened = FALSE;
573
+ return errcode;
574
+ }
575
+
576
+ //=============================================================================
577
+
578
+ int MSXqual_isSame(double c1[], double c2[])
579
+ /*
580
+ ** Purpose:
581
+ ** checks if two sets of concentrations are the same
582
+ **
583
+ ** Input:
584
+ ** c1[] = first set of species concentrations
585
+ ** c2[] = second set of species concentrations
586
+ **
587
+ ** Returns:
588
+ ** 1 if the concentrations are all within a specific tolerance of each
589
+ ** other or 0 if they are not.
590
+ */
591
+ {
592
+ int m;
593
+ for (m=1; m<=MSX.Nobjects[SPECIES]; m++)
594
+ {
595
+ if (fabs(c1[m] - c2[m]) >= MSX.Species[m].aTol ) return 0;
596
+ }
597
+ return 1;
598
+ }
599
+
600
+
601
+ //=============================================================================
602
+
603
+ int getHydVars()
604
+ /*
605
+ ** Purpose:
606
+ ** retrieves hydraulic solution and time step for next hydraulic event
607
+ ** from a hydraulics file.
608
+ **
609
+ ** Input:
610
+ ** none.
611
+ **
612
+ ** Returns:
613
+ ** error code
614
+ **
615
+ ** NOTE:
616
+ ** A hydraulic solution consists of the current time
617
+ ** (hydtime), nodal demands (D) and heads (H), link
618
+ ** flows (Q), and link status values and settings (which are not used).
619
+ */
620
+ {
621
+ int errcode = 0;
622
+ long hydtime, hydstep;
623
+ INT4 n;
624
+
625
+ // --- read hydraulic time, demands, heads, and flows from the file
626
+
627
+ if (fread(&n, sizeof(INT4), 1, MSX.HydFile.file) < 1)
628
+ return ERR_READ_HYD_FILE;
629
+ hydtime = (long)n;
630
+ n = MSX.Nobjects[NODE];
631
+ if (fread(MSX.D+1, sizeof(REAL4), n, MSX.HydFile.file) < (unsigned)n)
632
+ return ERR_READ_HYD_FILE;
633
+ if (fread(MSX.H+1, sizeof(REAL4), n, MSX.HydFile.file) < (unsigned)n)
634
+ return ERR_READ_HYD_FILE;
635
+ n = MSX.Nobjects[LINK];
636
+ if (fread(MSX.Q+1, sizeof(REAL4), n, MSX.HydFile.file) < (unsigned)n)
637
+ return ERR_READ_HYD_FILE;
638
+ if (fread(MSX.S + 1, sizeof(REAL4), n, MSX.HydFile.file) < (unsigned)n) //03/17/2022
639
+ return ERR_READ_HYD_FILE;
640
+
641
+ for (int pi = 1; pi <= n; pi++) //06/10/2021 Shang
642
+ if (fabs(MSX.Q[pi]) < Q_STAGNANT)
643
+ MSX.Q[pi] = 0.0;
644
+
645
+ // --- skip over link settings
646
+
647
+ fseek(MSX.HydFile.file, 1*n*sizeof(REAL4), SEEK_CUR);
648
+
649
+ // --- read time step until next hydraulic event
650
+
651
+ if (fread(&n, sizeof(INT4), 1, MSX.HydFile.file) < 1)
652
+ return ERR_READ_HYD_FILE;
653
+ hydstep = (long)n;
654
+
655
+ // --- update elapsed time until next hydraulic event
656
+
657
+ MSX.Htime = hydtime + hydstep;
658
+ MSX.Htime *= 1000;
659
+
660
+ /*
661
+ if (MSX.Qtime < MSX.Dur)
662
+ {
663
+ if (MSX.Qtime == 0)
664
+ {
665
+ flowchanged = 1;
666
+ initSegs();
667
+ }
668
+ else flowchanged = flowdirchanged();
669
+ }
670
+ return flowchanged;*/
671
+
672
+ return errcode;
673
+ }
674
+
675
+ //=============================================================================
676
+
677
+ int transport(int64_t tstep)
678
+ /*
679
+ ** Purpose:
680
+ ** transports constituent mass through pipe network
681
+ ** under a period of constant hydraulic conditions.
682
+ **
683
+ ** Input:
684
+ ** tstep = length of current time step (sec).
685
+ **
686
+ ** Returns:
687
+ ** an error code or 0 if no error.
688
+ */
689
+ {
690
+ int64_t qtime, dt64;
691
+ double dt;
692
+ int errcode = 0;
693
+
694
+ // --- repeat until time step is exhausted
695
+
696
+ MSXerr_clearMathError(); // clear math error flag
697
+ qtime = 0;
698
+ while (!MSX.OutOfMemory &&
699
+ !errcode &&
700
+ qtime < tstep)
701
+ { // Qstep is nominal quality time step
702
+ dt64 = MIN(MSX.Qstep, tstep-qtime); // get actual time step
703
+ qtime += dt64; // update amount of input tstep taken
704
+ dt = dt64 / 1000.; // time step as fractional seconds
705
+
706
+ errcode = MSXchem_react(dt); // react species in each pipe & tank
707
+ if ( errcode ) return errcode;
708
+ advectSegs(dt); // advect segments in each pipe
709
+
710
+ topological_transport(dt); //replace accumulate, updateNodes, sourceInput and release
711
+
712
+ if (MSXerr_mathError()) // check for any math error
713
+ {
714
+ MSXerr_writeMathErrorMsg();
715
+ errcode = ERR_ILLEGAL_MATH;
716
+ }
717
+ }
718
+ return errcode;
719
+ }
720
+
721
+ //=============================================================================
722
+
723
+ void initSegs()
724
+ /*
725
+ ** Purpose:
726
+ ** initializes water quality in pipe segments.
727
+ **
728
+ ** Input:
729
+ ** none.
730
+ */
731
+ {
732
+ int j, k, m;
733
+ double v;
734
+
735
+ // --- examine each link
736
+
737
+ for (k=1; k<=MSX.Nobjects[LINK]; k++)
738
+ {
739
+ // --- establish flow direction
740
+
741
+ if (fabs(MSX.Q[k]) < Q_STAGNANT)
742
+ MSX.FlowDir[k] = ZERO_FLOW;
743
+ else if (MSX.Q[k] > 0.0)
744
+ MSX.FlowDir[k] = POSITIVE;
745
+ else
746
+ MSX.FlowDir[k] = NEGATIVE;
747
+
748
+ // --- start with no segments
749
+
750
+ MSX.LastSeg[k] = NULL;
751
+ MSX.FirstSeg[k] = NULL;
752
+ MSX.NewSeg[k] = NULL;
753
+
754
+ // --- use quality of downstream node for BULK species
755
+ // if no initial link quality supplied
756
+
757
+ // j = DOWN_NODE(k);
758
+ j = MSX.Link[k].n2;
759
+ for (m=1; m<=MSX.Nobjects[SPECIES]; m++)
760
+ {
761
+ if ( MSX.Link[k].c0[m] != MISSING )
762
+ MSX.C1[m] = MSX.Link[k].c0[m];
763
+ else if ( MSX.Species[m].type == BULK )
764
+ MSX.C1[m] = MSX.Node[j].c0[m];
765
+ else MSX.C1[m] = 0.0;
766
+ }
767
+
768
+ // --- fill link with a single segment of this quality
769
+
770
+ MSXchem_equil(LINK, k, MSX.C1);
771
+ v = LINKVOL(k);
772
+ if (v > 0.0)
773
+ {
774
+ int ninitsegs = MIN(100, MSX.MaxSegments);
775
+
776
+ for (int ns = 0; ns < ninitsegs; ns++)
777
+ MSXqual_addSeg(k, MSXqual_getFreeSeg(v / (1.0*ninitsegs), MSX.C1));
778
+ }
779
+ }
780
+
781
+ // --- initialize segments in tanks
782
+
783
+ for (j=1; j<=MSX.Nobjects[TANK]; j++)
784
+ {
785
+ // --- skip reservoirs
786
+
787
+ if ( MSX.Tank[j].a == 0.0 ) continue;
788
+
789
+ // --- tank segment pointers are stored after those for links
790
+
791
+ k = MSX.Tank[j].node;
792
+ for (m=1; m<=MSX.Nobjects[SPECIES]; m++)
793
+ MSX.C1[m] = MSX.Node[k].c0[m];
794
+ k = MSX.Nobjects[LINK] + j;
795
+ MSX.LastSeg[k] = NULL;
796
+ MSX.FirstSeg[k] = NULL;
797
+
798
+ MSXchem_equil(NODE, j, MSX.C1);
799
+
800
+ // --- add 2 segments for 2-compartment model
801
+
802
+ if (MSX.Tank[j].mixModel == MIX2)
803
+ {
804
+ v = MAX(0, MSX.Tank[j].v - MSX.Tank[j].vMix);
805
+ MSXqual_addSeg(k, MSXqual_getFreeSeg(v, MSX.C1));
806
+ v = MSX.Tank[j].v - v;
807
+ MSXqual_addSeg(k, MSXqual_getFreeSeg(v, MSX.C1));
808
+ }
809
+
810
+ // --- add one segment for all other models
811
+
812
+ else
813
+ {
814
+ v = MSX.Tank[j].v;
815
+ MSXqual_addSeg(k, MSXqual_getFreeSeg(v, MSX.C1));
816
+ }
817
+ }
818
+
819
+ findstoredmass(MSX.MassBalance.initial); // initial mass
820
+ }
821
+
822
+ //=============================================================================
823
+
824
+ int flowdirchanged()
825
+ /*
826
+ ** Purpose:
827
+ ** re-orients pipe segments (if flow reverses).
828
+ **
829
+ ** Input:
830
+ ** none.
831
+ */
832
+ {
833
+ int k, flowchanged=0;
834
+ FlowDirection newdir;
835
+
836
+
837
+ // --- examine each link
838
+
839
+ for (k=1; k<=MSX.Nobjects[LINK]; k++)
840
+ {
841
+ // --- find new flow direction
842
+
843
+ newdir = POSITIVE;
844
+ if (fabs(MSX.Q[k]) < Q_STAGNANT)
845
+ newdir = ZERO_FLOW;
846
+ else if (MSX.Q[k] < 0.0) newdir = NEGATIVE;
847
+
848
+ // --- if direction changes, then reverse the order of segments
849
+ // (first to last) and save new direction
850
+
851
+ if (newdir*MSX.FlowDir[k] < 0)
852
+ {
853
+ MSXqual_reversesegs(k);
854
+ }
855
+ if (newdir != MSX.FlowDir[k])
856
+ {
857
+ flowchanged = 1;
858
+ }
859
+ MSX.FlowDir[k] = newdir;
860
+ }
861
+ return flowchanged;
862
+ }
863
+
864
+
865
+ //=============================================================================
866
+
867
+ void advectSegs(double dt)
868
+ /*
869
+ ** Purpose:
870
+ ** advects WQ segments within each pipe.
871
+ **
872
+ ** Input:
873
+ ** dt = current WQ time step (sec).
874
+ */
875
+ {
876
+ int k, m;
877
+
878
+ // --- examine each link
879
+
880
+ for (k=1; k<=MSX.Nobjects[LINK]; k++)
881
+ {
882
+ // --- zero out WQ in new segment to be added at entrance of link
883
+
884
+ for (m=1; m<=MSX.Nobjects[SPECIES]; m++) MSX.C1[m] = 0.0;
885
+
886
+ // --- get a free segment to add to entrance of link
887
+
888
+ MSX.NewSeg[k] = MSXqual_getFreeSeg(0.0, MSX.C1);
889
+
890
+ // --- skip zero-length links (pumps & valves) & no-flow links
891
+
892
+ if ( MSX.NewSeg[k] == NULL ||
893
+ MSX.Link[(k)].len == 0.0 || MSX.Q[k] == 0.0 ) continue;
894
+
895
+ // --- find conc. of wall species in new segment to be added
896
+ // and adjust conc. of wall species to reflect shifted
897
+ // positions of existing segments
898
+
899
+ if ( MSX.HasWallSpecies )
900
+ {
901
+ getNewSegWallQual(k, dt, MSX.NewSeg[k]);
902
+ shiftSegWallQual(k, dt);
903
+ }
904
+ }
905
+ }
906
+
907
+ //=============================================================================
908
+
909
+ void getNewSegWallQual(int k, double dt, Pseg newseg)
910
+ /*
911
+ ** Purpose:
912
+ ** computes wall species concentrations for a new WQ segment that
913
+ ** enters a pipe from its upstream node.
914
+ **
915
+ ** Input:
916
+ ** k = link index
917
+ ** dt = current WQ time step (sec)
918
+ ** newseg = pointer to a new, unused WQ segment
919
+ **
920
+ ** Output:
921
+ ** newseg->c[] = wall species concentrations in the new WQ segment
922
+ */
923
+ {
924
+ Pseg seg;
925
+ int m;
926
+ double v, vin, vsum, vadded, vleft;
927
+
928
+ // --- get volume of inflow to link
929
+
930
+ if ( newseg == NULL ) return;
931
+ v = LINKVOL(k);
932
+ vin = ABS(MSX.Q[k])*dt;
933
+ if (vin > v) vin = v;
934
+
935
+ // --- start at last (most upstream) existing WQ segment
936
+
937
+ seg = MSX.LastSeg[k];
938
+ vsum = 0.0;
939
+ vleft = vin;
940
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
941
+ {
942
+ if ( MSX.Species[m].type == WALL ) newseg->c[m] = 0.0;
943
+ }
944
+
945
+ // --- repeat while some inflow volume still remains
946
+
947
+ while ( vleft > 0.0 && seg != NULL )
948
+ {
949
+
950
+ // --- find volume added by this segment
951
+
952
+ vadded = seg->v;
953
+ if ( vadded > vleft ) vadded = vleft;
954
+
955
+ // --- update total volume added and inflow volume remaining
956
+
957
+ vsum += vadded;
958
+ vleft -= vadded;
959
+
960
+ // --- add wall species mass contributed by this segment to new segment
961
+
962
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
963
+ {
964
+ if ( MSX.Species[m].type == WALL ) newseg->c[m] += vadded*seg->c[m];
965
+ }
966
+
967
+ // --- move to next downstream WQ segment
968
+
969
+ seg = seg->next;
970
+ }
971
+
972
+ // --- convert mass of wall species in new segment to concentration
973
+
974
+ if ( vsum > 0.0 )
975
+ {
976
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
977
+ {
978
+ if ( MSX.Species[m].type == WALL ) newseg->c[m] /= vsum;
979
+ }
980
+ }
981
+ }
982
+
983
+ //=============================================================================
984
+
985
+ void shiftSegWallQual(int k, double dt)
986
+ /*
987
+ ** Purpose:
988
+ ** recomputes wall species concentrations in segments that remain
989
+ ** within a pipe after flow is advected over current time step.
990
+ **
991
+ ** Input:
992
+ ** k = link index
993
+ ** dt = current WQ time step (sec)
994
+ */
995
+ {
996
+ Pseg seg1, seg2;
997
+ int m;
998
+ double v, vin, vstart, vend, vcur, vsum;
999
+
1000
+ // --- find volume of water displaced in pipe
1001
+
1002
+ v = LINKVOL(k);
1003
+ vin = ABS((double)MSX.Q[k])*dt;
1004
+ if (vin > v) vin = v;
1005
+
1006
+ // --- set future start position (measured by pipe volume) of original last segment
1007
+
1008
+ vstart = vin;
1009
+
1010
+ // --- examine each segment, from upstream to downstream
1011
+
1012
+ for( seg1 = MSX.LastSeg[k]; seg1 != NULL; seg1 = seg1->next )
1013
+ {
1014
+
1015
+ // --- initialize a "mixture" WQ
1016
+ // if vstart >= v the segment seg1 will be out of the pipe,
1017
+ // so no need to track wall concentration of this segment
1018
+ if (vstart >= v) break; //2020 moved up
1019
+
1020
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++) MSX.C1[m] = 0.0;
1021
+
1022
+ // --- find the future end position of this segment
1023
+
1024
+ vend = vstart + seg1->v; //
1025
+ if (vend > v) vend = v;
1026
+ vcur = vstart;
1027
+ vsum = 0;
1028
+
1029
+ // --- find volume taken up by the segment after it moves down the pipe
1030
+
1031
+ for (seg2 = MSX.LastSeg[k]; seg2 != NULL; seg2 = seg2->next)
1032
+ {
1033
+ if ( seg2->v == 0.0 ) continue;
1034
+ vsum += seg2->v;
1035
+ if ( vsum >= vstart && vsum <= vend ) //DS end of seg2 is between vstart and vend
1036
+ {
1037
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
1038
+ {
1039
+ if ( MSX.Species[m].type == WALL )
1040
+ MSX.C1[m] += (vsum - vcur) * seg2->c[m];
1041
+ }
1042
+ vcur = vsum;
1043
+ }
1044
+ if ( vsum >= vend ) break; //DS of seg2 is at DS of vend
1045
+ }
1046
+
1047
+ // --- update the wall species concentrations in the segment
1048
+
1049
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
1050
+ {
1051
+ if ( MSX.Species[m].type != WALL ) continue;
1052
+ if (seg2 != NULL) MSX.C1[m] += (vend - vcur) * seg2->c[m]; //only part of seg2
1053
+ seg1->c[m] = MSX.C1[m] / (vend - vstart);
1054
+ if ( seg1->c[m] < 0.0 ) seg1->c[m] = 0.0;
1055
+ }
1056
+
1057
+ // --- re-start at the current end location
1058
+
1059
+ vstart = vend;
1060
+ // if ( vstart >= v ) break; //2020 moved up
1061
+ }
1062
+ }
1063
+
1064
+
1065
+ //=============================================================================
1066
+
1067
+ void sourceInput(int n, double volout, double dt)
1068
+ /*
1069
+ ** Purpose:
1070
+ ** computes contribution (if any) of mass additions from WQ
1071
+ ** sources at each node.
1072
+ **
1073
+ ** Input:
1074
+ ** n = nodeindex
1075
+ ** dt = current WQ time step (sec)
1076
+ */
1077
+ {
1078
+ int m;
1079
+ double qout, qcutoff;
1080
+ Psource source;
1081
+
1082
+ // --- establish a flow cutoff which indicates no outflow from a node
1083
+
1084
+ qcutoff = 10.0*TINY;
1085
+
1086
+ // --- consider each node
1087
+
1088
+ // --- skip node if no WQ source
1089
+
1090
+ source = MSX.Node[n].sources;
1091
+ if (source == NULL) return;
1092
+
1093
+
1094
+ qout = volout / dt;
1095
+
1096
+ // --- evaluate source input only if node outflow > cutoff flow
1097
+ if (qout <= qcutoff) return;
1098
+
1099
+ // --- add contribution of each source species
1100
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
1101
+ MSX.SourceIn[m] = 0.0;
1102
+ while (source)
1103
+ {
1104
+ addSource(n, source, volout, dt);
1105
+ source = source->next;
1106
+ }
1107
+
1108
+ // --- compute a new chemical equilibrium at the source node
1109
+ MSXchem_equil(NODE, 0, MSX.Node[n].c);
1110
+
1111
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
1112
+ {
1113
+ MSX.MassBalance.inflow[m] += MSX.SourceIn[m] * LperFT3;
1114
+ }
1115
+ }
1116
+
1117
+ //=============================================================================
1118
+
1119
+ void addSource(int n, Psource source, double volout, double dt)
1120
+ /*
1121
+ ** Purpose:
1122
+ ** updates concentration of particular species leaving a node
1123
+ ** that receives external source input.
1124
+ **
1125
+ ** Input:
1126
+ ** n = index of source node
1127
+ ** source = pointer to WQ source data
1128
+ ** volout = volume of water leaving node during current time step
1129
+ ** dt = current WQ time step (sec)
1130
+ */
1131
+ {
1132
+ int m;
1133
+ double massadded, s;
1134
+
1135
+ // --- only analyze bulk species
1136
+
1137
+ m = source->species;
1138
+ massadded = 0.0;
1139
+ if (source->c0 > 0.0 && MSX.Species[m].type == BULK)
1140
+ {
1141
+
1142
+ // --- mass added depends on type of source
1143
+
1144
+ s = getSourceQual(source);
1145
+ switch(source->type)
1146
+ {
1147
+ // Concen. Source:
1148
+ // Mass added = source concen. * -(demand)
1149
+
1150
+ case CONCEN:
1151
+
1152
+ // Only add source mass if demand is negative
1153
+ if (MSX.Node[n].tank <=0 && MSX.D[n] < 0.0) massadded = -s*MSX.D[n]*dt;
1154
+
1155
+ // If node is a tank then set concen. to 0.
1156
+ // (It will be re-set to true value later on)
1157
+
1158
+ // if (MSX.Node[n].tank > 0) MSX.Node[n].c[m] = 0.0;
1159
+ break;
1160
+
1161
+ // Mass Inflow Booster Source:
1162
+
1163
+ case MASS:
1164
+ massadded = s*dt/LperFT3;
1165
+ break;
1166
+
1167
+ // Setpoint Booster Source:
1168
+ // Mass added is difference between source
1169
+ // & node concen. times outflow volume
1170
+
1171
+ case SETPOINT:
1172
+ if (s > MSX.Node[n].c[m])
1173
+ massadded = (s - MSX.Node[n].c[m])*volout;
1174
+ break;
1175
+
1176
+ // Flow-Paced Booster Source:
1177
+ // Mass added = source concen. times outflow volume
1178
+
1179
+ case FLOWPACED:
1180
+ massadded = s*volout;
1181
+ break;
1182
+ }
1183
+
1184
+ // --- adjust nodal concentration to reflect source addition
1185
+ MSX.Node[n].c[m] += massadded / volout;
1186
+ MSX.SourceIn[m] += massadded;
1187
+ }
1188
+ }
1189
+
1190
+
1191
+ //=============================================================================
1192
+
1193
+ double getSourceQual(Psource source)
1194
+ /*
1195
+ ** Input: j = source index
1196
+ ** Output: returns source WQ value
1197
+ ** Purpose: determines source concentration in current time period
1198
+ */
1199
+ {
1200
+ int i;
1201
+ long k;
1202
+ double c, f = 1.0;
1203
+
1204
+ // --- get source concentration (or mass flow) in original units
1205
+ c = source->c0;
1206
+
1207
+ // --- convert mass flow rate from min. to sec.
1208
+ if (source->type == MASS) c /= 60.0;
1209
+
1210
+ // --- apply time pattern if assigned
1211
+ i = source->pat;
1212
+ if (i == 0) return(c);
1213
+ k = (int)((MSX.Qtime + MSX.Pstart*1000) / (MSX.Pstep*1000)) % MSX.Pattern[i].length;
1214
+ if (k != MSX.Pattern[i].interval)
1215
+ {
1216
+ if ( k < MSX.Pattern[i].interval )
1217
+ {
1218
+ MSX.Pattern[i].current = MSX.Pattern[i].first;
1219
+ MSX.Pattern[i].interval = 0;
1220
+ }
1221
+ while (MSX.Pattern[i].current && MSX.Pattern[i].interval < k)
1222
+ {
1223
+ MSX.Pattern[i].current = MSX.Pattern[i].current->next;
1224
+ MSX.Pattern[i].interval++;
1225
+ }
1226
+ }
1227
+ if (MSX.Pattern[i].current) f = MSX.Pattern[i].current->value;
1228
+ return c*f;
1229
+ }
1230
+
1231
+ //=============================================================================
1232
+
1233
+ void removeAllSegs(int k)
1234
+ /*
1235
+ ** Purpose:
1236
+ ** removes all segments in a pipe link.
1237
+ **
1238
+ ** Input:
1239
+ ** k = link index.
1240
+ */
1241
+ {
1242
+ Pseg seg;
1243
+ seg = MSX.FirstSeg[k];
1244
+ while (seg != NULL)
1245
+ {
1246
+ MSX.FirstSeg[k] = seg->prev;
1247
+ MSXqual_removeSeg(seg);
1248
+ seg = MSX.FirstSeg[k];
1249
+ }
1250
+ MSX.LastSeg[k] = NULL;
1251
+ if (k <= MSX.Nobjects[LINK])
1252
+ MSX.Link[k].nsegs = 0;
1253
+ }
1254
+
1255
+ void topological_transport(double dt)
1256
+ {
1257
+ int j, n, k, m;
1258
+ double volin, volout;
1259
+ Padjlist alink;
1260
+
1261
+
1262
+ // Analyze each node in topological order
1263
+ for (j = 1; j <= MSX.Nobjects[NODE]; j++)
1264
+ {
1265
+ // ... index of node to be processed
1266
+ n = MSX.SortedNodes[j];
1267
+
1268
+ // ... zero out mass & flow volumes for this node
1269
+ volin = 0.0;
1270
+ volout = 0.0;
1271
+ memset(MSX.MassIn, 0, (MSX.Nobjects[SPECIES] + 1) * sizeof(double));
1272
+ memset(MSX.SourceIn, 0, (MSX.Nobjects[SPECIES] + 1) * sizeof(double));
1273
+
1274
+ // ... examine each link with flow into the node
1275
+ for (alink = MSX.Adjlist[n]; alink != NULL; alink = alink->next)
1276
+ {
1277
+ // ... k is index of next link incident on node n
1278
+ k = alink->link;
1279
+
1280
+ // ... link has flow into node - add it to node's inflow
1281
+ // (m is index of link's downstream node)
1282
+ m = MSX.Link[k].n2;
1283
+ if (MSX.FlowDir[k] < 0) m = MSX.Link[k].n1;
1284
+ if (m == n)
1285
+ {
1286
+ evalnodeinflow(k, dt, &volin, MSX.MassIn);
1287
+ }
1288
+
1289
+ // ... link has flow out of node - add it to node's outflow
1290
+ else volout += fabs(MSX.Q[k]);
1291
+ }
1292
+
1293
+ // ... if node is a junction, add on any external outflow (e.g., demands)
1294
+ if (MSX.Node[n].tank == 0)
1295
+ {
1296
+ volout += fmax(0.0, MSX.D[n]);
1297
+ }
1298
+
1299
+ // ... convert from outflow rate to volume
1300
+ volout *= dt;
1301
+
1302
+ // ... find the concentration of flow leaving the node
1303
+ findnodequal(n, volin, MSX.MassIn, volout, dt);
1304
+
1305
+ // ... examine each link with flow out of the node
1306
+ for (alink = MSX.Adjlist[n]; alink != NULL; alink = alink->next)
1307
+ {
1308
+ // ... link k incident on node n has upstream node m equal to n
1309
+ k = alink->link;
1310
+ m = MSX.Link[k].n1;
1311
+ if (MSX.FlowDir[k] < 0) m = MSX.Link[k].n2;
1312
+ if (m == n)
1313
+ {
1314
+ // ... send flow at new node concen. into link
1315
+ evalnodeoutflow(k, MSX.Node[n].c, dt);
1316
+ }
1317
+ }
1318
+
1319
+ }
1320
+ /*Advection-Reaction Done, Dispersion Starts*/
1321
+ //1. Linear relationship for each pipe
1322
+ //2. Compose the nodal equations
1323
+ //3. Solve the matrix to update nodal concentration
1324
+ //4. Update segment concentration
1325
+ for (int m = 1; m <= MSX.Nobjects[SPECIES]; m++)
1326
+ {
1327
+ if (MSX.Dispersion.md[m] > 0 || MSX.Dispersion.ld[m] > 0)
1328
+ {
1329
+ dispersion_pipe(m, dt);
1330
+ solve_nodequal(m, dt);
1331
+ segqual_update(m, dt);
1332
+ }
1333
+ }
1334
+ }
1335
+
1336
+
1337
+ void evalnodeoutflow(int k, double * upnodequal, double tstep)
1338
+ /*
1339
+ **--------------------------------------------------------------
1340
+ ** Input: k = link index
1341
+ ** c = quality from upstream node
1342
+ ** tstep = time step
1343
+ ** Output: none
1344
+ ** Purpose: releases flow volume and mass from the upstream
1345
+ ** node of a link over a time step.
1346
+ **--------------------------------------------------------------
1347
+ */
1348
+ {
1349
+
1350
+ double v;
1351
+ Pseg seg;
1352
+ int m;
1353
+ int useNewSeg = 0;
1354
+
1355
+ // Find flow volume (v) released over time step
1356
+ v = fabs(MSX.Q[k]) * tstep;
1357
+ if (v == 0.0) return;
1358
+
1359
+ // Release flow and mass into upstream end of the link
1360
+
1361
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
1362
+ {
1363
+ if (MSX.Species[m].type == BULK)
1364
+ MSX.NewSeg[k]->c[m] = upnodequal[m];
1365
+ }
1366
+
1367
+ // ... case where link has a last (most upstream) segment
1368
+ seg = MSX.LastSeg[k];
1369
+
1370
+ if (seg)
1371
+ {
1372
+ if (!MSXqual_isSame(seg->c, upnodequal) && MSX.Link[k].nsegs < MSX.MaxSegments)
1373
+ {
1374
+ useNewSeg = 1;
1375
+ }
1376
+ else
1377
+ useNewSeg = 0;
1378
+
1379
+ // if (MSX.DispersionFlag == 1 && MSX.Link[k].nsegs >= MSX.MaxSegments)
1380
+ // useNewSeg = 0;
1381
+
1382
+ if (useNewSeg == 0)
1383
+ {
1384
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
1385
+ {
1386
+ if (MSX.Species[m].type == BULK)
1387
+ seg->c[m] = (seg->c[m]*seg->v+upnodequal[m]*v)/(seg->v+v);
1388
+ }
1389
+ seg->v += v;
1390
+ MSXqual_removeSeg(MSX.NewSeg[k]);
1391
+ }
1392
+
1393
+ // --- otherwise add the new seg to the end of the link
1394
+
1395
+ else
1396
+ {
1397
+ MSX.NewSeg[k]->v = v;
1398
+ MSXqual_addSeg(k, MSX.NewSeg[k]);
1399
+ }
1400
+ }
1401
+
1402
+ // ... link has no segments so add one
1403
+ else
1404
+ {
1405
+ MSX.NewSeg[k]->v = v;
1406
+ MSXqual_addSeg(k, MSX.NewSeg[k]);
1407
+ }
1408
+
1409
+ }
1410
+
1411
+
1412
+ void evalnodeinflow(int k, double tstep, double* volin, double* massin)
1413
+ /*
1414
+ **--------------------------------------------------------------
1415
+ ** Input: k = link index
1416
+ ** tstep = quality routing time step
1417
+ ** Output: volin = flow volume entering a node
1418
+ ** massin = constituent mass entering a node
1419
+ ** Purpose: adds the contribution of a link's outflow volume
1420
+ ** and constituent mass to the total inflow into its
1421
+ ** downstream node over a time step.
1422
+ **--------------------------------------------------------------
1423
+ */
1424
+ {
1425
+
1426
+ double q, v, vseg;
1427
+ int sindex;
1428
+ Pseg seg;
1429
+
1430
+ // Get flow rate (q) and flow volume (v) through link
1431
+ q = MSX.Q[k];
1432
+ v = fabs(q) * tstep;
1433
+
1434
+ // Transport flow volume v from link's leading segments into downstream
1435
+ // node, removing segments once their full volume is consumed
1436
+ while (v > 0.0)
1437
+ {
1438
+ seg = MSX.FirstSeg[k];
1439
+ if (!seg) break;
1440
+
1441
+ // ... volume transported from first segment is smaller of
1442
+ // remaining flow volume & segment volume
1443
+ vseg = seg->v;
1444
+ vseg = MIN(vseg, v);
1445
+
1446
+ // ... update total volume & mass entering downstream node
1447
+ *volin += vseg;
1448
+ for (sindex = 1; sindex <= MSX.Nobjects[SPECIES]; sindex++)
1449
+ massin[sindex] += vseg * seg->c[sindex] * LperFT3;
1450
+
1451
+ // ... reduce remaining flow volume by amount transported
1452
+ v -= vseg;
1453
+
1454
+ // ... if all of segment's volume was transferred
1455
+ if (v >= 0.0 && vseg >= seg->v)
1456
+ {
1457
+ // ... replace this leading segment with the one behind it
1458
+ MSX.FirstSeg[k] = seg->prev;
1459
+ MSX.Link[k].nsegs--;
1460
+ if (MSX.FirstSeg[k] == NULL) MSX.LastSeg[k] = NULL;
1461
+ else MSX.FirstSeg[k]->next = NULL; //03/19/2024 added to break the linked segments
1462
+
1463
+ // ... recycle the used up segment
1464
+ seg->prev = MSX.FreeSeg;
1465
+ MSX.FreeSeg = seg;
1466
+ }
1467
+
1468
+ // ... otherwise just reduce this segment's volume
1469
+ else seg->v -= vseg;
1470
+ }
1471
+ }
1472
+
1473
+
1474
+ void findnodequal(int n, double volin, double* massin, double volout, double tstep)
1475
+ /*
1476
+ **--------------------------------------------------------------
1477
+ ** Input: n = node index
1478
+ ** volin = flow volume entering node
1479
+ ** massin = mass entering node
1480
+ ** volout = flow volume leaving node
1481
+ ** tstep = length of current time step
1482
+ ** Output: returns water quality in a node's outflow
1483
+ ** Purpose: computes a node's new quality from its inflow
1484
+ ** volume and mass, including any source contribution.
1485
+ **--------------------------------------------------------------
1486
+ */
1487
+ {
1488
+ int m, j;
1489
+ // Node is a junction - update its water quality
1490
+ j = MSX.Node[n].tank;
1491
+ if (j <= 0)
1492
+ {
1493
+ // ... dilute inflow with any external negative demand
1494
+ volin -= fmin(0.0, MSX.D[n]) * tstep;
1495
+
1496
+ // ... new concen. is mass inflow / volume inflow
1497
+ if (volin > 0.0)
1498
+ {
1499
+ for(m = 1; m <= MSX.Nobjects[SPECIES]; m++)
1500
+ MSX.Node[n].c[m] = massin[m] / volin / LperFT3;
1501
+ }
1502
+
1503
+ // ... if no inflow adjust quality for reaction in connecting pipes
1504
+ else
1505
+ noflowqual(n);
1506
+
1507
+ MSXchem_equil(NODE, 0, MSX.Node[n].c);
1508
+
1509
+ }
1510
+ else
1511
+ {
1512
+ // --- use initial quality for reservoirs
1513
+
1514
+ if (MSX.Tank[j].a == 0.0)
1515
+ {
1516
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
1517
+ {
1518
+ MSX.Node[n].c[m] = MSX.Node[n].c0[m];
1519
+ }
1520
+ MSXchem_equil(NODE, 0, MSX.Node[n].c);
1521
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
1522
+ {
1523
+
1524
+ MSX.MassBalance.inflow[m] += MSX.Node[n].c[m] * volout * LperFT3;
1525
+ MSX.MassBalance.outflow[m] += massin[m];
1526
+ }
1527
+ }
1528
+
1529
+ // --- otherwise update tank WQ based on mixing model
1530
+
1531
+ else
1532
+ {
1533
+ if (volin > 0.0)
1534
+ {
1535
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
1536
+ {
1537
+ MSX.C1[m] = massin[m] / volin / LperFT3;
1538
+ }
1539
+ }
1540
+ else for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
1541
+ MSX.C1[m] = 0.0;
1542
+ switch (MSX.Tank[j].mixModel)
1543
+ {
1544
+ case MIX1: MSXtank_mix1(j, volin, massin, volin-volout);
1545
+ break;
1546
+ case MIX2: MSXtank_mix2(j, volin, massin, volin-volout);
1547
+ break;
1548
+ case FIFO: MSXtank_mix3(j, volin, massin, volin-volout);
1549
+ break;
1550
+ case LIFO: MSXtank_mix4(j, volin, massin, volin-volout);
1551
+ break;
1552
+ }
1553
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
1554
+ {
1555
+ MSX.Node[n].c[m] = MSX.Tank[j].c[m];
1556
+ }
1557
+ MSX.Tank[j].v += (double)MSX.D[n] * tstep;
1558
+ }
1559
+ }
1560
+
1561
+ // Find quality contribued by any external chemical source
1562
+ sourceInput(n, volout, tstep);
1563
+ if (MSX.Node[n].tank == 0)
1564
+ {
1565
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
1566
+ if(MSX.Species[m].type == BULK)
1567
+ MSX.MassBalance.outflow[m] += MAX(0.0, MSX.D[n]) * tstep * MSX.Node[n].c[m]*LperFT3;
1568
+ }
1569
+ }
1570
+
1571
+
1572
+ int sortNodes()
1573
+ /*
1574
+ **--------------------------------------------------------------
1575
+ ** Input: none
1576
+ ** Output: returns an error code
1577
+ ** Purpose: topologically sorts nodes from upstream to downstream.
1578
+ ** Note: links with negligible flow are ignored since they can
1579
+ ** create spurious cycles that cause the sort to fail.
1580
+ **--------------------------------------------------------------
1581
+ */
1582
+ {
1583
+
1584
+ int i, j, k, n;
1585
+ int* indegree = NULL;
1586
+ int* stack = NULL;
1587
+ int stacksize = 0;
1588
+ int numsorted = 0;
1589
+ int errcode = 0;
1590
+ FlowDirection dir;
1591
+ Padjlist alink;
1592
+
1593
+ // Allocate an array to count # links with inflow to each node
1594
+ // and for a stack to hold nodes waiting to be processed
1595
+ indegree = (int*)calloc(MSX.Nobjects[NODE] + 1, sizeof(int));
1596
+ stack = (int*)calloc(MSX.Nobjects[NODE] + 1, sizeof(int));
1597
+ if (indegree && stack)
1598
+ {
1599
+ // Count links with "non-negligible" inflow to each node
1600
+ for (k = 1; k <= MSX.Nobjects[LINK]; k++)
1601
+ {
1602
+ dir = MSX.FlowDir[k];
1603
+ if (dir == POSITIVE) n = MSX.Link[k].n2;
1604
+ else if (dir == NEGATIVE) n = MSX.Link[k].n1;
1605
+ else continue;
1606
+ indegree[n]++;
1607
+ }
1608
+
1609
+ // Place nodes with no inflow onto a stack
1610
+ for (i = 1; i <= MSX.Nobjects[NODE]; i++)
1611
+ {
1612
+ if (indegree[i] == 0)
1613
+ {
1614
+ stacksize++;
1615
+ stack[stacksize] = i;
1616
+ }
1617
+ }
1618
+
1619
+ // Examine each node on the stack until none are left
1620
+ while (numsorted < MSX.Nobjects[NODE])
1621
+ {
1622
+ // ... if stack is empty then a cycle exists
1623
+ if (stacksize == 0)
1624
+ {
1625
+ // ... add a non-sorted node connected to a sorted one to stack
1626
+ j = selectnonstacknode(numsorted, indegree);
1627
+ if (j == 0) break; // This shouldn't happen.
1628
+ indegree[j] = 0;
1629
+ stacksize++;
1630
+ stack[stacksize] = j;
1631
+ }
1632
+
1633
+ // ... make the last node added to the stack the next
1634
+ // in sorted order & remove it from the stack
1635
+ i = stack[stacksize];
1636
+ stacksize--;
1637
+ numsorted++;
1638
+ MSX.SortedNodes[numsorted] = i;
1639
+
1640
+ // ... for each outflow link from this node reduce the in-degree
1641
+ // of its downstream node
1642
+ for (alink = MSX.Adjlist[i]; alink != NULL; alink = alink->next)
1643
+ {
1644
+ // ... k is the index of the next link incident on node i
1645
+ k = alink->link;
1646
+
1647
+ // ... skip link if flow is negligible
1648
+ if (MSX.FlowDir[k] == 0) continue;
1649
+
1650
+ // ... link has flow out of node (downstream node n not equal to i)
1651
+ n = MSX.Link[k].n2;
1652
+ if (MSX.FlowDir[k] < 0) n = MSX.Link[k].n1;
1653
+
1654
+ // ... reduce degree of node n
1655
+ if (n != i && indegree[n] > 0)
1656
+ {
1657
+ indegree[n]--;
1658
+
1659
+ // ... no more degree left so add node n to stack
1660
+ if (indegree[n] == 0)
1661
+ {
1662
+ stacksize++;
1663
+ stack[stacksize] = n;
1664
+ }
1665
+ }
1666
+ }
1667
+ }
1668
+ }
1669
+ else errcode = 101;
1670
+ if (numsorted < MSX.Nobjects[NODE]) errcode = 120;
1671
+ FREE(indegree);
1672
+ FREE(stack);
1673
+ return errcode;
1674
+ }
1675
+
1676
+ int selectnonstacknode(int numsorted, int* indegree)
1677
+ /*
1678
+ **--------------------------------------------------------------
1679
+ ** Input: numsorted = number of nodes that have been sorted
1680
+ ** indegree = number of inflow links to each node
1681
+ ** Output: returns a node index
1682
+ ** Purpose: selects a next node for sorting when a cycle exists.
1683
+ **--------------------------------------------------------------
1684
+ */
1685
+ {
1686
+
1687
+ int i, m, n;
1688
+ Padjlist alink;
1689
+
1690
+ // Examine each sorted node in last in - first out order
1691
+ for (i = numsorted; i > 0; i--)
1692
+ {
1693
+ // For each link connected to the sorted node
1694
+ m = MSX.SortedNodes[i];
1695
+ for (alink = MSX.Adjlist[m]; alink != NULL; alink = alink->next)
1696
+ {
1697
+ // ... n is the node of link k opposite to node m
1698
+ n = alink->node;
1699
+
1700
+ // ... select node n if it still has inflow links
1701
+ if (indegree[n] > 0) return n;
1702
+ }
1703
+ }
1704
+
1705
+ // If no node was selected by the above process then return the
1706
+ // first node that still has inflow links remaining
1707
+ for (i = 1; i <= MSX.Nobjects[NODE]; i++)
1708
+ {
1709
+ if (indegree[i] > 0) return i;
1710
+ }
1711
+
1712
+ // If all else fails return 0 indicating that no node was selected
1713
+ return 0;
1714
+ }
1715
+
1716
+ void noflowqual(int n)
1717
+ /*
1718
+ **--------------------------------------------------------------
1719
+ ** Input: n = node index
1720
+ ** Output: quality for node n
1721
+ ** Purpose: sets the quality for a junction node that has no
1722
+ ** inflow to the average of the quality in its
1723
+ ** adjoining link segments.
1724
+ ** Note: this function is only used for reactive substances.
1725
+ **--------------------------------------------------------------
1726
+ */
1727
+ {
1728
+
1729
+ int k, m, inflow, kount = 0;
1730
+ double c = 0.0;
1731
+ FlowDirection dir;
1732
+ Padjlist alink;
1733
+
1734
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
1735
+ MSX.Node[n].c[m] = 0.0;
1736
+ // Examine each link incident on the node
1737
+ for (alink = MSX.Adjlist[n]; alink != NULL; alink = alink->next)
1738
+ {
1739
+ // ... index of an incident link
1740
+ k = alink->link;
1741
+ dir = MSX.FlowDir[k];
1742
+
1743
+ // Node n is link's downstream node - add quality
1744
+ // of link's first segment to average
1745
+ if (MSX.Link[k].n2 == n && dir >= 0) inflow = TRUE;
1746
+ else if (MSX.Link[k].n1 == n && dir < 0) inflow = TRUE;
1747
+ else inflow = FALSE;
1748
+ if (inflow == TRUE && MSX.FirstSeg[k] != NULL)
1749
+ {
1750
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
1751
+ MSX.Node[n].c[m] += MSX.FirstSeg[k]->c[m];
1752
+ kount++;
1753
+ }
1754
+
1755
+ // Node n is link's upstream node - add quality
1756
+ // of link's last segment to average
1757
+ else if (inflow == FALSE && MSX.LastSeg[k] != NULL)
1758
+ {
1759
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
1760
+ MSX.Node[n].c[m] += MSX.LastSeg[k]->c[m];
1761
+ kount++;
1762
+ }
1763
+ }
1764
+ if (kount > 0)
1765
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
1766
+ MSX.Node[n].c[m] = MSX.Node[n].c[m] / (double)kount;
1767
+ }
1768
+
1769
+ void findstoredmass(double * mass)
1770
+ /*
1771
+ **--------------------------------------------------------------
1772
+ ** Input: none
1773
+ ** Output: returns total constituent mass stored in the network
1774
+ ** Purpose: finds the current mass stored in
1775
+ ** all pipes and tanks.
1776
+ **--------------------------------------------------------------
1777
+ */
1778
+ {
1779
+
1780
+ int i, k, m;
1781
+ Pseg seg;
1782
+
1783
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
1784
+ {
1785
+ mass[m] = 0;
1786
+ }
1787
+
1788
+ // Mass residing in each pipe
1789
+ for (k = 1; k <= MSX.Nobjects[LINK]; k++)
1790
+ {
1791
+ // Sum up the quality and volume in each segment of the link
1792
+ seg = MSX.FirstSeg[k];
1793
+ while (seg != NULL)
1794
+ {
1795
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
1796
+ {
1797
+ if (MSX.Species[m].type == BULK)
1798
+ mass[m] += seg->c[m] * seg->v * LperFT3; //M/L * ft3 * L/Ft3 = M
1799
+ else
1800
+ mass[m] += seg->c[m] * seg->v * 4.0 / MSX.Link[k].diam * MSX.Ucf[AREA_UNITS]; //Mass per area unit * ft3 / ft * area unit per ft2;
1801
+ }
1802
+ seg = seg->prev;
1803
+ }
1804
+ }
1805
+
1806
+ // Mass residing in each tank
1807
+ for (i = 1; i <= MSX.Nobjects[TANK]; i++)
1808
+ {
1809
+ // ... skip reservoirs
1810
+ if (MSX.Tank[i].a == 0.0) continue;
1811
+
1812
+ // ... add up mass in each volume segment
1813
+ else
1814
+ {
1815
+ k = MSX.Nobjects[LINK] + i;
1816
+ seg = MSX.FirstSeg[k];
1817
+ while (seg != NULL)
1818
+ {
1819
+ for (m = 1; m <= MSX.Nobjects[SPECIES]; m++)
1820
+ {
1821
+ if (MSX.Species[m].type == BULK)
1822
+ mass[m] += seg->c[m] * seg->v * LperFT3;
1823
+ }
1824
+ seg = seg->prev;
1825
+ }
1826
+ }
1827
+ }
1828
+ }
1829
+
1830
+ void MSXqual_reversesegs(int k)
1831
+ /*
1832
+ **--------------------------------------------------------------
1833
+ ** Input: k = link index
1834
+ ** Output: none
1835
+ ** Purpose: re-orients a link's segments when flow reverses.
1836
+ **--------------------------------------------------------------
1837
+ */
1838
+ {
1839
+ Pseg seg, cseg, pseg;
1840
+
1841
+ seg = MSX.FirstSeg[k];
1842
+ MSX.FirstSeg[k] = MSX.LastSeg[k];
1843
+ MSX.LastSeg[k] = seg;
1844
+ pseg = NULL;
1845
+ while (seg != NULL)
1846
+ {
1847
+ cseg = seg->prev;
1848
+ seg->prev = pseg;
1849
+ seg->next = cseg;
1850
+ pseg = seg;
1851
+ seg = cseg;
1852
+ }
1853
+ }
1854
+
1855
+
1856
+
1857
+ //=============================================================================
1858
+
1859
+ void MSXqual_removeSeg(Pseg seg)
1860
+ /*
1861
+ ** Purpose:
1862
+ ** places a WQ segment back into the memory pool of segments.
1863
+ **
1864
+ ** Input:
1865
+ ** seg = pointer to a WQ segment.
1866
+ */
1867
+ {
1868
+ if ( seg == NULL ) return;
1869
+ seg->prev = MSX.FreeSeg;
1870
+ seg->next = NULL;
1871
+ MSX.FreeSeg = seg;
1872
+ }
1873
+
1874
+ //=============================================================================
1875
+
1876
+ Pseg MSXqual_getFreeSeg(double v, double c[])
1877
+ /*
1878
+ ** Purpose:
1879
+ ** retrieves an unused water quality volume segment from the memory pool.
1880
+ **
1881
+ ** Input:
1882
+ ** v = segment volume (ft3)
1883
+ ** c[] = segment quality
1884
+ **
1885
+ ** Returns:
1886
+ ** a pointer to an unused water quality segment.
1887
+ */
1888
+ {
1889
+ Pseg seg;
1890
+ int m;
1891
+
1892
+ // --- try using the last discarded segment if one is available
1893
+
1894
+ if (MSX.FreeSeg != NULL)
1895
+ {
1896
+ seg = MSX.FreeSeg;
1897
+ MSX.FreeSeg = seg->prev;
1898
+ }
1899
+
1900
+ // --- otherwise create a new segment from the memory pool
1901
+
1902
+ else
1903
+ {
1904
+ seg = (struct Sseg *) Alloc(sizeof(struct Sseg));
1905
+ if (seg == NULL)
1906
+ {
1907
+ MSX.OutOfMemory = TRUE;
1908
+ return NULL;
1909
+ }
1910
+ seg->c = (double *) Alloc((MSX.Nobjects[SPECIES]+1)*sizeof(double));
1911
+ seg->lastc = (double *)Alloc((MSX.Nobjects[SPECIES] + 1) * sizeof(double));
1912
+ if ( seg->c == NULL||seg->lastc == NULL)
1913
+ {
1914
+ MSX.OutOfMemory = TRUE;
1915
+ return NULL;
1916
+ }
1917
+ }
1918
+
1919
+ // --- assign volume, WQ, & integration time step to the new segment
1920
+
1921
+ seg->v = v;
1922
+ for (m=1; m<=MSX.Nobjects[SPECIES]; m++) seg->c[m] = c[m];
1923
+ seg->hstep = 0.0;
1924
+ return seg;
1925
+ }
1926
+
1927
+ //=============================================================================
1928
+
1929
+ void MSXqual_addSeg(int k, Pseg seg)
1930
+ /*
1931
+ ** Purpose:
1932
+ ** adds a new segment to the upstream end of a link.
1933
+ **
1934
+ ** Input:
1935
+ ** k = link index
1936
+ ** seg = pointer to a free WQ segment.
1937
+ */
1938
+
1939
+ {
1940
+ seg->prev = NULL;
1941
+ seg->next = NULL;
1942
+ if (MSX.FirstSeg[k] == NULL) MSX.FirstSeg[k] = seg;
1943
+ if (MSX.LastSeg[k] != NULL)
1944
+ {
1945
+ MSX.LastSeg[k]->prev = seg;
1946
+ seg->next = MSX.LastSeg[k];
1947
+ }
1948
+ MSX.LastSeg[k] = seg;
1949
+ if (k <= MSX.Nobjects[LINK])
1950
+ MSX.Link[k].nsegs++;
1951
+ }
1952
+
1953
+ void evalHydVariables(int k)
1954
+ /*
1955
+ ** Purpose:
1956
+ ** retrieves current values of hydraulic variables for the
1957
+ ** current link being analyzed.
1958
+ **
1959
+ ** Input:
1960
+ ** k = link index
1961
+ **
1962
+ ** Output:
1963
+ ** updates values stored in vector HydVar[]
1964
+ */
1965
+ {
1966
+ double dh; // headloss in ft
1967
+ double diam = MSX.Link[k].diam; // diameter in ft
1968
+ double length = MSX.Link[k].len; // length in ft
1969
+ double av; // area per unit volume
1970
+
1971
+ // --- pipe diameter and length in user's units (ft or m)
1972
+ MSX.Link[k].HydVar[DIAMETER] = diam * MSX.Ucf[LENGTH_UNITS];
1973
+ MSX.Link[k].HydVar[LENGTH] = length * MSX.Ucf[LENGTH_UNITS];
1974
+
1975
+ // --- flow rate in user's units
1976
+ MSX.Link[k].HydVar[FLOW] = fabs(MSX.Q[k]) * MSX.Ucf[FLOW_UNITS];
1977
+
1978
+ // --- flow velocity in ft/sec
1979
+ if (diam == 0.0) MSX.Link[k].HydVar[VELOCITY] = 0.0;
1980
+ else MSX.Link[k].HydVar[VELOCITY] = fabs(MSX.Q[k]) * 4.0 / PI / SQR(diam);
1981
+
1982
+ // --- Reynolds number
1983
+ MSX.Link[k].HydVar[REYNOLDS] = MSX.Link[k].HydVar[VELOCITY] * diam / VISCOS;
1984
+
1985
+ // --- flow velocity in user's units (ft/sec or m/sec)
1986
+ MSX.Link[k].HydVar[VELOCITY] *= MSX.Ucf[LENGTH_UNITS];
1987
+
1988
+ // --- Darcy Weisbach friction factor
1989
+ if (MSX.Link[k].len == 0.0) MSX.Link[k].HydVar[FRICTION] = 0.0;
1990
+ else
1991
+ {
1992
+ dh = ABS(MSX.H[MSX.Link[k].n1] - MSX.H[MSX.Link[k].n2]);
1993
+ MSX.Link[k].HydVar[FRICTION] = 39.725 * dh * pow(diam, 5.0) /
1994
+ MSX.Link[k].len / SQR((double)MSX.Q[k]);
1995
+ }
1996
+
1997
+ // --- shear velocity in user's units (ft/sec or m/sec)
1998
+ MSX.Link[k].HydVar[SHEAR] = MSX.Link[k].HydVar[VELOCITY] * sqrt(MSX.Link[k].HydVar[FRICTION] / 8.0);
1999
+
2000
+ // --- pipe surface area / volume in area_units/L
2001
+ MSX.Link[k].HydVar[AREAVOL] = 1.0;
2002
+ if (diam > 0.0)
2003
+ {
2004
+ av = 4.0 / diam; // ft2/ft3
2005
+ av *= MSX.Ucf[AREA_UNITS]; // area_units/ft3
2006
+ av /= LperFT3; // area_units/L
2007
+ MSX.Link[k].HydVar[AREAVOL] = av;
2008
+ }
2009
+
2010
+ MSX.Link[k].HydVar[ROUGHNESS] = MSX.Link[k].roughness;
2011
+ }