epanet-plus 0.0.1__cp310-cp310-macosx_10_9_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of epanet-plus might be problematic. Click here for more details.
- docs/conf.py +67 -0
- epanet-msx-src/dispersion.h +27 -0
- epanet-msx-src/hash.c +107 -0
- epanet-msx-src/hash.h +28 -0
- epanet-msx-src/include/epanetmsx.h +104 -0
- epanet-msx-src/include/epanetmsx_export.h +42 -0
- epanet-msx-src/mathexpr.c +937 -0
- epanet-msx-src/mathexpr.h +39 -0
- epanet-msx-src/mempool.c +204 -0
- epanet-msx-src/mempool.h +24 -0
- epanet-msx-src/msxchem.c +1285 -0
- epanet-msx-src/msxcompiler.c +368 -0
- epanet-msx-src/msxdict.h +42 -0
- epanet-msx-src/msxdispersion.c +586 -0
- epanet-msx-src/msxerr.c +116 -0
- epanet-msx-src/msxfile.c +260 -0
- epanet-msx-src/msxfuncs.c +175 -0
- epanet-msx-src/msxfuncs.h +35 -0
- epanet-msx-src/msxinp.c +1504 -0
- epanet-msx-src/msxout.c +398 -0
- epanet-msx-src/msxproj.c +791 -0
- epanet-msx-src/msxqual.c +2011 -0
- epanet-msx-src/msxrpt.c +400 -0
- epanet-msx-src/msxtank.c +422 -0
- epanet-msx-src/msxtoolkit.c +1164 -0
- epanet-msx-src/msxtypes.h +551 -0
- epanet-msx-src/msxutils.c +524 -0
- epanet-msx-src/msxutils.h +56 -0
- epanet-msx-src/newton.c +158 -0
- epanet-msx-src/newton.h +34 -0
- epanet-msx-src/rk5.c +287 -0
- epanet-msx-src/rk5.h +39 -0
- epanet-msx-src/ros2.c +293 -0
- epanet-msx-src/ros2.h +35 -0
- epanet-msx-src/smatrix.c +816 -0
- epanet-msx-src/smatrix.h +29 -0
- epanet-src/AUTHORS +60 -0
- epanet-src/LICENSE +21 -0
- epanet-src/enumstxt.h +151 -0
- epanet-src/epanet.c +5937 -0
- epanet-src/epanet2.c +961 -0
- epanet-src/epanet2.def +131 -0
- epanet-src/errors.dat +79 -0
- epanet-src/flowbalance.c +186 -0
- epanet-src/funcs.h +219 -0
- epanet-src/genmmd.c +1000 -0
- epanet-src/hash.c +177 -0
- epanet-src/hash.h +28 -0
- epanet-src/hydcoeffs.c +1303 -0
- epanet-src/hydraul.c +1164 -0
- epanet-src/hydsolver.c +781 -0
- epanet-src/hydstatus.c +442 -0
- epanet-src/include/epanet2.h +466 -0
- epanet-src/include/epanet2_2.h +1962 -0
- epanet-src/include/epanet2_enums.h +518 -0
- epanet-src/inpfile.c +884 -0
- epanet-src/input1.c +672 -0
- epanet-src/input2.c +970 -0
- epanet-src/input3.c +2265 -0
- epanet-src/leakage.c +527 -0
- epanet-src/mempool.c +146 -0
- epanet-src/mempool.h +24 -0
- epanet-src/output.c +853 -0
- epanet-src/project.c +1691 -0
- epanet-src/quality.c +695 -0
- epanet-src/qualreact.c +800 -0
- epanet-src/qualroute.c +696 -0
- epanet-src/report.c +1559 -0
- epanet-src/rules.c +1500 -0
- epanet-src/smatrix.c +871 -0
- epanet-src/text.h +508 -0
- epanet-src/types.h +928 -0
- epanet-src/util/cstr_helper.c +59 -0
- epanet-src/util/cstr_helper.h +38 -0
- epanet-src/util/errormanager.c +92 -0
- epanet-src/util/errormanager.h +39 -0
- epanet-src/util/filemanager.c +212 -0
- epanet-src/util/filemanager.h +81 -0
- epanet-src/validate.c +408 -0
- epanet.cpython-310-darwin.so +0 -0
- epanet_plus/VERSION +1 -0
- epanet_plus/__init__.py +8 -0
- epanet_plus/epanet_plus.c +118 -0
- epanet_plus/epanet_toolkit.py +2730 -0
- epanet_plus/epanet_wrapper.py +2414 -0
- epanet_plus/include/epanet_plus.h +9 -0
- epanet_plus-0.0.1.dist-info/METADATA +152 -0
- epanet_plus-0.0.1.dist-info/RECORD +105 -0
- epanet_plus-0.0.1.dist-info/WHEEL +6 -0
- epanet_plus-0.0.1.dist-info/licenses/LICENSE +21 -0
- epanet_plus-0.0.1.dist-info/top_level.txt +11 -0
- examples/basic_usage.py +35 -0
- python-extension/ext.c +344 -0
- python-extension/pyepanet.c +2133 -0
- python-extension/pyepanet.h +143 -0
- python-extension/pyepanet2.c +1823 -0
- python-extension/pyepanet2.h +141 -0
- python-extension/pyepanet_plus.c +37 -0
- python-extension/pyepanet_plus.h +4 -0
- python-extension/pyepanetmsx.c +388 -0
- python-extension/pyepanetmsx.h +35 -0
- tests/test_epanet.py +16 -0
- tests/test_epanetmsx.py +36 -0
- tests/test_epyt.py +114 -0
- tests/test_load_inp_from_buffer.py +18 -0
epanet-msx-src/msxqual.c
ADDED
|
@@ -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
|
+
}
|