diff --git a/include/epanet2.bas b/include/epanet2.bas index 663c7325..985db05f 100644 --- a/include/epanet2.bas +++ b/include/epanet2.bas @@ -5,7 +5,7 @@ Attribute VB_Name = "Module1" 'Declarations of functions in the EPANET PROGRAMMERs TOOLKIT '(EPANET2.DLL) -'Last updated on 07/18/2023 +'Last updated on 09/28/2023 ' These are codes used by the DLL functions Public Const EN_ELEVATION = 0 ' Node parameters @@ -280,6 +280,7 @@ Public Const EN_SET_OPEN As Double = 1.0E10 Declare Function ENepanet Lib "epanet2.dll" (ByVal inpFile As String, ByVal rptFile As String, ByVal outFile As String, ByVal pviewprog As Any) As Long Declare Function ENinit Lib "epanet2.dll" (ByVal rptFile As String, ByVal outFile As String, ByVal unitsType As Long, ByVal headlossType As Long) As Long Declare Function ENopen Lib "epanet2.dll" (ByVal inpFile As String, ByVal rptFile As String, ByVal outFile As String) As Long + Declare Function ENopenX Lib "epanet2.dll" (ByVal inpFile As String, ByVal rptFile As String, ByVal outFile As String) As Long Declare Function ENgettitle Lib "epanet2.dll" (ByVal line1 As String, ByVal line2 As String, ByVal line3 As String) As Long Declare Function ENsettitle Lib "epanet2.dll" (ByVal titleline1 As String, ByVal titleline2 As String, ByVal titleline3 As String) As Long Declare Function ENsaveinpfile Lib "epanet2.dll" (ByVal filename As String) As Long diff --git a/include/epanet2.cs b/include/epanet2.cs index 0a6fedc8..70f8a38d 100644 --- a/include/epanet2.cs +++ b/include/epanet2.cs @@ -3,7 +3,7 @@ using System.Runtime.InteropServices; //epanet2.cs[By Oscar Vegas] -//Last updated on 07/18/2023 +//Last updated on 09/28/2023 //Declarations of functions in the EPANET PROGRAMMERs TOOLKIT //(EPANET2.DLL) for use with C# @@ -286,6 +286,9 @@ public static class Epanet [DllImport(EPANETDLL, EntryPoint = "ENopen")] public static extern int ENopen(string inpFile, string rptFile, string outFile); + [DllImport(EPANETDLL, EntryPoint = "ENopenX")] + public static extern int ENopenX(string inpFile, string rptFile, string outFile); + [DllImport(EPANETDLL, EntryPoint = "ENgettitle")] public static extern int ENgettitle(string titleline1, string titleline2, string titleline3); diff --git a/include/epanet2.def b/include/epanet2.def index d72da8ea..fda32cc6 100644 --- a/include/epanet2.def +++ b/include/epanet2.def @@ -131,4 +131,5 @@ EXPORTS ENstepQ = _ENstepQ@4 ENusehydfile = _ENusehydfile@4 ENwriteline = _ENwriteline@4 - ENtimetonextevent = _ENtimetonextevent@12 \ No newline at end of file + ENtimetonextevent = _ENtimetonextevent@12 + ENopenX = _ENopenX@12 diff --git a/include/epanet2.h b/include/epanet2.h index 25651f0d..5cc6a415 100644 --- a/include/epanet2.h +++ b/include/epanet2.h @@ -7,7 +7,7 @@ Authors: see AUTHORS Copyright: see AUTHORS License: see LICENSE - Last Updated: 02/01/2020 + Last Updated: 09/28/2023 ****************************************************************************** */ @@ -73,6 +73,9 @@ extern "C" { int DLLEXPORT ENopen(const char *inpFile, const char *rptFile, const char *outFile); + int DLLEXPORT ENopenX(const char *inpFile, const char *rptFile, + const char *outFile); + int DLLEXPORT ENgettitle(char *line1, char *line2, char *line3); int DLLEXPORT ENsettitle(const char *line1, const char *line2, const char *line3); diff --git a/include/epanet2.pas b/include/epanet2.pas index 3ae75ad9..6daaea04 100644 --- a/include/epanet2.pas +++ b/include/epanet2.pas @@ -3,7 +3,7 @@ { Declarations of imported procedures from the EPANET PROGRAMMERs TOOLKIT } { (EPANET2.DLL) } -{Last updated on 09/11/2023} +{Last updated on 09/28/2023} interface @@ -281,6 +281,7 @@ interface function ENepanet(F1: PAnsiChar; F2: PAnsiChar; F3: PAnsiChar; F4: Pointer): Integer; stdcall; external EpanetLib; function ENinit(F2: PAnsiChar; F3: PAnsiChar; UnitsType: Integer; HeadlossType: Integer): Integer; stdcall; external EpanetLib; function ENopen(F1: PAnsiChar; F2: PAnsiChar; F3: PAnsiChar): Integer; stdcall; external EpanetLib; + function ENopenX(F1: PAnsiChar; F2: PAnsiChar; F3: PAnsiChar): Integer; stdcall; external EpanetLib; function ENgetcount(Code: Integer; var Count: Integer): Integer; stdcall; external EpanetLib; function ENgettitle(Line1: PAnsiChar; Line2: PAnsiChar; Line3: PAnsiChar): Integer; stdcall; external EpanetLib; function ENsettitle(Line1: PAnsiChar; Line2: PAnsiChar; Line3: PAnsiChar): Integer; stdcall; external EpanetLib; diff --git a/include/epanet2.vb b/include/epanet2.vb index 1a6bf114..8d944da1 100644 --- a/include/epanet2.vb +++ b/include/epanet2.vb @@ -4,7 +4,7 @@ 'Declarations of functions in the EPANET PROGRAMMERs TOOLKIT '(EPANET2.DLL) for use with VB.Net. -'Last updated on 07/18/2023 +'Last updated on 09/28/2023 Imports System.Runtime.InteropServices Imports System.Text @@ -268,6 +268,7 @@ Public Const EN_SET_OPEN As Double = 1.0E10 Declare Function ENepanet Lib "epanet2.dll" (ByVal inpFile As String, ByVal rptFile As String, ByVal outFile As String, ByVal pviewprog As Any) As Int32 Declare Function ENinit Lib "epanet2.dll" (ByVal rptFile As String, ByVal outFile As String, ByVal unitsType As Int32, ByVal headlossType As Int32) As Int32 Declare Function ENopen Lib "epanet2.dll" (ByVal inpFile As String, ByVal rptFile As String, ByVal outFile As String) As Int32 + Declare Function ENopenX Lib "epanet2.dll" (ByVal inpFile As String, ByVal rptFile As String, ByVal outFile As String) As Int32 Declare Function ENgettitle Lib "epanet2.dll" (ByVal titleline1 As String, ByVal titleline2 As String, ByVal titleline3 As String) As Int32 Declare Function ENsettitle Lib "epanet2.dll" (ByVal titleline1 As String, ByVal titleline2 As String, ByVal titleline3 As String) As Int32 Declare Function ENsaveinpfile Lib "epanet2.dll" (ByVal filename As String) As Int32 diff --git a/include/epanet2_2.h b/include/epanet2_2.h index a25036c9..5a3c3af2 100644 --- a/include/epanet2_2.h +++ b/include/epanet2_2.h @@ -11,7 +11,7 @@ Authors: see AUTHORS Copyright: see AUTHORS License: see LICENSE - Last Updated: 02/01/2020 + Last Updated: 09/28/2023 ****************************************************************************** */ @@ -113,7 +113,7 @@ typedef struct Project *EN_Project; int unitsType, int headLossType); /** - @brief Opens an EPANET input file & reads in network data. + @brief Reads an EPANET input file with no errors allowed. @param ph an EPANET project handle. @param inpFile the name of an existing EPANET-formatted input file. @param rptFile the name of a report file to be created (or "" if not needed). @@ -121,11 +121,27 @@ typedef struct Project *EN_Project; @return an error code. This function should be called immediately after ::EN_createproject if an EPANET-formatted - input file will be used to supply network data. + input file will be used to supply network data. If errors are detected then the project is + not opened and will not accept toolkit function calls. */ int DLLEXPORT EN_open(EN_Project ph, const char *inpFile, const char *rptFile, const char *outFile); + /** + @brief Reads an EPANET input file with errors allowed. + @param ph an EPANET project handle. + @param inpFile the name of an existing EPANET-formatted input file. + @param rptFile the name of a report file to be created (or "" if not needed). + @param outFile the name of a binary output file to be created (or "" if not needed). + @return an error code. + + This function should be called immediately after ::EN_createproject if an EPANET-formatted + input file will be used to supply network data. If formatting errors are detected (error + code = 200) then the project remains open and will accept toolkit function calls. + */ + int DLLEXPORT EN_openX(EN_Project ph, const char *inpFile, const char *rptFile, + const char *outFile); + /** @brief Retrieves the title lines of the project @param ph an EPANET project handle. diff --git a/src/epanet.c b/src/epanet.c index bbbfb620..354a51cf 100644 --- a/src/epanet.c +++ b/src/epanet.c @@ -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. **---------------------------------------------------------------- */ -{ - int errcode = 0; - - // 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; - } + { + writewin(p->viewprog, FMT100); + return openproject(p, inpFile, rptFile, outFile, FALSE); + } - // If using previously saved hydraulics file then open it - if (p->outfile.Hydflag == USE) ERRCODE(openhydfile(p)); +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. + **---------------------------------------------------------------- + */ +{ + writewin(p->viewprog, FMT100); + return openproject(p, inpFile, rptFile, outFile, TRUE); +} - // 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; } /******************************************************************** diff --git a/src/epanet2.c b/src/epanet2.c index d8148924..7d7cd50f 100644 --- a/src/epanet2.c +++ b/src/epanet2.c @@ -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) ; diff --git a/src/errors.dat b/src/errors.dat index 65cb39ae..35ecf7bf 100644 --- a/src/errors.dat +++ b/src/errors.dat @@ -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: ") diff --git a/src/funcs.h b/src/funcs.h index a6d51502..8910c361 100755 --- a/src/funcs.h +++ b/src/funcs.h @@ -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); diff --git a/src/hydraul.c b/src/hydraul.c index 96433f3d..7d1718b0 100755 --- a/src/hydraul.c +++ b/src/hydraul.c @@ -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; } + diff --git a/src/input1.c b/src/input1.c index c7344325..ee40d1ec 100644 --- a/src/input1.c +++ b/src/input1.c @@ -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 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); - } - } + // 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); - // 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 diff --git a/src/input2.c b/src/input2.c index 0ece6fd7..0810d7bf 100644 --- a/src/input2.c +++ b/src/input2.c @@ -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); diff --git a/src/input3.c b/src/input3.c index 713c07ec..cac9f2e2 100644 --- a/src/input3.c +++ b/src/input3.c @@ -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); + if (pattern < 0) + { + errcode = 205; + errtok = 2; + } } } - else if (n < 6) return 201; - - // Tank is a storage tank - else + + // Node is a storage tank + else if (!errcode) { - 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 (n < 6) errcode = 201; + else { - if (strlen(parser->Tok[7]) > 0 && *(parser->Tok[7]) != '*') + // 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 volume curve supplied check it exists + if (!errcode && n >= 8) { - curve = findcurve(net, parser->Tok[7]); - if (curve == 0) return setError(parser, 7, 206); - net->Curve[curve].Type = VOLUME_CURVE; + 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; + } } - } - // Parse overflow indicator if present - if (n >= 9) - { - 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 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; + } + } } - - 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); } 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) + // Assign default data to pipe + link = &net->Link[net->Nlinks]; + link->N1 = j1; + link->N2 = j2; + + if (parser->Unitsflag == SI) { - 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); + link->Len = 100.0; + link->Diam = 254.0; } - - // Both a loss coeff. and a status is supplied - if (n == 8) + else { - 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); + link->Len = 330.0; + link->Diam = 10.0; } - if (lcoeff < 0.0) return setError(parser, 6, 211); - - // Save pipe data - link = &net->Link[net->Nlinks]; - link->N1 = j1; - link->N2 = j2; - link->Len = length; - link->Diam = diam; - link->Kc = rcoeff; - link->Km = lcoeff; + 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) /* diff --git a/src/project.c b/src/project.c index 258b9b0d..2104d060 100644 --- a/src/project.c +++ b/src/project.c @@ -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 diff --git a/src/rules.c b/src/rules.c index 99d0a024..cf43365f 100644 --- a/src/rules.c +++ b/src/rules.c @@ -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 "); diff --git a/src/validate.c b/src/validate.c new file mode 100644 index 00000000..486c82fb --- /dev/null +++ b/src/validate.c @@ -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 +#include +#include +#include + +#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); + } +} +