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