Fixed water quality mass balance issue (#160)
This commit is contained in:
11
src/epanet.c
11
src/epanet.c
@@ -1997,6 +1997,9 @@ int DLLEXPORT EN_getstatistic(EN_ProjectHandle ph, int code, EN_API_FLOAT_TYPE *
|
||||
case EN_MAXFLOWCHANGE:
|
||||
*value = (EN_API_FLOAT_TYPE)(p->hydraulics.MaxFlowChange * p->Ucf[FLOW]);
|
||||
break;
|
||||
case EN_MASSBALANCE:
|
||||
*value = (EN_API_FLOAT_TYPE)(p->quality.massbalance.ratio);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -3759,7 +3762,7 @@ int DLLEXPORT EN_setoption(EN_ProjectHandle ph, int code, EN_API_FLOAT_TYPE v)
|
||||
}
|
||||
else
|
||||
{
|
||||
error = EN_getpatternid(p, value, &*tmpId);
|
||||
error = EN_getpatternid(p, (int)value, tmpId);
|
||||
if (error != 0)
|
||||
return set_error(p->error_handle, error);
|
||||
}
|
||||
@@ -3768,12 +3771,12 @@ int DLLEXPORT EN_setoption(EN_ProjectHandle ph, int code, EN_API_FLOAT_TYPE v)
|
||||
Snode *node = &net->Node[i];
|
||||
for (demand = node->D; demand != NULL; demand = demand->next) {
|
||||
if (demand->Pat == tmpPat) {
|
||||
demand->Pat = value;
|
||||
demand->Pat = (int)value;
|
||||
}
|
||||
}
|
||||
}
|
||||
strncpy(p->parser.DefPatID, tmpId, MAXID);
|
||||
hyd->DefPat = value;
|
||||
hyd->DefPat = (int)value;
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -4952,7 +4955,7 @@ int DLLEXPORT EN_addnode(EN_ProjectHandle ph, char *id, EN_NodeType nodeType) {
|
||||
Snode *node;
|
||||
Scoord *coord;
|
||||
Scontrol *control;
|
||||
rules_t *rule;
|
||||
// rules_t *rule;
|
||||
Premise *pchain, *pnext;
|
||||
|
||||
|
||||
|
||||
36
src/funcs.h
36
src/funcs.h
@@ -127,8 +127,6 @@ void freerules(EN_Project *pr); /* Frees rule base memory
|
||||
int writeRuleinInp(EN_Project *pr, FILE *f, /* Writes rule to an INP file */
|
||||
int RuleIdx);
|
||||
|
||||
int writeRuleinInp(EN_Project *pr, FILE *f, int RuleIdx);
|
||||
|
||||
/* ------------- REPORT.C --------------*/
|
||||
int writereport(EN_Project *pr); /* Writes formatted report */
|
||||
void writelogo(EN_Project *pr); /* Writes program logo */
|
||||
@@ -144,6 +142,7 @@ void writecontrolaction(EN_Project *pr, int, int); /* Writes control acti
|
||||
void writeruleaction(EN_Project *pr, int, char *); /* Writes rule action taken */
|
||||
int writehydwarn(EN_Project *pr, int,double); /* Writes hydraulic warnings */
|
||||
void writehyderr(EN_Project *pr, int); /* Writes hydraulic error msg.*/
|
||||
void writemassbalance(EN_Project *pr); // Writes mass balance ratio
|
||||
int disconnected(EN_Project *pr); /* Checks for disconnections */
|
||||
void marknodes(EN_Project *pr, int, int *, char *); /* Identifies connected nodes */
|
||||
void getclosedlink(EN_Project *pr, int, char *); /* Finds a disconnecting link */
|
||||
@@ -192,41 +191,12 @@ int linsolve(EN_Project *pr, int); /* Solves set of linear eqns
|
||||
|
||||
/* ----------- QUALITY.C ---------------*/
|
||||
int openqual(EN_Project *pr); /* Opens WQ solver system */
|
||||
void initqual(EN_Project *pr); /* Initializes WQ solver */
|
||||
int initqual(EN_Project *pr); /* Initializes WQ solver */
|
||||
int runqual(EN_Project *pr, long *); /* Gets current WQ results */
|
||||
int nextqual(EN_Project *pr, long *); /* Updates WQ by hyd.timestep */
|
||||
int stepqual(EN_Project *pr, long *); /* Updates WQ by WQ time step */
|
||||
int closequal(EN_Project *pr); /* Closes WQ solver system */
|
||||
int gethyd(EN_Project *pr, long *, long *); /* Gets next hyd. results */
|
||||
char setReactflag(EN_Project *pr); /* Checks for reactive chem. */
|
||||
void transport(EN_Project *pr, long); /* Transports mass in network */
|
||||
void initsegs(EN_Project *pr); /* Initializes WQ segments */
|
||||
void reorientsegs(EN_Project *pr); /* Re-orients WQ segments */
|
||||
void updatesegs(EN_Project *pr, long); /* Updates quality in segments*/
|
||||
void removesegs(EN_Project *pr, int); /* Removes a WQ segment */
|
||||
void addseg(EN_Project *pr, int,double,double); /* Adds a WQ segment to pipe */
|
||||
void accumulate(EN_Project *pr, long); /* Sums mass flow into node */
|
||||
void updatenodes(EN_Project *pr, long); /* Updates WQ at nodes */
|
||||
void sourceinput(EN_Project *pr, long); /* Computes source inputs */
|
||||
void release(EN_Project *pr, long); /* Releases mass from nodes */
|
||||
void updatetanks(EN_Project *pr, long); /* Updates WQ in tanks */
|
||||
void updatesourcenodes(EN_Project *pr, long); /* Updates WQ at source nodes */
|
||||
void tankmix1(EN_Project *pr, int, long); /* Complete mix tank model */
|
||||
void tankmix2(EN_Project *pr, int, long); /* 2-compartment tank model */
|
||||
void tankmix3(EN_Project *pr, int, long); /* FIFO tank model */
|
||||
void tankmix4(EN_Project *pr, int, long); /* LIFO tank model */
|
||||
double sourcequal(EN_Project *pr, Psource); /* Finds WQ input from source */
|
||||
double avgqual(EN_Project *pr, int); /* Finds avg. quality in pipe */
|
||||
void ratecoeffs(EN_Project *pr); /* Finds wall react. coeffs. */
|
||||
double piperate(EN_Project *pr, int); /* Finds wall react. coeff. */
|
||||
double pipereact(EN_Project *pr, int,double,
|
||||
double,long); /* Reacts water in a pipe */
|
||||
double tankreact(EN_Project *pr, double,double,
|
||||
double,long); /* Reacts water in a tank */
|
||||
double bulkrate(EN_Project *pr, double,double,
|
||||
double); /* Finds bulk reaction rate */
|
||||
double wallrate(EN_Project *pr, double,double,
|
||||
double,double); /* Finds wall reaction rate */
|
||||
|
||||
/* ------------ OUTPUT.C ---------------*/
|
||||
int savenetdata(EN_Project *pr); /* Saves basic data to file */
|
||||
@@ -234,7 +204,7 @@ int savehyd(EN_Project *pr, long *); /* Saves hydraulic solution
|
||||
int savehydstep(EN_Project *pr, long *); /* Saves hydraulic timestep */
|
||||
int saveenergy(EN_Project *pr); /* Saves energy usage */
|
||||
int readhyd(EN_Project *pr, long *); /* Reads hydraulics from file */
|
||||
int readhydstep(FILE *hydFile, long *); /* Reads time step from file */
|
||||
int readhydstep(EN_Project *pr, long *); /* Reads time step from file */
|
||||
int saveoutput(EN_Project *pr); /* Saves results to file */
|
||||
int nodeoutput(EN_Project *pr, int, REAL4 *,
|
||||
double); /* Saves node results to file */
|
||||
|
||||
@@ -276,20 +276,21 @@ int nexthyd(EN_Project *pr, long *tstep)
|
||||
addenergy(pr,hydstep);
|
||||
}
|
||||
|
||||
/* Update current time. */
|
||||
if (top->Htime < top->Dur) /* More time remains */
|
||||
/* More time remains - update current time. */
|
||||
if (top->Htime < top->Dur)
|
||||
{
|
||||
top->Htime += hydstep;
|
||||
if (top->Htime >= top->Rtime) {
|
||||
top->Rtime += top->Rstep;
|
||||
}
|
||||
if (!pr->quality.OpenQflag)
|
||||
{
|
||||
if (top->Htime >= top->Rtime) top->Rtime += top->Rstep;
|
||||
}
|
||||
}
|
||||
|
||||
/* No more time remains - force completion of analysis. */
|
||||
else
|
||||
{
|
||||
top->Htime++; /* Force completion of analysis */
|
||||
if (pr->quality.OpenQflag) {
|
||||
pr->quality.Qtime++; // force completion of wq analysis too
|
||||
}
|
||||
top->Htime++;
|
||||
if (pr->quality.OpenQflag) pr->quality.Qtime++;
|
||||
}
|
||||
*tstep = hydstep;
|
||||
return(errcode);
|
||||
|
||||
@@ -167,7 +167,8 @@ void setdefaults(EN_Project *pr)
|
||||
hyd->RQtol = RQTOL; /* Default hydraulics parameters */
|
||||
hyd->CheckFreq = CHECKFREQ;
|
||||
hyd->MaxCheck = MAXCHECK;
|
||||
hyd->DampLimit = DAMPLIMIT;
|
||||
hyd->DampLimit = DAMPLIMIT;
|
||||
qu->massbalance.ratio = 0.0;
|
||||
} /* End of setdefaults */
|
||||
|
||||
void initreport(report_options_t *r)
|
||||
|
||||
@@ -386,7 +386,7 @@ int readhyd(EN_Project *pr, long *hydtime)
|
||||
return result;
|
||||
} /* End of readhyd */
|
||||
|
||||
int readhydstep(FILE *hydFile, long *hydstep)
|
||||
int readhydstep(EN_Project *pr, long *hydstep)
|
||||
/*
|
||||
**--------------------------------------------------------------
|
||||
** Input: none
|
||||
@@ -397,8 +397,8 @@ int readhydstep(FILE *hydFile, long *hydstep)
|
||||
*/
|
||||
{
|
||||
INT4 t;
|
||||
if (fread(&t, sizeof(INT4), 1, hydFile) < 1)
|
||||
return (0);
|
||||
FILE *hydFile = pr->out_files.HydFile;
|
||||
if (fread(&t, sizeof(INT4), 1, hydFile) < 1) return (0);
|
||||
*hydstep = t;
|
||||
return (1);
|
||||
} /* End of readhydstep */
|
||||
|
||||
2284
src/quality.c
2284
src/quality.c
File diff suppressed because it is too large
Load Diff
721
src/qualreact.c
Normal file
721
src/qualreact.c
Normal file
@@ -0,0 +1,721 @@
|
||||
/*
|
||||
*********************************************************************
|
||||
|
||||
QUALREACT.C -- water quality reaction module for the EPANET program
|
||||
|
||||
*********************************************************************
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include "types.h"
|
||||
|
||||
// Exported Functions
|
||||
char setreactflag(EN_Project *pr);
|
||||
double getucf(double);
|
||||
void ratecoeffs(EN_Project *pr);
|
||||
void reactpipes(EN_Project *pr, long);
|
||||
void reacttanks(EN_Project *pr, long);
|
||||
double mixtank(EN_Project *pr, int, double, double ,double);
|
||||
|
||||
// Imported Functions
|
||||
extern void addseg(EN_Project *pr, int, double, double);
|
||||
|
||||
// Local Functions
|
||||
static double piperate(EN_Project *pr, int);
|
||||
static double pipereact(EN_Project *pr, int, double, double, long);
|
||||
static double tankreact(EN_Project *pr, double, double, double, long);
|
||||
static double bulkrate(EN_Project *pr, double, double, double);
|
||||
static double wallrate(EN_Project *pr, double, double, double, double);
|
||||
|
||||
static void tankmix1(EN_Project *pr, int, double, double, double);
|
||||
static void tankmix2(EN_Project *pr, int, double, double, double);
|
||||
static void tankmix3(EN_Project *pr, int, double, double, double);
|
||||
static void tankmix4(EN_Project *pr, int, double, double, double);
|
||||
|
||||
|
||||
char setreactflag(EN_Project *pr)
|
||||
/*
|
||||
**-----------------------------------------------------------
|
||||
** Input: none
|
||||
** Output: returns 1 for reactive WQ constituent, 0 otherwise
|
||||
** Purpose: checks if reactive chemical being simulated
|
||||
**-----------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
int i;
|
||||
EN_Network *net = &pr->network;
|
||||
|
||||
if (pr->quality.Qualflag == TRACE) return 0;
|
||||
else if (pr->quality.Qualflag == AGE) return 1;
|
||||
else
|
||||
{
|
||||
for (i = 1; i <= net->Nlinks; i++)
|
||||
{
|
||||
if (net->Link[i].Type <= EN_PIPE)
|
||||
{
|
||||
if (net->Link[i].Kb != 0.0 || net->Link[i].Kw != 0.0) return 1;
|
||||
}
|
||||
}
|
||||
for (i = 1; i <= net->Ntanks; i++)
|
||||
{
|
||||
if (net->Tank[i].Kb != 0.0) return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
double getucf(double order)
|
||||
/*
|
||||
**--------------------------------------------------------------
|
||||
** Input: order = bulk reaction order
|
||||
** Output: returns a unit conversion factor
|
||||
** Purpose: converts bulk reaction rates from per Liter to
|
||||
** per FT3 basis
|
||||
**--------------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
if (order < 0.0) order = 0.0;
|
||||
if (order == 1.0) return (1.0);
|
||||
else return (1. / pow(LperFT3, (order - 1.0)));
|
||||
}
|
||||
|
||||
|
||||
void ratecoeffs(EN_Project *pr)
|
||||
/*
|
||||
**--------------------------------------------------------------
|
||||
** Input: none
|
||||
** Output: none
|
||||
** Purpose: determines wall reaction coeff. for each pipe
|
||||
**--------------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
int k;
|
||||
double kw;
|
||||
|
||||
EN_Network *net = &pr->network;
|
||||
quality_t *qual = &pr->quality;
|
||||
|
||||
for (k = 1; k <= net->Nlinks; k++)
|
||||
{
|
||||
kw = net->Link[k].Kw;
|
||||
if (kw != 0.0) kw = piperate(pr, k);
|
||||
net->Link[k].Rc = kw;
|
||||
qual->PipeRateCoeff[k] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void reactpipes(EN_Project *pr, long dt)
|
||||
/*
|
||||
**--------------------------------------------------------------
|
||||
** Input: dt = time step
|
||||
** Output: none
|
||||
** Purpose: reacts water within each pipe over a time step.
|
||||
**--------------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
int k;
|
||||
Pseg seg;
|
||||
double cseg, rsum, vsum;
|
||||
|
||||
EN_Network *net = &pr->network;
|
||||
quality_t *qual = &pr->quality;
|
||||
|
||||
// Examine each link in network
|
||||
for (k = 1; k <= net->Nlinks; k++)
|
||||
{
|
||||
// Skip non-pipe links (pumps & valves)
|
||||
if (net->Link[k].Type != EN_PIPE) continue;
|
||||
rsum = 0.0;
|
||||
vsum = 0.0;
|
||||
|
||||
// Examine each segment of the pipe
|
||||
seg = qual->FirstSeg[k];
|
||||
while (seg != NULL)
|
||||
{
|
||||
// React segment over time dt
|
||||
cseg = seg->c;
|
||||
seg->c = pipereact(pr, k, seg->c, seg->v, dt);
|
||||
|
||||
// Update reaction component of mass balance
|
||||
qual->massbalance.reacted += (cseg - seg->c) * seg->v;
|
||||
|
||||
// Accumulate volume-weighted reaction rate
|
||||
if (qual->Qualflag == CHEM)
|
||||
{
|
||||
rsum += fabs(seg->c - cseg) * seg->v;
|
||||
vsum += seg->v;
|
||||
}
|
||||
seg = seg->prev;
|
||||
}
|
||||
|
||||
// Normalize volume-weighted reaction rate
|
||||
if (vsum > 0.0) qual->PipeRateCoeff[k] = rsum / vsum / dt * SECperDAY;
|
||||
else qual->PipeRateCoeff[k] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void reacttanks(EN_Project *pr, long dt)
|
||||
/*
|
||||
**--------------------------------------------------------------
|
||||
** Input: dt = time step
|
||||
** Output: none
|
||||
** Purpose: reacts water within each tank over a time step.
|
||||
**--------------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
int i, k;
|
||||
double c;
|
||||
Pseg seg;
|
||||
|
||||
EN_Network *net = &pr->network;
|
||||
quality_t *qual = &pr->quality;
|
||||
Stank *tank;
|
||||
|
||||
// Examine each tank in network
|
||||
for (i = 1; i <= net->Ntanks; i++)
|
||||
{
|
||||
// Skip reservoirs
|
||||
tank = &net->Tank[i];
|
||||
if (tank->A == 0.0) continue;
|
||||
|
||||
// k is segment chain belonging to tank i
|
||||
k = net->Nlinks + i;
|
||||
|
||||
// React each volume segment in the chain
|
||||
seg = qual->FirstSeg[k];
|
||||
while (seg != NULL)
|
||||
{
|
||||
c = seg->c;
|
||||
seg->c = tankreact(pr, seg->c, seg->v, tank->Kb, dt);
|
||||
qual->massbalance.reacted += (c - seg->c) * seg->v;
|
||||
seg = seg->prev;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
double piperate(EN_Project *pr, int k)
|
||||
/*
|
||||
**--------------------------------------------------------------
|
||||
** Input: k = link index
|
||||
** Output: returns reaction rate coeff. for 1st-order wall
|
||||
** reactions or mass transfer rate coeff. for 0-order
|
||||
** reactions
|
||||
** Purpose: finds wall reaction rate coeffs.
|
||||
**--------------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
double a, d, u, q, kf, kw, y, Re, Sh;
|
||||
|
||||
EN_Network *net = &pr->network;
|
||||
hydraulics_t *hyd = &pr->hydraulics;
|
||||
quality_t *qual = &pr->quality;
|
||||
|
||||
d = net->Link[k].Diam; // Pipe diameter, ft
|
||||
|
||||
// Ignore mass transfer if Schmidt No. is 0
|
||||
if (qual->Sc == 0.0)
|
||||
{
|
||||
if (qual->WallOrder == 0.0) return BIG;
|
||||
else return (net->Link[k].Kw * (4.0 / d) / pr->Ucf[ELEV]);
|
||||
}
|
||||
|
||||
// Compute Reynolds No.
|
||||
// Flow rate made consistent with how its saved to hydraulics file
|
||||
q = (hyd->LinkStatus[k] <= CLOSED) ? 0.0 : hyd->LinkFlows[k];
|
||||
a = PI * d * d / 4.0; // pipe area
|
||||
u = fabs(q) / a; // flow velocity
|
||||
Re = u * d / hyd->Viscos; // Reynolds number
|
||||
|
||||
// Compute Sherwood No. for stagnant flow
|
||||
// (mass transfer coeff. = Diffus./radius)
|
||||
if (Re < 1.0) Sh = 2.0;
|
||||
|
||||
// Compute Sherwood No. for turbulent flow using the Notter-Sleicher formula.
|
||||
else if (Re >= 2300.0) Sh = 0.0149 * pow(Re, 0.88) * pow(qual->Sc, 0.333);
|
||||
|
||||
// Compute Sherwood No. for laminar flow using Graetz solution formula.
|
||||
else
|
||||
{
|
||||
y = d / net->Link[k].Len * Re * qual->Sc;
|
||||
Sh = 3.65 + 0.0668 * y / (1.0 + 0.04 * pow(y, 0.667));
|
||||
}
|
||||
|
||||
// Compute mass transfer coeff. (in ft/sec)
|
||||
kf = Sh * qual->Diffus / d;
|
||||
|
||||
// For zero-order reaction, return mass transfer coeff.
|
||||
if (qual->WallOrder == 0.0) return kf;
|
||||
|
||||
// For first-order reaction, return apparent wall coeff.
|
||||
kw = net->Link[k].Kw / pr->Ucf[ELEV]; // Wall coeff, ft/sec
|
||||
kw = (4.0 / d) * kw * kf / (kf + fabs(kw)); // Wall coeff, 1/sec
|
||||
return kw;
|
||||
}
|
||||
|
||||
|
||||
double pipereact(EN_Project *pr, int k, double c, double v, long dt)
|
||||
/*
|
||||
**------------------------------------------------------------
|
||||
** Input: k = link index
|
||||
** c = current quality in segment
|
||||
** v = segment volume
|
||||
** dt = time step
|
||||
** Output: returns new WQ value
|
||||
** Purpose: computes new quality in a pipe segment after
|
||||
** reaction occurs
|
||||
**------------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
double cnew, dc, dcbulk, dcwall, rbulk, rwall;
|
||||
|
||||
EN_Network *net = &pr->network;
|
||||
quality_t *qual = &pr->quality;
|
||||
|
||||
// For water age (hrs), update concentration by timestep
|
||||
if (qual->Qualflag == AGE)
|
||||
{
|
||||
dc = (double)dt / 3600.0;
|
||||
cnew = c + dc;
|
||||
cnew = MAX(0.0, cnew);
|
||||
return cnew;
|
||||
}
|
||||
|
||||
// Otherwise find bulk & wall reaction rates
|
||||
rbulk = bulkrate(pr, c, net->Link[k].Kb, qual->BulkOrder) * qual->Bucf;
|
||||
rwall = wallrate(pr, c, net->Link[k].Diam, net->Link[k].Kw, net->Link[k].Rc);
|
||||
|
||||
// Find change in concentration over timestep
|
||||
dcbulk = rbulk * (double)dt;
|
||||
dcwall = rwall * (double)dt;
|
||||
|
||||
// Update cumulative mass reacted
|
||||
if (pr->time_options.Htime >= pr->time_options.Rstart)
|
||||
{
|
||||
qual->Wbulk += fabs(dcbulk) * v;
|
||||
qual->Wwall += fabs(dcwall) * v;
|
||||
}
|
||||
|
||||
// Update concentration
|
||||
dc = dcbulk + dcwall;
|
||||
cnew = c + dc;
|
||||
cnew = MAX(0.0, cnew);
|
||||
return cnew;
|
||||
}
|
||||
|
||||
|
||||
double tankreact(EN_Project *pr, double c, double v, double kb, long dt)
|
||||
/*
|
||||
**-------------------------------------------------------
|
||||
** Input: c = current quality in tank
|
||||
** v = tank volume
|
||||
** kb = reaction coeff.
|
||||
** dt = time step
|
||||
** Output: returns new WQ value
|
||||
** Purpose: computes new quality in a tank after
|
||||
** reaction occurs
|
||||
**-------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
double cnew, dc, rbulk;
|
||||
quality_t *qual = &pr->quality;
|
||||
|
||||
// For water age, update concentration by timestep
|
||||
if (qual->Qualflag == AGE)
|
||||
{
|
||||
dc = (double)dt / 3600.0;
|
||||
}
|
||||
|
||||
// For chemical analysis apply bulk reaction rate
|
||||
else
|
||||
{
|
||||
// Find bulk reaction rate
|
||||
rbulk = bulkrate(pr, c, kb, qual->TankOrder) * qual->Tucf;
|
||||
|
||||
// Find concentration change & update quality
|
||||
dc = rbulk * (double)dt;
|
||||
if (pr->time_options.Htime >= pr->time_options.Rstart)
|
||||
{
|
||||
qual->Wtank += fabs(dc) * v;
|
||||
}
|
||||
}
|
||||
cnew = c + dc;
|
||||
cnew = MAX(0.0, cnew);
|
||||
return cnew;
|
||||
}
|
||||
|
||||
|
||||
double bulkrate(EN_Project *pr, double c, double kb, double order)
|
||||
/*
|
||||
**-----------------------------------------------------------
|
||||
** Input: c = current WQ concentration
|
||||
** kb = bulk reaction coeff.
|
||||
** order = bulk reaction order
|
||||
** Output: returns bulk reaction rate
|
||||
** Purpose: computes bulk reaction rate (mass/volume/time)
|
||||
**-----------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
double c1;
|
||||
quality_t *qual = &pr->quality;
|
||||
|
||||
// Find bulk reaction potential taking into account
|
||||
// limiting potential & reaction order.
|
||||
|
||||
// Zero-order kinetics:
|
||||
if (order == 0.0) c = 1.0;
|
||||
|
||||
// Michaelis-Menton kinetics:
|
||||
else if (order < 0.0)
|
||||
{
|
||||
c1 = qual->Climit + SGN(kb) * c;
|
||||
if (fabs(c1) < TINY) c1 = SGN(c1) * TINY;
|
||||
c = c / c1;
|
||||
}
|
||||
|
||||
// N-th order kinetics:
|
||||
else
|
||||
{
|
||||
// Account for limiting potential
|
||||
if (qual->Climit == 0.0) c1 = c;
|
||||
else c1 = MAX(0.0, SGN(kb) * (qual->Climit - c));
|
||||
|
||||
// Compute concentration potential
|
||||
if (order == 1.0) c = c1;
|
||||
else if (order == 2.0) c = c1 * c;
|
||||
else c = c1 * pow(MAX(0.0, c), order - 1.0);
|
||||
}
|
||||
|
||||
// Reaction rate = bulk coeff. * potential
|
||||
if (c < 0) c = 0;
|
||||
return kb * c;
|
||||
}
|
||||
|
||||
|
||||
double wallrate(EN_Project *pr, double c, double d, double kw, double kf)
|
||||
/*
|
||||
**------------------------------------------------------------
|
||||
** Input: c = current WQ concentration
|
||||
** d = pipe diameter
|
||||
** kw = intrinsic wall reaction coeff.
|
||||
** kf = mass transfer coeff. for 0-order reaction
|
||||
** (ft/sec) or apparent wall reaction coeff.
|
||||
** for 1-st order reaction (1/sec)
|
||||
** Output: returns wall reaction rate in mass/ft3/sec
|
||||
** Purpose: computes wall reaction rate
|
||||
**------------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
quality_t *qual = &pr->quality;
|
||||
if (kw == 0.0 || d == 0.0) return (0.0);
|
||||
|
||||
if (qual->WallOrder == 0.0) // 0-order reaction */
|
||||
{
|
||||
kf = SGN(kw) * c * kf; //* Mass transfer rate (mass/ft2/sec)
|
||||
kw = kw * SQR(pr->Ucf[ELEV]); // Reaction rate (mass/ft2/sec)
|
||||
if (fabs(kf) < fabs(kw)) kw = kf; // Reaction mass transfer limited
|
||||
return (kw * 4.0 / d); // Reaction rate (mass/ft3/sec)
|
||||
}
|
||||
else return (c * kf); // 1st-order reaction
|
||||
}
|
||||
|
||||
|
||||
double mixtank(EN_Project *pr, int n, double volin, double massin, double volout)
|
||||
/*
|
||||
**------------------------------------------------------------
|
||||
** Input: n = node index
|
||||
** volin = inflow volume to tank over time step
|
||||
** massin = mass inflow to tank over time step
|
||||
** volout = outflow volume from tank over time step
|
||||
** Output: returns new quality for tank
|
||||
** Purpose: mixes inflow with tank's contents to update its quality.
|
||||
**------------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
int i;
|
||||
double vnet;
|
||||
EN_Network *net = &pr->network;
|
||||
quality_t *qual = &pr->quality;
|
||||
|
||||
i = n - net->Njuncs;
|
||||
vnet = volin - volout;
|
||||
switch (net->Tank[i].MixModel)
|
||||
{
|
||||
case MIX1: tankmix1(pr, i, volin, massin, vnet); break;
|
||||
case MIX2: tankmix2(pr, i, volin, massin, vnet); break;
|
||||
case FIFO: tankmix3(pr, i, volin, massin, vnet); break;
|
||||
case LIFO: tankmix4(pr, i, volin, massin, vnet); break;
|
||||
}
|
||||
return net->Tank[i].C;
|
||||
}
|
||||
|
||||
|
||||
void tankmix1(EN_Project *pr, int i, double vin, double win, double vnet)
|
||||
/*
|
||||
**---------------------------------------------
|
||||
** Input: i = tank index
|
||||
** vin = inflow volume
|
||||
** win = mass inflow
|
||||
** vnet = inflow - outflow
|
||||
** Output: none
|
||||
** Purpose: updates quality in a complete mix tank model
|
||||
**---------------------------------------------
|
||||
*/
|
||||
{
|
||||
int k;
|
||||
double vnew;
|
||||
Pseg seg;
|
||||
|
||||
EN_Network *net = &pr->network;
|
||||
hydraulics_t *hyd = &pr->hydraulics;
|
||||
quality_t *qual = &pr->quality;
|
||||
Stank *tank = &net->Tank[i];
|
||||
|
||||
k = net->Nlinks + i;
|
||||
seg = qual->FirstSeg[k];
|
||||
if (seg)
|
||||
{
|
||||
vnew = seg->v + vin;
|
||||
if (vnew > 0.0) seg->c = (seg->c * seg->v + win) / vnew;
|
||||
seg->v += vnet;
|
||||
seg->v = MAX(0.0, seg->v);
|
||||
tank->C = seg->c;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void tankmix2(EN_Project *pr, int i, double vin, double win, double vnet)
|
||||
/*
|
||||
**------------------------------------------------
|
||||
** Input: i = tank index
|
||||
** vin = inflow volume
|
||||
** win = mass inflow
|
||||
** vnet = inflow - outflow
|
||||
** Output: none
|
||||
** Purpose: updates quality in a 2-compartment tank model
|
||||
**------------------------------------------------
|
||||
*/
|
||||
{
|
||||
int k;
|
||||
double vt, // Transferred volume
|
||||
vmz; // Full mixing zone volume
|
||||
Pseg mixzone, // Mixing zone segment
|
||||
stagzone; // Stagnant zone segment
|
||||
|
||||
EN_Network *net = &pr->network;
|
||||
quality_t *qual = &pr->quality;
|
||||
Stank *tank = &pr->network.Tank[i];
|
||||
|
||||
// Identify segments for each compartment
|
||||
k = net->Nlinks + i;
|
||||
mixzone = qual->LastSeg[k];
|
||||
stagzone = qual->FirstSeg[k];
|
||||
if (mixzone == NULL || stagzone == NULL) return;
|
||||
|
||||
// Full mixing zone volume
|
||||
vmz = tank->V1max;
|
||||
|
||||
// Tank is filling
|
||||
vt = 0.0;
|
||||
if (vnet > 0.0)
|
||||
{
|
||||
vt = MAX(0.0, (mixzone->v + vnet - vmz));
|
||||
if (vin > 0.0)
|
||||
{
|
||||
mixzone->c = ((stagzone->c) * (stagzone->v) + win) /
|
||||
(mixzone->v + vin);
|
||||
}
|
||||
if (vt > 0.0)
|
||||
{
|
||||
stagzone->c = ((stagzone->c) * (stagzone->v) +
|
||||
(mixzone->c) * vt) / (stagzone->v + vt);
|
||||
}
|
||||
}
|
||||
|
||||
// Tank is emptying
|
||||
else if (vnet < 0.0)
|
||||
{
|
||||
if (stagzone->v > 0.0) vt = MIN(stagzone->v, (-vnet));
|
||||
if (vin + vt > 0.0)
|
||||
{
|
||||
mixzone->c = ((mixzone->c) * (mixzone->v) + win +
|
||||
(stagzone->c) * vt) / (mixzone->v + vin + vt);
|
||||
}
|
||||
}
|
||||
|
||||
// Update segment volumes
|
||||
if (vt > 0.0)
|
||||
{
|
||||
mixzone->v = vmz;
|
||||
if (vnet > 0.0) stagzone->v += vt;
|
||||
else stagzone->v = MAX(0.0, ((stagzone->v) - vt));
|
||||
}
|
||||
else
|
||||
{
|
||||
mixzone->v += vnet;
|
||||
mixzone->v = MIN(mixzone->v, vmz);
|
||||
mixzone->v = MAX(0.0, mixzone->v);
|
||||
stagzone->v = 0.0;
|
||||
}
|
||||
|
||||
// Use quality of mixing zone to represent quality of
|
||||
// tank since this is where outflow begins to flow from
|
||||
tank->C = mixzone->c;
|
||||
}
|
||||
|
||||
|
||||
void tankmix3(EN_Project *pr, int i, double vin, double win, double vnet)
|
||||
/*
|
||||
**----------------------------------------------------------
|
||||
** Input: i = tank index
|
||||
** vin = inflow volume
|
||||
** win = mass inflow
|
||||
** vnet = inflow - outflow
|
||||
** Output: none
|
||||
** Purpose: Updates quality in a First-In-First-Out (FIFO) tank model.
|
||||
**----------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
int k;
|
||||
double vout, vseg;
|
||||
double cin, vsum, wsum;
|
||||
Pseg seg;
|
||||
|
||||
EN_Network *net = &pr->network;
|
||||
hydraulics_t *hyd = &pr->hydraulics;
|
||||
quality_t *qual = &pr->quality;
|
||||
Stank *tank = &pr->network.Tank[i];
|
||||
|
||||
k = net->Nlinks + i;
|
||||
if (qual->LastSeg[k] == NULL || qual->FirstSeg[k] == NULL) return;
|
||||
|
||||
// Add new last segment for flow entering the tank
|
||||
if (vin > 0.0)
|
||||
{
|
||||
// ... increase segment volume if inflow has same quality as segment
|
||||
cin = win / vin;
|
||||
seg = qual->LastSeg[k];
|
||||
if (fabs(seg->c - cin) < qual->Ctol) seg->v += vin;
|
||||
|
||||
// ... otherwise add a new last segment to the tank
|
||||
else addseg(pr, k, vin, cin);
|
||||
}
|
||||
|
||||
// Withdraw flow from first segment
|
||||
vsum = 0.0;
|
||||
wsum = 0.0;
|
||||
vout = vin - vnet;
|
||||
while (vout > 0.0)
|
||||
{
|
||||
seg = qual->FirstSeg[k];
|
||||
if (seg == NULL) break;
|
||||
vseg = seg->v; // Flow volume from leading seg
|
||||
vseg = MIN(vseg, vout);
|
||||
if (seg == qual->LastSeg[k]) vseg = vout;
|
||||
vsum += vseg;
|
||||
wsum += (seg->c) * vseg;
|
||||
vout -= vseg; // Remaining flow volume
|
||||
if (vout >= 0.0 && vseg >= seg->v) // Seg used up
|
||||
{
|
||||
if (seg->prev)
|
||||
{
|
||||
qual->FirstSeg[k] = seg->prev;
|
||||
seg->prev = qual->FreeSeg;
|
||||
qual->FreeSeg = seg;
|
||||
}
|
||||
}
|
||||
else seg->v -= vseg; // Remaining volume in segment
|
||||
}
|
||||
|
||||
// Use quality withdrawn from 1st segment
|
||||
// to represent overall quality of tank
|
||||
if (vsum > 0.0) tank->C = wsum / vsum;
|
||||
else if (qual->FirstSeg[k] == NULL) tank->C = 0.0;
|
||||
else tank->C = qual->FirstSeg[k]->c;
|
||||
}
|
||||
|
||||
|
||||
void tankmix4(EN_Project *pr, int i, double vin, double win, double vnet)
|
||||
/*
|
||||
**----------------------------------------------------------
|
||||
** Input: i = tank index
|
||||
** vin = inflow volume
|
||||
** win = mass inflow
|
||||
** vnet = inflow - outflow
|
||||
** Output: none
|
||||
** Purpose: Updates quality in a Last In-First Out (LIFO) tank model.
|
||||
**----------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
int k, n;
|
||||
double cin, vsum, wsum, vseg;
|
||||
Pseg seg;
|
||||
|
||||
EN_Network *net = &pr->network;
|
||||
quality_t *qual = &pr->quality;
|
||||
Stank *tank = &pr->network.Tank[i];
|
||||
|
||||
k = net->Nlinks + i;
|
||||
if (qual->LastSeg[k] == NULL || qual->FirstSeg[k] == NULL) return;
|
||||
|
||||
// Find inflows & outflows
|
||||
n = tank->Node;
|
||||
if (vin > 0.0) cin = win / vin;
|
||||
else cin = 0.0;
|
||||
|
||||
// If tank filling, then create new last seg
|
||||
tank->C = qual->LastSeg[k]->c;
|
||||
seg = qual->LastSeg[k];
|
||||
if (vnet > 0.0)
|
||||
{
|
||||
// ... quality is the same, so just add flow volume to last seg
|
||||
if (fabs(seg->c - cin) < qual->Ctol) seg->v += vnet;
|
||||
|
||||
// ... otherwise add a new last seg to tank which points to old last seg
|
||||
else
|
||||
{
|
||||
qual->LastSeg[k] = NULL;
|
||||
addseg(pr, k, vnet, cin);
|
||||
qual->LastSeg[k]->prev = seg;
|
||||
}
|
||||
|
||||
// ... update reported tank quality
|
||||
tank->C = qual->LastSeg[k]->c;
|
||||
}
|
||||
|
||||
// If tank emptying then remove last segments until vnet consumed
|
||||
else if (vnet < 0.0)
|
||||
{
|
||||
vsum = 0.0;
|
||||
wsum = 0.0;
|
||||
vnet = -vnet;
|
||||
while (vnet > 0.0)
|
||||
{
|
||||
seg = qual->LastSeg[k];
|
||||
if (seg == NULL) break;
|
||||
vseg = seg->v;
|
||||
vseg = MIN(vseg, vnet);
|
||||
if (seg == qual->FirstSeg[k]) vseg = vnet;
|
||||
vsum += vseg;
|
||||
wsum += (seg->c) * vseg;
|
||||
vnet -= vseg;
|
||||
if (vnet >= 0.0 && vseg >= seg->v) // Seg used up
|
||||
{
|
||||
if (seg->prev)
|
||||
{
|
||||
qual->LastSeg[k] = seg->prev;
|
||||
seg->prev = qual->FreeSeg;
|
||||
qual->FreeSeg = seg;
|
||||
}
|
||||
}
|
||||
else seg->v -= vseg; // Remaining volume in segment
|
||||
}
|
||||
|
||||
// Reported tank quality is mixture of flow released and any inflow
|
||||
tank->C = (wsum + win) / (vsum + vin);
|
||||
}
|
||||
}
|
||||
776
src/qualroute.c
Normal file
776
src/qualroute.c
Normal file
@@ -0,0 +1,776 @@
|
||||
/*
|
||||
*********************************************************************
|
||||
|
||||
QUALROUTE.C -- water quality routing module for the EPANET program
|
||||
|
||||
*********************************************************************
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include "mempool.h"
|
||||
#include "types.h"
|
||||
|
||||
// Macro to compute the volume of a link
|
||||
#define LINKVOL(k) (0.785398 * net->Link[(k)].Len * SQR(net->Link[(k)].Diam))
|
||||
// Macro to get link flow compatible with flow saved to hydraulics file
|
||||
#define LINKFLOW(k) ((hyd->LinkStatus[k] <= CLOSED) ? 0.0 : hyd->LinkFlows[k])
|
||||
|
||||
// Exported Functions
|
||||
int buildilists(EN_Project *pr);
|
||||
int sortnodes(EN_Project *pr);
|
||||
void transport(EN_Project *pr, long);
|
||||
void initsegs(EN_Project *pr);
|
||||
void reversesegs(EN_Project *pr, int);
|
||||
void addseg(EN_Project *pr, int, double, double);
|
||||
|
||||
// Imported Functions
|
||||
extern double findsourcequal(EN_Project *pr, int, double, double, long);
|
||||
extern void reactpipes(EN_Project *pr, long);
|
||||
extern void reacttanks(EN_Project *pr, long);
|
||||
extern double mixtank(EN_Project *pr, int, double, double, double);
|
||||
|
||||
// Local Functions
|
||||
static void evalnodeinflow(EN_Project *pr, int, long, double *, double *);
|
||||
static void evalnodeoutflow(EN_Project *pr, int, double, long);
|
||||
static double findnodequal(EN_Project *pr, int, double, double, double, long);
|
||||
static double noflowqual(EN_Project *pr, int);
|
||||
static void updatemassbalance(EN_Project *pr, int, double, double, long);
|
||||
static int selectnonstacknode(EN_Project *pr, int, int *);
|
||||
|
||||
|
||||
void transport(EN_Project *pr, long tstep)
|
||||
/*
|
||||
**--------------------------------------------------------------
|
||||
** Input: tstep = length of current time step
|
||||
** Output: none
|
||||
** Purpose: transports constituent mass through the pipe network
|
||||
** under a period of constant hydraulic conditions.
|
||||
**--------------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
int i, j, k, m, n;
|
||||
double volin, massin, volout, nodequal;
|
||||
|
||||
hydraulics_t *hyd = &pr->hydraulics;
|
||||
quality_t *qual = &pr->quality;
|
||||
EN_Network *net = &pr->network;
|
||||
|
||||
// React contents of each pipe and tank
|
||||
if (qual->Reactflag)
|
||||
{
|
||||
reactpipes(pr, tstep);
|
||||
reacttanks(pr, tstep);
|
||||
}
|
||||
|
||||
// Analyze each node in topological order
|
||||
for (j = 1; j <= net->Nnodes; j++)
|
||||
{
|
||||
// ... index of node to be processed
|
||||
n = qual->SortedNodes[j];
|
||||
|
||||
// ... zero out mass & flow volumes for this node
|
||||
volin = 0.0;
|
||||
massin = 0.0;
|
||||
volout = 0.0;
|
||||
|
||||
// ... examine each link with flow into the node
|
||||
for (i = qual->IlistPtr[n]; i < qual->IlistPtr[n + 1]; i++)
|
||||
{
|
||||
// ... k is index of next link incident on node n
|
||||
k = qual->Ilist[i];
|
||||
|
||||
// ... link has flow into node - add it to node's inflow
|
||||
// (m is index of link's downstream node)
|
||||
m = net->Link[k].N2;
|
||||
if (qual->FlowDir[k] < 0) m = net->Link[k].N1;
|
||||
if (m == n)
|
||||
{
|
||||
evalnodeinflow(pr, k, tstep, &volin, &massin);
|
||||
}
|
||||
|
||||
// ... link has flow out of node - add it to node's outflow
|
||||
else volout += fabs(LINKFLOW(k));
|
||||
}
|
||||
|
||||
// ... if node is a junction, add on any external outflow (e.g., demands)
|
||||
if (net->Node[n].Type == EN_JUNCTION)
|
||||
{
|
||||
volout += MAX(0.0, hyd->NodeDemand[n]);
|
||||
}
|
||||
|
||||
// ... convert from outflow rate to volume
|
||||
volout *= tstep;
|
||||
|
||||
// ... find the concentration of flow leaving the node
|
||||
nodequal = findnodequal(pr, n, volin, massin, volout, tstep);
|
||||
|
||||
// ... examine each link with flow out of the node
|
||||
for (i = qual->IlistPtr[n]; i < qual->IlistPtr[n + 1]; i++)
|
||||
{
|
||||
// ... link k incident on node n has upstream node m equal to n
|
||||
k = qual->Ilist[i];
|
||||
m = net->Link[k].N1;
|
||||
if (qual->FlowDir[k] < 0) m = net->Link[k].N2;
|
||||
if (m == n)
|
||||
{
|
||||
// ... send flow at new node concen. into link
|
||||
evalnodeoutflow(pr, k, nodequal, tstep);
|
||||
}
|
||||
}
|
||||
updatemassbalance(pr, n, massin, volout, tstep);
|
||||
}
|
||||
}
|
||||
|
||||
void evalnodeinflow(EN_Project *pr, int k, long tstep, double *volin,
|
||||
double *massin)
|
||||
/*
|
||||
**--------------------------------------------------------------
|
||||
** Input: k = link index
|
||||
** tstep = quality routing time step
|
||||
** Output: volin = flow volume entering a node
|
||||
** massin = constituent mass entering a node
|
||||
** Purpose: adds the contribution of a link's outflow volume
|
||||
** and constituent mass to the total inflow into its
|
||||
** downstream node over a time step.
|
||||
**--------------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
double q, v, vseg;
|
||||
Pseg seg;
|
||||
|
||||
EN_Network *net = &pr->network;
|
||||
hydraulics_t *hyd = &pr->hydraulics;
|
||||
quality_t *qual = &pr->quality;
|
||||
|
||||
// Get flow rate (q) and flow volume (v) through link
|
||||
q = LINKFLOW(k);
|
||||
v = fabs(q) * tstep;
|
||||
|
||||
// Transport flow volume v from link's leading segments into downstream
|
||||
// node, removing segments once their full volume is consumed
|
||||
while (v > 0.0)
|
||||
{
|
||||
seg = qual->FirstSeg[k];
|
||||
if (!seg) break;
|
||||
|
||||
// ... volume transported from first segment is smaller of
|
||||
// remaining flow volume & segment volume
|
||||
vseg = seg->v;
|
||||
vseg = MIN(vseg, v);
|
||||
|
||||
// ... update total volume & mass entering downstream node
|
||||
*volin += vseg;
|
||||
*massin += vseg * seg->c;
|
||||
|
||||
// ... reduce remaining flow volume by amount transported
|
||||
v -= vseg;
|
||||
|
||||
// ... if all of segment's volume was transferred
|
||||
if (v >= 0.0 && vseg >= seg->v)
|
||||
{
|
||||
// ... replace this leading segment with the one behind it
|
||||
qual->FirstSeg[k] = seg->prev;
|
||||
if (qual->FirstSeg[k] == NULL) qual->LastSeg[k] = NULL;
|
||||
|
||||
// ... recycle the used up segment
|
||||
seg->prev = qual->FreeSeg;
|
||||
qual->FreeSeg = seg;
|
||||
}
|
||||
|
||||
// ... otherwise just reduce this segment's volume
|
||||
else seg->v -= vseg;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
double findnodequal(EN_Project *pr, int n, double volin,
|
||||
double massin, double volout, long tstep)
|
||||
/*
|
||||
**--------------------------------------------------------------
|
||||
** Input: n = node index
|
||||
** volin = flow volume entering node
|
||||
** massin = mass entering node
|
||||
** volout = flow volume leaving node
|
||||
** tstep = length of current time step
|
||||
** Output: returns water quality in a node's outflow
|
||||
** Purpose: computes a node's new quality from its inflow
|
||||
** volume and mass, including any source contribution.
|
||||
**--------------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
EN_Network *net = &pr->network;
|
||||
hydraulics_t *hyd = &pr->hydraulics;
|
||||
quality_t *qual = &pr->quality;
|
||||
|
||||
// Node is a junction - update its water quality
|
||||
if (net->Node[n].Type == EN_JUNCTION)
|
||||
{
|
||||
// ... dilute inflow with any external negative demand
|
||||
volin -= MIN(0.0, hyd->NodeDemand[n]) * tstep;
|
||||
|
||||
// ... new concen. is mass inflow / volume inflow
|
||||
if (volin > 0.0) qual->NodeQual[n] = massin / volin;
|
||||
|
||||
// ... if no inflow adjust quality for reaction in connecting pipes
|
||||
else if (qual->Reactflag) qual->NodeQual[n] = noflowqual(pr, n);
|
||||
}
|
||||
|
||||
// Node is a tank - use its mixing model to update its quality
|
||||
else if (net->Node[n].Type == EN_TANK)
|
||||
{
|
||||
qual->NodeQual[n] = mixtank(pr, n, volin, massin, volout);
|
||||
}
|
||||
|
||||
// Add any external quality source onto node's concen.
|
||||
qual->SourceQual = 0.0;
|
||||
|
||||
// For source tracing analysis find tracer added at source node
|
||||
if (qual->Qualflag == TRACE)
|
||||
{
|
||||
if (n == qual->TraceNode)
|
||||
{
|
||||
// ... quality added to network is difference between tracer
|
||||
// concentration (100 mg/L) and current node quality
|
||||
if (net->Node[n].Type == EN_RESERVOIR) qual->SourceQual = 100.0;
|
||||
else qual->SourceQual = MAX(100.0 - qual->NodeQual[n], 0.0);
|
||||
qual->NodeQual[n] = 100.0;
|
||||
}
|
||||
return qual->NodeQual[n];
|
||||
}
|
||||
|
||||
// Find quality contribued by any external chemical source
|
||||
else qual->SourceQual = findsourcequal(pr, n, volin, volout, tstep);
|
||||
if (qual->SourceQual == 0.0) return qual->NodeQual[n];
|
||||
|
||||
// Combine source quality with node quality
|
||||
switch (net->Node[n].Type)
|
||||
{
|
||||
case EN_JUNCTION:
|
||||
qual->NodeQual[n] += qual->SourceQual;
|
||||
return qual->NodeQual[n];
|
||||
|
||||
case EN_TANK:
|
||||
return qual->NodeQual[n] + qual->SourceQual;
|
||||
|
||||
case EN_RESERVOIR:
|
||||
qual->NodeQual[n] = qual->SourceQual;
|
||||
return qual->SourceQual;
|
||||
}
|
||||
return qual->NodeQual[n];
|
||||
}
|
||||
|
||||
|
||||
double noflowqual(EN_Project *pr, int n)
|
||||
/*
|
||||
**--------------------------------------------------------------
|
||||
** Input: n = node index
|
||||
** Output: quality for node n
|
||||
** Purpose: sets the quality for a junction node that has no
|
||||
** inflow to the average of the quality in its
|
||||
** adjoining link segments.
|
||||
** Note: this function is only used for reactive substances.
|
||||
**--------------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
int i, k, inflow, kount = 0;
|
||||
double c = 0.0;
|
||||
FlowDirection dir;
|
||||
|
||||
EN_Network *net = &pr->network;
|
||||
hydraulics_t *hyd = &pr->hydraulics;
|
||||
quality_t *qual = &pr->quality;
|
||||
|
||||
// Examine each link incident on the node
|
||||
for (i = qual->IlistPtr[n]; i < qual->IlistPtr[n + 1]; i++)
|
||||
{
|
||||
// ... index of an incident link
|
||||
k = qual->Ilist[i];
|
||||
dir = qual->FlowDir[k];
|
||||
|
||||
// Node n is link's downstream node - add quality
|
||||
// of link's first segment to average
|
||||
if (net->Link[k].N2 == n && dir >= 0) inflow = TRUE;
|
||||
else if (net->Link[k].N1 == n && dir < 0) inflow = TRUE;
|
||||
else inflow = FALSE;
|
||||
if (inflow == TRUE && qual->FirstSeg[k] != NULL)
|
||||
{
|
||||
c += qual->FirstSeg[k]->c;
|
||||
kount++;
|
||||
}
|
||||
|
||||
// Node n is link's upstream node - add quality
|
||||
// of link's last segment to average
|
||||
else if (inflow == FALSE && qual->LastSeg[k] != NULL)
|
||||
{
|
||||
c += qual->LastSeg[k]->c;
|
||||
kount++;
|
||||
}
|
||||
}
|
||||
if (kount > 0) c = c / (double)kount;
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
void evalnodeoutflow(EN_Project *pr, int k, double c, long tstep)
|
||||
/*
|
||||
**--------------------------------------------------------------
|
||||
** Input: k = link index
|
||||
** c = quality from upstream node
|
||||
** tstep = time step
|
||||
** Output: none
|
||||
** Purpose: releases flow volume and mass from the upstream
|
||||
** node of a link over a time step.
|
||||
**--------------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
double v;
|
||||
Pseg seg;
|
||||
|
||||
hydraulics_t *hyd = &pr->hydraulics;
|
||||
quality_t *qual = &pr->quality;
|
||||
|
||||
// Find flow volume (v) released over time step
|
||||
v = fabs(LINKFLOW(k)) * tstep;
|
||||
if (v == 0.0) return;
|
||||
|
||||
// Release flow and mass into upstream end of the link
|
||||
|
||||
// ... case where link has a last (most upstream) segment
|
||||
seg = qual->LastSeg[k];
|
||||
if (seg)
|
||||
{
|
||||
// ... if node quality close to segment quality then mix
|
||||
// the nodal outflow volume with the segment's volume
|
||||
if (fabs(seg->c - c) < qual->Ctol)
|
||||
{
|
||||
seg->c = (seg->c*seg->v + c*v) / (seg->v + v);
|
||||
seg->v += v;
|
||||
}
|
||||
|
||||
// ... otherwise add a new segment at upstream end of link
|
||||
else addseg(pr, k, v, c);
|
||||
}
|
||||
|
||||
// ... link has no segments so add one
|
||||
else addseg(pr, k, v, c);
|
||||
}
|
||||
|
||||
|
||||
void updatemassbalance(EN_Project *pr, int n, double massin,
|
||||
double volout, long tstep)
|
||||
/*
|
||||
**--------------------------------------------------------------
|
||||
** Input: n = node index
|
||||
** massin = mass inflow to node
|
||||
** volout = outflow volume from node
|
||||
** Output: none
|
||||
** Purpose: Adds a node's external mass inflow and outflow
|
||||
** over the current time step to the network's
|
||||
** overall mass balance.
|
||||
**--------------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
double masslost = 0.0,
|
||||
massadded = 0.0;
|
||||
|
||||
EN_Network *net = &pr->network;
|
||||
hydraulics_t *hyd = &pr->hydraulics;
|
||||
quality_t *qual = &pr->quality;
|
||||
|
||||
|
||||
switch (net->Node[n].Type)
|
||||
{
|
||||
// Junctions lose mass from outflow demand & gain it from source inflow
|
||||
case EN_JUNCTION:
|
||||
masslost = MAX(0.0, hyd->NodeDemand[n]) * tstep * qual->NodeQual[n];
|
||||
massadded = qual->SourceQual * volout;
|
||||
break;
|
||||
|
||||
// Reservoirs add mass from quality source if specified or from a fixed
|
||||
// initial quality
|
||||
case EN_RESERVOIR:
|
||||
masslost = massin;
|
||||
if (qual->SourceQual > 0.0) massadded = qual->SourceQual * volout;
|
||||
else massadded = qual->NodeQual[n] * volout;
|
||||
break;
|
||||
|
||||
// Tanks add mass only from external source inflow
|
||||
case EN_TANK:
|
||||
massadded = qual->SourceQual * volout;
|
||||
break;
|
||||
}
|
||||
qual->massbalance.outflow += masslost;
|
||||
qual->massbalance.inflow += massadded;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int buildilists(EN_Project *pr)
|
||||
/*
|
||||
**--------------------------------------------------------------
|
||||
** Input: none
|
||||
** Output: returns an error code
|
||||
** Purpose: Creates link incidence lists for each node stored
|
||||
** in compact form.
|
||||
**--------------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
int i, j, k, n, n1, n2;
|
||||
int *degree = NULL;
|
||||
|
||||
quality_t *qual = &pr->quality;
|
||||
EN_Network *net = &pr->network;
|
||||
|
||||
// Allocate an array to count # links incident on each node
|
||||
n = net->Nnodes + 1;
|
||||
degree = (int *)calloc(n, sizeof(int));
|
||||
if (degree == NULL) return 101;
|
||||
|
||||
// Count # links incident on each node
|
||||
for (k = 1; k <= net->Nlinks; k++)
|
||||
{
|
||||
degree[net->Link[k].N1]++;
|
||||
degree[net->Link[k].N2]++;
|
||||
}
|
||||
|
||||
// Use incidence counts to determine start position of
|
||||
// each node's incidence list in Xilist
|
||||
qual->IlistPtr[1] = 1;
|
||||
for (i = 1; i <= n; i++)
|
||||
{
|
||||
qual->IlistPtr[i + 1] = qual->IlistPtr[i] + degree[i];
|
||||
}
|
||||
|
||||
// Add each link to the incidence lists of its start & end nodes
|
||||
for (i = 1; i <= net->Nnodes; i++) degree[i] = 0;
|
||||
for (k = 1; k <= net->Nlinks; k++)
|
||||
{
|
||||
// j is index of next unused location in link's start node list
|
||||
n1 = net->Link[k].N1;
|
||||
j = qual->IlistPtr[n1] + degree[n1];
|
||||
qual->Ilist[j] = k;
|
||||
degree[n1]++;
|
||||
|
||||
// Repeat same for end node
|
||||
n2 = net->Link[k].N2;
|
||||
j = qual->IlistPtr[n2] + degree[n2];
|
||||
qual->Ilist[j] = k;
|
||||
degree[n2]++;
|
||||
}
|
||||
free(degree);
|
||||
|
||||
/*//////// QA CHECK
|
||||
for (i = 1; i <= net->Nnodes; i++)
|
||||
{
|
||||
printf("\nNode %s: ", net->Node[i].ID);
|
||||
for (j = qual->IlistPtr[i]; j < qual->IlistPtr[i + 1]; j++)
|
||||
{
|
||||
printf(" %s,", net->Link[qual->Ilist[j]].ID);
|
||||
}
|
||||
}
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int sortnodes(EN_Project *pr)
|
||||
/*
|
||||
**--------------------------------------------------------------
|
||||
** Input: none
|
||||
** Output: returns an error code
|
||||
** Purpose: topologically sorts nodes from upstream to downstream.
|
||||
** Note: links with negligible flow are ignored since they can
|
||||
** create spurious cycles that cause the sort to fail.
|
||||
**--------------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
int i, j, k, n;
|
||||
int *indegree = NULL;;
|
||||
int *stack = NULL;
|
||||
int stacksize = 0;
|
||||
int numsorted = 0;
|
||||
int errcode = 0;
|
||||
FlowDirection dir;
|
||||
|
||||
EN_Network *net = &pr->network;
|
||||
hydraulics_t *hyd = &pr->hydraulics;
|
||||
quality_t *qual = &pr->quality;
|
||||
|
||||
// Allocate an array to count # links with inflow to each node
|
||||
// and for a stack to hold nodes waiting to be processed
|
||||
indegree = (int *)calloc(net->Nnodes + 1, sizeof(int));
|
||||
stack = (int *)calloc(net->Nnodes + 1, sizeof(int));
|
||||
if (indegree && stack)
|
||||
{
|
||||
// Count links with "non-negligible" inflow to each node
|
||||
for (k = 1; k <= net->Nlinks; k++)
|
||||
{
|
||||
dir = qual->FlowDir[k];
|
||||
if (dir == POSITIVE) n = net->Link[k].N2;
|
||||
else if (dir == NEGATIVE) n = net->Link[k].N1;
|
||||
else continue;
|
||||
indegree[n]++;
|
||||
}
|
||||
|
||||
// Place nodes with no inflow onto a stack
|
||||
for (i = 1; i <= net->Nnodes; i++)
|
||||
{
|
||||
if (indegree[i] == 0)
|
||||
{
|
||||
stacksize++;
|
||||
stack[stacksize] = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Examine each node on the stack until none are left
|
||||
while (numsorted < net->Nnodes)
|
||||
{
|
||||
// ... if stack is empty then a cycle exists
|
||||
if (stacksize == 0)
|
||||
{
|
||||
// ... add a non-sorted node connected to a sorted one to stack
|
||||
j = selectnonstacknode(pr, numsorted, indegree);
|
||||
if (j == 0) break; // This shouldn't happen.
|
||||
indegree[j] = 0;
|
||||
stacksize++;
|
||||
stack[stacksize] = j;
|
||||
}
|
||||
|
||||
// ... make the last node added to the stack the next
|
||||
// in sorted order & remove it from the stack
|
||||
i = stack[stacksize];
|
||||
stacksize--;
|
||||
numsorted++;
|
||||
qual->SortedNodes[numsorted] = i;
|
||||
|
||||
// ... for each outflow link from this node reduce the in-degree
|
||||
// of its downstream node
|
||||
for (j = qual->IlistPtr[i]; j < qual->IlistPtr[i + 1]; j++)
|
||||
{
|
||||
// ... k is the index of the next link incident on node i
|
||||
k = qual->Ilist[j];
|
||||
|
||||
// ... skip link if flow is negligible
|
||||
if (qual->FlowDir[k] == 0) continue;
|
||||
|
||||
// ... link has flow out of node (downstream node n not equal to i)
|
||||
n = net->Link[k].N2;
|
||||
if (qual->FlowDir[k] < 0) n = net->Link[k].N1;
|
||||
|
||||
// ... reduce degree of node n
|
||||
if (n != i && indegree[n] > 0)
|
||||
{
|
||||
indegree[n]--;
|
||||
|
||||
// ... no more degree left so add node n to stack
|
||||
if (indegree[n] == 0)
|
||||
{
|
||||
stacksize++;
|
||||
stack[stacksize] = n;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else errcode = 101;
|
||||
if (numsorted < net->Nnodes) errcode = 120;
|
||||
FREE(indegree);
|
||||
FREE(stack);
|
||||
/*
|
||||
/////////////////// QA CHECK
|
||||
snprintf(pr->Msg, MAXMSG, "\n\nSorted Nodes:");
|
||||
writeline(pr, pr->Msg);
|
||||
for (i = 1; i <= net->Nnodes; i++)
|
||||
{
|
||||
j = qual->SortedNodes[i];
|
||||
snprintf(pr->Msg, MAXMSG, "%s", net->Node[j].ID);
|
||||
writeline(pr, pr->Msg);
|
||||
}
|
||||
//printf("\n");
|
||||
//system("pause");
|
||||
/////////////////
|
||||
*/
|
||||
return errcode;
|
||||
}
|
||||
|
||||
|
||||
int selectnonstacknode(EN_Project *pr, int numsorted, int *indegree)
|
||||
/*
|
||||
**--------------------------------------------------------------
|
||||
** Input: numsorted = number of nodes that have been sorted
|
||||
** indegree = number of inflow links to each node
|
||||
** Output: returns a node index
|
||||
** Purpose: selects a next node for sorting when a cycle exists.
|
||||
**--------------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
int i, j, k, m, n;
|
||||
|
||||
quality_t *qual = &pr->quality;
|
||||
EN_Network *net = &pr->network;
|
||||
|
||||
// Examine each sorted node in last in - first out order
|
||||
for (i = numsorted; i > 0; i--)
|
||||
{
|
||||
// For each link connected to the sorted node
|
||||
m = qual->SortedNodes[i];
|
||||
for (j = qual->IlistPtr[m]; j < qual->IlistPtr[m + 1]; j++)
|
||||
{
|
||||
// ... k is index of next link incident on node m
|
||||
k = qual->Ilist[j];
|
||||
|
||||
// ... n is the node of link k opposite to node m
|
||||
n = net->Link[k].N2;
|
||||
if (n == m) n = net->Link[k].N1;
|
||||
|
||||
// ... select node n if it still has inflow links
|
||||
if (indegree[n] > 0) return n;
|
||||
}
|
||||
}
|
||||
|
||||
// If no node was selected by the above process then return the
|
||||
// first node that still has inflow links remaining
|
||||
for (i = 1; i <= net->Nnodes; i++)
|
||||
{
|
||||
if (indegree[i] > 0) return i;
|
||||
}
|
||||
|
||||
// If all else fails return 0 indicating that no node was selected
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void initsegs(EN_Project *pr)
|
||||
/*
|
||||
**--------------------------------------------------------------
|
||||
** Input: none
|
||||
** Output: none
|
||||
** Purpose: initializes water quality volume segments in each
|
||||
** pipe and tank.
|
||||
**--------------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
int j, k;
|
||||
double c, v, v1;
|
||||
|
||||
EN_Network *net = &pr->network;
|
||||
hydraulics_t *hyd = &pr->hydraulics;
|
||||
quality_t *qual = &pr->quality;
|
||||
|
||||
// Add one segment with assigned downstream node quality to each pipe
|
||||
for (k = 1; k <= net->Nlinks; k++)
|
||||
{
|
||||
qual->FirstSeg[k] = NULL;
|
||||
qual->LastSeg[k] = NULL;
|
||||
if (net->Link[k].Type == EN_PIPE)
|
||||
{
|
||||
v = LINKVOL(k);
|
||||
j = net->Link[k].N2;
|
||||
c = qual->NodeQual[j];
|
||||
addseg(pr, k, v, c);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize segments in tanks
|
||||
for (j = 1; j <= net->Ntanks; j++)
|
||||
{
|
||||
// Skip reservoirs
|
||||
if (net->Tank[j].A == 0.0) continue;
|
||||
|
||||
// Establish initial tank quality & volume
|
||||
k = net->Tank[j].Node;
|
||||
c = net->Node[k].C0;
|
||||
v = net->Tank[j].V0;
|
||||
|
||||
// Create one volume segment for entire tank
|
||||
k = net->Nlinks + j;
|
||||
qual->FirstSeg[k] = NULL;
|
||||
qual->LastSeg[k] = NULL;
|
||||
addseg(pr, k, v, c);
|
||||
|
||||
// Create a 2nd segment for the 2-compartment tank model
|
||||
if (net->Tank[j].MixModel == MIX2)
|
||||
{
|
||||
// ... mixing zone segment
|
||||
v1 = MAX(0, v - net->Tank[j].V1max);
|
||||
qual->FirstSeg[k]->v = v1;
|
||||
|
||||
// ... stagnant zone segment
|
||||
v = v - v1;
|
||||
addseg(pr, k, v, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void reversesegs(EN_Project *pr, int k)
|
||||
/*
|
||||
**--------------------------------------------------------------
|
||||
** Input: k = link index
|
||||
** Output: none
|
||||
** Purpose: re-orients a link's segments when flow reverses.
|
||||
**--------------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
Pseg seg, nseg, pseg;
|
||||
quality_t *qual = &pr->quality;
|
||||
|
||||
seg = qual->FirstSeg[k];
|
||||
qual->FirstSeg[k] = qual->LastSeg[k];
|
||||
qual->LastSeg[k] = seg;
|
||||
pseg = NULL;
|
||||
while (seg != NULL)
|
||||
{
|
||||
nseg = seg->prev;
|
||||
seg->prev = pseg;
|
||||
pseg = seg;
|
||||
seg = nseg;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void addseg(EN_Project *pr, int k, double v, double c)
|
||||
/*
|
||||
**-------------------------------------------------------------
|
||||
** Input: k = segment chain index
|
||||
** v = segment volume
|
||||
** c = segment quality
|
||||
** Output: none
|
||||
** Purpose: adds a segment to the start of a link
|
||||
** upstream of its current last segment.
|
||||
**-------------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
Pseg seg;
|
||||
quality_t *qual = &pr->quality;
|
||||
|
||||
// Grab the next free segment from the segment pool if available
|
||||
if (qual->FreeSeg != NULL)
|
||||
{
|
||||
seg = qual->FreeSeg;
|
||||
qual->FreeSeg = seg->prev;
|
||||
}
|
||||
|
||||
// Otherwise allocate a new segment
|
||||
else
|
||||
{
|
||||
seg = (struct Sseg *) mempool_alloc(qual->SegPool, sizeof(struct Sseg));
|
||||
if (seg == NULL)
|
||||
{
|
||||
qual->OutOfMemory = TRUE;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Assign volume and quality to the segment
|
||||
seg->v = v;
|
||||
seg->c = c;
|
||||
|
||||
// Add the new segment to the end of the segment chain
|
||||
seg->prev = NULL;
|
||||
if (qual->FirstSeg[k] == NULL) qual->FirstSeg[k] = seg;
|
||||
if (qual->LastSeg[k] != NULL) qual->LastSeg[k]->prev = seg;
|
||||
qual->LastSeg[k] = seg;
|
||||
}
|
||||
46
src/report.c
46
src/report.c
@@ -376,6 +376,52 @@ void writehydstat(EN_Project *pr, int iter, double relerr)
|
||||
writeline(pr, " ");
|
||||
} /* End of writehydstat */
|
||||
|
||||
|
||||
void writemassbalance(EN_Project *pr)
|
||||
/*
|
||||
**-------------------------------------------------------------
|
||||
** Input: none
|
||||
** Output: none
|
||||
** Purpose: writes water quality mass balance ratio
|
||||
** (Outflow + Final Storage) / Inflow + Initial Storage)
|
||||
** to report file.
|
||||
**-------------------------------------------------------------
|
||||
*/
|
||||
{
|
||||
char s1[MAXMSG+1];
|
||||
char *units[] = {"", " (mg)", " (ug)", " (hrs)"};
|
||||
int kunits = 0;
|
||||
quality_t *qual = &pr->quality;
|
||||
|
||||
if (qual->Qualflag == TRACE) kunits = 1;
|
||||
else if (qual->Qualflag == AGE) kunits = 3;
|
||||
else
|
||||
{
|
||||
if (match(qual->ChemUnits, "mg")) kunits = 1;
|
||||
else if (match(qual->ChemUnits, "ug")) kunits = 2;
|
||||
}
|
||||
|
||||
snprintf(s1, MAXMSG, "Water Quality Mass Balance%s", units[kunits]);
|
||||
writeline(pr, s1);
|
||||
snprintf(s1, MAXMSG, "================================");
|
||||
writeline(pr, s1);
|
||||
snprintf(s1, MAXMSG, "Initial Mass: %12.5e", qual->massbalance.initial);
|
||||
writeline(pr, s1);
|
||||
snprintf(s1, MAXMSG, "Mass Inflow: %12.5e", qual->massbalance.inflow);
|
||||
writeline(pr, s1);
|
||||
snprintf(s1, MAXMSG, "Mass Outflow: %12.5e", qual->massbalance.outflow);
|
||||
writeline(pr, s1);
|
||||
snprintf(s1, MAXMSG, "Mass Reacted: %12.5e", qual->massbalance.reacted);
|
||||
writeline(pr, s1);
|
||||
snprintf(s1, MAXMSG, "Final Mass: %12.5e", qual->massbalance.final);
|
||||
writeline(pr, s1);
|
||||
snprintf(s1, MAXMSG, "Mass Ratio: %-0.5f", qual->massbalance.ratio);
|
||||
writeline(pr, s1);
|
||||
snprintf(s1, MAXMSG, "================================\n");
|
||||
writeline(pr, s1);
|
||||
}
|
||||
|
||||
|
||||
void writeenergy(EN_Project *pr)
|
||||
/*
|
||||
**-------------------------------------------------------------
|
||||
|
||||
103
src/types.h
103
src/types.h
@@ -23,6 +23,7 @@ AUTHOR: L. Rossman
|
||||
#include "epanet2.h"
|
||||
#include "hash.h"
|
||||
#include "util/errormanager.h"
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
/*********************************************************/
|
||||
@@ -34,7 +35,7 @@ AUTHOR: L. Rossman
|
||||
-------------------------------------------
|
||||
*/
|
||||
typedef float REAL4;
|
||||
typedef int INT4;
|
||||
typedef int INT4;
|
||||
|
||||
/*
|
||||
-----------------------------
|
||||
@@ -286,8 +287,9 @@ typedef enum {
|
||||
} HdrType;
|
||||
|
||||
typedef enum {
|
||||
POSITIVE,
|
||||
NEGATIVE
|
||||
NEGATIVE = -1, // Flow in reverse of pre-assigned direction
|
||||
ZERO_FLOW = 0, // Zero flow
|
||||
POSITIVE = 1 // Flow in pre-assigned direction
|
||||
} FlowDirection;
|
||||
|
||||
typedef enum {
|
||||
@@ -361,7 +363,7 @@ struct Sdemand /* DEMAND CATEGORY OBJECT */
|
||||
{
|
||||
double Base; /* Baseline demand */
|
||||
int Pat; /* Pattern index */
|
||||
char Name[MAXMSG+1]; /* Demand category name */
|
||||
char Name[MAXMSG+1]; /* Demand category name */
|
||||
struct Sdemand *next; /* Next record */
|
||||
};
|
||||
typedef struct Sdemand *Pdemand; /* Pointer to demand object */
|
||||
@@ -538,61 +540,74 @@ typedef struct s_ActItem /* Action list item */
|
||||
struct s_ActItem *next;
|
||||
} ActItem;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
double initial;
|
||||
double inflow;
|
||||
double outflow;
|
||||
double reacted;
|
||||
double final;
|
||||
double ratio;
|
||||
} MassBalance;
|
||||
|
||||
// Forward declaration of the Mempool structure defined in mempool.h
|
||||
struct Mempool;
|
||||
|
||||
typedef struct {
|
||||
char
|
||||
Qualflag, /// Water quality flag
|
||||
OpenQflag, /// Quality system opened flag
|
||||
Reactflag; /// Reaction indicator
|
||||
Qualflag, // Water quality flag
|
||||
OpenQflag, // Quality system opened flag
|
||||
Reactflag, // Reaction indicator
|
||||
OutOfMemory; // Out of memory indicator
|
||||
|
||||
char
|
||||
ChemName[MAXID+1], /* Name of chemical */
|
||||
ChemUnits[MAXID+1]; /* Units of chemical */
|
||||
ChemName[MAXID+1], // Name of chemical
|
||||
ChemUnits[MAXID+1]; // Units of chemical
|
||||
|
||||
int
|
||||
TraceNode; /// Source node for flow tracing
|
||||
|
||||
TraceNode, // Source node for flow tracing
|
||||
*SortedNodes, // Topologically sorted node indexes
|
||||
*Ilist, // Link incidence lists for all nodes
|
||||
*IlistPtr; // Start index of each node in Ilist
|
||||
|
||||
double
|
||||
Ctol, /// Water quality tolerance
|
||||
Diffus, /// Diffusivity (sq ft/sec)
|
||||
Wbulk, /// Avg. bulk reaction rate
|
||||
Wwall, /// Avg. wall reaction rate
|
||||
Wtank, /// Avg. tank reaction rate
|
||||
Wsource, /// Avg. mass inflow
|
||||
Rfactor, /// Roughness-reaction factor
|
||||
BulkOrder, /// Bulk flow reaction order
|
||||
WallOrder, /// Pipe wall reaction order
|
||||
TankOrder, /// Tank reaction order
|
||||
Kbulk, /// Global bulk reaction coeff.
|
||||
Kwall, /// Global wall reaction coeff.
|
||||
Climit, /// Limiting potential quality
|
||||
*NodeQual, /// Node quality state
|
||||
*TempQual, /// General purpose array for water quality
|
||||
*QTempVolumes,
|
||||
*QTankVolumes,
|
||||
*QLinkFlow,
|
||||
*PipeRateCoeff;
|
||||
|
||||
Ctol, // Water quality tolerance
|
||||
Diffus, // Diffusivity (sq ft/sec)
|
||||
Wbulk, // Avg. bulk reaction rate
|
||||
Wwall, // Avg. wall reaction rate
|
||||
Wtank, // Avg. tank reaction rate
|
||||
Wsource, // Avg. mass inflow
|
||||
Rfactor, // Roughness-reaction factor
|
||||
Sc, // Schmidt Number
|
||||
Bucf, // Bulk reaction units conversion factor
|
||||
Tucf, // Tank reaction units conversion factor
|
||||
BulkOrder, // Bulk flow reaction order
|
||||
WallOrder, // Pipe wall reaction order
|
||||
TankOrder, // Tank reaction order
|
||||
Kbulk, // Global bulk reaction coeff.
|
||||
Kwall, // Global wall reaction coeff.
|
||||
Climit, // Limiting potential quality
|
||||
SourceQual, // External source quality
|
||||
*NodeQual, // Reported node quality state
|
||||
*PipeRateCoeff; // Pipe reaction rate coeffs.
|
||||
|
||||
long
|
||||
Qstep, /// Quality time step (sec)
|
||||
Qtime; /// Current quality time (sec)
|
||||
Qstep, // Quality time step (sec)
|
||||
Qtime; // Current quality time (sec)
|
||||
|
||||
char OutOfMemory; /* Out of memory indicator */
|
||||
struct Mempool *SegPool; // Memory pool for water quality segments
|
||||
struct Mempool
|
||||
*SegPool; // Memory pool for water quality segments
|
||||
|
||||
Pseg FreeSeg; /* Pointer to unused segment */
|
||||
Pseg *FirstSeg, /* First (downstream) segment in each pipe */
|
||||
*LastSeg; /* Last (upstream) segment in each pipe */
|
||||
FlowDirection *FlowDir; /* Flow direction for each pipe */
|
||||
double *VolIn; /* Total volume inflow to node */
|
||||
double *MassIn; /* Total mass inflow to node */
|
||||
double Sc; /* Schmidt Number */
|
||||
double Bucf; /* Bulk reaction units conversion factor */
|
||||
double Tucf; /* Tank reaction units conversion factor */
|
||||
Pseg
|
||||
FreeSeg, // Pointer to unused segment
|
||||
*FirstSeg, // First (downstream) segment in each pipe
|
||||
*LastSeg; // Last (upstream) segment in each pipe
|
||||
|
||||
FlowDirection
|
||||
*FlowDir; // Flow direction for each pipe
|
||||
|
||||
MassBalance
|
||||
massbalance; // Mass balance components
|
||||
} quality_t;
|
||||
|
||||
typedef struct {
|
||||
|
||||
16
src/vars.h
16
src/vars.h
@@ -1,16 +0,0 @@
|
||||
/*
|
||||
************************************************************************
|
||||
Global Variables for EPANET Program
|
||||
************************************************************************
|
||||
*/
|
||||
#ifndef VARS_H
|
||||
#define VARS_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include "hash.h"
|
||||
|
||||
// this single global variable is used only when the library is called in "legacy mode"
|
||||
// with the 2.1-style API.
|
||||
EXTERN void *_defaultModel;
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user