1050 lines
33 KiB
C
1050 lines
33 KiB
C
/*
|
|
*********************************************************************
|
|
|
|
HYDSOLVER.C -- Equilibrium hydraulic solver for the EPANET Program
|
|
|
|
This module contains a pipe network hydraulic solver that computes
|
|
flows and pressures within the network at a specific point in time.
|
|
|
|
The solver implements Todini's Global Gradient Algorithm.
|
|
*******************************************************************
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#ifndef __APPLE__
|
|
#include <malloc.h>
|
|
#else
|
|
#include <stdlib.h>
|
|
#endif
|
|
#include <math.h>
|
|
#include "text.h"
|
|
#include "types.h"
|
|
#include "funcs.h"
|
|
|
|
// Error in network being hydraulically balanced
|
|
typedef struct {
|
|
double maxheaderror;
|
|
double maxflowerror;
|
|
double maxflowchange;
|
|
int maxheadlink;
|
|
int maxflownode;
|
|
int maxflowlink;
|
|
} Hydbalance;
|
|
|
|
// External functions
|
|
//int hydsolve(EN_Project *pr, int *iter, double *relerr);
|
|
|
|
// Local functions
|
|
static int badvalve(EN_Project *pr, int);
|
|
static int valvestatus(EN_Project *pr);
|
|
static int linkstatus(EN_Project *pr);
|
|
static StatType cvstatus(EN_Project *pr, StatType, double, double);
|
|
static StatType pumpstatus(EN_Project *pr, int, double);
|
|
static StatType prvstatus(EN_Project *pr, int, StatType, double, double, double);
|
|
static StatType psvstatus(EN_Project *pr, int, StatType, double, double, double);
|
|
static StatType fcvstatus(EN_Project *pr, int, StatType, double, double);
|
|
static void tankstatus(EN_Project *pr, int, int, int);
|
|
static int pswitch(EN_Project *pr);
|
|
|
|
static double newflows(EN_Project *pr, Hydbalance *hbal);
|
|
static double emitflowchange(EN_Project *pr, int i);
|
|
static void checkhydbalance(EN_Project *pr, Hydbalance *hbal);
|
|
static int hasconverged(EN_Project *pr, double *relerr, Hydbalance *hbal);
|
|
static void reporthydbal(EN_Project *pr, Hydbalance *hbal);
|
|
|
|
|
|
int hydsolve(EN_Project *pr, int *iter, double *relerr)
|
|
/*
|
|
**-------------------------------------------------------------------
|
|
** Input: none
|
|
** Output: *iter = # of iterations to reach solution
|
|
** *relerr = convergence error in solution
|
|
** returns error code
|
|
** Purpose: solves network nodal equations for heads and flows
|
|
** using Todini's Gradient algorithm
|
|
**
|
|
*** Updated 9/7/00 ***
|
|
*** Updated 2.00.11 ***
|
|
*** Updated 2.00.12 ***
|
|
** Notes: Status checks on CVs, pumps and pipes to tanks are made
|
|
** every hyd->CheckFreq iteration, up until MaxCheck iterations
|
|
** are reached. Status checks on control valves are made
|
|
** every iteration if DampLimit = 0 or only when the
|
|
** convergence error is at or below DampLimit. If DampLimit
|
|
** is > 0 then future computed flow changes are only 60% of
|
|
** their full value. A complete status check on all links
|
|
** is made when convergence is achieved. If convergence is
|
|
** not achieved in hyd->MaxIter trials and hyd->ExtraIter > 0 then
|
|
** another hyd->ExtraIter trials are made with no status changes
|
|
** made to any links and a warning message is generated.
|
|
**
|
|
** This procedure calls linsolve() which appears in SMATRIX.C.
|
|
**-------------------------------------------------------------------
|
|
*/
|
|
{
|
|
int i; /* Node index */
|
|
int errcode = 0; /* Node causing solution error */
|
|
int nextcheck; /* Next status check trial */
|
|
int maxtrials; /* Max. trials for convergence */
|
|
double newerr; /* New convergence error */
|
|
int valveChange; /* Valve status change flag */
|
|
int statChange; /* Non-valve status change flag */
|
|
Hydbalance hydbal; /* Hydraulic balance errors */
|
|
|
|
EN_Network *net = &pr->network;
|
|
hydraulics_t *hyd = &pr->hydraulics;
|
|
solver_t *sol = &hyd->solver;
|
|
report_options_t *rep = &pr->report;
|
|
|
|
/* Initialize status checking & relaxation factor */
|
|
nextcheck = hyd->CheckFreq;
|
|
hyd->RelaxFactor = 1.0;
|
|
|
|
/* Repeat iterations until convergence or trial limit is exceeded. */
|
|
/* (hyd->ExtraIter used to increase trials in case of status cycling.) */
|
|
if (pr->report.Statflag == FULL) {
|
|
writerelerr(pr, 0, 0);
|
|
}
|
|
maxtrials = hyd->MaxIter;
|
|
if (hyd->ExtraIter > 0) {
|
|
maxtrials += hyd->ExtraIter;
|
|
}
|
|
*iter = 1;
|
|
while (*iter <= maxtrials) {
|
|
/*
|
|
** Compute coefficient matrices A & F and solve A*H = F
|
|
** where H = heads, A = Jacobian coeffs. derived from
|
|
** head loss gradients, & F = flow correction terms.
|
|
** Solution for H is returned in F from call to linsolve().
|
|
*/
|
|
for (i = 1; i <= net->Nlinks; i++) {
|
|
hlosscoeff(pr, i);
|
|
}
|
|
matrixcoeffs(pr);
|
|
errcode = linsolve(pr, net->Njuncs);
|
|
|
|
/* Take action depending on error code */
|
|
if (errcode < 0) {
|
|
break; /* Memory allocation problem */
|
|
}
|
|
if (errcode > 0) { /* Ill-conditioning problem */
|
|
/* If control valve causing problem, fix its status & continue, */
|
|
/* otherwise end the iterations with no solution. */
|
|
if (badvalve(pr, sol->Order[errcode])) {
|
|
continue;
|
|
}
|
|
else break;
|
|
}
|
|
|
|
/* Update current solution. */
|
|
/* (Row[i] = row of solution matrix corresponding to node i). */
|
|
for (i = 1; i <= net->Njuncs; i++) {
|
|
hyd->NodeHead[i] = sol->F[sol->Row[i]]; /* Update heads */
|
|
}
|
|
newerr = newflows(pr, &hydbal); /* Update flows */
|
|
*relerr = newerr;
|
|
|
|
/* Write convergence error to status report if called for */
|
|
if (rep->Statflag == FULL) {
|
|
writerelerr(pr, *iter, *relerr);
|
|
}
|
|
|
|
/* Apply solution damping & check for change in valve status */
|
|
hyd->RelaxFactor = 1.0;
|
|
valveChange = FALSE;
|
|
if (hyd->DampLimit > 0.0) {
|
|
if (*relerr <= hyd->DampLimit) {
|
|
hyd->RelaxFactor = 0.6;
|
|
valveChange = valvestatus(pr);
|
|
}
|
|
}
|
|
else {
|
|
valveChange = valvestatus(pr);
|
|
}
|
|
|
|
/* Check for convergence */
|
|
if (hasconverged(pr, relerr, &hydbal)) {
|
|
/* We have convergence. Quit if we are into extra iterations. */
|
|
if (*iter > hyd->MaxIter) {
|
|
break;
|
|
}
|
|
|
|
/* Quit if no status changes occur. */
|
|
statChange = FALSE;
|
|
if (valveChange) {
|
|
statChange = TRUE;
|
|
}
|
|
if (linkstatus(pr)) {
|
|
statChange = TRUE;
|
|
}
|
|
if (pswitch(pr)) {
|
|
statChange = TRUE;
|
|
}
|
|
if (!statChange) {
|
|
break;
|
|
}
|
|
|
|
/* We have a status change so continue the iterations */
|
|
nextcheck = *iter + hyd->CheckFreq;
|
|
}
|
|
|
|
/* No convergence yet. See if its time for a periodic status */
|
|
/* check on pumps, CV's, and pipes connected to tanks. */
|
|
else if (*iter <= hyd->MaxCheck && *iter == nextcheck)
|
|
{
|
|
linkstatus(pr);
|
|
nextcheck += hyd->CheckFreq;
|
|
}
|
|
(*iter)++;
|
|
}
|
|
|
|
/* Iterations ended. Report any errors. */
|
|
if (errcode < 0) errcode = 101; /* Memory allocation error */
|
|
else if (errcode > 0)
|
|
{
|
|
writehyderr(pr, sol->Order[errcode]); /* Ill-conditioned eqns. error */
|
|
errcode = 110;
|
|
}
|
|
|
|
/* Add any emitter flows to junction demands */
|
|
for (i = 1; i <= net->Njuncs; i++) {
|
|
hyd->NodeDemand[i] += hyd->EmitterFlows[i];
|
|
}
|
|
return(errcode);
|
|
} /* End of netsolve */
|
|
|
|
|
|
int badvalve(EN_Project *pr, int n)
|
|
/*
|
|
**-----------------------------------------------------------------
|
|
** Input: n = node index
|
|
** Output: returns 1 if node n belongs to an active control valve,
|
|
** 0 otherwise
|
|
** Purpose: determines if a node belongs to an active control valve
|
|
** whose setting causes an inconsistent set of eqns. If so,
|
|
** the valve status is fixed open and a warning condition
|
|
** is generated.
|
|
**-----------------------------------------------------------------
|
|
*/
|
|
{
|
|
int i, k, n1, n2;
|
|
|
|
EN_Network *net = &pr->network;
|
|
hydraulics_t *hyd = &pr->hydraulics;
|
|
report_options_t *rep = &pr->report;
|
|
time_options_t *time = &pr->time_options;
|
|
Slink *link;
|
|
|
|
for (i = 1; i <= net->Nvalves; i++) {
|
|
k = net->Valve[i].Link;
|
|
link = &net->Link[k];
|
|
n1 = link->N1;
|
|
n2 = link->N2;
|
|
if (n == n1 || n == n2) {
|
|
EN_LinkType t = link->Type;
|
|
if (t == EN_PRV || t == EN_PSV || t == EN_FCV) {
|
|
if (hyd->LinkStatus[k] == ACTIVE) {
|
|
if (rep->Statflag == FULL) {
|
|
sprintf(pr->Msg, FMT61, clocktime(rep->Atime, time->Htime), link->ID);
|
|
writeline(pr, pr->Msg);
|
|
}
|
|
if (link->Type == EN_FCV) {
|
|
hyd->LinkStatus[k] = XFCV;
|
|
}
|
|
else {
|
|
hyd->LinkStatus[k] = XPRESSURE;
|
|
}
|
|
return(1);
|
|
}
|
|
}
|
|
return(0);
|
|
}
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
|
|
int valvestatus(EN_Project *pr)
|
|
/*
|
|
**-----------------------------------------------------------------
|
|
** Input: none
|
|
** Output: returns 1 if any pressure or flow control valve
|
|
** changes status, 0 otherwise
|
|
** Purpose: updates status for PRVs & PSVs whose status
|
|
** is not fixed to OPEN/CLOSED
|
|
**-----------------------------------------------------------------
|
|
*/
|
|
{
|
|
int change = FALSE, /* Status change flag */
|
|
i, k, /* Valve & link indexes */
|
|
n1, n2; /* Start & end nodes */
|
|
StatType status; /* Valve status settings */
|
|
double hset; /* Valve head setting */
|
|
Slink *link;
|
|
|
|
EN_Network *net = &pr->network;
|
|
hydraulics_t *hyd = &pr->hydraulics;
|
|
report_options_t *rep = &pr->report;
|
|
|
|
for (i = 1; i <= net->Nvalves; i++) /* Examine each valve */
|
|
{
|
|
k = net->Valve[i].Link; /* Link index of valve */
|
|
link = &net->Link[k];
|
|
if (hyd->LinkSetting[k] == MISSING) {
|
|
continue; /* Valve status fixed */
|
|
}
|
|
n1 = link->N1; /* Start & end nodes */
|
|
n2 = link->N2;
|
|
status = hyd->LinkStatus[k]; /* Save current status */
|
|
|
|
// if (s != CLOSED /* No change if flow is */
|
|
// && ABS(hyd->LinkFlows[k]) < hyd->Qtol) continue; /* negligible. */
|
|
|
|
switch (link->Type) /* Evaluate new status: */
|
|
{
|
|
case EN_PRV:
|
|
hset = net->Node[n2].El + hyd->LinkSetting[k];
|
|
hyd->LinkStatus[k] = prvstatus(pr, k, status, hset, hyd->NodeHead[n1], hyd->NodeHead[n2]);
|
|
break;
|
|
case EN_PSV:
|
|
hset = net->Node[n1].El + hyd->LinkSetting[k];
|
|
hyd->LinkStatus[k] = psvstatus(pr, k, status, hset, hyd->NodeHead[n1], hyd->NodeHead[n2]);
|
|
break;
|
|
|
|
//// FCV status checks moved back into the linkstatus() function ////
|
|
// case FCV: S[k] = fcvstatus(k,s,hyd->NodeHead[n1],hyd->NodeHead[n2]);
|
|
// break;
|
|
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
/*** Updated 9/7/00 ***/
|
|
/* Do not reset flow in valve if its status changes. */
|
|
/* This strategy improves convergence. */
|
|
|
|
/* Check for status change */
|
|
if (status != hyd->LinkStatus[k])
|
|
{
|
|
if (rep->Statflag == FULL) {
|
|
writestatchange(pr, k, status, hyd->LinkStatus[k]);
|
|
}
|
|
change = TRUE;
|
|
}
|
|
}
|
|
return(change);
|
|
} /* End of valvestatus() */
|
|
|
|
|
|
/*** Updated 9/7/00 ***/
|
|
int linkstatus(EN_Project *pr)
|
|
/*
|
|
**--------------------------------------------------------------
|
|
** Input: none
|
|
** Output: returns 1 if any link changes status, 0 otherwise
|
|
** Purpose: determines new status for pumps, CVs, FCVs & pipes
|
|
** to tanks.
|
|
**--------------------------------------------------------------
|
|
*/
|
|
{
|
|
int change = FALSE, /* Status change flag */
|
|
k, /* Link index */
|
|
n1, /* Start node index */
|
|
n2; /* End node index */
|
|
double dh; /* Head difference */
|
|
StatType status; /* Current status */
|
|
|
|
EN_Network *net = &pr->network;
|
|
hydraulics_t *hyd = &pr->hydraulics;
|
|
report_options_t *rep = &pr->report;
|
|
Slink *link;
|
|
|
|
/* Examine each Slink */
|
|
for (k = 1; k <= net->Nlinks; k++)
|
|
{
|
|
link = &net->Link[k];
|
|
n1 = link->N1;
|
|
n2 = link->N2;
|
|
dh = hyd->NodeHead[n1] - hyd->NodeHead[n2];
|
|
|
|
/* Re-open temporarily closed links (status = XHEAD or TEMPCLOSED) */
|
|
status = hyd->LinkStatus[k];
|
|
if (status == XHEAD || status == TEMPCLOSED) {
|
|
hyd->LinkStatus[k] = OPEN;
|
|
}
|
|
|
|
/* Check for status changes in CVs and pumps */
|
|
if (link->Type == EN_CVPIPE) {
|
|
hyd->LinkStatus[k] = cvstatus(pr, hyd->LinkStatus[k], dh, hyd->LinkFlows[k]);
|
|
}
|
|
if (link->Type == EN_PUMP && hyd->LinkStatus[k] >= OPEN && hyd->LinkSetting[k] > 0.0) {
|
|
hyd->LinkStatus[k] = pumpstatus(pr, k, -dh);
|
|
}
|
|
|
|
/* Check for status changes in non-fixed FCVs */
|
|
if (link->Type == EN_FCV && hyd->LinkSetting[k] != MISSING) {
|
|
hyd->LinkStatus[k] = fcvstatus(pr, k, status, hyd->NodeHead[n1], hyd->NodeHead[n2]); //
|
|
}
|
|
|
|
/* Check for flow into (out of) full (empty) tanks */
|
|
if (n1 > net->Njuncs || n2 > net->Njuncs) {
|
|
tankstatus(pr, k, n1, n2);
|
|
}
|
|
|
|
/* Note change in link status; do not revise link flow */
|
|
if (status != hyd->LinkStatus[k]) {
|
|
change = TRUE;
|
|
if (rep->Statflag == FULL) {
|
|
writestatchange(pr, k, status, hyd->LinkStatus[k]);
|
|
}
|
|
|
|
//if (S[k] <= CLOSED) hyd->LinkFlows[k] = QZERO;
|
|
//else setlinkflow(k, dh);
|
|
}
|
|
}
|
|
return(change);
|
|
} /* End of linkstatus */
|
|
|
|
|
|
StatType cvstatus(EN_Project *pr, StatType s, double dh, double q)
|
|
/*
|
|
**--------------------------------------------------
|
|
** Input: s = current status
|
|
** dh = headloss
|
|
** q = flow
|
|
** Output: returns new link status
|
|
** Purpose: updates status of a check valve.
|
|
**--------------------------------------------------
|
|
*/
|
|
{
|
|
hydraulics_t *hyd = &pr->hydraulics;
|
|
|
|
/* Prevent reverse flow through CVs */
|
|
if (ABS(dh) > hyd->Htol)
|
|
{
|
|
if (dh < -hyd->Htol) {
|
|
return(CLOSED);
|
|
}
|
|
else if (q < -hyd->Qtol) {
|
|
return(CLOSED);
|
|
}
|
|
else {
|
|
return(OPEN);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (q < -hyd->Qtol) {
|
|
return(CLOSED);
|
|
}
|
|
else {
|
|
return(s);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
StatType pumpstatus(EN_Project *pr, int k, double dh)
|
|
/*
|
|
**--------------------------------------------------
|
|
** Input: k = link index
|
|
** dh = head gain
|
|
** Output: returns new pump status
|
|
** Purpose: updates status of an open pump.
|
|
**--------------------------------------------------
|
|
*/
|
|
{
|
|
int p;
|
|
double hmax;
|
|
hydraulics_t *hyd = &pr->hydraulics;
|
|
EN_Network *net = &pr->network;
|
|
|
|
/* Prevent reverse flow through pump */
|
|
p = findpump(net, k);
|
|
if (net->Pump[p].Ptype == CONST_HP) {
|
|
hmax = BIG;
|
|
}
|
|
else {
|
|
hmax = SQR(hyd->LinkSetting[k]) * net->Pump[p].Hmax;
|
|
}
|
|
if (dh > hmax + hyd->Htol) {
|
|
return(XHEAD);
|
|
}
|
|
|
|
/*** Flow higher than pump curve no longer results in a status change ***/
|
|
/* Check if pump cannot deliver flow */
|
|
//if (hyd->LinkFlows[k] > K[k]*Pump[p].Qmax + hyd->Qtol) return(XFLOW);
|
|
return(OPEN);
|
|
}
|
|
|
|
|
|
StatType prvstatus(EN_Project *pr, int k, StatType s, double hset, double h1, double h2)
|
|
/*
|
|
**-----------------------------------------------------------
|
|
** Input: k = link index
|
|
** s = current status
|
|
** hset = valve head setting
|
|
** h1 = head at upstream node
|
|
** h2 = head at downstream node
|
|
** Output: returns new valve status
|
|
** Purpose: updates status of a pressure reducing valve.
|
|
**-----------------------------------------------------------
|
|
*/
|
|
{
|
|
StatType status; /* New valve status */
|
|
double hml; /* Minor headloss */
|
|
hydraulics_t *hyd = &pr->hydraulics;
|
|
|
|
double htol = hyd->Htol;
|
|
Slink *link = &pr->network.Link[k];
|
|
|
|
status = s;
|
|
if (hyd->LinkSetting[k] == MISSING)
|
|
return(status); /* Status fixed by user */
|
|
hml = link->Km*SQR(hyd->LinkFlows[k]); /* Head loss when open */
|
|
|
|
/*** Status rules below have changed. ***/
|
|
|
|
switch (s)
|
|
{
|
|
case ACTIVE:
|
|
if (hyd->LinkFlows[k] < -hyd->Qtol) {
|
|
status = CLOSED;
|
|
}
|
|
else if (h1 - hml < hset - htol) {
|
|
status = OPEN;
|
|
}
|
|
else {
|
|
status = ACTIVE;
|
|
}
|
|
break;
|
|
case OPEN:
|
|
if (hyd->LinkFlows[k] < -hyd->Qtol) {
|
|
status = CLOSED;
|
|
}
|
|
else if (h2 >= hset + htol) {
|
|
status = ACTIVE;
|
|
}
|
|
else {
|
|
status = OPEN;
|
|
}
|
|
break;
|
|
case CLOSED:
|
|
if (h1 >= hset + htol && h2 < hset - htol) {
|
|
status = ACTIVE;
|
|
}
|
|
else if (h1 < hset - htol && h1 > h2 + htol) {
|
|
status = OPEN;
|
|
}
|
|
else {
|
|
status = CLOSED;
|
|
}
|
|
break;
|
|
case XPRESSURE:
|
|
if (hyd->LinkFlows[k] < -hyd->Qtol) {
|
|
status = CLOSED;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return(status);
|
|
}
|
|
|
|
|
|
StatType psvstatus(EN_Project *pr, int k, StatType s, double hset, double h1, double h2)
|
|
/*
|
|
**-----------------------------------------------------------
|
|
** Input: k = link index
|
|
** s = current status
|
|
** hset = valve head setting
|
|
** h1 = head at upstream node
|
|
** h2 = head at downstream node
|
|
** Output: returns new valve status
|
|
** Purpose: updates status of a pressure sustaining valve.
|
|
**-----------------------------------------------------------
|
|
*/
|
|
{
|
|
StatType status; /* New valve status */
|
|
double hml; /* Minor headloss */
|
|
hydraulics_t *hyd = &pr->hydraulics;
|
|
|
|
double htol = hyd->Htol;
|
|
|
|
Slink *link = &pr->network.Link[k];
|
|
|
|
status = s;
|
|
if (hyd->LinkSetting[k] == MISSING) {
|
|
return(status); /* Status fixed by user */
|
|
}
|
|
hml = link->Km*SQR(hyd->LinkFlows[k]); /* Head loss when open */
|
|
|
|
/*** Status rules below have changed. ***/
|
|
|
|
switch (s)
|
|
{
|
|
case ACTIVE:
|
|
if (hyd->LinkFlows[k] < -hyd->Qtol) {
|
|
status = CLOSED;
|
|
}
|
|
else if (h2 + hml > hset + htol) {
|
|
status = OPEN;
|
|
}
|
|
else {
|
|
status = ACTIVE;
|
|
}
|
|
break;
|
|
case OPEN:
|
|
if (hyd->LinkFlows[k] < -hyd->Qtol) {
|
|
status = CLOSED;
|
|
}
|
|
else if (h1 < hset - htol) {
|
|
status = ACTIVE;
|
|
}
|
|
else {
|
|
status = OPEN;
|
|
}
|
|
break;
|
|
case CLOSED:
|
|
if (h2 > hset + htol && h1 > h2 + htol) {
|
|
status = OPEN;
|
|
}
|
|
else if (h1 >= hset + htol && h1 > h2 + htol) {
|
|
status = ACTIVE;
|
|
}
|
|
else {
|
|
status = CLOSED;
|
|
}
|
|
break;
|
|
case XPRESSURE:
|
|
if (hyd->LinkFlows[k] < -hyd->Qtol) {
|
|
status = CLOSED;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return(status);
|
|
}
|
|
|
|
|
|
StatType fcvstatus(EN_Project *pr, int k, StatType s, double h1, double h2)
|
|
/*
|
|
**-----------------------------------------------------------
|
|
** Input: k = link index
|
|
** s = current status
|
|
** h1 = head at upstream node
|
|
** h2 = head at downstream node
|
|
** Output: returns new valve status
|
|
** Purpose: updates status of a flow control valve.
|
|
**
|
|
** Valve status changes to XFCV if flow reversal.
|
|
** If current status is XFCV and current flow is
|
|
** above setting, then valve becomes active.
|
|
** If current status is XFCV, and current flow
|
|
** positive but still below valve setting, then
|
|
** status remains same.
|
|
**-----------------------------------------------------------
|
|
*/
|
|
{
|
|
StatType status; /* New valve status */
|
|
hydraulics_t *hyd = &pr->hydraulics;
|
|
|
|
status = s;
|
|
if (h1 - h2 < -hyd->Htol) {
|
|
status = XFCV;
|
|
}
|
|
else if (hyd->LinkFlows[k] < -hyd->Qtol) {
|
|
status = XFCV;
|
|
}
|
|
else if (s == XFCV && hyd->LinkFlows[k] >= hyd->LinkSetting[k]) {
|
|
status = ACTIVE;
|
|
}
|
|
return(status);
|
|
}
|
|
|
|
|
|
/*** Updated 9/7/00 ***/
|
|
/*** Updated 11/19/01 ***/
|
|
void tankstatus(EN_Project *pr, int k, int n1, int n2)
|
|
/*
|
|
**----------------------------------------------------------------
|
|
** Input: k = link index
|
|
** n1 = start node of link
|
|
** n2 = end node of link
|
|
** Output: none
|
|
** Purpose: closes link flowing into full or out of empty tank
|
|
**----------------------------------------------------------------
|
|
*/
|
|
{
|
|
int i, n;
|
|
double h, q;
|
|
Stank *tank;
|
|
|
|
hydraulics_t *hyd = &pr->hydraulics;
|
|
EN_Network *net = &pr->network;
|
|
Slink *link = &net->Link[k];
|
|
|
|
/* Make node n1 be the tank */
|
|
q = hyd->LinkFlows[k];
|
|
i = n1 - net->Njuncs;
|
|
if (i <= 0) {
|
|
i = n2 - net->Njuncs;
|
|
if (i <= 0) {
|
|
return;
|
|
}
|
|
n = n1;
|
|
n1 = n2;
|
|
n2 = n;
|
|
q = -q;
|
|
}
|
|
h = hyd->NodeHead[n1] - hyd->NodeHead[n2];
|
|
tank = &net->Tank[i];
|
|
/* Skip reservoirs & closed links */
|
|
if (tank->A == 0.0 || hyd->LinkStatus[k] <= CLOSED) {
|
|
return;
|
|
}
|
|
|
|
/* If tank full, then prevent flow into it */
|
|
if (hyd->NodeHead[n1] >= tank->Hmax - hyd->Htol)
|
|
{
|
|
|
|
/* Case 1: Link is a pump discharging into tank */
|
|
if (link->Type == EN_PUMP) {
|
|
if (link->N2 == n1)
|
|
hyd->LinkStatus[k] = TEMPCLOSED;
|
|
}
|
|
|
|
/* Case 2: Downstream head > tank head */
|
|
/* (i.e., an open outflow check valve would close) */
|
|
else if (cvstatus(pr, OPEN, h, q) == CLOSED) {
|
|
hyd->LinkStatus[k] = TEMPCLOSED;
|
|
}
|
|
}
|
|
|
|
/* If tank empty, then prevent flow out of it */
|
|
if (hyd->NodeHead[n1] <= tank->Hmin + hyd->Htol) {
|
|
|
|
/* Case 1: Link is a pump discharging from tank */
|
|
if (link->Type == EN_PUMP) {
|
|
if (link->N1 == n1) {
|
|
hyd->LinkStatus[k] = TEMPCLOSED;
|
|
}
|
|
}
|
|
|
|
/* Case 2: Tank head > downstream head */
|
|
/* (i.e., a closed outflow check valve would open) */
|
|
else if (cvstatus(pr, CLOSED, h, q) == OPEN) {
|
|
hyd->LinkStatus[k] = TEMPCLOSED;
|
|
}
|
|
}
|
|
} /* End of tankstatus */
|
|
|
|
|
|
int pswitch(EN_Project *pr)
|
|
/*
|
|
**--------------------------------------------------------------
|
|
** Input: none
|
|
** Output: returns 1 if status of any link changes, 0 if not
|
|
** Purpose: adjusts settings of links controlled by junction
|
|
** pressures after a hydraulic solution is found
|
|
**--------------------------------------------------------------
|
|
*/
|
|
{
|
|
int i, /* Control statement index */
|
|
k, /* Link being controlled */
|
|
n, /* Node controlling Slink */
|
|
reset, /* Flag on control conditions */
|
|
change, /* Flag for status or setting change */
|
|
anychange = 0; /* Flag for 1 or more changes */
|
|
char s; /* Current link status */
|
|
|
|
EN_Network *net = &pr->network;
|
|
hydraulics_t *hyd = &pr->hydraulics;
|
|
report_options_t *rep = &pr->report;
|
|
Slink *link;
|
|
|
|
/* Check each control statement */
|
|
for (i = 1; i <= net->Ncontrols; i++)
|
|
{
|
|
reset = 0;
|
|
if ((k = net->Control[i].Link) <= 0) {
|
|
continue;
|
|
}
|
|
|
|
/* Determine if control based on a junction, not a tank */
|
|
if ((n = net->Control[i].Node) > 0 && n <= net->Njuncs) {
|
|
/* Determine if control conditions are satisfied */
|
|
if (net->Control[i].Type == LOWLEVEL
|
|
&& hyd->NodeHead[n] <= net->Control[i].Grade + hyd->Htol) {
|
|
reset = 1;
|
|
}
|
|
if (net->Control[i].Type == HILEVEL
|
|
&& hyd->NodeHead[n] >= net->Control[i].Grade - hyd->Htol) {
|
|
reset = 1;
|
|
}
|
|
}
|
|
|
|
/* Determine if control forces a status or setting change */
|
|
if (reset == 1)
|
|
{
|
|
link = &net->Link[k];
|
|
change = 0;
|
|
s = hyd->LinkStatus[k];
|
|
if (link->Type == EN_PIPE) {
|
|
if (s != net->Control[i].Status) {
|
|
change = 1;
|
|
}
|
|
}
|
|
if (link->Type == EN_PUMP) {
|
|
if (hyd->LinkSetting[k] != net->Control[i].Setting) {
|
|
change = 1;
|
|
}
|
|
}
|
|
if (link->Type >= EN_PRV) {
|
|
if (hyd->LinkSetting[k] != net->Control[i].Setting) {
|
|
change = 1;
|
|
}
|
|
else if (hyd->LinkSetting[k] == MISSING && s != net->Control[i].Status) {
|
|
change = 1;
|
|
}
|
|
}
|
|
|
|
/* If a change occurs, update status & setting */
|
|
if (change) {
|
|
hyd->LinkStatus[k] = net->Control[i].Status;
|
|
if (link->Type > EN_PIPE) {
|
|
hyd->LinkSetting[k] = net->Control[i].Setting;
|
|
}
|
|
if (rep->Statflag == FULL) {
|
|
writestatchange(pr, k, s, hyd->LinkStatus[k]);
|
|
}
|
|
|
|
/* Re-set flow if status has changed */
|
|
// if (S[k] != s) initlinkflow(k, S[k], K[k]);
|
|
anychange = 1;
|
|
}
|
|
}
|
|
}
|
|
return(anychange);
|
|
} /* End of pswitch */
|
|
|
|
|
|
double newflows(EN_Project *pr, Hydbalance *hbal)
|
|
/*
|
|
**----------------------------------------------------------------
|
|
** Input: none
|
|
** Output: returns solution convergence error
|
|
** Purpose: updates link flows after new nodal heads computed
|
|
**----------------------------------------------------------------
|
|
*/
|
|
{
|
|
double dh, /* Link head loss */
|
|
dq; /* Link flow change */
|
|
double dqsum, /* Network flow change */
|
|
qsum; /* Network total flow */
|
|
int k, n, n1, n2;
|
|
|
|
EN_Network *net = &pr->network;
|
|
hydraulics_t *hyd = &pr->hydraulics;
|
|
solver_t *sol = &hyd->solver;
|
|
Slink *link;
|
|
|
|
/* Initialize net inflows (i.e., demands) at tanks */
|
|
for (n = net->Njuncs + 1; n <= net->Nnodes; n++) {
|
|
hyd->NodeDemand[n] = 0.0;
|
|
}
|
|
|
|
/* Initialize sum of flows & corrections */
|
|
qsum = 0.0;
|
|
dqsum = 0.0;
|
|
|
|
hbal->maxflowchange = 0.0;
|
|
hbal->maxflowlink = 1;
|
|
|
|
/* Update flows in all links */
|
|
for (k = 1; k <= net->Nlinks; k++)
|
|
{
|
|
link = &net->Link[k];
|
|
/*
|
|
** Apply flow update formula:
|
|
** dq = Y - P*(new head loss)
|
|
** P = 1/(dh/dq)
|
|
** Y = P*(head loss based on current flow)
|
|
** where P & Y were computed in newcoeffs().
|
|
*/
|
|
|
|
n1 = link->N1;
|
|
n2 = link->N2;
|
|
dh = hyd->NodeHead[n1] - hyd->NodeHead[n2];
|
|
dq = sol->Y[k] - sol->P[k] * dh;
|
|
|
|
/* Adjust flow change by the relaxation factor */
|
|
dq *= hyd->RelaxFactor;
|
|
|
|
/* Prevent flow in constant HP pumps from going negative */
|
|
if (link->Type == EN_PUMP) {
|
|
n = findpump(net, k);
|
|
if (net->Pump[n].Ptype == CONST_HP && dq > hyd->LinkFlows[k]) {
|
|
dq = hyd->LinkFlows[k] / 2.0;
|
|
}
|
|
}
|
|
hyd->LinkFlows[k] -= dq;
|
|
|
|
/* Update sum of absolute flows & flow corrections */
|
|
qsum += ABS(hyd->LinkFlows[k]);
|
|
dqsum += ABS(dq);
|
|
|
|
/* Update identity of link with max. flow change */
|
|
if (ABS(dq) > hbal->maxflowchange) {
|
|
hbal->maxflowchange = ABS(dq);
|
|
hbal->maxflowlink = k;
|
|
}
|
|
|
|
/* Update net flows to tanks */
|
|
if (hyd->LinkStatus[k] > CLOSED)
|
|
{
|
|
if (n1 > net->Njuncs) {
|
|
hyd->NodeDemand[n1] -= hyd->LinkFlows[k];
|
|
}
|
|
if (n2 > net->Njuncs) {
|
|
hyd->NodeDemand[n2] += hyd->LinkFlows[k];
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Update emitter flows */
|
|
for (k = 1; k <= net->Njuncs; k++)
|
|
{
|
|
if (net->Node[k].Ke == 0.0) {
|
|
continue;
|
|
}
|
|
dq = emitflowchange(pr, k);
|
|
hyd->EmitterFlows[k] -= dq;
|
|
qsum += ABS(hyd->EmitterFlows[k]);
|
|
dqsum += ABS(dq);
|
|
}
|
|
|
|
/* Return ratio of total flow corrections to total flow */
|
|
if (qsum > hyd->Hacc) {
|
|
return(dqsum / qsum);
|
|
}
|
|
else {
|
|
return(dqsum);
|
|
}
|
|
} /* End of newflows */
|
|
|
|
|
|
double emitflowchange(EN_Project *pr, int i)
|
|
/*
|
|
**--------------------------------------------------------------
|
|
** Input: i = node index
|
|
** Output: returns change in flow at an emitter node
|
|
** Purpose: computes flow change at an emitter node
|
|
**--------------------------------------------------------------
|
|
*/
|
|
{
|
|
double ke, p;
|
|
hydraulics_t *hyd = &pr->hydraulics;
|
|
EN_Network *n = &pr->network;
|
|
Snode *node = &n->Node[i];
|
|
|
|
ke = MAX(CSMALL, node->Ke);
|
|
p = hyd->Qexp * ke * pow(ABS(hyd->EmitterFlows[i]), (hyd->Qexp - 1.0));
|
|
if (p < hyd->RQtol) {
|
|
p = 1 / hyd->RQtol;
|
|
}
|
|
else {
|
|
p = 1.0 / p;
|
|
}
|
|
return(hyd->EmitterFlows[i] / hyd->Qexp - p * (hyd->NodeHead[i] - node->El));
|
|
}
|
|
|
|
|
|
void checkhydbalance(EN_Project *pr, Hydbalance *hbal)
|
|
/*
|
|
**--------------------------------------------------------------
|
|
** Input: hbal = hydraulic balance errors
|
|
** Output: none
|
|
** Purpose: finds the link with the largest head imbalance
|
|
**--------------------------------------------------------------
|
|
*/
|
|
{
|
|
int k, n1, n2;
|
|
double dh, headerror, headloss;
|
|
|
|
EN_Network *net = &pr->network;
|
|
hydraulics_t *hyd = &pr->hydraulics;
|
|
solver_t *sol = &hyd->solver;
|
|
Slink *link;
|
|
hbal->maxheaderror = 0.0;
|
|
hbal->maxheadlink = 1;
|
|
for (k = 1; k <= net->Nlinks; k++) {
|
|
if (hyd->LinkStatus[k] <= CLOSED) continue;
|
|
hlosscoeff(pr, k);
|
|
if (sol->P[k] == 0.0) continue;
|
|
link = &net->Link[k];
|
|
n1 = link->N1;
|
|
n2 = link->N2;
|
|
dh = hyd->NodeHead[n1] - hyd->NodeHead[n2];
|
|
headloss = sol->Y[k] / sol->P[k];
|
|
headerror = ABS(dh - headloss);
|
|
if (headerror > hbal->maxheaderror) {
|
|
hbal->maxheaderror = headerror;
|
|
hbal->maxheadlink = k;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int hasconverged(EN_Project *pr, double *relerr, Hydbalance *hbal)
|
|
/*
|
|
**--------------------------------------------------------------
|
|
** Input: relerr = current total relative flow change
|
|
** hbal = current hydraulic balance errors
|
|
** Output: returns 1 if system has converged or 0 if not
|
|
** Purpose: checks various criteria to see if system has
|
|
** become hydraulically balanced
|
|
**--------------------------------------------------------------
|
|
*/
|
|
{
|
|
hydraulics_t *hyd = &pr->hydraulics;
|
|
|
|
if (*relerr > hyd->Hacc) return 0;
|
|
checkhydbalance(pr, hbal);
|
|
if (pr->report.Statflag == FULL) {
|
|
reporthydbal(pr, hbal);
|
|
}
|
|
if (hyd->HeadErrorLimit > 0.0 &&
|
|
hbal->maxheaderror > hyd->HeadErrorLimit) return 0;
|
|
if (hyd->FlowChangeLimit > 0.0 &&
|
|
hbal->maxflowchange > hyd->FlowChangeLimit) return 0;
|
|
return 1;
|
|
}
|
|
|
|
|
|
void reporthydbal(EN_Project *pr, Hydbalance *hbal)
|
|
/*
|
|
**--------------------------------------------------------------
|
|
** Input: hbal = current hydraulic balance errors
|
|
** Output: none
|
|
** Purpose: identifies links with largest flow change and
|
|
** largest head loss error.
|
|
**--------------------------------------------------------------
|
|
*/
|
|
{
|
|
double qchange = hbal->maxflowchange * pr->Ucf[FLOW];
|
|
double herror = hbal->maxheaderror * pr->Ucf[HEAD];
|
|
int qlink = hbal->maxflowlink;
|
|
int hlink = hbal->maxheadlink;
|
|
if (qlink >= 1) {
|
|
sprintf(pr->Msg, FMT66, qchange, pr->network.Link[qlink].ID);
|
|
writeline(pr, pr->Msg);
|
|
}
|
|
if (hlink >= 1) {
|
|
sprintf(pr->Msg, FMT67, herror, pr->network.Link[hlink].ID);
|
|
writeline(pr, pr->Msg);
|
|
}
|
|
}
|