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