Add EN_openX function

EN_openX allows an EPANET input file to be opened even if it has errors. This required re-arranging code, mainly in input3.c, so that default values are assigned to an object before its input line is parsed.
This commit is contained in:
Lew Rossman
2023-10-04 09:53:05 -04:00
parent c84c6baee2
commit 10d5079d75
18 changed files with 994 additions and 724 deletions

View File

@@ -7,7 +7,7 @@
Authors: see AUTHORS
Copyright: see AUTHORS
License: see LICENSE
Last Updated: 07/17/2023
Last Updated: 09/28/2023
******************************************************************************
*/
@@ -179,61 +179,30 @@ int DLLEXPORT EN_open(EN_Project p, const char *inpFile, const char *rptFile,
** outFile = name of binary output file
** Output: none
** Returns: error code
** Purpose: opens an EPANET input file & reads in network data
** Purpose: reads an EPANET input file with no errors allowed.
**----------------------------------------------------------------
*/
{
writewin(p->viewprog, FMT100);
return openproject(p, inpFile, rptFile, outFile, FALSE);
}
int DLLEXPORT EN_openX(EN_Project p, const char *inpFile,
const char *rptFile, const char *outFile)
/*----------------------------------------------------------------
** Input: inpFile = name of input file
** rptFile = name of report file
** outFile = name of binary output file
** Output: none
** Returns: error code
** Purpose: reads an EPANET input file with errors allowed.
**----------------------------------------------------------------
*/
{
int errcode = 0;
writewin(p->viewprog, FMT100);
return openproject(p, inpFile, rptFile, outFile, TRUE);
}
// Set system flags
p->Openflag = FALSE;
p->hydraul.OpenHflag = FALSE;
p->quality.OpenQflag = FALSE;
p->outfile.SaveHflag = FALSE;
p->outfile.SaveQflag = FALSE;
p->Warnflag = FALSE;
p->report.Messageflag = TRUE;
p->report.Rptflag = 1;
// Initialize data arrays to NULL
initpointers(p);
// Open input & report files
ERRCODE(openfiles(p, inpFile, rptFile, outFile));
if (errcode > 0)
{
errmsg(p, errcode);
return errcode;
}
// Allocate memory for project's data arrays
writewin(p->viewprog, FMT100);
ERRCODE(netsize(p));
ERRCODE(allocdata(p));
// Read input data
ERRCODE(getdata(p));
// Close input file
if (p->parser.InFile != NULL)
{
fclose(p->parser.InFile);
p->parser.InFile = NULL;
}
// If using previously saved hydraulics file then open it
if (p->outfile.Hydflag == USE) ERRCODE(openhydfile(p));
// Write input summary to report file
if (!errcode)
{
if (p->report.Summaryflag) writesummary(p);
writetime(p, FMT104);
p->Openflag = TRUE;
}
else errmsg(p, errcode);
return errcode;
}
int DLLEXPORT EN_gettitle(EN_Project p, char *line1, char *line2, char *line3)
/*----------------------------------------------------------------
@@ -359,7 +328,6 @@ int DLLEXPORT EN_close(EN_Project p)
*/
{
// Free all project data
if (p->Openflag) writetime(p, FMT105);
freedata(p);
// Close output file
@@ -495,7 +463,11 @@ int DLLEXPORT EN_openH(EN_Project p)
// Open hydraulics solver
ERRCODE(openhyd(p));
if (!errcode) p->hydraul.OpenHflag = TRUE;
if (!errcode)
{
p->hydraul.OpenHflag = TRUE;
writetime(p, FMT104);
}
else errmsg(p, errcode);
return errcode;
}
@@ -834,6 +806,7 @@ int DLLEXPORT EN_closeQ(EN_Project p)
closequal(p);
p->quality.OpenQflag = FALSE;
closeoutfile(p);
writetime(p, FMT105);
return 0;
}
@@ -1929,7 +1902,7 @@ int DLLEXPORT EN_addnode(EN_Project p, const char *id, int nodeType, int *index)
if (control->Node > net->Njuncs - 1) control->Node += 1;
}
// adjust indices of tanks/reservoirs in Rule premises (see RULES.C)
adjusttankrules(p);
adjusttankrules(p, 1);
}
// Actions taken when a new Tank/Reservoir is added
@@ -4086,12 +4059,7 @@ int DLLEXPORT EN_setlinkvalue(EN_Project p, int index, int property, double valu
pumpIndex = findpump(&p->network, index);
net->Pump[pumpIndex].Ptype = CONST_HP;
net->Pump[pumpIndex].Hcurve = 0;
net->Link[index].Km = value;
updatepumpparams(p, pumpIndex);
net->Pump[pumpIndex].R /= Ucf[POWER];
net->Pump[pumpIndex].Q0 /= Ucf[FLOW];
net->Pump[pumpIndex].Qmax /= Ucf[FLOW];
net->Pump[pumpIndex].Hmax /= Ucf[HEAD];
net->Link[index].Km = value / Ucf[POWER];
}
break;
@@ -4380,10 +4348,7 @@ int DLLEXPORT EN_setheadcurveindex(EN_Project p, int linkIndex, int curveIndex)
{
Network *net = &p->network;
double *Ucf = p->Ucf;
int pumpIndex;
int oldCurveIndex;
int newCurveType;
int err = 0;
Spump *pump;
@@ -4393,43 +4358,12 @@ int DLLEXPORT EN_setheadcurveindex(EN_Project p, int linkIndex, int curveIndex)
if (PUMP != net->Link[linkIndex].Type) return 0;
if (curveIndex < 0 || curveIndex > net->Ncurves) return 206;
// Save values that need to be restored in case new curve is invalid
pumpIndex = findpump(net, linkIndex);
pump = &p->network.Pump[pumpIndex];
oldCurveIndex = pump->Hcurve;
newCurveType = p->network.Curve[curveIndex].Type;
// Assign the new curve to the pump
pump->Ptype = NOCURVE;
pumpIndex = findpump(net, linkIndex);
pump = &net->Pump[pumpIndex];
pump->Hcurve = curveIndex;
if (curveIndex == 0) return 0;
// Update the pump's head curve parameters (which also changes
// the new curve's Type to PUMP_CURVE)
err = updatepumpparams(p, pumpIndex);
// If the parameter updating failed (new curve was not a valid pump curve)
// restore the pump's original curve and its parameters
if (err > 0)
{
p->network.Curve[curveIndex].Type = newCurveType;
pump->Ptype = NOCURVE;
pump->Hcurve = oldCurveIndex;
if (oldCurveIndex == 0) return err;
updatepumpparams(p, pumpIndex);
}
// Convert the units of the updated pump parameters to feet and cfs
if (pump->Ptype == POWER_FUNC)
{
pump->H0 /= Ucf[HEAD];
pump->R *= (pow(Ucf[FLOW], pump->N) / Ucf[HEAD]);
}
pump->Q0 /= Ucf[FLOW];
pump->Qmax /= Ucf[FLOW];
pump->Hmax /= Ucf[HEAD];
return err;
net->Link[linkIndex].Km = 0.0;
return 0;
}
/********************************************************************
@@ -4907,7 +4841,7 @@ int DLLEXPORT EN_setcurvetype(EN_Project p, int index, int type)
Network *net = &p->network;
if (!p->Openflag) return 102;
if (index < 1 || index > net->Ncurves) return 206;
if (type < 0 || type > EN_GENERIC_CURVE) return 251;
if (type < 0 || type > EN_VALVE_CURVE) return 251;
net->Curve[index].Type = type;
return 0;
}
@@ -4981,9 +4915,7 @@ int DLLEXPORT EN_setcurvevalue(EN_Project p, int curveIndex, int pointIndex,
// Insert new point into curve
curve->X[n] = x;
curve->Y[n] = y;
// Adjust parameters for pumps using curve as a head curve
return adjustpumpparams(p, curveIndex);
return 0;
}
int DLLEXPORT EN_getcurve(EN_Project p, int index, char *id, int *nPoints,
@@ -5055,9 +4987,7 @@ int DLLEXPORT EN_setcurve(EN_Project p, int index, double *xValues,
curve->X[j] = xValues[j];
curve->Y[j] = yValues[j];
}
// Adjust parameters for pumps using curve as a head curve
return adjustpumpparams(p, index);
return 0;
}
/********************************************************************

View File

@@ -7,7 +7,7 @@
Authors: see AUTHORS
Copyright: see AUTHORS
License: see LICENSE
Last Updated: 02/01/2020
Last Updated: 09/28/2023
******************************************************************************
*/
@@ -99,6 +99,14 @@ int DLLEXPORT ENopen(const char *inpFile, const char *rptFile, const char *outFi
return errcode;
}
int DLLEXPORT ENopenX(const char *inpFile, const char *rptFile, const char *outFile)
{
int errcode = 0;
createtmpfiles();
errcode = EN_openX(_defaultProject, inpFile, rptFile, outFile);
return errcode;
}
int DLLEXPORT ENgettitle(char *line1, char *line2, char *line3)
{
return EN_gettitle(_defaultProject, line1, line2, line3) ;

View File

@@ -14,6 +14,7 @@ DAT(120,"cannot solve water quality transport equations")
// These errors apply only to an input file
DAT(200,"one or more errors in input file")
DAT(201,"syntax error")
DAT(299,"invalid section keyword")
// These errors apply to both an input file and to API functions
DAT(202,"illegal numeric value")
@@ -43,6 +44,8 @@ DAT(225,"invalid lower/upper levels for tank")
DAT(226,"no head curve or power rating for pump")
DAT(227,"invalid head curve for pump")
DAT(230,"nonincreasing x-values for curve")
DAT(231,"no data provided for curve")
DAT(232,"no data provided for pattern")
DAT(233,"network has unconnected nodes")
DAT(234,"network has an unconnected node with ID: ")

View File

@@ -7,7 +7,7 @@
Authors: see AUTHORS
Copyright: see AUTHORS
License: see LICENSE
Last Updated: 04/29/2023
Last Updated: 09/28/2023
******************************************************************************
*/
#ifndef FUNCS_H
@@ -19,6 +19,7 @@ void initpointers(Project *);
int allocdata(Project *);
void freedata(Project *);
int openproject(Project *, const char *, const char *, const char *, int);
int openfiles(Project *, const char *, const char *,const char *);
int openhydfile(Project *);
int openoutfile(Project *);
@@ -48,7 +49,6 @@ void freelinkvertices(Slink *);
void adjustpatterns(Network *, int);
void adjustcurves(Network *, int);
int adjustpumpparams(Project *, int);
int resizecurve(Scurve *, int);
int setcontrol(Project *, int, int, double, int, double, Scontrol *);
@@ -70,7 +70,7 @@ int getdata(Project *);
void setdefaults(Project *);
void initreport(Report *);
void adjustdata(Project *);
int inittanks(Project *);
void inittanks(Project *);
void initunits(Project *);
void convertunits(Project *);
@@ -78,7 +78,6 @@ void convertunits(Project *);
int netsize(Project *);
int readdata(Project *);
int updatepumpparams(Project *, int);
int findmatch(char *, char *[]);
int match(const char *, const char *);
int gettokens(char *, char **, int, char *);
@@ -120,7 +119,7 @@ void freerules(Project *);
int ruledata(Project *);
void ruleerrmsg(Project *);
void adjustrules(Project *, int, int);
void adjusttankrules(Project *);
void adjusttankrules(Project *, int);
Spremise *getpremise(Spremise *, int);
Saction *getaction(Saction *, int);
int writerule(Project *, FILE *, int);

View File

@@ -7,7 +7,7 @@
Authors: see AUTHORS
Copyright: see AUTHORS
License: see LICENSE
Last Updated: 08/02/2023
Last Updated: 09/28/2023
******************************************************************************
*/
@@ -23,6 +23,7 @@
const double QZERO = 1.e-6; // Equivalent to zero flow in cfs
// Imported functions
extern int validateproject(Project *);
extern int createsparse(Project *);
extern void freesparse(Project *);
extern int hydsolve(Project *, int *, double *);
@@ -52,10 +53,10 @@ int openhyd(Project *pr)
int i;
int errcode = 0;
Slink *link;
// Check for too few nodes & no fixed grade nodes
if (pr->network.Nnodes < 2) errcode = 223;
else if (pr->network.Ntanks == 0) errcode = 224;
// Check for valid project data (see VALIDATE.C)
errcode = validateproject(pr);
if (errcode > 0) return errcode;
// Allocate memory for sparse matrix structures (see SMATRIX.C)
ERRCODE(createsparse(pr));
@@ -72,6 +73,7 @@ int openhyd(Project *pr)
link = &pr->network.Link[i];
initlinkflow(pr, i, link->Status, link->Kc);
}
else closehyd(pr);
return errcode;
}
@@ -1144,3 +1146,4 @@ void resetpumpflow(Project *pr, int i)
if (pump->Ptype == CONST_HP)
pr->hydraul.LinkFlow[i] = pump->Q0;
}

View File

@@ -7,7 +7,7 @@ Description: retrieves network data from an EPANET input file
Authors: see AUTHORS
Copyright: see AUTHORS
License: see LICENSE
Last Updated: 02/05/2023
Last Updated: 09/28/2023
******************************************************************************
*/
@@ -40,6 +40,8 @@ Last Updated: 02/05/2023
// Defined in ENUMSTXT.H
extern char *Fldname[];
extern char *RptFlowUnitsTxt[];
extern void reindextanks(Project *pr);
int getdata(Project *pr)
/*
@@ -58,13 +60,18 @@ int getdata(Project *pr)
// Read in network data
rewind(pr->parser.InFile);
ERRCODE(readdata(pr));
errcode = readdata(pr);
// Adjust data and convert it to internal units
if (!errcode) adjustdata(pr);
if (!errcode) initunits(pr);
ERRCODE(inittanks(pr));
if (!errcode) convertunits(pr);
// (error code 200 means there are non-fatal errors in input file)
if (errcode == 0 || errcode == 200)
{
reindextanks(pr);
adjustdata(pr);
inittanks(pr);
initunits(pr);
convertunits(pr);
}
return errcode;
}
@@ -328,11 +335,11 @@ void adjustdata(Project *pr)
if (qual->Qualflag == NONE) rpt->Field[QUALITY].Enabled = FALSE;
}
int inittanks(Project *pr)
void inittanks(Project *pr)
/*
**---------------------------------------------------------------
** Input: none
** Output: returns error code
** Output: none
** Purpose: initializes volumes in non-cylindrical tanks
**---------------------------------------------------------------
*/
@@ -341,7 +348,7 @@ int inittanks(Project *pr)
int i, j, n = 0;
double a;
int errcode = 0, levelerr;
int errcode = 0;
char errmsg[MAXMSG+1] = "";
Stank *tank;
Scurve *curve;
@@ -351,47 +358,23 @@ int inittanks(Project *pr)
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
// See if tank has a volume curve
i = tank->Vcurve;
if (i > 0)
{
curve = &net->Curve[i];
n = curve->Npts - 1;
if (tank->Hmin < curve->X[0] || tank->Hmax > curve->X[n])
{
levelerr = 1;
}
else
{
// Find min., max., and initial volumes from curve
tank->Vmin = interp(curve->Npts, curve->X, curve->Y, tank->Hmin);
tank->Vmax = interp(curve->Npts, curve->X, curve->Y, tank->Hmax);
tank->V0 = interp(curve->Npts, curve->X, curve->Y, tank->H0);
// Find min., max., and initial volumes from curve
tank->Vmin = interp(curve->Npts, curve->X, curve->Y, tank->Hmin);
tank->Vmax = interp(curve->Npts, curve->X, curve->Y, tank->Hmax);
tank->V0 = interp(curve->Npts, curve->X, curve->Y, tank->H0);
// Find a "nominal" diameter for tank
a = (curve->Y[n] - curve->Y[0]) / (curve->X[n] - curve->X[0]);
tank->A = sqrt(4.0 * a / PI);
}
}
// 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);
errcode = 200;
// Find a "nominal" diameter for tank
a = (curve->Y[n] - curve->Y[0]) / (curve->X[n] - curve->X[0]);
tank->A = sqrt(4.0 * a / PI);
}
}
return errcode;
}
void initunits(Project *pr)
@@ -530,7 +513,6 @@ void convertunits(Project *pr)
Snode *node;
Stank *tank;
Slink *link;
Spump *pump;
Scontrol *control;
// Convert nodal elevations & initial WQ
@@ -615,29 +597,9 @@ void convertunits(Project *pr)
else if (link->Type == PUMP)
{
// Convert units for pump curve parameters
i = findpump(net, k);
pump = &net->Pump[i];
if (pump->Ptype == CONST_HP)
{
// For constant hp pump, convert kw to hp
if (parser->Unitsflag == SI) pump->R /= pr->Ucf[POWER];
}
else
{
// For power curve pumps, convert shutoff head and flow coeff.
if (pump->Ptype == POWER_FUNC)
{
pump->H0 /= pr->Ucf[HEAD];
pump->R *= (pow(pr->Ucf[FLOW], pump->N) / pr->Ucf[HEAD]);
}
// Convert flow range & max. head units
pump->Q0 /= pr->Ucf[FLOW];
pump->Qmax /= pr->Ucf[FLOW];
pump->Hmax /= pr->Ucf[HEAD];
}
link->Km /= pr->Ucf[POWER];
}
else
{
// For flow control valves, convert flow setting

View File

@@ -7,7 +7,7 @@ Description: reads and interprets network data from an EPANET input file
Authors: see AUTHORS
Copyright: see AUTHORS
License: see LICENSE
Last Updated: 02/03/2020
Last Updated: 09/28/2023
******************************************************************************
*/
@@ -21,17 +21,13 @@ Last Updated: 02/03/2020
#include "hash.h"
#include "text.h"
#define MAXERRS 10 // Max. input errors reported
extern char *SectTxt[]; // Input section keywords (see ENUMSTXT.H)
// Exported functions
int addnodeID(Network *n, int, char *);
int addlinkID(Network *n, int, char *);
// Imported functions
extern int powercurve(double, double, double, double, double, double *,
double *, double *);
int addnodeID(Network *, int, char *);
int addlinkID(Network *, int, char *);
int getunitsoption(Project *, char *);
int getheadlossoption(Project *, char *);
// Local functions
static int newline(Project *, int, char *);
@@ -100,7 +96,11 @@ int netsize(Project *pr)
if (sect == _END) break;
continue;
}
else continue;
else
{
sect = -1;
continue;
}
}
// Add to count of current object
@@ -122,6 +122,12 @@ int netsize(Project *pr)
errcode = addcurve(&pr->network, tok);
parser->MaxCurves = pr->network.Ncurves;
break;
case _OPTIONS:
if (match(tok, w_UNITS))
getunitsoption(pr, strtok(line, SEPSTR));
else if (match(tok, w_HEADLOSS))
getheadlossoption(pr, strtok(line, SEPSTR));
break;
}
if (errcode) break;
}
@@ -226,9 +232,11 @@ int readdata(Project *pr)
}
else
{
inperrmsg(pr, 201, sect, line);
sect = -1;
parser->ErrTok = 0;
errsum++;
break;
inperrmsg(pr, 299, sect, line);
continue;
}
}
@@ -244,23 +252,13 @@ int readdata(Project *pr)
errsum++;
}
}
else
{
errcode = 200;
break;
}
else continue;
}
// Stop if reach end of file or max. error count
if (errsum == MAXERRS) break;
}
// Check for errors
if (errsum > 0) errcode = 200;
// Determine pump curve parameters
if (!errcode) errcode = getpumpparams(pr);
// Free input buffer
free(parser->X);
return errcode;
@@ -307,6 +305,7 @@ int newline(Project *pr, int sect, char *line)
if (ruledata(pr) > 0)
{
ruleerrmsg(pr);
deleterule(pr, pr->network.Nrules);
return 200;
}
else return 0;
@@ -333,127 +332,6 @@ int newline(Project *pr, int sect, char *line)
return 201;
}
int getpumpparams(Project *pr)
/*
**-------------------------------------------------------------
** Input: none
** Output: returns error code
** Purpose: computes pump curve coefficients for all pumps
**--------------------------------------------------------------
*/
{
Network *net = &pr->network;
int i, k, errcode = 0;
char errmsg[MAXMSG+1];
for (i = 1; i <= net->Npumps; i++)
{
errcode = updatepumpparams(pr, i);
if (errcode)
{
k = net->Pump[i].Link;
sprintf(pr->Msg, "Error %d: %s %s",
errcode, geterrmsg(errcode, errmsg), net->Link[k].ID);
writeline(pr, pr->Msg);
return 200;
}
}
return 0;
}
int updatepumpparams(Project *pr, int pumpindex)
/*
**-------------------------------------------------------------
** Input: pumpindex = index of a pump
** Output: returns error code
** Purpose: computes & checks a pump's head curve coefficients
**--------------------------------------------------------------
*/
{
Network *net = &pr->network;
Spump *pump;
Scurve *curve;
int m;
int curveindex;
int npts = 0;
int errcode = 0;
double a, b, c, h0 = 0.0, h1 = 0.0, h2 = 0.0, q1 = 0.0, q2 = 0.0;
pump = &net->Pump[pumpindex];
if (pump->Ptype == CONST_HP) // Constant Hp pump
{
pump->H0 = 0.0;
pump->R = -8.814 * net->Link[pump->Link].Km;
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 errcode;
}
else if (pump->Ptype == NOCURVE) // Pump curve specified
{
curveindex = pump->Hcurve;
if (curveindex == 0) return 226;
curve = &net->Curve[curveindex];
curve->Type = PUMP_CURVE;
npts = curve->Npts;
// 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];
}
// Custom pump curve
else
{
pump->Ptype = CUSTOM;
for (m = 1; m < npts; m++)
{
if (curve->Y[m] >= curve->Y[m - 1]) return 227;
}
pump->Qmax = curve->X[npts - 1];
pump->Q0 = (curve->X[0] + pump->Qmax) / 2.0;
pump->Hmax = curve->Y[0];
}
// Compute shape factors & limits of power function curves
if (pump->Ptype == POWER_FUNC)
{
if (!powercurve(h0, h1, h2, q1, q2, &a, &b, &c)) return 227;
else
{
pump->H0 = -a;
pump->R = -b;
pump->N = c;
pump->Q0 = q1;
pump->Qmax = pow((-a / b), (1.0 / c));
pump->Hmax = h0;
}
}
}
return 0;
}
int addnodeID(Network *net, int n, char *id)
/*
**-------------------------------------------------------------
@@ -564,6 +442,51 @@ int addcurve(Network *network, char *id)
return 0;
}
int getunitsoption(Project *pr, char *units)
/*
**-------------------------------------------------------------
** Input: units = name of flow units to be used
** Output: returns 1 if successful, 0 if not
** Purpose: sets the flows units to be used by a project.
**--------------------------------------------------------------
*/
{
Parser *parser = &pr->parser;
if (match(units, w_CFS)) parser->Flowflag = CFS;
else if (match(units, w_GPM)) parser->Flowflag = GPM;
else if (match(units, w_AFD)) parser->Flowflag = AFD;
else if (match(units, w_MGD)) parser->Flowflag = MGD;
else if (match(units, w_IMGD)) parser->Flowflag = IMGD;
else if (match(units, w_LPS)) parser->Flowflag = LPS;
else if (match(units, w_LPM)) parser->Flowflag = LPM;
else if (match(units, w_CMH)) parser->Flowflag = CMH;
else if (match(units, w_CMD)) parser->Flowflag = CMD;
else if (match(units, w_MLD)) parser->Flowflag = MLD;
else if (match(units, w_CMS)) parser->Flowflag = CMS;
else if (match(units, w_SI)) parser->Flowflag = LPS;
else return 0;
if (parser->Flowflag >= LPS) parser->Unitsflag = SI;
else parser->Unitsflag = US;
return 1;
}
int getheadlossoption(Project *pr, char *formula)
/*
**-------------------------------------------------------------
** Input: formula = name of head loss formula to be used
** Output: returns 1 if successful, 0 if not
** Purpose: sets the head loss formula to be used by a project.
**--------------------------------------------------------------
*/
{
Hydraul *hyd = &pr->hydraul;
if (match(formula, w_HW)) hyd->Formflag = HW;
else if (match(formula, w_DW)) hyd->Formflag = DW;
else if (match(formula, w_CM)) hyd->Formflag = CM;
else return 0;
return 1;
}
int findmatch(char *line, char *keyword[])
/*
**--------------------------------------------------------------
@@ -799,7 +722,11 @@ void inperrmsg(Project *pr, int err, int sect, char *line)
else strcpy(tok, "");
// write error message to report file
sprintf(pr->Msg, "Error %d: %s %s in %s section:",
if (err == 299)
sprintf(pr->Msg, "Error %d: %s %s: section contents ignored.",
err, geterrmsg(err, errStr), tok);
else
sprintf(pr->Msg, "Error %d: %s %s in %s section:",
err, geterrmsg(err, errStr), tok, SectTxt[sect]);
writeline(pr, pr->Msg);

View File

@@ -7,7 +7,7 @@ Description: parses network data from a line of an EPANET input file
Authors: see AUTHORS
Copyright: see AUTHORS
License: see LICENSE
Last Updated: 09/11/2023
Last Updated: 09/28/2023
******************************************************************************
*/
@@ -27,15 +27,15 @@ extern char *Fldname[];
extern char *DemandModelTxt[];
extern char *BackflowTxt[];
// Exported functions
int powercurve(double, double, double, double, double, double *, double *,
double *);
// Imported Functions
extern int addnodeID(Network *, int, char *);
extern int addlinkID(Network *, int, char *);
extern int getunitsoption(Project *, char *);
extern int getheadlossoption(Project *, char *);
// Local functions
static double gettokvalue(Project *, double, int, int *, int *);
static int getlinknodes(Project *, int *, int *);
static int optionchoice(Project *, int);
static int optionvalue(Project *, int);
static int getpumpcurve(Project *, int);
@@ -77,31 +77,52 @@ int juncdata(Project *pr)
int p = 0; // time pattern index
int n; // number of tokens
int njuncs; // number of network junction nodes
double el, // elevation
y = 0.0; // base demand
double el = 0.0, // elevation
d = 0.0, // base demand
x;
Snode *node;
int err = 0;
int errcode = 0;
int errtok = -1;
// Add new junction to data base
n = parser->Ntokens;
if (net->Nnodes == parser->MaxNodes) return 200;
errcode = addnodeID(net, net->Njuncs + 1, parser->Tok[0]);
if (errcode > 0) return setError(parser, 0, errcode);
net->Njuncs++;
net->Nnodes++;
njuncs = net->Njuncs;
err = addnodeID(net, net->Njuncs, parser->Tok[0]);
if (err) return setError(parser, 0, err);
// Check for valid data
if (n < 2) return 201;
if (!getfloat(parser->Tok[1], &el)) return setError(parser, 1, 202);
if (n >= 3 && !getfloat(parser->Tok[2], &y)) return setError(parser, 2, 202);
if (n >= 4)
n = parser->Ntokens;
if (n > 1)
{
if (!getfloat(parser->Tok[1], &x))
{
errcode = 202;
errtok = 1;
}
else el = x;
}
if (!errcode && n > 2)
{
if (!getfloat(parser->Tok[2], &x))
{
errcode = 202;
errtok = 2;
}
else d = x;
}
if (!errcode && n > 3)
{
p = findpattern(net, parser->Tok[3]);
if (p < 0) return setError(parser, 3, 205);
if (p < 0)
{
errcode = 205;
errtok = 3;
}
}
// Save junction data
njuncs = net->Njuncs;
node = &net->Node[njuncs];
node->X = MISSING;
node->Y = MISSING;
@@ -116,11 +137,14 @@ int juncdata(Project *pr)
// Create a demand for the junction and use NodeDemand as an indicator
// to be used when processing demands from the [DEMANDS] section
if (!adddemand(node, y, p, NULL)) return 101;
hyd->NodeDemand[njuncs] = y;
if (!adddemand(node, d, p, NULL)) return 101;
hyd->NodeDemand[njuncs] = d;
// Return error code
if (errcode > 0) return setError(parser, errtok, errcode);
return 0;
}
int tankdata(Project *pr)
/*
**--------------------------------------------------------------
@@ -131,7 +155,6 @@ int tankdata(Project *pr)
** [RESERVOIRS]
** id elev (pattern)
** [TANKS]
** id elev (pattern)
** id elev initlevel minlevel maxlevel diam (minvol vcurve)
**--------------------------------------------------------------
*/
@@ -155,68 +178,84 @@ int tankdata(Project *pr)
Snode *node;
Stank *tank;
int err = 0;
int errcode = 0;
int errtok = -1;
double x;
// Add new tank to data base
n = parser->Ntokens;
if (net->Ntanks == parser->MaxTanks ||
net->Nnodes == parser->MaxNodes) return 200;
i = parser->MaxJuncs + net->Ntanks + 1;
errcode = addnodeID(net, i, parser->Tok[0]);
if (errcode) return setError(parser, 0, errcode);
net->Ntanks++;
net->Nnodes++;
i = parser->MaxJuncs + net->Ntanks;
err = addnodeID(net, i, parser->Tok[0]);
if (err) return setError(parser, 0, err);
// Check for valid data
if (n < 2) return 201;
if (!getfloat(parser->Tok[1], &el)) return setError(parser, 1, 202);
n = parser->Ntokens;
if (n < 2) errcode = 201;
if (!errcode && !getfloat(parser->Tok[1], &x))
{
errcode = 202;
errtok = 1;
}
else el = x;
// Tank is reservoir
// Node is a reservoir
if (n <= 3)
{
// Head pattern supplied
if (n == 3)
if (n == 3 && !errcode)
{
pattern = findpattern(net, parser->Tok[2]);
if (pattern < 0) return setError(parser, 2, 205);
}
}
else if (n < 6) return 201;
// Tank is a storage tank
else
{
if (!getfloat(parser->Tok[2], &initlevel)) return setError(parser, 2, 202);
if (!getfloat(parser->Tok[3], &minlevel)) return setError(parser, 3, 202);
if (!getfloat(parser->Tok[4], &maxlevel)) return setError(parser, 4, 202);
if (!getfloat(parser->Tok[5], &diam)) return setError(parser, 5, 202);
if (n >= 7 && !getfloat(parser->Tok[6], &minvol)) return setError(parser, 6, 202);
// If volume curve supplied check it exists
if (n >= 8)
{
if (strlen(parser->Tok[7]) > 0 && *(parser->Tok[7]) != '*')
if (pattern < 0)
{
curve = findcurve(net, parser->Tok[7]);
if (curve == 0) return setError(parser, 7, 206);
net->Curve[curve].Type = VOLUME_CURVE;
errcode = 205;
errtok = 2;
}
}
// Parse overflow indicator if present
if (n >= 9)
}
// Node is a storage tank
else if (!errcode)
{
if (n < 6) errcode = 201;
else
{
if (match(parser->Tok[8], w_YES)) overflow = TRUE;
else if (match(parser->Tok[8], w_NO)) overflow = FALSE;
else return setError(parser, 8, 213);
}
// Read required data
initlevel = gettokvalue(pr, initlevel, 2, &errcode, &errtok);
minlevel = gettokvalue(pr, minlevel, 3, &errcode, &errtok);
maxlevel = gettokvalue(pr, maxlevel, 4, &errcode, &errtok);
diam = gettokvalue(pr, diam, 5, &errcode, &errtok);
if (n >= 7) minvol = gettokvalue(pr, minvol, 6, &errcode, &errtok);
if (initlevel < 0.0) return setError(parser, 2, 209);
if (minlevel < 0.0) return setError(parser, 3, 209);
if (maxlevel < 0.0) return setError(parser, 4, 209);
if (diam < 0.0) return setError(parser, 5, 209);
if (minvol < 0.0) return setError(parser, 6, 209);
// If volume curve supplied check it exists
if (!errcode && n >= 8)
{
if (strlen(parser->Tok[7]) > 0 && *(parser->Tok[7]) != '*')
{
curve = findcurve(net, parser->Tok[7]);
if (curve == 0)
{
errcode = 206;
errtok = 7;
}
else net->Curve[curve].Type = VOLUME_CURVE;
}
}
// Read overflow indicator if present
if (!errcode && n >= 9)
{
if (match(parser->Tok[8], w_YES)) overflow = TRUE;
else if (match(parser->Tok[8], w_NO)) overflow = FALSE;
else
{
errcode = 213;
errtok = 8;
}
}
}
}
node = &net->Node[i];
tank = &net->Tank[net->Ntanks];
@@ -254,9 +293,38 @@ int tankdata(Project *pr)
tank->Vcurve = curve;
tank->MixModel = MIX1; // Completely mixed
tank->V1frac = 1.0; // Mixing compartment size fraction
// Return error code
if (errcode > 0) return setError(parser, errtok, errcode);
return 0;
}
double gettokvalue(Project *pr, double x, int itok, int *errcode, int *errtok)
/*
**--------------------------------------------------------------
** Input: x = default numerical value
** itok = index into an array of string tokens
** Output: errcode = an error code or 0 if successful
** errtok = itok if an error occurs
** returns a numerical data value
** Purpose: converts a string token into a numerical value.
**--------------------------------------------------------------
*/
{
Parser *parser = &pr->parser;
double result;
if (*errcode) return x;
if (!getfloat(parser->Tok[itok], &result)) *errcode = 202;
else if (result < 0.0) *errcode = 209;
if (*errcode > 0)
{
result = x;
*errtok = itok;
}
return result;
}
int pipedata(Project *pr)
/*
**--------------------------------------------------------------
@@ -275,71 +343,101 @@ int pipedata(Project *pr)
int j1, // Start-node index
j2, // End-node index
n; // # data items
double length, // Pipe length
diam, // Pipe diameter
rcoeff, // Roughness coeff.
lcoeff = 0.0; // Minor loss coeff
LinkType type = PIPE; // Link type
StatusType status = OPEN; // Link status
double x;
Slink *link;
int err = 0;
// Add new pipe to data base
n = parser->Ntokens;
int errcode = 0;
// Check that end nodes exist
if (net->Nlinks == parser->MaxLinks) return 200;
net->Npipes++;
net->Nlinks++;
err = addlinkID(net, net->Nlinks, parser->Tok[0]);
if (err) return setError(parser, 0, err);
// Check for valid data
if (n < 6) return 201;
n = parser->Ntokens;
if (n < 3) return setError(parser, -1, errcode);
if ((j1 = findnode(net, parser->Tok[1])) == 0) return setError(parser, 1, 203);
if ((j2 = findnode(net, parser->Tok[2])) == 0) return setError(parser, 2, 203);
if (j1 == j2) return setError(parser, 0, 222);
if (!getfloat(parser->Tok[3], &length)) return setError(parser, 3, 202);
if (length <= 0.0) return setError(parser, 3, 211);
if (!getfloat(parser->Tok[4], &diam)) return setError(parser, 4, 202);
if (diam <= 0.0) return setError(parser, 4, 211);
if (!getfloat(parser->Tok[5], &rcoeff)) return setError(parser, 5, 202);
if (rcoeff <= 0.0) return setError(parser, 5, 211);
// Add new pipe to data base
errcode = addlinkID(net, net->Nlinks+1, parser->Tok[0]);
if (errcode) return setError(parser, 0, errcode);
net->Npipes++;
net->Nlinks++;
// Either a loss coeff. or a status is supplied
if (n == 7)
{
if (match(parser->Tok[6], w_CV)) type = CVPIPE;
else if (match(parser->Tok[6], w_CLOSED)) status = CLOSED;
else if (match(parser->Tok[6], w_OPEN)) status = OPEN;
else if (!getfloat(parser->Tok[6], &lcoeff)) return setError(parser, 6, 202);
}
// Both a loss coeff. and a status is supplied
if (n == 8)
{
if (!getfloat(parser->Tok[6], &lcoeff)) return setError(parser, 6, 202);
if (match(parser->Tok[7], w_CV)) type = CVPIPE;
else if (match(parser->Tok[7], w_CLOSED)) status = CLOSED;
else if (match(parser->Tok[7], w_OPEN)) status = OPEN;
else return setError(parser, 7, 213);
}
if (lcoeff < 0.0) return setError(parser, 6, 211);
// Save pipe data
// Assign default data to pipe
link = &net->Link[net->Nlinks];
link->N1 = j1;
link->N2 = j2;
link->Len = length;
link->Diam = diam;
link->Kc = rcoeff;
link->Km = lcoeff;
if (parser->Unitsflag == SI)
{
link->Len = 100.0;
link->Diam = 254.0;
}
else
{
link->Len = 330.0;
link->Diam = 10.0;
}
switch (pr->hydraul.Formflag)
{
case HW: link->Kc = 130; break;
case DW: link->Kc = 0.0005; break;
case CM: link->Kc = 0.01; break;
default: link->Kc = 1.0;
}
link->Km = 0.0;
link->Kb = MISSING;
link->Kw = MISSING;
link->Type = type;
link->Status = status;
link->Type = PIPE;
link->Status = OPEN;
link->Rpt = 0;
link->ResultIndex = 0;
link->Comment = xstrcpy(&link->Comment, parser->Comment, MAXMSG);
// Parse data values from input tokens
if (n > 3)
{
if (!getfloat(parser->Tok[3], &x) || x <= 0.0)
return setError(parser, 3, 202);
link->Len = x;
}
if (n > 4)
{
if (!getfloat(parser->Tok[4], &x) || x <= 0.0)
return setError(parser, 4, 202);
link->Diam = x;
}
if (n > 5)
{
if (!getfloat(parser->Tok[5], &x) || x <= 0.0)
return setError(parser, 5, 202);
link->Kc = x;
}
// Either a loss coeff. or a status is supplied
if (n > 6)
{
if (match(parser->Tok[6], w_CV)) link->Type = CVPIPE;
else if (match(parser->Tok[6], w_CLOSED)) link->Status = CLOSED;
else if (match(parser->Tok[6], w_OPEN)) link->Status = OPEN;
else
{
if (!getfloat(parser->Tok[6], &x) || x < 0.0)
return setError(parser, 6, 202);
link->Km = x;
}
}
// Both a loss coeff. and a status is supplied
if (n > 7)
{
if (!getfloat(parser->Tok[6], &x) || x < 0.0)
return setError(parser, 6, 202);
link->Km = x;
if (match(parser->Tok[7], w_CV)) link->Type = CVPIPE;
else if (match(parser->Tok[7], w_CLOSED)) link->Status = CLOSED;
else if (match(parser->Tok[7], w_OPEN)) link->Status = OPEN;
else return setError(parser, 7, 213);
}
return 0;
}
@@ -351,11 +449,6 @@ int pumpdata(Project *pr)
** Purpose: processes pump data
** Formats:
** [PUMP]
** (Version 1.x Format):
** id node1 node2 power
** id node1 node2 h1 q1
** id node1 node2 h0 h1 q1 h2 q2
** (Version 2 Format):
** id node1 node2 KEYWORD value {KEYWORD value ...}
** where KEYWORD = [POWER,HEAD,PATTERN,SPEED]
**--------------------------------------------------------------
@@ -364,7 +457,7 @@ int pumpdata(Project *pr)
Network *net = &pr->network;
Parser *parser = &pr->parser;
int j, m, // Token array indexes
int m, // Token array indexes
j1, // Start-node index
j2, // End-node index
n, // # data items
@@ -372,24 +465,24 @@ int pumpdata(Project *pr)
double y;
Slink *link;
Spump *pump;
int err = 0;
/* Add new pump to data base */
n = parser->Ntokens;
int errcode = 0;
// Check that end nodes exist
if (net->Nlinks == parser->MaxLinks ||
net->Npumps == parser->MaxPumps) return 200;
net->Nlinks++;
net->Npumps++;
err = addlinkID(net, net->Nlinks, parser->Tok[0]);
if (err) return setError(parser, 0, err);
// Check for valid data
if (n < 3) return 201;
n = parser->Ntokens;
if (n < 3) return setError(parser, -1, errcode);
if ((j1 = findnode(net, parser->Tok[1])) == 0) return setError(parser, 1, 203);
if ((j2 = findnode(net, parser->Tok[2])) == 0) return setError(parser, 2, 203);
if (j1 == j2) return setError(parser, 0, 222);
// Save pump data
// Add new pump to data base
errcode = addlinkID(net, net->Nlinks+1, parser->Tok[0]);
if (errcode) return setError(parser, 0, errcode);
net->Nlinks++;
net->Npumps++;
// Assign default data to pump
link = &net->Link[net->Nlinks];
pump = &net->Pump[net->Npumps];
@@ -415,28 +508,14 @@ int pumpdata(Project *pr)
pump->Epat = 0;
if (n < 4) return 0;
// If 4-th token is a number then input follows Version 1.x format
// so retrieve pump curve parameters
if (getfloat(parser->Tok[3], &parser->X[0]))
{
m = 1;
for (j = 4; j < n; j++)
{
if (!getfloat(parser->Tok[j], &parser->X[m])) return setError(parser, j, 202);
m++;
}
return (getpumpcurve(pr, m));
}
// Otherwise input follows Version 2 format
// so retrieve keyword/value pairs
// Retrieve keyword/value pairs
m = 4;
while (m < n)
{
if (match(parser->Tok[m - 1], w_POWER)) // Const. HP curve
{
y = atof(parser->Tok[m]);
if (y <= 0.0) return setError(parser, m, 202);
if (!getfloat(parser->Tok[m], &y) || y <= 0.0)
return setError(parser, m, 202);
pump->Ptype = CONST_HP;
link->Km = y;
}
@@ -454,11 +533,11 @@ int pumpdata(Project *pr)
}
else if (match(parser->Tok[m - 1], w_SPEED)) // Speed setting
{
if (!getfloat(parser->Tok[m], &y)) return setError(parser, m, 202);
if (y < 0.0) return setError(parser, m, 211);
if (!getfloat(parser->Tok[m], &y) || y < 0.0)
return setError(parser, m, 202);
link->Kc = y;
}
else return 201;
else return setError(parser, m-1, 201);;
m = m + 2; // Move to next keyword token
}
return 0;
@@ -485,74 +564,30 @@ int valvedata(Project *pr)
n; // # data items
char status = ACTIVE, // Valve status
type; // Valve type
double diam = 0.0, // Valve diameter
setting, // Valve setting
lcoeff = 0.0; // Minor loss coeff.
double x;
Slink *link;
int err = 0,
int errcode = 0,
losscurve = 0; // Loss coeff. curve
// Add new valve to data base
n = parser->Ntokens;
// Check that end nodes exist
if (net->Nlinks == parser->MaxLinks ||
net->Nvalves == parser->MaxValves) return 200;
net->Nvalves++;
net->Nlinks++;
err = addlinkID(net, net->Nlinks, parser->Tok[0]);
if (err) return setError(parser, 0, err);
// Check for valid data
if (n < 6)
return 201;
if ((j1 = findnode(net, parser->Tok[1])) == 0)
return setError(parser, 1, 203);
if ((j2 = findnode(net, parser->Tok[2])) == 0)
return setError(parser, 2, 203);
if (j1 == j2)
return setError(parser, 0, 222);
if (match(parser->Tok[4], w_PRV))
type = PRV;
else if (match(parser->Tok[4], w_PSV))
type = PSV;
else if (match(parser->Tok[4], w_PBV))
type = PBV;
else if (match(parser->Tok[4], w_FCV))
type = FCV;
else if (match(parser->Tok[4], w_TCV))
type = TCV;
else if (match(parser->Tok[4], w_GPV))
type = GPV;
else if (match(parser->Tok[4], w_PCV))
type = PCV;
else
return setError(parser, 4, 213);
if (!getfloat(parser->Tok[3], &diam)) return setError(parser, 3, 202);
if (diam <= 0.0) return setError(parser, 3, 211);
// Find headloss curve for GPV
if (type == GPV)
{
c = findcurve(net, parser->Tok[5]);
if (c == 0) return setError(parser, 5, 206);
setting = c;
net->Curve[c].Type = HLOSS_CURVE;
status = OPEN;
}
else if (!getfloat(parser->Tok[5], &setting)) return setError(parser, 5, 202);
if (n >= 7 && !getfloat(parser->Tok[6], &lcoeff)) return setError(parser, 6, 202);
n = parser->Ntokens;
if (n < 5) return setError(parser, -1, errcode);
if ((j1 = findnode(net, parser->Tok[1])) == 0) return setError(parser, 1, 203);
if ((j2 = findnode(net, parser->Tok[2])) == 0) return setError(parser, 2, 203);
if (j1 == j2) return setError(parser, 0, 222);
// Parse valve type
if (match(parser->Tok[4], w_PRV)) type = PRV;
else if (match(parser->Tok[4], w_PSV)) type = PSV;
else if (match(parser->Tok[4], w_PBV)) type = PBV;
else if (match(parser->Tok[4], w_FCV)) type = FCV;
else if (match(parser->Tok[4], w_TCV)) type = TCV;
else if (match(parser->Tok[4], w_GPV)) type = GPV;
else if (match(parser->Tok[4], w_PCV)) type = PCV;
else return setError(parser, 4, 213);
// Find loss coeff. curve for PCV
if (type == PCV && n >= 8)
{
c = findcurve(net, parser->Tok[7]);
if (c == 0) return setError(parser, 7, 206);
losscurve = c;
net->Curve[c].Type = VALVE_CURVE;
if (setting > 100.0) setting = 100.0;
}
// Check for illegal connections
if (valvecheck(pr, net->Nlinks, type, j1, j2))
{
@@ -561,23 +596,67 @@ int valvedata(Project *pr)
else return setError(parser, -1, 220);
}
// Save valve data
// Add new valve to data base
errcode = addlinkID(net, net->Nlinks+1, parser->Tok[0]);
if (errcode) return setError(parser, 0, errcode);
net->Nvalves++;
net->Nlinks++;
// Assign default data to valve
link = &net->Link[net->Nlinks];
link->N1 = j1;
link->N2 = j2;
link->Diam = diam;
if (parser->Unitsflag == SI) link->Diam = 254.0;
else link->Diam = 10.0;
link->Len = 0.0;
link->Kc = setting;
link->Km = lcoeff;
link->Kc = 0.0;
link->Km = 0.0;
link->Kb = 0.0;
link->Kw = 0.0;
link->Type = type;
link->Status = status;
link->Status = ACTIVE;
link->Rpt = 0;
link->ResultIndex = 0;
link->Comment = xstrcpy(&link->Comment, parser->Comment, MAXMSG);
net->Valve[net->Nvalves].Link = net->Nlinks;
net->Valve[net->Nvalves].Curve = losscurve;
net->Valve[net->Nvalves].Curve = 0;
// Parse data values
if (!getfloat(parser->Tok[3], &x) || x <= 0.0)
return setError(parser, 3, 202);
link->Diam = x;
if (n > 5)
{
// Find headloss curve for GPV
if (type == GPV)
{
c = findcurve(net, parser->Tok[5]);
if (c == 0) return setError(parser, 5, 206);
link->Kc = c;
net->Curve[c].Type = HLOSS_CURVE;
link->Status = OPEN;
}
else
{
if (!getfloat(parser->Tok[5], &x)) return setError(parser, 5, 202);
link->Kc = x;
}
}
if (n > 6)
{
if (!getfloat(parser->Tok[6], &x) || x < 0.0)
return setError(parser, 6, 202);
link->Km = x;
}
if (n > 7 && type == PCV)
{
// Find loss coeff. curve for PCV
c = findcurve(net, parser->Tok[7]);
if (c == 0) return setError(parser, 7, 206);
net->Valve[net->Nvalves].Curve = c;
net->Curve[c].Type = VALVE_CURVE;
if (link->Kc > 100.0) link->Kc = 100.0;
}
return 0;
}
@@ -628,6 +707,7 @@ int patterndata(Project *pr)
pattern->F = realloc(pattern->F, pattern->Length * sizeof(double));
// Add parsed multipliers to the pattern
for (j = 1; j <= n; j++) pattern->F[n1 + j - 1] = 1.0;
for (j = 1; j <= n; j++)
{
if (!getfloat(parser->Tok[j], &x)) return setError(parser, j, 202);
@@ -776,7 +856,6 @@ int demanddata(Project *pr)
** Purpose: processes node demand data
** Format:
** [DEMANDS]
** MULTIPLY factor
** node base_demand (pattern)
**
** NOTE: Demands entered in this section replace those
@@ -798,15 +877,7 @@ int demanddata(Project *pr)
if (n < 2) return 201;
if (!getfloat(parser->Tok[1], &y)) return setError(parser, 1, 202);
// If MULTIPLY command, save multiplier
if (match(parser->Tok[0], w_MULTIPLY))
{
if (y <= 0.0) return setError(parser, 1, 213);
else hyd->Dmult = y;
return 0;
}
// Otherwise find node (and pattern) being referenced
// Find node (and pattern) being referenced
if ((j = findnode(net, parser->Tok[0])) == 0) return setError(parser, 0, 203);
if (j > net->Njuncs) return 0;
if (n >= 3)
@@ -1781,25 +1852,14 @@ int optionchoice(Project *pr, int n)
if (match(parser->Tok[0], w_UNITS))
{
if (n < 1) return 0;
else if (match(parser->Tok[1], w_CFS)) parser->Flowflag = CFS;
else if (match(parser->Tok[1], w_GPM)) parser->Flowflag = GPM;
else if (match(parser->Tok[1], w_AFD)) parser->Flowflag = AFD;
else if (match(parser->Tok[1], w_MGD)) parser->Flowflag = MGD;
else if (match(parser->Tok[1], w_IMGD)) parser->Flowflag = IMGD;
else if (match(parser->Tok[1], w_LPS)) parser->Flowflag = LPS;
else if (match(parser->Tok[1], w_LPM)) parser->Flowflag = LPM;
else if (match(parser->Tok[1], w_CMH)) parser->Flowflag = CMH;
else if (match(parser->Tok[1], w_CMD)) parser->Flowflag = CMD;
else if (match(parser->Tok[1], w_MLD)) parser->Flowflag = MLD;
else if (match(parser->Tok[1], w_CMS)) parser->Flowflag = CMS;
else if (match(parser->Tok[1], w_SI)) parser->Flowflag = LPS;
else return setError(parser, 1, 213);
if (!getunitsoption(pr, parser->Tok[1]))
return setError(parser, 1, 213);
}
// PRESSURE units
else if (match(parser->Tok[0], w_PRESSURE))
{
if (n < 1) return 0;
if (n < 1) return 0;
else if (match(parser->Tok[1], w_EXPONENT)) return -1;
else if (match(parser->Tok[1], w_PSI)) parser->Pressflag = PSI;
else if (match(parser->Tok[1], w_KPA)) parser->Pressflag = KPA;
@@ -1811,10 +1871,8 @@ int optionchoice(Project *pr, int n)
else if (match(parser->Tok[0], w_HEADLOSS))
{
if (n < 1) return 0;
else if (match(parser->Tok[1], w_HW)) hyd->Formflag = HW;
else if (match(parser->Tok[1], w_DW)) hyd->Formflag = DW;
else if (match(parser->Tok[1], w_CM)) hyd->Formflag = CM;
else return setError(parser, 1, 213);
if (!getheadlossoption(pr, parser->Tok[1]))
return setError(parser, 1, 213);
}
// HYDRUALICS USE/SAVE file option
@@ -2063,101 +2121,6 @@ int optionvalue(Project *pr, int n)
return 0;
}
int getpumpcurve(Project *pr, int n)
/*
**--------------------------------------------------------
** Input: n = number of parameters for pump curve
** Output: returns error code
** Purpose: processes pump curve data for Version 1.1-
** style input data
** Notes:
** 1. Called by pumpdata() in INPUT3.C
** 2. Current link index & pump index of pump being
** processed is found in network variables Nlinks
** and Npumps, respectively
** 3. Curve data read from input line is found in
** parser's array X[0],...X[n-1]
**---------------------------------------------------------
*/
{
Network *net = &pr->network;
Parser *parser = &pr->parser;
double a, b, c, h0, h1, h2, q1, q2;
Spump *pump = &net->Pump[net->Npumps];
// Constant HP curve
if (n == 1)
{
if (parser->X[0] <= 0.0) return 202;
pump->Ptype = CONST_HP;
net->Link[net->Nlinks].Km = parser->X[0];
}
// Power function curve
else
{
// Single point power curve
if (n == 2)
{
q1 = parser->X[1];
h1 = parser->X[0];
h0 = 1.33334 * h1;
q2 = 2.0 * q1;
h2 = 0.0;
}
// 3-point power curve
else if (n >= 5)
{
h0 = parser->X[0];
h1 = parser->X[1];
q1 = parser->X[2];
h2 = parser->X[3];
q2 = parser->X[4];
}
else return 202;
pump->Ptype = POWER_FUNC;
if (!powercurve(h0, h1, h2, q1, q2, &a, &b, &c)) return 206;
pump->H0 = -a;
pump->R = -b;
pump->N = c;
pump->Q0 = q1;
pump->Qmax = pow((-a / b), (1.0 / c));
pump->Hmax = h0;
}
return 0;
}
int powercurve(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 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;
}
void changestatus(Network *net, int j, StatusType status, double y)
/*

View File

@@ -7,7 +7,7 @@
Authors: see AUTHORS
Copyright: see AUTHORS
License: see LICENSE
Last Updated: 08/02/2023
Last Updated: 09/28/2023
******************************************************************************
*/
@@ -24,6 +24,81 @@
#include "types.h"
#include "funcs.h"
int openproject(Project *pr, const char *inpFile, const char *rptFile,
const char *outFile, int allowerrors)
/*----------------------------------------------------------------
** Input: inpFile = name of input file
** rptFile = name of report file
** outFile = name of binary output file
** allowerrors = TRUE if project can be opened with errors
** Output: none
** Returns: error code
** Purpose: opens an EPANET input file & reads in network data
**----------------------------------------------------------------
*/
{
int errcode = 0;
int hyderrcode = 0;
int projectopened;
// Set system flags
pr->Openflag = FALSE;
pr->hydraul.OpenHflag = FALSE;
pr->quality.OpenQflag = FALSE;
pr->outfile.SaveHflag = FALSE;
pr->outfile.SaveQflag = FALSE;
pr->Warnflag = FALSE;
pr->report.Messageflag = TRUE;
pr->report.Rptflag = 1;
// Initialize data arrays to NULL
initpointers(pr);
// Open input & report files
ERRCODE(openfiles(pr, inpFile, rptFile, outFile));
if (errcode > 0)
{
errmsg(pr, errcode);
return errcode;
}
// Allocate memory for project's data arrays
ERRCODE(netsize(pr));
ERRCODE(allocdata(pr));
// Read input data
ERRCODE(getdata(pr));
// Close input file
if (pr->parser.InFile != NULL)
{
fclose(pr->parser.InFile);
pr->parser.InFile = NULL;
}
// Input file read with no fatal errors
if (allowerrors) projectopened = (errcode == 0 || errcode == 200);
else projectopened = (errcode == 0);
if (projectopened)
{
// If using previously saved hydraulics file then open it
if (pr->outfile.Hydflag == USE)
{
hyderrcode = openhydfile(pr);
if (hyderrcode > 0)
{
errmsg(pr, hyderrcode);
pr->outfile.Hydflag = SCRATCH;
}
}
// Write input summary to report file
if (pr->report.Summaryflag) writesummary(pr);
pr->Openflag = TRUE;
}
errmsg(pr, errcode);
return errcode;
}
int openfiles(Project *pr, const char *f1, const char *f2, const char *f3)
/*----------------------------------------------------------------
@@ -1060,47 +1135,6 @@ void adjustcurves(Network *network, int index)
}
}
int adjustpumpparams(Project *pr, int curveIndex)
/*----------------------------------------------------------------
** Input: curveIndex = index of a data curve
** Output: returns an error code
** Purpose: updates head curve parameters for pumps using a
** curve whose data have been modified.
**----------------------------------------------------------------
*/
{
Network *network = &pr->network;
double *Ucf = pr->Ucf;
int j, err = 0;
Spump *pump;
// Check each pump
for (j = 1; j <= network->Npumps; j++)
{
// Pump uses curve as head curve
pump = &network->Pump[j];
if ( curveIndex == pump->Hcurve)
{
// Update its head curve parameters
pump->Ptype = NOCURVE;
err = updatepumpparams(pr, j);
if (err > 0) break;
// Convert parameters to internal units
if (pump->Ptype == POWER_FUNC)
{
pump->H0 /= Ucf[HEAD];
pump->R *= (pow(Ucf[FLOW], pump->N) / Ucf[HEAD]);
}
pump->Q0 /= Ucf[FLOW];
pump->Qmax /= Ucf[FLOW];
pump->Hmax /= Ucf[HEAD];
}
}
return err;
}
int resizecurve(Scurve *curve, int size)
/*----------------------------------------------------------------
@@ -1448,6 +1482,7 @@ double interp(int n, double x[], double y[], double xx)
int k, m;
double dx, dy;
if (n == 0) return 0.0;
m = n - 1; // Highest data index
if (xx <= x[0]) return (y[0]); // xx off low end of curve
for (k = 1; k <= m; k++) // Bracket xx on curve

View File

@@ -404,7 +404,7 @@ void adjustrules(Project *pr, int objtype, int index)
}
}
void adjusttankrules(Project *pr)
void adjusttankrules(Project *pr, int ndiff)
//-----------------------------------------------------------
// Adjusts tank indices in rule premises.
//-----------------------------------------------------------
@@ -420,7 +420,8 @@ void adjusttankrules(Project *pr)
p = net->Rule[i].Premises;
while (p != NULL)
{
if (p->object == r_NODE && p->index > njuncs) p->index++;
if (p->object == r_NODE && p->index > njuncs)
p->index += ndiff;
p = p->next;
}
}
@@ -472,7 +473,7 @@ int writerule(Project *pr, FILE *f, int ruleIndex)
Srule *rule = &net->Rule[ruleIndex];
Spremise *p;
Saction *a;
// Write each premise clause to the file
p = rule->Premises;
fprintf(f, "\nIF ");

413
src/validate.c Normal file
View File

@@ -0,0 +1,413 @@
/*
******************************************************************************
Project: OWA EPANET
Version: 2.2
Module: validate.c
Description: validates project data
Authors: see AUTHORS
Copyright: see AUTHORS
License: see LICENSE
Last Updated: 09/28/2023
******************************************************************************
*/
#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 *pr);
void reindextanks(Project *pr);
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;
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
i = tank->Vcurve;
if (i > 0)
{
curve = &net->Curve[i];
n = curve->Npts - 1;
if (tank->Hmin * pr->Ucf[ELEV] < curve->X[0] ||
tank->Hmax * pr->Ucf[ELEV]> curve->X[n])
{
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 powercurve(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 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 findpumpparams(Project *pr, int pumpindex)
/*
**-------------------------------------------------------------
** Input: pumpindex = index of a pump
** Output: returns error code
** Purpose: computes & checks a pump's head curve coefficients
**--------------------------------------------------------------
*/
{
Network *net = &pr->network;
Spump *pump;
Scurve *curve;
int m;
int curveindex;
int npts = 0;
int errcode = 0;
double a, b, c, h0 = 0.0, h1 = 0.0, h2 = 0.0, q1 = 0.0, q2 = 0.0;
pump = &net->Pump[pumpindex];
if (pump->Ptype == CONST_HP) // Constant Hp pump
{
pump->H0 = 0.0;
pump->R = -8.814 * net->Link[pump->Link].Km;
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;
}
else if (pump->Ptype == NOCURVE) // Pump curve specified
{
curveindex = pump->Hcurve;
if (curveindex == 0) return 226;
curve = &net->Curve[curveindex];
curve->Type = PUMP_CURVE;
npts = curve->Npts;
// 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];
}
// Custom pump curve
else
{
pump->Ptype = CUSTOM;
for (m = 1; m < npts; m++)
{
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;
pump->Hmax = curve->Y[0];
}
// Compute shape factors & limits of power function curves
if (pump->Ptype == POWER_FUNC)
{
if (!powercurve(h0, h1, h2, q1, q2, &a, &b, &c))
{
pump->Ptype = NOCURVE;
return 227;
}
else
{
pump->H0 = -a;
pump->R = -b;
pump->N = c;
pump->Q0 = q1;
pump->Qmax = pow((-a / b), (1.0 / c));
pump->Hmax = h0;
}
}
// Convert units of pump coefficients
if (pump->Ptype == POWER_FUNC)
{
pump->H0 /= pr->Ucf[HEAD];
pump->R *= (pow(pr->Ucf[FLOW], pump->N) / pr->Ucf[HEAD]);
}
pump->R /= pr->Ucf[POWER];
pump->Q0 /= pr->Ucf[FLOW];
pump->Qmax /= pr->Ucf[FLOW];
pump->Hmax /= pr->Ucf[HEAD];
}
return 0;
}
int validatepumps(Project *pr)
/*
**-------------------------------------------------------------------
** Input: none
** Output: returns 1 if successful, 0 if not
** Purpose: checks if pumps assigned pump curves.
**-------------------------------------------------------------------
*/
{
Network *net = &pr->network;
int i, k, errcode, result = 1;
char errmsg[MAXMSG+1] = "";
Spump *pump;
for (i = 1; i <= net->Npumps; i++)
{
// Check if pump has neither a head curve nor power setting
pump = &net->Pump[i];
k = pump->Link;
if (net->Link[k].Km == 0.0 && pump->Hcurve <= 0)
{
sprintf(pr->Msg, "Error 226: %s %s",
geterrmsg(226, errmsg), net->Link[k].ID);
writeline(pr, pr->Msg);
result = 0;
}
// Compute & check pump's head curve coefficients
else
{
errcode = findpumpparams(pr, i);
if (errcode)
{
sprintf(pr->Msg, "Error %d: %s %s",
errcode, geterrmsg(errcode, errmsg), net->Link[k].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);
}
}