Fixes false negative when checking if a tank's min/max levels fall within its volume curve (see issue #767 ).
409 lines
12 KiB
C
409 lines
12 KiB
C
/*
|
|
******************************************************************************
|
|
Project: OWA EPANET
|
|
Version: 2.3
|
|
Module: validate.c
|
|
Description: validates project data
|
|
Authors: see AUTHORS
|
|
Copyright: see AUTHORS
|
|
License: see LICENSE
|
|
Last Updated: 03/18/2024
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#include "types.h"
|
|
#include "funcs.h"
|
|
#include "text.h"
|
|
|
|
// Exported functions
|
|
int validateproject(Project *);
|
|
void reindextanks(Project *);
|
|
|
|
int validatetanks(Project *pr)
|
|
/*
|
|
**-------------------------------------------------------------------
|
|
** Input: none
|
|
** Output: returns 1 if successful, 0 if not
|
|
** Purpose: checks for valid tank levels.
|
|
**-------------------------------------------------------------------
|
|
*/
|
|
{
|
|
Network *net = &pr->network;
|
|
int i, j, n, result = 1, levelerr;
|
|
char errmsg[MAXMSG+1] = "";
|
|
Stank *tank;
|
|
Scurve *curve;
|
|
double elev;
|
|
|
|
for (j = 1; j <= net->Ntanks; j++)
|
|
{
|
|
tank = &net->Tank[j];
|
|
if (tank->A == 0.0) continue; // Skip reservoirs
|
|
|
|
// Check for valid lower/upper tank levels
|
|
levelerr = 0;
|
|
if (tank->H0 > tank->Hmax ||
|
|
tank->Hmin > tank->Hmax ||
|
|
tank->H0 < tank->Hmin
|
|
) levelerr = 1;
|
|
|
|
// Check that tank heights are within volume curve
|
|
elev = net->Node[tank->Node].El;
|
|
i = tank->Vcurve;
|
|
if (i > 0)
|
|
{
|
|
curve = &net->Curve[i];
|
|
n = curve->Npts - 1;
|
|
if ((tank->Hmin - elev) * pr->Ucf[ELEV] < curve->X[0] - TINY ||
|
|
(tank->Hmax - elev) * pr->Ucf[ELEV] > curve->X[n] + TINY)
|
|
{
|
|
levelerr = 1;
|
|
}
|
|
}
|
|
|
|
// Report error in levels if found
|
|
if (levelerr)
|
|
{
|
|
sprintf(pr->Msg, "Error 225: %s node %s", geterrmsg(225, errmsg),
|
|
net->Node[tank->Node].ID);
|
|
writeline(pr, pr->Msg);
|
|
result = 0;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int validatepatterns(Project *pr)
|
|
/*
|
|
**-------------------------------------------------------------------
|
|
** Input: none
|
|
** Output: returns 1 if successful, 0 if not
|
|
** Purpose: checks if time patterns have data.
|
|
**-------------------------------------------------------------------
|
|
*/
|
|
{
|
|
Network *net = &pr->network;
|
|
int j, result = 1;
|
|
char errmsg[MAXMSG+1] = "";
|
|
|
|
if (pr->network.Pattern != NULL)
|
|
{
|
|
for (j = 0; j <= pr->network.Npats; j++)
|
|
{
|
|
if (pr->network.Pattern[j].Length == 0)
|
|
{
|
|
sprintf(pr->Msg, "Error 232: %s %s", geterrmsg(232, errmsg),
|
|
pr->network.Pattern[j].ID);
|
|
writeline(pr, pr->Msg);
|
|
result = 0;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int validatecurves(Project *pr)
|
|
/*
|
|
**-------------------------------------------------------------------
|
|
** Input: none
|
|
** Output: returns 1 if successful, 0 if not
|
|
** Purpose: checks if data curves have data.
|
|
**-------------------------------------------------------------------
|
|
*/
|
|
{
|
|
int i, j, npts, result = 1;
|
|
char errmsg[MAXMSG+1] = "";
|
|
Scurve *curve;
|
|
|
|
if (pr->network.Curve != NULL)
|
|
{
|
|
for (j = 1; j <= pr->network.Ncurves; j++)
|
|
{
|
|
// Check that curve has data
|
|
curve = &pr->network.Curve[j];
|
|
npts = curve->Npts;
|
|
if (npts == 0)
|
|
{
|
|
sprintf(pr->Msg, "Error 231: %s %s", geterrmsg(231, errmsg),
|
|
curve->ID);
|
|
writeline(pr, pr->Msg);
|
|
result = 0;
|
|
}
|
|
|
|
// Check that x values are increasing
|
|
for (i = 1; i < npts; i++)
|
|
{
|
|
if (curve->X[i-1] >= curve->X[i])
|
|
{
|
|
sprintf(pr->Msg, "Error 230: %s %s", geterrmsg(230, errmsg),
|
|
curve->ID);
|
|
writeline(pr, pr->Msg);
|
|
result = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int powerfuncpump(double h0, double h1, double h2, double q1, double q2,
|
|
double *a, double *b, double *c)
|
|
/*
|
|
**---------------------------------------------------------
|
|
** Input: h0 = shutoff head
|
|
** h1 = design head
|
|
** h2 = head at max. flow
|
|
** q1 = design flow
|
|
** q2 = max. flow
|
|
** Output: *a, *b, *c = pump curve coeffs. (H = a-bQ^c),
|
|
** Returns 1 if sucessful, 0 otherwise.
|
|
** Purpose: computes coeffs. for a power function pump curve
|
|
**----------------------------------------------------------
|
|
*/
|
|
{
|
|
double h4, h5;
|
|
|
|
if (h0 < TINY || h0 - h1 < TINY || h1 - h2 < TINY ||
|
|
q1 < TINY || q2 - q1 < TINY
|
|
) return 0;
|
|
*a = h0;
|
|
h4 = h0 - h1;
|
|
h5 = h0 - h2;
|
|
*c = log(h5 / h4) / log(q2 / q1);
|
|
if (*c <= 0.0 || *c > 20.0) return 0;
|
|
*b = -h4 / pow(q1, *c);
|
|
if (*b >= 0.0) return 0;
|
|
return 1;
|
|
}
|
|
|
|
int customcurvepump(Project *pr, Spump *pump, Scurve *curve)
|
|
/*
|
|
**-------------------------------------------------------------------
|
|
** Input: pump = a pump object
|
|
** curve = a data curve object
|
|
** Output: returns an error code
|
|
** Purpose: computes properties for a pump with a custom pump curve.
|
|
**-------------------------------------------------------------------
|
|
*/
|
|
{
|
|
int m, npts = curve->Npts;
|
|
pump->Ptype = CUSTOM;
|
|
for (m = 1; m < npts; m++)
|
|
{
|
|
// Curve must have continuously decreasing head (the Y value)
|
|
if (curve->Y[m] >= curve->Y[m - 1])
|
|
{
|
|
pump->Ptype = NOCURVE;
|
|
return 227;
|
|
}
|
|
}
|
|
pump->Qmax = curve->X[npts - 1];
|
|
pump->Q0 = (curve->X[0] + pump->Qmax) / 2.0 / pr->Ucf[FLOW];
|
|
pump->Qmax /= pr->Ucf[FLOW];
|
|
pump->Hmax = curve->Y[0] / pr->Ucf[HEAD];
|
|
return 0;
|
|
}
|
|
|
|
int pumpcurvepump(Project *pr, Spump *pump, Scurve *curve)
|
|
/*
|
|
**-------------------------------------------------------------------
|
|
** Input: pump = a pump object
|
|
** curve = a data curve object
|
|
** Output: returns an error code
|
|
** Purpose: computes properties for a pump assigned a pump curve.
|
|
**-------------------------------------------------------------------
|
|
*/
|
|
{
|
|
double a, b, c, h0 = 0.0, h1 = 0.0, h2 = 0.0, q1 = 0.0, q2 = 0.0;
|
|
int npts = curve->Npts;
|
|
|
|
curve->Type = PUMP_CURVE;
|
|
|
|
// Generic power function curve
|
|
if (npts == 1)
|
|
{
|
|
pump->Ptype = POWER_FUNC;
|
|
q1 = curve->X[0];
|
|
h1 = curve->Y[0];
|
|
h0 = 1.33334 * h1;
|
|
q2 = 2.0 * q1;
|
|
h2 = 0.0;
|
|
}
|
|
|
|
// 3 point curve with shutoff head
|
|
else if (npts == 3 && curve->X[0] == 0.0)
|
|
{
|
|
pump->Ptype = POWER_FUNC;
|
|
h0 = curve->Y[0];
|
|
q1 = curve->X[1];
|
|
h1 = curve->Y[1];
|
|
q2 = curve->X[2];
|
|
h2 = curve->Y[2];
|
|
}
|
|
else return customcurvepump(pr, pump, curve);
|
|
|
|
// Compute shape factors & limits of power function curves
|
|
if (!powerfuncpump(h0, h1, h2, q1, q2, &a, &b, &c))
|
|
{
|
|
pump->Ptype = NOCURVE;
|
|
return 227;
|
|
}
|
|
else
|
|
{
|
|
pump->H0 = -a / pr->Ucf[HEAD];
|
|
pump->R = -b * (pow(pr->Ucf[FLOW], c) / pr->Ucf[HEAD]);
|
|
pump->N = c;
|
|
pump->Q0 = q1 / pr->Ucf[FLOW];
|
|
pump->Qmax = pow((-a / b), (1.0 / c)) / pr->Ucf[FLOW];
|
|
pump->Hmax = h0 / pr->Ucf[HEAD];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int constpowerpump(Project *pr, Spump *pump)
|
|
/*
|
|
**-------------------------------------------------------------------
|
|
** Input: pump = a pump object
|
|
** Output: returns an error code
|
|
** Purpose: computes properties for a constant power pump.
|
|
**-------------------------------------------------------------------
|
|
*/
|
|
{
|
|
pump->Ptype = CONST_HP;
|
|
pump->H0 = 0.0;
|
|
pump->R = -8.814 * pr->network.Link[pump->Link].Km / pr->Ucf[POWER];
|
|
pump->N = -1.0;
|
|
pump->Hmax = BIG; // No head limit
|
|
pump->Qmax = BIG; // No flow limit
|
|
pump->Q0 = 1.0; // Init. flow = 1 cfs
|
|
return 0;
|
|
}
|
|
|
|
int validatepumps(Project *pr)
|
|
/*
|
|
**-------------------------------------------------------------------
|
|
** Input: none
|
|
** Output: returns 1 if successful, 0 if not
|
|
** Purpose: checks if pumps are assigned valid pump curve data.
|
|
**-------------------------------------------------------------------
|
|
*/
|
|
{
|
|
Network *net = &pr->network;
|
|
int i, errcode, result = 1;
|
|
char errmsg[MAXMSG+1] = "";
|
|
Spump *pump;
|
|
|
|
for (i = 1; i <= net->Npumps; i++)
|
|
{
|
|
// Pump has a designated pump curve
|
|
pump = &net->Pump[i];
|
|
if (pump->Hcurve > 0)
|
|
errcode = pumpcurvepump(pr, pump, &net->Curve[pump->Hcurve]);
|
|
|
|
// Pump has a constant power setting
|
|
else if (net->Link[pump->Link].Km > 0.0)
|
|
errcode = constpowerpump(pr, pump);
|
|
|
|
// Pump has no pump curve info assigned
|
|
else
|
|
{
|
|
pump->Ptype = NOCURVE;
|
|
errcode = 226;
|
|
}
|
|
|
|
if (errcode)
|
|
{
|
|
sprintf(pr->Msg, "Error %d: %s %s",
|
|
errcode, geterrmsg(errcode, errmsg), net->Link[pump->Link].ID);
|
|
writeline(pr, pr->Msg);
|
|
result = 0;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int validateproject(Project *pr)
|
|
/*
|
|
*--------------------------------------------------------------
|
|
* Input: none
|
|
* Output: returns error code
|
|
* Purpose: checks for valid network data.
|
|
*--------------------------------------------------------------
|
|
*/
|
|
{
|
|
int errcode = 0;
|
|
if (pr->network.Nnodes < 2) return 223;
|
|
if (pr->network.Ntanks == 0) return 224;
|
|
if (!validatetanks(pr)) errcode = 110;
|
|
if (!validatepumps(pr)) errcode = 110;
|
|
if (!validatepatterns(pr)) errcode = 110;
|
|
if (!validatecurves(pr)) errcode = 110;
|
|
return errcode;
|
|
}
|
|
|
|
void reindextanks(Project *pr)
|
|
/*
|
|
*--------------------------------------------------------------
|
|
* Input: none
|
|
* Output: none
|
|
* Purpose: adjusts tank node indexes when the number of
|
|
* junctions created from an input file is less than
|
|
* the total number of junction lines in the file.
|
|
*--------------------------------------------------------------
|
|
*/
|
|
{
|
|
Network *net = &pr->network;
|
|
Parser *parser = &pr->parser;
|
|
Quality *qual = &pr->quality;
|
|
Scontrol *control;
|
|
int i, j, ndiff, n1, n2, size;
|
|
|
|
// ndiff = # unused entries in Node array before first tank node
|
|
ndiff = parser->MaxJuncs - net->Njuncs;
|
|
if (ndiff > 0)
|
|
{
|
|
for (i = 1; i <= net->Ntanks; ++i)
|
|
{
|
|
// n1 is current tank index in Node array, n2 is adjusted index
|
|
n1 = net->Tank[i].Node;
|
|
n2 = n1 - ndiff;
|
|
|
|
// Update the tank node's hash table entry
|
|
hashtable_update(net->NodeHashTable, net->Node[n1].ID, n2);
|
|
|
|
// Update the tank's node index
|
|
net->Tank[i].Node = n2;
|
|
|
|
// Re-position tank node in Node array
|
|
net->Node[n2] = net->Node[n1];
|
|
|
|
// Replace all references to old tank node index with new one
|
|
for (j = 1; j <= net->Nlinks; ++j)
|
|
{
|
|
if (net->Link[j].N1 == n1) net->Link[j].N1 = n2;
|
|
if (net->Link[j].N2 == n1) net->Link[j].N2 = n2;
|
|
}
|
|
for (j = 1; j <= net->Ncontrols; ++j)
|
|
{
|
|
control = &net->Control[j];
|
|
if (control->Node == n1) control->Node = n2;
|
|
}
|
|
adjusttankrules(pr, -ndiff);
|
|
if (qual->TraceNode == n1) qual->TraceNode = n2;
|
|
}
|
|
|
|
// Reallocate the Node array (shouldn't fail as new size < old size)
|
|
parser->MaxJuncs = net->Njuncs;
|
|
parser->MaxNodes = net->Njuncs + net->Ntanks;
|
|
size = (net->Nnodes + 2) * sizeof(Snode);
|
|
net->Node = (Snode *)realloc(net->Node, size);
|
|
}
|
|
}
|
|
|