/* ****************************************************************************** Project: OWA EPANET Version: 2.3 Module: epanet.c Description: implementation of EPANET's API functions Authors: see AUTHORS Copyright: see AUTHORS License: see LICENSE Last Updated: 04/19/2025 ****************************************************************************** */ #include #include #include #include #include #include "epanet2_2.h" #include "types.h" #include "funcs.h" #include "text.h" #include "enumstxt.h" #ifdef _WIN32 #define snprintf _snprintf #endif /******************************************************************** Project Functions ********************************************************************/ int DLLEXPORT EN_createproject(EN_Project *p) /*---------------------------------------------------------------- ** Input: none ** Output: p = pointer to a new EPANET project ** Returns: error code ** Purpose: creates a new EPANET project **---------------------------------------------------------------- */ { struct Project *project = (struct Project *)calloc(1, sizeof(struct Project)); if (project == NULL) return -1; getTmpName(project->TmpHydFname); getTmpName(project->TmpOutFname); getTmpName(project->TmpStatFname); *p = project; return 0; } int DLLEXPORT EN_deleteproject(EN_Project p) /*---------------------------------------------------------------- ** Input: none ** Output: none ** Returns: error code ** Purpose: deletes an EPANET project **---------------------------------------------------------------- */ { if (p == NULL) return -1; if (p->Openflag) { EN_close(p); } remove(p->TmpHydFname); remove(p->TmpOutFname); remove(p->TmpStatFname); free(p); return 0; } int DLLEXPORT EN_runproject(EN_Project p, const char *inpFile, const char *rptFile, const char *outFile, void (*pviewprog)(char *)) /*------------------------------------------------------------------------ ** Input: inpFile = name of EPANET formatted input file ** rptFile = name of report file ** outFile = name of binary output file ** pviewprog = see note below ** Output: none ** Returns: error code ** Purpose: runs a complete EPANET simulation ** ** The pviewprog() argument is a pointer to a callback function ** that takes a character string (char *) as its only parameter. ** The function would reside in and be used by the calling ** program to display the progress messages that EPANET generates ** as it carries out its computations. If this feature is not ** needed then the argument should be NULL. **------------------------------------------------------------------------- */ { int errcode = 0; // Read in project data from an input file ERRCODE(EN_open(p, inpFile, rptFile, outFile)); p->viewprog = pviewprog; // Solve for system hydraulics if (p->outfile.Hydflag != USE) { ERRCODE(EN_solveH(p)); } // Solve for system water quality ERRCODE(EN_solveQ(p)); // Write a formatted output report ERRCODE(EN_report(p)); EN_close(p); // Return any error or warning code if (p->Warnflag) errcode = MAX(errcode, p->Warnflag); return errcode; } int DLLEXPORT EN_init(EN_Project p, const char *rptFile, const char *outFile, int unitsType, int headLossType) /*---------------------------------------------------------------- ** Input: rptFile = name of report file ** outFile = name of binary output file ** unitsType = type of flow units (see FlowUnitsType) ** headLossType = type of head loss formula (see HeadLossType) ** Output: none ** Returns: error code ** Purpose: initializes an EPANET project that isn't opened with ** an input file **---------------------------------------------------------------- */ { 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; // Check for valid arguments if (unitsType < 0 || unitsType > CMS) return 251; if (headLossType < 0 || headLossType > CM) return 251; // Open files errcode = openfiles(p, "", rptFile, outFile); // Initialize memory used for project's data objects initpointers(p); ERRCODE(netsize(p)); ERRCODE(allocdata(p)); if (errcode) return (errcode); // Set analysis options setdefaults(p); p->parser.Flowflag = unitsType; p->hydraul.Formflag = headLossType; // Perform additional initializations adjustdata(p); initreport(&p->report); initunits(p); inittanks(p); convertunits(p); p->parser.MaxPats = 0; p->Openflag = TRUE; return errcode; } int DLLEXPORT EN_open(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 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. **---------------------------------------------------------------- */ { writewin(p->viewprog, FMT100); return openproject(p, inpFile, rptFile, outFile, TRUE); } int DLLEXPORT EN_gettitle(EN_Project p, char *line1, char *line2, char *line3) /*---------------------------------------------------------------- ** Input: None ** Output: line1, line2, line3 = project's title lines ** Returns: error code ** Purpose: retrieves the title lines of the project **---------------------------------------------------------------- */ { if (!p->Openflag) return 102; strncpy(line1, p->Title[0], TITLELEN); strncpy(line2, p->Title[1], TITLELEN); strncpy(line3, p->Title[2], TITLELEN); return 0; } int DLLEXPORT EN_settitle(EN_Project p, const char *line1, const char *line2, const char *line3) /*---------------------------------------------------------------- ** Input: line1, line2, line3 = project's title lines ** Returns: error code ** Purpose: sets the title lines of the project **---------------------------------------------------------------- */ { if (!p->Openflag) return 102; strncpy(p->Title[0], line1, TITLELEN); strncpy(p->Title[1], line2, TITLELEN); strncpy(p->Title[2], line3, TITLELEN); return 0; } int DLLEXPORT EN_getcomment(EN_Project p, int object, int index, char *comment) /*---------------------------------------------------------------- ** Input: object = a type of object (see EN_ObjectType) ** index = the object's index ** Output: comment = the object's descriptive comment ** Returns: error code ** Purpose: Retrieves an object's descriptive comment **---------------------------------------------------------------- */ { return getcomment(&p->network, object, index, comment); } int DLLEXPORT EN_setcomment(EN_Project p, int object, int index, const char *comment) /*---------------------------------------------------------------- ** Input: object = a type of object (see EN_ObjectType) ** index = the object's index ** comment = a descriptive comment to assign ** Returns: error code ** Purpose: Assigns a descriptive comment to an object **---------------------------------------------------------------- */ { return setcomment(&p->network, object, index, comment); } int DLLEXPORT EN_gettag(EN_Project p, int object, int index, char *tag) /*---------------------------------------------------------------- ** Input: object = either EN_NODE or EN_LINK ** index = the object's index ** Output: tag = the tag string assigned to the object ** Returns: error code ** Purpose: Retrieves an object's tag string **---------------------------------------------------------------- */ { return gettag(&p->network, object, index, tag); } int DLLEXPORT EN_settag(EN_Project p, int object, int index, const char *tag) /*---------------------------------------------------------------- ** Input: object = either EN_NODE or EN_LINK ** index = the object's index ** tag = a descriptive comment to assign ** Returns: error code ** Purpose: Assigns a tag string to an object **---------------------------------------------------------------- */ { return settag(&p->network, object, index, tag); } int DLLEXPORT EN_getcount(EN_Project p, int object, int *count) /*---------------------------------------------------------------- ** Input: object = type of object to count (see EN_CountType) ** Output: count = number of objects of the specified type ** Returns: error code ** Purpose: Retrieves number of network objects of a given type **---------------------------------------------------------------- */ { Network *net = &p->network; *count = 0; if (!p->Openflag) return 102; switch (object) { case EN_NODECOUNT: *count = net->Nnodes; break; case EN_TANKCOUNT: *count = net->Ntanks; break; case EN_LINKCOUNT: *count = net->Nlinks; break; case EN_PATCOUNT: *count = net->Npats; break; case EN_CURVECOUNT: *count = net->Ncurves; break; case EN_CONTROLCOUNT: *count = net->Ncontrols; break; case EN_RULECOUNT: *count = net->Nrules; break; default: return 251; } return 0; } int DLLEXPORT EN_saveinpfile(EN_Project p, const char *filename) /*---------------------------------------------------------------- ** Input: filename = name of file to which project is saved ** Output: none ** Returns: error code ** Purpose: saves project to an EPANET formatted file **---------------------------------------------------------------- */ { if (!p->Openflag) return 102; return saveinpfile(p, filename); } int DLLEXPORT EN_close(EN_Project p) /*---------------------------------------------------------------- ** Input: none ** Output: none ** Returns: error code ** Purpose: frees all memory & files used by a project **---------------------------------------------------------------- */ { // Free all project data freedata(p); // Close output file closeoutfile(p); // Close input file if (p->parser.InFile != NULL) { fclose(p->parser.InFile); p->parser.InFile = NULL; } // Close report file if (p->report.RptFile != NULL && p->report.RptFile != stdout) { fclose(p->report.RptFile); p->report.RptFile = NULL; } // Close hydraulics file if (p->outfile.HydFile != NULL) { fclose(p->outfile.HydFile); p->outfile.HydFile = NULL; } // Reset system flags p->Openflag = FALSE; p->hydraul.OpenHflag = FALSE; p->outfile.SaveHflag = FALSE; p->quality.OpenQflag = FALSE; p->outfile.SaveQflag = FALSE; return 0; } /******************************************************************** Hydraulic Analysis Functions ********************************************************************/ int DLLEXPORT EN_solveH(EN_Project p) /*---------------------------------------------------------------- ** Input: none ** Output: none ** Returns: error code ** Purpose: solves for network hydraulics in all time periods **---------------------------------------------------------------- */ { int errcode; long t, tstep; // Open hydraulics solver errcode = EN_openH(p); if (!errcode) { // Initialize hydraulics errcode = EN_initH(p, EN_SAVE); // Analyze each hydraulic time period if (!errcode) do { // Display progress message sprintf(p->Msg, "%-10s", clocktime(p->report.Atime, p->times.Htime)); sprintf(p->Msg, FMT101, p->report.Atime); writewin(p->viewprog, p->Msg); // Solve for hydraulics & advance to next time period tstep = 0; ERRCODE(EN_runH(p, &t)); ERRCODE(EN_nextH(p, &tstep)); } while (tstep > 0); } // Close hydraulics solver EN_closeH(p); errcode = MAX(errcode, p->Warnflag); return errcode; } int DLLEXPORT EN_saveH(EN_Project p) /*---------------------------------------------------------------- ** Input: none ** Output: none ** Returns: error code ** Purpose: saves hydraulic results to binary file ** ** Must be called before EN_report() if no WQ simulation made. ** Should not be called if EN_solveQ() will be used. **---------------------------------------------------------------- */ { int tmpflag; int errcode; // Check if hydraulic results exist if (!p->outfile.SaveHflag) return 104; // Temporarily turn off WQ analysis tmpflag = p->quality.Qualflag; p->quality.Qualflag = NONE; // Call WQ solver to simply transfer results from Hydraulics file // to Output file at fixed length reporting time intervals errcode = EN_solveQ(p); // Restore WQ analysis option p->quality.Qualflag = tmpflag; if (errcode) errmsg(p, errcode); return errcode; } int DLLEXPORT EN_openH(EN_Project p) /*---------------------------------------------------------------- ** Input: none ** Output: none ** Returns: error code ** Purpose: opens a project's hydraulic solver **---------------------------------------------------------------- */ { int errcode = 0; // Check that input data exists p->hydraul.OpenHflag = FALSE; p->outfile.SaveHflag = FALSE; if (!p->Openflag) return 102; // Check that previously saved hydraulics file not in use if (p->outfile.Hydflag == USE) return 107; // Open hydraulics solver ERRCODE(openhyd(p)); if (!errcode) { p->hydraul.OpenHflag = TRUE; writetime(p, FMT104); } else errmsg(p, errcode); return errcode; } int DLLEXPORT EN_initH(EN_Project p, int initFlag) /*---------------------------------------------------------------- ** Input: initFlag = 2-digit flag where 1st (left) digit indicates ** if link flows should be re-initialized (1) or ** not (0) and 2nd digit indicates if hydraulic ** results should be saved to file (1) or not (0) ** Output: none ** Returns: error code ** Purpose: initializes a project's hydraulic solver **---------------------------------------------------------------- */ { int errcode = 0; int sflag, fflag; // Reset status flags p->outfile.SaveHflag = FALSE; p->Warnflag = FALSE; // Get values of save-to-file flag and reinitialize-flows flag fflag = initFlag / EN_INITFLOW; sflag = initFlag - fflag * EN_INITFLOW; // Check that hydraulics solver was opened if (!p->hydraul.OpenHflag) return 103; // Open hydraulics file if requested p->outfile.Saveflag = FALSE; if (sflag > 0) { errcode = openhydfile(p); if (!errcode) p->outfile.Saveflag = TRUE; else { errmsg(p, errcode); return errcode; } } // Open pipe leakage modeling system errcode = openleakage(p); if (errcode) return errcode; // Initialize hydraulics solver inithyd(p, fflag); if (p->report.Statflag > 0) writeheader(p, STATHDR, 0); return errcode; } int DLLEXPORT EN_runH(EN_Project p, long *currentTime) /*---------------------------------------------------------------- ** Input: none ** Output: currentTime = current elapsed time (sec) ** Returns: error code ** Purpose: solves network hydraulics at current time point **---------------------------------------------------------------- */ { int errcode; *currentTime = 0; if (!p->hydraul.OpenHflag) return 103; errcode = runhyd(p, currentTime); if (errcode) errmsg(p, errcode); return errcode; } int DLLEXPORT EN_nextH(EN_Project p, long *tStep) /*---------------------------------------------------------------- ** Input: none ** Output: tStep = next hydraulic time step to take (sec) ** Returns: error code ** Purpose: determines the time step until the next hydraulic event **---------------------------------------------------------------- */ { int errcode; *tStep = 0; if (!p->hydraul.OpenHflag) return 103; errcode = nexthyd(p, tStep); if (errcode) errmsg(p, errcode); else if (p->outfile.Saveflag && *tStep == 0) p->outfile.SaveHflag = TRUE; return errcode; } int DLLEXPORT EN_closeH(EN_Project p) /*---------------------------------------------------------------- ** Input: none ** Output: none ** Returns: error code ** Purpose: closes a project's hydraulic solver **---------------------------------------------------------------- */ { if (!p->Openflag) return 102; if (p->hydraul.OpenHflag) { closeleakage(p); closehyd(p); } p->hydraul.OpenHflag = FALSE; return 0; } int DLLEXPORT EN_savehydfile(EN_Project p, const char *filename) /*---------------------------------------------------------------- ** Input: filename = name of file to which hydraulic results are saved ** Output: none ** Returns: error code ** Purpose: saves results from a scratch hydraulics file to a ** permanent one **---------------------------------------------------------------- */ { FILE *f; FILE *HydFile; int c; // Check that hydraulics results exist if (p->outfile.HydFile == NULL || !p->outfile.SaveHflag) return 104; // Open the permanent hydraulics file if ((f = fopen(filename, "w+b")) == NULL) return 305; // Copy from the scratch file to f HydFile = p->outfile.HydFile; fseek(HydFile, 0, SEEK_SET); while ((c = fgetc(HydFile)) != EOF) fputc(c, f); fclose(f); return 0; } int DLLEXPORT EN_usehydfile(EN_Project p, const char *filename) /*---------------------------------------------------------------- ** Input: filename = name of previously saved hydraulics file ** Output: none ** Returns: error code ** Purpose: uses contents of a previously saved hydraulics file to ** run a water quality analysis **---------------------------------------------------------------- */ { int errcode; // Check that project was opened & hydraulic solver is closed if (!p->Openflag) return 102; if (p->hydraul.OpenHflag) return 108; // Try to open hydraulics file strncpy(p->outfile.HydFname, filename, MAXFNAME); p->outfile.Hydflag = USE; p->outfile.SaveHflag = TRUE; errcode = openhydfile(p); // If error, then reset flags if (errcode) { strcpy(p->outfile.HydFname, ""); p->outfile.Hydflag = SCRATCH; p->outfile.SaveHflag = FALSE; } return errcode; } /******************************************************************** Water Quality Analysis Functions ********************************************************************/ int DLLEXPORT EN_solveQ(EN_Project p) /*---------------------------------------------------------------- ** Input: none ** Output: none ** Returns: error code ** Purpose: solves for network water quality in all time periods **---------------------------------------------------------------- */ { int errcode; long t, tstep; // Open WQ solver errcode = EN_openQ(p); if (!errcode) { // Initialize WQ solver errcode = EN_initQ(p, EN_SAVE); if (!p->quality.Qualflag) writewin(p->viewprog, FMT106); // Analyze each hydraulic period if (!errcode) do { // Display progress message sprintf(p->Msg, "%-10s", clocktime(p->report.Atime, p->times.Htime)); if (p->quality.Qualflag) { sprintf(p->Msg, FMT102, p->report.Atime); writewin(p->viewprog, p->Msg); } // Retrieve current hydraulic results & update water quality // to start of next time period tstep = 0; ERRCODE(EN_runQ(p, &t)); ERRCODE(EN_nextQ(p, &tstep)); } while (tstep > 0); } // Close WQ solver EN_closeQ(p); return errcode; } int DLLEXPORT EN_openQ(EN_Project p) /*---------------------------------------------------------------- ** Input: none ** Output: none ** Returns: error code ** Purpose: opens a project's water quality solver **---------------------------------------------------------------- */ { int errcode = 0; // Check that hydraulics results exist p->quality.OpenQflag = FALSE; p->outfile.SaveQflag = FALSE; if (!p->Openflag) return 102; if (!p->hydraul.OpenHflag && !p->outfile.SaveHflag) return 104; // Open water quality solver ERRCODE(openqual(p)); if (!errcode) p->quality.OpenQflag = TRUE; else errmsg(p, errcode); return errcode; } int DLLEXPORT EN_initQ(EN_Project p, int saveFlag) /*---------------------------------------------------------------- ** Input: saveFlag = flag indicating if results should be saved ** to the binary output file or not ** Output: none ** Returns: error code ** Purpose: initializes the water quality solver **---------------------------------------------------------------- */ { int errcode = 0; if (!p->quality.OpenQflag) return 105; initqual(p); p->outfile.SaveQflag = FALSE; p->outfile.Saveflag = FALSE; if (saveFlag) { errcode = openoutfile(p); if (!errcode) p->outfile.Saveflag = TRUE; } return errcode; } int DLLEXPORT EN_runQ(EN_Project p, long *currentTime) /*---------------------------------------------------------------- ** Input: none ** Output: currentTime = current simulation time (sec) ** Returns: error code ** Purpose: retrieves current hydraulic results and saves current ** results to file. **---------------------------------------------------------------- */ { int errcode; *currentTime = 0; if (!p->quality.OpenQflag) return 105; errcode = runqual(p, currentTime); if (errcode) errmsg(p, errcode); return errcode; } int DLLEXPORT EN_nextQ(EN_Project p, long *tStep) /*---------------------------------------------------------------- ** Input: none ** Output: tStep = time step over which water quality is updated (sec) ** Returns: error code ** Purpose: updates water quality throughout the network until ** next hydraulic event occurs **---------------------------------------------------------------- */ { int errcode; *tStep = 0; if (!p->quality.OpenQflag) return 105; errcode = nextqual(p, tStep); if (!errcode && p->outfile.Saveflag && *tStep == 0) { p->outfile.SaveQflag = TRUE; } if (errcode) errmsg(p, errcode); return errcode; } int DLLEXPORT EN_stepQ(EN_Project p, long *timeLeft) /*---------------------------------------------------------------- ** Input: none ** Output: timeLeft = amount of simulation time remaining (sec) ** Returns: error code ** Purpose: updates water quality throughout the network over ** fixed time step **---------------------------------------------------------------- */ { int errcode; *timeLeft = 0; if (!p->quality.OpenQflag) return 105; errcode = stepqual(p, timeLeft); if (!errcode && p->outfile.Saveflag && *timeLeft == 0) { p->outfile.SaveQflag = TRUE; } if (errcode) errmsg(p, errcode); return errcode; } int DLLEXPORT EN_closeQ(EN_Project p) /*---------------------------------------------------------------- ** Input: none ** Output: none ** Returns: error code ** Purpose: closes a project's water quality solver **---------------------------------------------------------------- */ { if (!p->Openflag) return 102; closequal(p); p->quality.OpenQflag = FALSE; closeoutfile(p); writetime(p, FMT105); return 0; } /******************************************************************** Reporting Functions ********************************************************************/ int DLLEXPORT EN_setreportcallback(EN_Project p, void (*callback)(void*,void*,const char*)) /*---------------------------------------------------------------- ** Input: callback = a pointer to a reporting function ** Output: none ** Returns: error code ** Purpose: replaces EPANET's normal use of a designated report file **---------------------------------------------------------------- */ { p->report.reportCallback = callback; return 0; } int DLLEXPORT EN_setreportcallbackuserdata(EN_Project p, void *userData) /*---------------------------------------------------------------- ** Input: userData = a pointer to a client-side data object ** Output: none ** Returns: error code ** Purpose: sets the client-side data object used in conjunction with ** the callback function in EN_setreportcallback **---------------------------------------------------------------- */ { p->report.reportCallbackUserData = userData; return 0; } int DLLEXPORT EN_writeline(EN_Project p, const char *line) /*---------------------------------------------------------------- ** Input: line = line of text ** Output: none ** Returns: error code ** Purpose: write a line of text to a project's report file **---------------------------------------------------------------- */ { if (!p->Openflag) return 102; writeline(p, line); return 0; } int DLLEXPORT EN_report(EN_Project p) /*---------------------------------------------------------------- ** Input: none ** Output: none ** Returns: error code ** Purpose: writes formatted simulation results to a project's ** report file **---------------------------------------------------------------- */ { int errcode; // Check if results have been saved to binary output file if (!p->outfile.SaveQflag) return 106; writewin(p->viewprog, FMT103); // Write the formatted report errcode = writereport(p); if (errcode) errmsg(p, errcode); return errcode; } int DLLEXPORT EN_copyreport(EN_Project p, const char *filename) /*---------------------------------------------------------------- ** Input: filename = name of file to receive copy of report ** Output: none ** Returns: error code ** Purpose: copies the contents of a project's report file to ** another file **---------------------------------------------------------------- */ { return copyreport(p, filename); } int DLLEXPORT EN_clearreport(EN_Project p) /*---------------------------------------------------------------- ** Input: none ** Output: none ** Returns: error code ** Purpose: clears the contents of a project's report file **---------------------------------------------------------------- */ { return clearreport(p); } int DLLEXPORT EN_resetreport(EN_Project p) /*---------------------------------------------------------------- ** Input: none ** Output: none ** Returns: error code ** Purpose: resets reporting options to their default values **---------------------------------------------------------------- */ { int i; if (!p->Openflag) return 102; initreport(&p->report); for (i = 1; i <= p->network.Nnodes; i++) { p->network.Node[i].Rpt = 0; } for (i = 1; i <= p->network.Nlinks; i++) { p->network.Link[i].Rpt = 0; } return 0; } int DLLEXPORT EN_setreport(EN_Project p, const char *format) /*---------------------------------------------------------------- ** Input: format = a report formatting command ** Output: none ** Returns: error code ** Purpose: sets a specific set of reporting options **---------------------------------------------------------------- */ { char s1[MAXLINE + 1]; if (!p->Openflag) return 102; if (strlen(format) >= MAXLINE) return 250; strcpy(s1, format); strcat(s1, "\n"); if (setreport(p, s1) > 0) return 250; else return 0; } int DLLEXPORT EN_setstatusreport(EN_Project p, int level) /*---------------------------------------------------------------- ** Input: level = level of reporting to use (see EN_StatusReport) ** Output: none ** Returns: error code ** Purpose: sets the level of hydraulic status reporting **---------------------------------------------------------------- */ { int errcode = 0; if (level >= EN_NO_REPORT && level <= EN_FULL_REPORT) { p->report.Statflag = (char)level; } else errcode = 251; return errcode; } int DLLEXPORT EN_getversion(int *version) /*---------------------------------------------------------------- ** Input: none ** Output: version = version number of the source code ** Returns: error code (should always be 0) ** Purpose: retrieves the toolkit API version number ** ** The version number is set by the constant CODEVERSION found in ** TYPES.H and is to be interpreted with implied decimals, i.e., ** "20100" == "2(.)01(.)00". **---------------------------------------------------------------- */ { *version = CODEVERSION; return 0; } int DLLEXPORT EN_geterror(int errcode, char *errmsg, int maxLen) /*---------------------------------------------------------------- ** Input: errcode = an error or warnng code ** maxLen = maximum characters that errmsg can hold ** Output: errmsg = text of error/warning message ** Returns: error code ** Purpose: retrieves the text of the message associated with ** a particular error/warning code **---------------------------------------------------------------- */ { char msg1[MAXMSG+1] = ""; char msg2[MAXMSG+1] = ""; switch (errcode) { case 1: strncpy(errmsg, WARN1, maxLen); break; case 2: strncpy(errmsg, WARN2, maxLen); break; case 3: strncpy(errmsg, WARN3, maxLen); break; case 4: strncpy(errmsg, WARN4, maxLen); break; case 5: strncpy(errmsg, WARN5, maxLen); break; case 6: strncpy(errmsg, WARN6, maxLen); break; default: sprintf(msg1, "Error %d: ", errcode); if ((errcode >= 202 && errcode <= 222) || (errcode >= 240 && errcode <= 261)) strcat(msg1, "function call contains "); snprintf(errmsg, maxLen, "%s%s", msg1, geterrmsg(errcode, msg2)); } if (strlen(errmsg) == 0) return 251; else return 0; } int DLLEXPORT EN_getstatistic(EN_Project p, int type, double *value) /*---------------------------------------------------------------- ** Input: type = type of simulation statistic (see EN_AnalysisStatistic) ** Output: value = simulation analysis statistic value ** Returns: error code ** Purpose: retrieves the value of a simulation analysis statistic **---------------------------------------------------------------- */ { switch (type) { case EN_ITERATIONS: *value = p->hydraul.Iterations; break; case EN_RELATIVEERROR: *value = p->hydraul.RelativeError; break; case EN_MAXHEADERROR: *value = p->hydraul.MaxHeadError * p->Ucf[HEAD]; break; case EN_MAXFLOWCHANGE: *value = p->hydraul.MaxFlowChange * p->Ucf[FLOW]; break; case EN_DEFICIENTNODES: *value = p->hydraul.DeficientNodes; break; case EN_DEMANDREDUCTION: *value = p->hydraul.DemandReduction; break; case EN_LEAKAGELOSS: *value = p->hydraul.LeakageLoss; break; case EN_MASSBALANCE: *value = p->quality.MassBalance.ratio; break; default: *value = 0.0; return 251; } return 0; } int DLLEXPORT EN_getresultindex(EN_Project p, int type, int index, int *value) /*---------------------------------------------------------------- ** Input: type = type of object (either EN_NODE or EN_LINK) ** index = the object's index ** Output: value = the order in which the object's results were saved ** Returns: error code ** Purpose: retrieves the order in which a node's or link's results ** were saved to an output file. **---------------------------------------------------------------- */ { *value = 0; if (!p->Openflag) return 102; if (type == EN_NODE) { if (index <= 0 || index > p->network.Nnodes) return 203; *value = p->network.Node[index].ResultIndex; } else if (type == EN_LINK) { if (index <= 0 || index > p->network.Nlinks) return 204; *value = p->network.Link[index].ResultIndex; } else return 251; return 0; } /******************************************************************** Analysis Options Functions ********************************************************************/ int DLLEXPORT EN_getoption(EN_Project p, int option, double *value) /*---------------------------------------------------------------- ** Input: option = an analysis option code (see EN_Option) ** Output: value = analysis option value ** Returns: error code ** Purpose: retrieves the value of an analysis option **---------------------------------------------------------------- */ { Hydraul *hyd = &p->hydraul; Quality *qual = &p->quality; double *Ucf = p->Ucf; double v = 0.0; *value = 0.0; if (!p->Openflag) return 102; switch (option) { case EN_TRIALS: v = (double)hyd->MaxIter; break; case EN_ACCURACY: v = hyd->Hacc; break; case EN_TOLERANCE: v = qual->Ctol * Ucf[QUALITY]; break; case EN_EMITEXPON: if (hyd->Qexp > 0.0) v = 1.0 / hyd->Qexp; break; case EN_DEMANDMULT: v = hyd->Dmult; break; case EN_HEADERROR: v = hyd->HeadErrorLimit * Ucf[HEAD]; break; case EN_FLOWCHANGE: v = hyd->FlowChangeLimit * Ucf[FLOW]; break; case EN_HEADLOSSFORM: v = hyd->Formflag; break; case EN_GLOBALEFFIC: v = hyd->Epump; break; case EN_GLOBALPRICE: v = hyd->Ecost; break; case EN_GLOBALPATTERN: v = hyd->Epat; break; case EN_DEMANDCHARGE: v = hyd->Dcost; break; case EN_SP_GRAVITY: v = hyd->SpGrav; break; case EN_SP_VISCOS: v = hyd->Viscos / VISCOS; break; case EN_UNBALANCED: v = hyd->ExtraIter; break; case EN_CHECKFREQ: v = hyd->CheckFreq; break; case EN_MAXCHECK: v = hyd->MaxCheck; break; case EN_DAMPLIMIT: v = hyd->DampLimit; break; case EN_SP_DIFFUS: v = qual->Diffus / DIFFUS; break; case EN_BULKORDER: v = qual->BulkOrder; break; case EN_WALLORDER: v = qual->WallOrder; break; case EN_TANKORDER: v = qual->TankOrder; break; case EN_CONCENLIMIT: v = qual->Climit * p->Ucf[QUALITY]; break; case EN_DEMANDPATTERN: v = hyd->DefPat; break; case EN_EMITBACKFLOW: v = hyd->EmitBackFlag; break; case EN_PRESS_UNITS: v = (double)p->parser.Pressflag; break; case EN_STATUS_REPORT: v = (double)( p->report.Statflag); break; default: return 251; } *value = (double)v; return 0; } int DLLEXPORT EN_setoption(EN_Project p, int option, double value) /*---------------------------------------------------------------- ** Input: option = analysis option code (see EN_Option) ** value = analysis option value ** Output: none ** Returns: error code ** Purpose: sets the value for an analysis option **---------------------------------------------------------------- */ { Network *net = &p->network; Hydraul *hyd = &p->hydraul; Quality *qual = &p->quality; int Njuncs = net->Njuncs; double *Ucf = p->Ucf; int i, j, pat, unit; double Ke, n, ucf; double qfactor, hfactor, pfactor, dfactor; double dcf, pcf, hcf, qcf; if (!p->Openflag) return 102; // The EN_UNBALANCED option can be < 0 indicating that the simulation // should be halted if no convergence is reached in EN_TRIALS. Other // values set the number of additional trials to use with no more // link status changes to achieve convergence. if (option == EN_UNBALANCED) { hyd->ExtraIter = (int)value; if (hyd->ExtraIter < 0) hyd->ExtraIter = -1; return 0; } // All other option values must be non-negative if (value < 0.0) return 213; // Process the speficied option switch (option) { case EN_TRIALS: if (value < 1.0) return 213; hyd->MaxIter = (int)value; break; case EN_ACCURACY: if (value < 1.e-8 || value > 1.e-1) return 213; hyd->Hacc = value; break; case EN_TOLERANCE: qual->Ctol = value / Ucf[QUALITY]; break; case EN_EMITEXPON: if (value <= 0.0) return 213; n = 1.0 / value; ucf = pow(Ucf[FLOW], n) / Ucf[PRESSURE]; for (i = 1; i <= Njuncs; i++) { j = EN_getnodevalue(p, i, EN_EMITTER, &Ke); if (j == 0 && Ke > 0.0) net->Node[i].Ke = ucf / pow(Ke, n); } hyd->Qexp = n; break; case EN_DEMANDMULT: hyd->Dmult = value; break; case EN_HEADERROR: hyd->HeadErrorLimit = value / Ucf[HEAD]; break; case EN_FLOWCHANGE: hyd->FlowChangeLimit = value / Ucf[FLOW]; break; case EN_HEADLOSSFORM: // Can't change if hydraulic solver is open if (p->hydraul.OpenHflag) return 262; i = ROUND(value); if (i < HW || i > CM) return 213; hyd->Formflag = i; if (hyd->Formflag == HW) hyd->Hexp = 1.852; else hyd->Hexp = 2.0; break; case EN_GLOBALEFFIC: if (value <= 1.0 || value > 100.0) return 213; hyd->Epump = value; break; case EN_GLOBALPRICE: hyd->Ecost = value; break; case EN_GLOBALPATTERN: pat = ROUND(value); if (pat < 0 || pat > net->Npats) return 205; hyd->Epat = pat; break; case EN_DEMANDCHARGE: hyd->Dcost = value; break; case EN_SP_GRAVITY: if (value <= 0.0) return 213; Ucf[PRESSURE] *= (value / hyd->SpGrav); hyd->SpGrav = value; break; case EN_SP_VISCOS: if (value <= 0.0) return 213; hyd->Viscos = value * VISCOS; break; case EN_CHECKFREQ: hyd->CheckFreq = (int)value; break; case EN_MAXCHECK: hyd->MaxCheck = (int)value; break; case EN_DAMPLIMIT: hyd->DampLimit = value; break; case EN_SP_DIFFUS: qual->Diffus = value * DIFFUS; break; case EN_BULKORDER: qual->BulkOrder = value; break; case EN_WALLORDER: if (value == 0.0 || value == 1.0) qual->WallOrder = value; else return 213; break; case EN_TANKORDER: qual->TankOrder = value; break; case EN_CONCENLIMIT: qual->Climit = value / p->Ucf[QUALITY]; break; case EN_DEMANDPATTERN: pat = ROUND(value); if (pat < 0 || pat > net->Npats) return 205; hyd->DefPat = pat; break; case EN_EMITBACKFLOW: if (value == 0.0 || value == 1.0) hyd->EmitBackFlag = (int)value; else return 213; break; case EN_PRESS_UNITS: unit = ROUND(value); if (unit < 0 || unit > METERS) return 205; if (p->parser.Unitsflag == US && unit > PSI) return 0; if (p->parser.Unitsflag == SI && unit == PSI) return 0; p->parser.Pressflag = unit; dfactor = Ucf[DEMAND]; pfactor = Ucf[PRESSURE]; hfactor = Ucf[HEAD]; qfactor = Ucf[FLOW]; initunits(p); // Update units in rules dcf = Ucf[DEMAND] / dfactor; pcf = Ucf[PRESSURE] / pfactor; hcf = Ucf[HEAD] / hfactor; qcf = Ucf[FLOW] / qfactor; updateruleunits(p, dcf, pcf, hcf, qcf); break; case EN_STATUS_REPORT: i = ROUND(value); if (i < EN_NO_REPORT || i > EN_FULL_REPORT) return 213; p->report.Statflag = i; break; default: return 251; } return 0; } int DLLEXPORT EN_getflowunits(EN_Project p, int *units) /*---------------------------------------------------------------- ** Input: none ** Output: units = flow units code (see EN_FlowUnits) ** Returns: error code ** Purpose: retrieves the flow units used by a project **---------------------------------------------------------------- */ { *units = -1; if (!p->Openflag) return 102; *units = p->parser.Flowflag; return 0; } int DLLEXPORT EN_setflowunits(EN_Project p, int units) /*---------------------------------------------------------------- ** Input: units = flow units code (see EN_FlowUnits) ** Output: none ** Returns: error code ** Purpose: sets the flow units used by a project **---------------------------------------------------------------- */ { Network *net = &p->network; int i, j; double qfactor, vfactor, hfactor, efactor, pfactor, dfactor, xfactor, yfactor; double dcf, pcf, hcf, qcf; double *Ucf = p->Ucf; if (!p->Openflag) return 102; // Determine unit system based on flow units qfactor = Ucf[FLOW]; vfactor = Ucf[VOLUME]; hfactor = Ucf[HEAD]; efactor = Ucf[ELEV]; pfactor = Ucf[PRESSURE]; dfactor = Ucf[DEMAND]; p->parser.Flowflag = units; switch (units) { case LPS: case LPM: case MLD: case CMH: case CMD: case CMS: p->parser.Unitsflag = SI; break; default: p->parser.Unitsflag = US; break; } // Revise pressure units depending on flow units if (p->parser.Unitsflag != SI) p->parser.Pressflag = PSI; else if (p->parser.Pressflag == PSI) p->parser.Pressflag = METERS; initunits(p); // Update pressure units in rules dcf = Ucf[DEMAND] / dfactor; pcf = Ucf[PRESSURE] / pfactor; hcf = Ucf[HEAD] / hfactor; qcf = Ucf[FLOW] / qfactor; updateruleunits(p, dcf, pcf, hcf, qcf); //update curves for (i = 1; i <= net->Ncurves; i++) { switch (net->Curve[i].Type) { case VOLUME_CURVE: xfactor = efactor / Ucf[ELEV]; yfactor = vfactor / Ucf[VOLUME]; break; case HLOSS_CURVE: case PUMP_CURVE: xfactor = qfactor / Ucf[FLOW]; yfactor = hfactor / Ucf[HEAD]; break; case EFFIC_CURVE: xfactor = qfactor / Ucf[FLOW]; yfactor = 1; break; default: xfactor = 1; yfactor = 1; } for (j = 0; j < net->Curve[i].Npts; j++) { net->Curve[i].X[j] = net->Curve[i].X[j] / xfactor; net->Curve[i].Y[j] = net->Curve[i].Y[j] / yfactor; } } return 0; } int DLLEXPORT EN_gettimeparam(EN_Project p, int param, long *value) /*---------------------------------------------------------------- ** Input: param = time parameter code (see EN_TimeParameter) ** Output: value = time parameter value ** Returns: error code ** Purpose: retrieves the value of a time parameter **---------------------------------------------------------------- */ { Report *rpt = &p->report; Times *time = &p->times; int i; *value = 0; if (!p->Openflag) return 102; if (param < EN_DURATION || param > EN_NEXTEVENTTANK) return 251; switch (param) { case EN_DURATION: *value = time->Dur; break; case EN_HYDSTEP: *value = time->Hstep; break; case EN_QUALSTEP: *value = time->Qstep; break; case EN_PATTERNSTEP: *value = time->Pstep; break; case EN_PATTERNSTART: *value = time->Pstart; break; case EN_REPORTSTEP: *value = time->Rstep; break; case EN_REPORTSTART: *value = time->Rstart; break; case EN_RULESTEP: *value = time->Rulestep; break; case EN_STATISTIC: *value = rpt->Tstatflag; break; case EN_PERIODS: *value = rpt->Nperiods; break; case EN_STARTTIME: *value = time->Tstart; break; case EN_HTIME: *value = time->Htime; break; case EN_QTIME: *value = time->Qtime; case EN_HALTFLAG: break; case EN_NEXTEVENT: *value = time->Hstep; // find the lesser of the hydraulic time step length, // or the time to next full/empty tank tanktimestep(p, value); break; case EN_NEXTEVENTTANK: *value = time->Hstep; i = tanktimestep(p, value); *value = i; break; default: return 251; } return 0; } int DLLEXPORT EN_settimeparam(EN_Project p, int param, long value) /*---------------------------------------------------------------- ** Input: param = time parameter code (see EN_TimeParameter) ** value = time parameter value ** Output: none ** Returns: error code ** Purpose: sets the value of a time parameter **---------------------------------------------------------------- */ { Report *rpt = &p->report; Times *time = &p->times; if (!p->Openflag) return 102; if (value < 0) return 213; switch (param) { case EN_DURATION: time->Dur = value; if (time->Rstart > time->Dur) time->Rstart = 0; break; case EN_HYDSTEP: if (value == 0) return 213; time->Hstep = value; time->Hstep = MIN(time->Pstep, time->Hstep); time->Hstep = MIN(time->Rstep, time->Hstep); time->Qstep = MIN(time->Qstep, time->Hstep); break; case EN_QUALSTEP: if (value == 0) return 213; time->Qstep = value; time->Qstep = MIN(time->Qstep, time->Hstep); break; case EN_PATTERNSTEP: if (value == 0) return 213; time->Pstep = value; if (time->Hstep > time->Pstep) time->Hstep = time->Pstep; break; case EN_PATTERNSTART: time->Pstart = value; break; case EN_REPORTSTEP: if (value == 0) return 213; time->Rstep = value; if (time->Hstep > time->Rstep) time->Hstep = time->Rstep; break; case EN_REPORTSTART: if (time->Rstart > time->Dur) return 213; time->Rstart = value; break; case EN_RULESTEP: if (value == 0) return 213; time->Rulestep = value; time->Rulestep = MIN(time->Rulestep, time->Hstep); break; case EN_STATISTIC: if (value > RANGE) return 213; rpt->Tstatflag = (char)value; break; case EN_HTIME: time->Htime = value; break; case EN_QTIME: time->Qtime = value; break; case EN_STARTTIME: if (value > SECperDAY) return 213; time->Tstart = value; break; default: return 251; } return 0; } /// get the time to next event, and give a reason for the time step truncation int DLLEXPORT EN_timetonextevent(EN_Project p, int *eventType, long *duration, int *elementIndex) /*---------------------------------------------------------------- ** Input: none ** Output: eventType = event causing a new time step ** to occur (see EN_TimestepEvent) ** duration = seconds until next time step occurs ** elementIndex = index of tank node or simple control ** that triggers a new time step ** Returns: error code ** Purpose: Get information about when the next hydraulic time step occurs **---------------------------------------------------------------- */ { Times *time = &p->times; long hydStep, tankStep, controlStep; int iTank, iControl; hydStep = time->Hstep; tankStep = hydStep; controlStep = hydStep; iTank = tanktimestep(p, &tankStep); iControl = controltimestep(p, &controlStep); // return the lesser of the three step lengths if (controlStep < tankStep) { *eventType = (int)EN_STEP_CONTROLEVENT; *duration = controlStep; *elementIndex = iControl; } else if (tankStep < hydStep) { *eventType = (int)EN_STEP_TANKEVENT; *duration = tankStep; *elementIndex = iTank; } else { *eventType = (int)EN_STEP_HYD; *duration = hydStep; *elementIndex = 0; } return 0; } int DLLEXPORT EN_getqualinfo(EN_Project p, int *qualType, char *chemName, char *chemUnits, int *traceNode) /*---------------------------------------------------------------- ** Input: none ** Output: qualType = type of quality analysis to run (see EN_QualityType) ** chemName = name of chemical constituent ** chemUnits = concentration units of constituent ** traceNode = index of node being traced (if applicable) ** Returns: error code ** Purpose: retrieves water quality analysis options **---------------------------------------------------------------- */ { EN_getqualtype(p, qualType, traceNode); if (p->quality.Qualflag == CHEM) { strncpy(chemName, p->quality.ChemName, MAXID); strncpy(chemUnits, p->quality.ChemUnits, MAXID); } else if (p->quality.Qualflag == TRACE) { strncpy(chemName, w_TRACE, MAXID); strncpy(chemUnits, u_PERCENT, MAXID); } else if (p->quality.Qualflag == AGE) { strncpy(chemName, w_AGE, MAXID); strncpy(chemUnits, u_HOURS, MAXID); } else { strncpy(chemName, "", MAXID); strncpy(chemUnits, "", MAXID); } return 0; } int DLLEXPORT EN_getqualtype(EN_Project p, int *qualType, int *traceNode) /*---------------------------------------------------------------- ** Input: none ** Output: qualType = type of quality analysis to run (see EN_QualityType) ** traceNode = index of node being traced (for qualType = EN_TRACE) ** Output: none ** Returns: error code ** Purpose: retrieves type of quality analysis being made **---------------------------------------------------------------- */ { *traceNode = 0; if (!p->Openflag) return 102; *qualType = p->quality.Qualflag; if (p->quality.Qualflag == TRACE) *traceNode = p->quality.TraceNode; return 0; } int DLLEXPORT EN_setqualtype(EN_Project p, int qualType, const char *chemName, const char *chemUnits, const char *traceNode) /*---------------------------------------------------------------- ** Input: qualType = type of quality analysis to run (see EN_QualityType) ** chemname = name of chemical constituent ** chemunits = concentration units of constituent ** tracenode = ID name of node being traced (if applicable) ** Output: none ** Returns: error code ** Purpose: sets water quality analysis options **---------------------------------------------------------------- */ { Network *net = &p->network; Report *rpt = &p->report; Quality *qual = &p->quality; double *Ucf = p->Ucf; int i, oldQualFlag, traceNodeIndex; double ccf = 1.0; if (!p->Openflag) return 102; if (qual->OpenQflag) return 262; if (qualType < NONE || qualType > TRACE) return 251; if (qualType == TRACE) { traceNodeIndex = findnode(net, traceNode); if (traceNodeIndex == 0) return 212; } oldQualFlag = qual->Qualflag; qual->Qualflag = qualType; qual->Ctol *= Ucf[QUALITY]; if (qual->Qualflag == CHEM) // Chemical analysis { strncpy(qual->ChemName, chemName, MAXID); strncpy(qual->ChemUnits, chemUnits, MAXID); strncpy(rpt->Field[QUALITY].Units, qual->ChemUnits, MAXID); strncpy(rpt->Field[REACTRATE].Units, qual->ChemUnits, MAXID); strcat(rpt->Field[REACTRATE].Units, t_PERDAY); ccf = 1.0 / LperFT3; } if (qual->Qualflag == TRACE) // Source trace analysis { qual->TraceNode = findnode(net, traceNode); if (qual->TraceNode == 0) return 212; strncpy(qual->ChemName, w_TRACE, MAXID); strncpy(qual->ChemUnits, u_PERCENT, MAXID); strcpy(rpt->Field[QUALITY].Units, u_PERCENT); } if (qual->Qualflag == AGE) // Water age analysis { strncpy(qual->ChemName, w_AGE, MAXID); strncpy(qual->ChemUnits, u_HOURS, MAXID); strcpy(rpt->Field[QUALITY].Units, u_HOURS); } // When changing from CHEM to AGE or TRACE, nodes initial quality // values must be returned to their original ones if ((qual->Qualflag == AGE || qual->Qualflag == TRACE) && oldQualFlag == CHEM) { for (i = 1; i <= p->network.Nnodes; i++) { p->network.Node[i].C0 *= Ucf[QUALITY]; } } Ucf[QUALITY] = ccf; Ucf[LINKQUAL] = ccf; Ucf[REACTRATE] = ccf; qual->Ctol /= Ucf[QUALITY]; return 0; } /******************************************************************** Node Functions ********************************************************************/ int DLLEXPORT EN_addnode(EN_Project p, const char *id, int nodeType, int *index) /*---------------------------------------------------------------- ** Input: id = node ID name ** nodeType = type of node (see EN_NodeType) ** Output: index = index of newly added node ** Returns: error code ** Purpose: adds a new node to a project **---------------------------------------------------------------- */ { Network *net = &p->network; Hydraul *hyd = &p->hydraul; Quality *qual = &p->quality; int i, nIdx, size; Stank *tank; Snode *node; Scontrol *control; // Cannot modify network structure while solvers are active *index = 0; if (!p->Openflag) return 102; if (hyd->OpenHflag || qual->OpenQflag) return 262; // Check if id name contains invalid characters if (!namevalid(id)) return 252; // Check if a node with same id already exists if (EN_getnodeindex(p, id, &i) == 0) return 215; // Check for valid node type if (nodeType < EN_JUNCTION || nodeType > EN_TANK) return 251; // Grow node-related arrays to accomodate the new node size = (net->Nnodes + 2) * sizeof(Snode); net->Node = (Snode *)realloc(net->Node, size); size = (net->Nnodes + 2) * sizeof(double); hyd->NodeDemand = (double *)realloc(hyd->NodeDemand, size); qual->NodeQual = (double *)realloc(qual->NodeQual, size); hyd->NodeHead = (double *)realloc(hyd->NodeHead, size); hyd->FullDemand = (double *)realloc(hyd->FullDemand, size); hyd->EmitterFlow = (double *)realloc(hyd->EmitterFlow, size); hyd->LeakageFlow = (double *)realloc(hyd->LeakageFlow, size); hyd->DemandFlow = (double *)realloc(hyd->DemandFlow, size); // Actions taken when a new Junction is added if (nodeType == EN_JUNCTION) { // shift indices of non-Junction nodes at end of Node array for (i = net->Nnodes; i > net->Njuncs; i--) { hashtable_update(net->NodeHashTable, net->Node[i].ID, i + 1); net->Node[i + 1] = net->Node[i]; } // set index of new Junction node net->Njuncs++; nIdx = net->Njuncs; node = &net->Node[nIdx]; node->D = NULL; adddemand(node, 0.0, 0, NULL); // shift indices of Tank array for (i = 1; i <= net->Ntanks; i++) { net->Tank[i].Node += 1; } // shift indices of Links, if necessary for (i = 1; i <= net->Nlinks; i++) { if (net->Link[i].N1 > net->Njuncs - 1) net->Link[i].N1 += 1; if (net->Link[i].N2 > net->Njuncs - 1) net->Link[i].N2 += 1; } // shift indices of tanks/reservoir nodes in controls for (i = 1; i <= net->Ncontrols; ++i) { control = &net->Control[i]; if (control->Node > net->Njuncs - 1) control->Node += 1; } // adjust indices of tanks/reservoirs in Rule premises (see RULES.C) adjusttankrules(p, 1); } // Actions taken when a new Tank/Reservoir is added else { nIdx = net->Nnodes + 1; node = &net->Node[nIdx]; node->D = NULL; net->Ntanks++; // resize tanks array net->Tank = (Stank *)realloc(net->Tank, (net->Ntanks + 1) * sizeof(Stank)); tank = &net->Tank[net->Ntanks]; // set default values for new tank or reservoir tank->Node = nIdx; tank->Pat = 0; if (nodeType == EN_TANK) tank->A = 1.0; else tank->A = 0; tank->Hmin = 0; tank->Hmax = 0; tank->H0 = 0; tank->Vmin = 0; tank->Vmax = 0; tank->V0 = 0; tank->Kb = 0; tank->V = 0; tank->C = 0; tank->Pat = 0; tank->Vcurve = 0; tank->MixModel = 0; tank->V1frac = 1; tank->CanOverflow = FALSE; } net->Nnodes++; p->parser.MaxNodes = net->Nnodes; strncpy(node->ID, id, MAXID); // set default values for new node node->Type = nodeType; node->El = 0; node->S = NULL; node->C0 = 0; node->Ke = 0; node->Rpt = 0; node->ResultIndex = 0; node->X = MISSING; node->Y = MISSING; node->Comment = NULL; node->Tag = NULL; // Insert new node into hash table hashtable_insert(net->NodeHashTable, node->ID, nIdx); *index = nIdx; return 0; } int DLLEXPORT EN_deletenode(EN_Project p, int index, int actionCode) /*---------------------------------------------------------------- ** Input: index = index of the node to delete ** actionCode = how to treat controls that contain the link ** or its incident links: ** EN_UNCONDITIONAL deletes all such controls plus the node, ** EN_CONDITIONAL does not delete the node if it or any of ** its links appear in a control and returns an error code ** Output: none ** Returns: error code ** Purpose: deletes a node from a project **---------------------------------------------------------------- */ { Network *net = &p->network; int i, nodeType, tankindex; Snode *node; // Cannot modify network structure while solvers are active if (!p->Openflag) return 102; if (p->hydraul.OpenHflag || p->quality.OpenQflag) return 262; // Check that node exists if (index <= 0 || index > net->Nnodes) return 203; if (actionCode < EN_UNCONDITIONAL || actionCode > EN_CONDITIONAL) return 251; // Can't delete a water quality trace node if (index == p->quality.TraceNode) return 260; // Do not delete a node contained in a control or is connected to a link if (actionCode == EN_CONDITIONAL) { if (incontrols(p, NODE, index)) return 261; for (i = 1; i <= net->Nlinks; i++) { if (net->Link[i].N1 == index || net->Link[i].N2 == index) return 259; } } // Get a reference to the node & its type node = &net->Node[index]; EN_getnodetype(p, index, &nodeType); // Remove node from its hash table hashtable_delete(net->NodeHashTable, node->ID); // Free memory allocated to node's demands, WQ source & comment freedemands(node); free(node->S); free(node->Comment); free(node->Tag); // Shift position of higher entries in Node & Coord arrays down one for (i = index; i <= net->Nnodes - 1; i++) { net->Node[i] = net->Node[i + 1]; // ... update node's entry in the hash table hashtable_update(net->NodeHashTable, net->Node[i].ID, i); } // If deleted node is a tank, remove it from the Tank array if (nodeType != EN_JUNCTION) { tankindex = findtank(net, index); for (i = tankindex; i <= net->Ntanks - 1; i++) { net->Tank[i] = net->Tank[i + 1]; } } // Shift higher node indices in Tank array down one for (i = 1; i <= net->Ntanks; i++) { if (net->Tank[i].Node > index) net->Tank[i].Node -= 1; } // Delete any links connected to the deleted node // (Process links in reverse order to maintain their indexing) for (i = net->Nlinks; i >= 1; i--) { if (net->Link[i].N1 == index || net->Link[i].N2 == index) EN_deletelink(p, i, EN_UNCONDITIONAL); } // Adjust indices of all link end nodes for (i = 1; i <= net->Nlinks; i++) { if (net->Link[i].N1 > index) net->Link[i].N1 -= 1; if (net->Link[i].N2 > index) net->Link[i].N2 -= 1; } // Delete any control containing the node for (i = net->Ncontrols; i >= 1; i--) { if (net->Control[i].Node == index) EN_deletecontrol(p, i); } // Adjust higher numbered link indices in remaining controls for (i = 1; i <= net->Ncontrols; i++) { if (net->Control[i].Node > index) net->Control[i].Node--; } // Make necessary adjustments to rule-based controls adjustrules(p, EN_R_NODE, index); // Reduce counts of node types if (nodeType == EN_JUNCTION) net->Njuncs--; else net->Ntanks--; net->Nnodes--; return 0; } int DLLEXPORT EN_getnodeindex(EN_Project p, const char *id, int *index) /*---------------------------------------------------------------- ** Input: id = node ID name ** Output: index = node index ** Returns: error code ** Purpose: retrieves the index of a node **---------------------------------------------------------------- */ { *index = 0; if (!p->Openflag) return 102; *index = findnode(&p->network, id); if (*index == 0) return 203; else return 0; } int DLLEXPORT EN_getnodeid(EN_Project p, int index, char *id) /*---------------------------------------------------------------- ** Input: index = node index ** Output: id = node ID name ** Returns: error code ** Purpose: retrieves the name of a node **---------------------------------------------------------------- */ { strcpy(id, ""); if (!p->Openflag) return 102; if (index < 1 || index > p->network.Nnodes) return 203; strcpy(id, p->network.Node[index].ID); return 0; } int DLLEXPORT EN_setnodeid(EN_Project p, int index, const char *newid) /*---------------------------------------------------------------- ** Input: index = node index ** newid = new node ID name ** Output: none ** Returns: error code ** Purpose: sets the ID name of a node **---------------------------------------------------------------- */ { Network *net = &p->network; // Check for valid arguments if (index <= 0 || index > net->Nnodes) return 203; if (!namevalid(newid)) return 252; // Check if another node with same name exists if (hashtable_find(net->NodeHashTable, newid) > 0) return 215; // Replace the existing node ID with the new value hashtable_delete(net->NodeHashTable, net->Node[index].ID); strncpy(net->Node[index].ID, newid, MAXID); hashtable_insert(net->NodeHashTable, net->Node[index].ID, index); return 0; } int DLLEXPORT EN_getnodetype(EN_Project p, int index, int *nodeType) /*---------------------------------------------------------------- ** Input: index = node index ** Output: nodeType = node type (see EN_NodeType) ** Returns: error code ** Purpose: retrieves the type of a node **---------------------------------------------------------------- */ { *nodeType = -1; if (!p->Openflag) return 102; if (index < 1 || index > p->network.Nnodes) return 203; if (index <= p->network.Njuncs) *nodeType = EN_JUNCTION; else { if (p->network.Tank[index - p->network.Njuncs].A == 0.0) { *nodeType = EN_RESERVOIR; } else *nodeType = EN_TANK; } return 0; } int DLLEXPORT EN_getnodevalue(EN_Project p, int index, int property, double *value) /*---------------------------------------------------------------- ** Input: index = node index ** property = node property code (see EN_NodeProperty) ** Output: value = node property value ** Returns: error code ** Purpose: retrieves a property value for a node **---------------------------------------------------------------- */ { Network *net = &p->network; Hydraul *hyd = &p->hydraul; Quality *qual = &p->quality; double v = 0.0; Psource source; Snode *Node = net->Node; Stank *Tank = net->Tank; int nJuncs = net->Njuncs; double *Ucf = p->Ucf; double *NodeHead = hyd->NodeHead; double *NodeQual = qual->NodeQual; // Check for valid arguments *value = 0.0; if (!p->Openflag) return 102; if (index <= 0 || index > net->Nnodes) return 203; // Retrieve requested property switch (property) { case EN_ELEVATION: v = Node[index].El * Ucf[ELEV]; break; case EN_BASEDEMAND: // NOTE: primary demand category is first on demand list if (index <= nJuncs) { if (Node[index].D) v = Node[index].D->Base * Ucf[FLOW]; } break; case EN_PATTERN: // NOTE: primary demand category is first on demand list if (index <= nJuncs) { if (Node[index].D) v = (double)(Node[index].D->Pat); } else v = (double)(Tank[index - nJuncs].Pat); break; case EN_EMITTER: v = 0.0; if (Node[index].Ke > 0.0) { v = Ucf[FLOW] / pow((Ucf[PRESSURE] * Node[index].Ke), (1.0 / hyd->Qexp)); } break; case EN_INITQUAL: v = Node[index].C0 * Ucf[QUALITY]; break; case EN_SOURCEQUAL: case EN_SOURCETYPE: case EN_SOURCEMASS: case EN_SOURCEPAT: source = Node[index].S; if (source == NULL) return 240; if (property == EN_SOURCEQUAL) v = source->C0; else if (property == EN_SOURCEMASS) v = source->Smass * 60.0; else if (property == EN_SOURCEPAT) v = source->Pat; else v = source->Type; break; case EN_TANKLEVEL: if (index <= nJuncs) return 0; v = (Tank[index - nJuncs].H0 - Node[index].El) * Ucf[ELEV]; break; case EN_INITVOLUME: v = 0.0; if (index > nJuncs) v = Tank[index - nJuncs].V0 * Ucf[VOLUME]; break; case EN_MIXMODEL: v = MIX1; if (index > nJuncs) v = Tank[index - nJuncs].MixModel; break; case EN_MIXZONEVOL: v = 0.0; if (index > nJuncs) v = Tank[index - nJuncs].V1frac * Tank[index - nJuncs].Vmax * Ucf[VOLUME]; break; case EN_DEMAND: // Consumer Demand + Emitter Flow + Leakage Flow v = hyd->NodeDemand[index] * Ucf[FLOW]; break; case EN_HEAD: v = NodeHead[index] * Ucf[HEAD]; break; case EN_PRESSURE: v = (NodeHead[index] - Node[index].El) * Ucf[PRESSURE]; break; case EN_QUALITY: v = NodeQual[index] * Ucf[QUALITY]; break; case EN_TANKDIAM: v = 0.0; if (index > nJuncs) { v = sqrt(4.0 / PI * Tank[index - nJuncs].A) * Ucf[ELEV]; } break; case EN_MINVOLUME: v = 0.0; if (index > nJuncs) v = Tank[index - nJuncs].Vmin * Ucf[VOLUME]; break; case EN_MAXVOLUME: v = 0.0; if (index > nJuncs) v = Tank[index - nJuncs].Vmax * Ucf[VOLUME]; break; case EN_VOLCURVE: v = 0.0; if (index > nJuncs) v = Tank[index - nJuncs].Vcurve; break; case EN_MINLEVEL: v = 0.0; if (index > nJuncs) { v = (Tank[index - nJuncs].Hmin - Node[index].El) * Ucf[ELEV]; } break; case EN_MAXLEVEL: v = 0.0; if (index > nJuncs) { v = (Tank[index - nJuncs].Hmax - Node[index].El) * Ucf[ELEV]; } break; case EN_MIXFRACTION: v = 1.0; if (index > nJuncs) { v = Tank[index - nJuncs].V1frac; } break; case EN_TANK_KBULK: v = 0.0; if (index > nJuncs) v = Tank[index - nJuncs].Kb * SECperDAY; break; case EN_TANKVOLUME: if (index <= nJuncs) return 0; v = tankvolume(p, index - nJuncs, NodeHead[index]) * Ucf[VOLUME]; break; case EN_CANOVERFLOW: if (Node[index].Type != TANK) return 0; v = Tank[index - nJuncs].CanOverflow; break; case EN_DEMANDDEFICIT: if (index > nJuncs) return 0; // FullDemand contains node's required consumer demand // while DemandFlow contains delivered consumer demand if (hyd->FullDemand[index] <= 0.0) return 0; v = (hyd->FullDemand[index] - hyd->DemandFlow[index]) * Ucf[FLOW]; if (v < 0.0) v = 0.0; break; case EN_NODE_INCONTROL: v = (double)incontrols(p, NODE, index); break; case EN_EMITTERFLOW: v = hyd->EmitterFlow[index] * Ucf[FLOW]; break; case EN_LEAKAGEFLOW: v = hyd->LeakageFlow[index] * Ucf[FLOW]; break; case EN_DEMANDFLOW: // Consumer demand delivered v = hyd->DemandFlow[index] * Ucf[FLOW]; break; case EN_FULLDEMAND: // Consumer demand requested v = hyd->FullDemand[index] * Ucf[FLOW]; break; default: return 251; } *value = v; return 0; } int DLLEXPORT EN_getnodevalues(EN_Project p, int property, double *values) /*---------------------------------------------------------------- ** Input: property = node property code (see EN_NodeProperty) ** Output: values = array of node property values ** Returns: error code ** Purpose: retrieves an array of node property values **---------------------------------------------------------------- */ { int errcode = 0, i = 0; for (i = 1; i <= p->network.Nnodes; i++) { errcode = EN_getnodevalue(p, i, property, &values[i - 1]); if (errcode != 0) { return errcode; } } return 0; } int DLLEXPORT EN_setnodevalue(EN_Project p, int index, int property, double value) /*---------------------------------------------------------------- ** Input: index = node index ** property = node property code (see EN_NodeProperty) ** value = node property value ** Output: none ** Returns: error code ** Purpose: sets a property value for a node **---------------------------------------------------------------- */ { Network *net = &p->network; Hydraul *hyd = &p->hydraul; Quality *qual = &p->quality; Snode *Node = net->Node; Stank *Tank = net->Tank; Scurve *curve; const int nNodes = net->Nnodes; const int nJuncs = net->Njuncs; const int nPats = net->Npats; double *Ucf = p->Ucf; int i, j, n; Psource source; double hTmp; if (!p->Openflag) return 102; if (index <= 0 || index > nNodes) return 203; switch (property) { case EN_ELEVATION: if (index <= nJuncs) Node[index].El = value / Ucf[ELEV]; else { value = (value / Ucf[ELEV]) - Node[index].El; j = index - nJuncs; Tank[j].H0 += value; Tank[j].Hmin += value; Tank[j].Hmax += value; Node[index].El += value; hyd->NodeHead[index] += value; } break; case EN_BASEDEMAND: // NOTE: primary demand category is first on demand list if (index <= nJuncs) { if (Node[index].D) Node[index].D->Base = value / Ucf[FLOW]; } break; case EN_PATTERN: // NOTE: primary demand category is first on demand list j = ROUND(value); if (j < 0 || j > nPats) return 205; if (index <= nJuncs) { if (Node[index].D) Node[index].D->Pat = j; } else Tank[index - nJuncs].Pat = j; break; case EN_EMITTER: if (index > nJuncs) return 0; if (value < 0.0) return 209; if (value > 0.0) value = pow((Ucf[FLOW] / value), hyd->Qexp) / Ucf[PRESSURE]; Node[index].Ke = value; if (hyd->EmitterFlow[index] == 0.0) hyd->EmitterFlow[index] = 1.0; break; case EN_INITQUAL: if (value < 0.0) return 209; Node[index].C0 = value / Ucf[QUALITY]; if (index > nJuncs) Tank[index - nJuncs].C = Node[index].C0; break; case EN_SOURCEQUAL: case EN_SOURCETYPE: case EN_SOURCEPAT: if (value < 0.0) return 209; source = Node[index].S; if (source == NULL) { source = (struct Ssource *)malloc(sizeof(struct Ssource)); if (source == NULL) return 101; source->Type = CONCEN; source->C0 = 0.0; source->Pat = 0; Node[index].S = source; } if (property == EN_SOURCEQUAL) source->C0 = value; else if (property == EN_SOURCEPAT) { j = ROUND(value); if (j < 0 || j > nPats) return 205; source->Pat = j; } else // property == EN_SOURCETYPE { j = ROUND(value); if (j < CONCEN || j > FLOWPACED) return 251; else source->Type = (char)j; } break; case EN_TANKLEVEL: if (index <= nJuncs) return 263; j = index - nJuncs; if (Tank[j].A == 0.0) /* Tank is a reservoir */ { Tank[j].H0 = value / Ucf[ELEV]; Tank[j].Hmin = Tank[j].H0; Tank[j].Hmax = Tank[j].H0; Node[index].El = Tank[j].H0; hyd->NodeHead[index] = Tank[j].H0; } else { value = Node[index].El + value / Ucf[ELEV]; if (value > Tank[j].Hmax || value < Tank[j].Hmin) return 225; Tank[j].H0 = value; Tank[j].V0 = tankvolume(p, j, Tank[j].H0); // Resetting Volume in addition to initial volume Tank[j].V = Tank[j].V0; hyd->NodeHead[index] = Tank[j].H0; } break; case EN_TANKDIAM: if (value <= 0.0) return 209; // invalid diameter if (index <= nJuncs) return 263; // node is not a tank j = index - nJuncs; // tank index if (Tank[j].A == 0.0) return 263; // tank is a reservoir value /= Ucf[ELEV]; // diameter in feet Tank[j].A = PI * SQR(value) / 4.0; // new tank area if (Tank[j].Vcurve > 0) // tank has a volume curve { Tank[j].Vcurve = 0; // remove volume curve // Since the volume curve no longer applies we assume that the tank's // shape below Hmin is cylindrical and Vmin equals area times Hmin Tank[j].Vmin = Tank[j].A * (Tank[j].Hmin - Node[index].El); } // Since tank's area has changed its volumes must be updated // NOTE: For a non-volume curve tank we can't change the Vmin // associated with a Hmin since we don't know the tank's // shape below Hmin. Vmin can always be changed by setting // EN_MINVOLUME in a subsequent function call. Tank[j].V0 = tankvolume(p, j, Tank[j].H0); // new init. volume Tank[j].Vmax = tankvolume(p, j, Tank[j].Hmax); // new max. volume break; case EN_MINVOLUME: if (value < 0.0) return 209; // invalid volume if (index <= nJuncs) return 263; // node is not a tank j = index - nJuncs; // tank index if (Tank[j].A == 0.0) return 263; // tank is a reservoir i = Tank[j].Vcurve; // volume curve index if (i > 0) // tank has a volume curve { curve = &net->Curve[i]; // curve object if (value < curve->Y[0]) return 225; // volume off of curve value /= Ucf[VOLUME]; // volume in ft3 hTmp = tankgrade(p, j, value); // head at given volume if (hTmp > Tank[j].H0 || hTmp > Tank[j].Hmax) return 225; // invalid water levels Tank[j].Hmin = hTmp; // new min. head Tank[j].Vmin = value; // new min. volume } else // tank has no volume curve { // If the volume supplied by the function is 0 then the tank shape // below Hmin is assumed to be cylindrical and a new Vmin value is // computed. Otherwise Vmin is set to the supplied value. if (value == 0.0) Tank[j].Vmin = Tank[j].A * (Tank[j].Hmin - Node[index].El); else Tank[j].Vmin = value / Ucf[VOLUME]; // Since Vmin changes the other volumes need updating Tank[j].V0 = tankvolume(p, j, Tank[j].H0); // new init. volume Tank[j].Vmax = tankvolume(p, j, Tank[j].Hmax); // new max. volume } break; case EN_VOLCURVE: // NOTE: Setting EN_VOLCURVE to 0 to remove a volume curve is not valid. // One should instead set a value for EN_TANKDIAM. i = ROUND(value); // curve index if (i <= 0 || i > net->Ncurves) return 205; // invalid curve index if (index <= nJuncs) return 263; // node not a tank j = index - nJuncs; // tank index if (Tank[j].A == 0.0) return 263; // tank is a reservoir curve = &net->Curve[i]; // curve object // Check that tank's min/max levels lie within curve value = (Tank[j].Hmin - Node[index].El) * Ucf[ELEV]; if (value < curve->X[0]) return 225; value = (Tank[j].Hmax - Node[index].El) * Ucf[ELEV]; n = curve->Npts - 1; if (value > curve->X[n]) return 225; Tank[j].Vcurve = i; // assign curve to tank Tank[j].Vmin = tankvolume(p, j, Tank[j].Hmin); // new min. volume Tank[j].V0 = tankvolume(p, j, Tank[j].H0); // new init. volume Tank[j].Vmax = tankvolume(p, j, Tank[j].Hmax); // new max. volume Tank[j].A = (curve->Y[n] - curve->Y[0]) / // nominal area (curve->X[n] - curve->X[0]); break; case EN_MINLEVEL: if (value < 0.0) return 209; // invalid water level if (index <= nJuncs) return 263; // node not a tank j = index - nJuncs; // tank index if (Tank[j].A == 0.0) return 263; // tank is a reservoir hTmp = value / Ucf[ELEV] + Node[index].El; // convert level to head if (hTmp >= Tank[j].Hmax || hTmp > Tank[j].H0) return 225; // invalid water levels i = Tank[j].Vcurve; // volume curve index if (i > 0) // tank has a volume curve { curve = &net->Curve[i]; if (value < curve->X[0]) return 225; // new level is off curve Tank[j].Vmin = tankvolume(p, j, hTmp); // new min. volume } Tank[j].Hmin = hTmp; // NOTE: We assume that for non-volume curve tanks Vmin doesn't change // with Hmin. If not the case then a subsequent call setting // EN_MINVOLUME must be made. break; case EN_MAXLEVEL: if (value <= 0.0) return 209; // invalid water level if (index <= nJuncs) return 263; // node not a tank j = index - nJuncs; // tank index if (Tank[j].A == 0.0) return 263; // tank is a reservoir hTmp = value / Ucf[ELEV] + Node[index].El; // convert level to head if (hTmp < Tank[j].Hmin || hTmp < Tank[j].H0) return 225; // invalid water levels i = Tank[j].Vcurve; // volume curve index if (i > 0) // tank has a volume curve { curve = &net->Curve[i]; n = curve->Npts - 1; // last point on curve if (value > curve->X[n]) return 225; // new level is off curve } Tank[j].Hmax = hTmp; // new max. head Tank[j].Vmax = tankvolume(p, j, hTmp); // new max. volume break; case EN_MIXMODEL: j = ROUND(value); if (index <= nJuncs) return 263; if (j < MIX1 || j > LIFO) return 251; if (Tank[index - nJuncs].A > 0.0) { Tank[index - nJuncs].MixModel = (char)j; } break; case EN_MIXFRACTION: if (index <= nJuncs) return 263; if (value < 0.0 || value > 1.0) return 209; j = index - nJuncs; if (Tank[j].A > 0.0) { Tank[j].V1frac = value; } break; case EN_TANK_KBULK: if (index <= nJuncs) return 263; j = index - nJuncs; if (Tank[j].A > 0.0) { Tank[j].Kb = value / SECperDAY; qual->Reactflag = 1; } break; case EN_CANOVERFLOW: if (Node[index].Type != TANK) return 263; Tank[index - nJuncs].CanOverflow = (value != 0.0); break; default: return 251; } return 0; } int DLLEXPORT EN_setjuncdata(EN_Project p, int index, double elev, double dmnd, const char *dmndpat) /*---------------------------------------------------------------- ** Input: index = junction node index ** elev = junction elevation ** dmnd = junction primary base demand ** dmndpat = name of primary demand time pattern ** Output: none ** Returns: error code ** Purpose: sets several properties for a junction node **---------------------------------------------------------------- */ { int patIndex = 0; Snode *node; // Check that junction exists if (!p->Openflag) return 102; if (index <= 0 || index > p->network.Njuncs) return 203; // Check that demand pattern exists if (dmndpat && strlen(dmndpat) > 0) { if (EN_getpatternindex(p, dmndpat, &patIndex) > 0) return 205; } // Assign demand parameters to junction's primary demand category node = &(p->network.Node[index]); dmnd /= p->Ucf[FLOW]; // Category exists - update its properties if (node->D) { (node->D)->Base = dmnd; (node->D)->Pat = patIndex; } // No demand categories exist -- create a new one else if (!adddemand(node, dmnd, patIndex, NULL)) return 101; // Assign new elevation value to junction node->El = elev / p->Ucf[ELEV]; return 0; } int DLLEXPORT EN_settankdata(EN_Project p, int index, double elev, double initlvl, double minlvl, double maxlvl, double diam, double minvol, const char *volcurve) /*---------------------------------------------------------------- ** Input: index = tank node index ** elev = tank bottom elevation ** initlvl = initial water depth ** minlvl = minimum water depth ** maxlvl = maximum water depth ** diam = tank diameter ** minvol = tank volume at minimum level ** volCurve = name of curve for volume v. level ** Output: none ** Returns: error code ** Purpose: sets several properties for a tank node **---------------------------------------------------------------- */ { Network *net = &p->network; int i, j, n, curveIndex = 0; double *Ucf = p->Ucf; double area; Stank *Tank = net->Tank; Scurve *curve; // Check that tank exists if (!p->Openflag) return 102; if (index <= net->Njuncs || index > net->Nnodes) return 263; j = index - net->Njuncs; if (Tank[j].A == 0) return 263; // Tank is a Reservoir // Check for valid parameter values if (initlvl < 0.0 || minlvl < 0.0 || maxlvl < 0.0) return 209; if (minlvl > initlvl || minlvl > maxlvl || initlvl > maxlvl) return 225; if (diam < 0.0 || minvol < 0.0) return 209; // volume curve supplied if (strlen(volcurve) > 0) { for (i = 1; i <= net->Ncurves; i++) { if (strcmp(volcurve, net->Curve[i].ID) == 0) { curveIndex = i; break; } } if (curveIndex == 0) return 206; curve = &net->Curve[curveIndex]; n = curve->Npts - 1; if (minlvl < curve->X[0] || maxlvl > curve->X[n]) return 225; area = (curve->Y[n] - curve->Y[0]) / (curve->X[n] - curve->X[0]); } // Tank diameter supplied else area = PI * diam * diam / 4.0; // Assign parameters to tank object net->Node[Tank[j].Node].El = elev / Ucf[ELEV]; Tank[j].A = area / Ucf[ELEV] / Ucf[ELEV]; Tank[j].H0 = (elev + initlvl) / Ucf[ELEV]; Tank[j].Hmin = (elev + minlvl) / Ucf[ELEV]; Tank[j].Hmax = (elev + maxlvl) / Ucf[ELEV]; Tank[j].Vcurve = curveIndex; if (curveIndex == 0) { if (minvol > 0.0) Tank[j].Vmin = minvol / Ucf[VOLUME]; else Tank[j].Vmin = Tank[j].A * (Tank[j].Hmin - elev / Ucf[ELEV]); } else Tank[j].Vmin = tankvolume(p, j, Tank[j].Hmin); Tank[j].V0 = tankvolume(p, j, Tank[j].H0); Tank[j].Vmax = tankvolume(p, j, Tank[j].Hmax); return 0; } int DLLEXPORT EN_getcoord(EN_Project p, int index, double *x, double *y) /*---------------------------------------------------------------- ** Input: index = node index ** Output: x = node x-coordinate ** y = node y-coordinate ** Returns: error code ** Purpose: retrieves the coordinates of a node **---------------------------------------------------------------- */ { Network *net = &p->network; Snode *node; if (!p->Openflag) return 102; if (index < 1 || index > p->network.Nnodes) return 203; // check if node has coords node = &net->Node[index]; if (node->X == MISSING || node->Y == MISSING) return 254; *x = (double)(node->X); *y = (double)(node->Y); return 0; } int DLLEXPORT EN_setcoord(EN_Project p, int index, double x, double y) /*---------------------------------------------------------------- ** Input: index = node index ** x = node x-coordinate ** y = node y-coordinate ** Output: none ** Returns: error code ** Purpose: sets the coordinates of a node **---------------------------------------------------------------- */ { Network *net = &p->network; Snode *node; if (!p->Openflag) return 102; if (index < 1 || index > p->network.Nnodes) return 203; node = &net->Node[index]; node->X = x; node->Y = y; return 0; } /******************************************************************** Nodal Demand Functions ********************************************************************/ int DLLEXPORT EN_getdemandmodel(EN_Project p, int *model, double *pmin, double *preq, double *pexp) /*---------------------------------------------------------------- ** Input: none ** Output: model = type of demand model (see EN_DemandModel) ** pmin = minimum pressure for any demand ** preq = required pressure for full demand ** pexp = exponent in pressure dependent demand formula ** Returns: error code ** Purpose: retrieves the parameters of a project's demand model **---------------------------------------------------------------- */ { *model = p->hydraul.DemandModel; *pmin = p->hydraul.Pmin * p->Ucf[PRESSURE]; *preq = p->hydraul.Preq * p->Ucf[PRESSURE]; *pexp = p->hydraul.Pexp; return 0; } int DLLEXPORT EN_setdemandmodel(EN_Project p, int model, double pmin, double preq, double pexp) /*---------------------------------------------------------------- ** Input: model = type of demand model (see EN_DemandModel) ** pmin = minimum pressure for any demand ** preq = required pressure for full demand ** pexp = exponent in pressure dependent demand formula ** Output: none ** Returns: error code ** Purpose: sets the parameters of a project's demand model **---------------------------------------------------------------- */ { if (model < 0 || model > EN_PDA) return 251; if (model == EN_PDA) { if (pexp <= 0.0) return 208; if (pmin < 0.0) return 208; if (preq - pmin < MINPDIFF) return 208; } p->hydraul.DemandModel = model; p->hydraul.Pmin = pmin / p->Ucf[PRESSURE]; p->hydraul.Preq = preq / p->Ucf[PRESSURE]; p->hydraul.Pexp = pexp; return 0; } int DLLEXPORT EN_adddemand(EN_Project p, int nodeIndex, double baseDemand, const char *demandPattern, const char *demandName) /*---------------------------------------------------------------- ** Input: nodeIndex = node index ** baseDemand = baseline demand value ** demandPattern = name of demand's time pattern (can be NULL or empty) ** demandName = name of demand's category (can be NULL or empty) ** Returns: error code ** Purpose: adds a new demand category to a junction node **---------------------------------------------------------------- */ { int patIndex = 0; Snode *node; // Check for valid arguments if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203; if (demandPattern && strlen(demandPattern) > 0) { if (EN_getpatternindex(p, demandPattern, &patIndex) > 0) return 205; } // Do nothing if node is not a junction if (nodeIndex > p->network.Njuncs) return 0; // Add the new demand to the node's demands list node = &(p->network.Node[nodeIndex]); if (!adddemand(node, baseDemand / p->Ucf[FLOW], patIndex, demandName)) return 101; return 0; } int DLLEXPORT EN_deletedemand(EN_Project p, int nodeIndex, int demandIndex) /*---------------------------------------------------------------- ** Input: nodeIndex = node index ** demandIndex = index of node's demand to be deleted ** Returns: error code ** Purpose: deletes an existing demand category from a junction node **---------------------------------------------------------------- */ { Pdemand d, dprev; Snode *node; int n = 1; // Check for valid arguments if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203; // Only junctions have demands if (nodeIndex <= p->network.Njuncs) { // Find head of node's list of demands node = &p->network.Node[nodeIndex]; d = node->D; if (d == NULL) return 253; dprev = d; // Check if target demand is head of demand list if (demandIndex == 1) { node->D = d->next; free(d->Name); free(d); return 0; } // Otherwise locate target demand in demand list while (d != NULL && n < demandIndex) { dprev = d; d = d->next; n++; } // Return error if target demand not found if (d == NULL) return 253; // Link the demands that precede and follow the target dprev->next = d->next; // Delete the target demand free(d->Name); free(d); } return 0; } int DLLEXPORT EN_getdemandindex(EN_Project p, int nodeIndex, const char *demandName, int *demandIndex) /*---------------------------------------------------------------- ** Input: nodeIndex = node index ** demandName = name of demand being sought ** Output: demandIndex = index of demand being sought ** Returns: error code ** Purpose: retrieves the position of a named demand category ** in a node's list of demands **---------------------------------------------------------------- */ { Pdemand d; int n = 0; int nameEmpty = FALSE; int found = FALSE; // Check for valid arguments *demandIndex = 0; if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203; if (demandName == NULL) return 253; // Check if target name is empty if (strlen(demandName) == 0) nameEmpty = TRUE; // Locate target demand in node's demands list for (d = p->network.Node[nodeIndex].D; d != NULL; d = d->next) { n++; if (d->Name == NULL) { if (nameEmpty) found = TRUE;; } else if (strcmp(d->Name, demandName) == 0) found = TRUE; if (found) break; } // Return target demand's index if (!found) return 253; *demandIndex = n; return 0; } int DLLEXPORT EN_getnumdemands(EN_Project p, int nodeIndex, int *numDemands) /*---------------------------------------------------------------- ** Input: nodeIndex = node index ** Output: numDemands = number of demand categories ** Returns: error code ** Purpose: retrieves the number of demand categories for a node **---------------------------------------------------------------- */ { Pdemand d; int n = 0; // Check for valid arguments if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203; // Count the number of demand categories assigned to node for (d = p->network.Node[nodeIndex].D; d != NULL; d = d->next) n++; *numDemands = n; return 0; } int DLLEXPORT EN_getbasedemand(EN_Project p, int nodeIndex, int demandIndex, double *baseDemand) /*---------------------------------------------------------------- ** Input: nodeIndex = node index ** demandIndex = demand category index ** Output: baseDemand = baseline demand value ** Returns: error code ** Purpose: retrieves the baseline value for a node's demand category **---------------------------------------------------------------- */ { Pdemand d; // Check for valid arguments *baseDemand = 0.0; if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203; // Locate target demand in node's demands list d = finddemand(p->network.Node[nodeIndex].D, demandIndex); if (d == NULL) return 253; // Retrieve target demand's base value *baseDemand = d->Base * p->Ucf[FLOW]; return 0; } int DLLEXPORT EN_setbasedemand(EN_Project p, int nodeIndex, int demandIndex, double baseDemand) /*---------------------------------------------------------------- ** Input: nodeIndex = node index ** demandIndex = demand category index ** baseDemand = baseline demand value ** Output: none ** Returns: error code ** Purpose: sets the baseline value for a node's demand category **---------------------------------------------------------------- */ { Pdemand d; // Check for valid arguments if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203; // Locate target demand in node's demands list d = finddemand(p->network.Node[nodeIndex].D, demandIndex); if (d == NULL) return 253; // Assign new base value to target demand d->Base = baseDemand / p->Ucf[FLOW]; return 0; } int DLLEXPORT EN_getdemandname(EN_Project p, int nodeIndex, int demandIndex, char *demandName) /*---------------------------------------------------------------- ** Input: nodeIndex = node index ** demandIndex = demand category index ** Output: demandname = demand category name ** Returns: error code ** Purpose: retrieves the name assigned to a node's demand category **---------------------------------------------------------------- */ { Pdemand d; strcpy(demandName, ""); // Check for valid arguments if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > p->network.Njuncs) return 203; // Locate target demand in node's demands list d = finddemand(p->network.Node[nodeIndex].D, demandIndex); if (d == NULL) return 253; // Retrieve target demand's category name if (d->Name) strcpy(demandName, d->Name); return 0; } int DLLEXPORT EN_setdemandname(EN_Project p, int nodeIndex, int demandIndex, const char *demandName) /*---------------------------------------------------------------- ** Input: nodeIndex = node index ** demandIndex = demand category index ** demandName = name of demand category ** Output: none ** Returns: error code ** Purpose: assigns a name to a node's demand category **---------------------------------------------------------------- */ { Pdemand d; // Check for valid arguments if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > p->network.Njuncs) return 203; // Locate target demand in node's demands list d = finddemand(p->network.Node[nodeIndex].D, demandIndex); if (d == NULL) return 253; // Assign category name to target demand d->Name = xstrcpy(&d->Name, demandName, MAXID); return 0; } int DLLEXPORT EN_getdemandpattern(EN_Project p, int nodeIndex, int demandIndex, int *patIndex) /*---------------------------------------------------------------- ** Input: nodeIndex = node index ** demandIndex = demand category index ** Output: patIndex = time pattern index ** Returns: error code ** Purpose: retrieves the time pattern assigned to a node's ** demand category **---------------------------------------------------------------- */ { Pdemand d; // Check for valid arguments *patIndex = 0; if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203; // Locate target demand in node's demand list d = finddemand(p->network.Node[nodeIndex].D, demandIndex); if (d == NULL) return 253; // Retrieve that demand's pattern index *patIndex = d->Pat; return 0; } int DLLEXPORT EN_setdemandpattern(EN_Project p, int nodeIndex, int demandIndex, int patIndex) /*---------------------------------------------------------------- ** Input: nodeIndex = node index ** demandIndex = demand category index ** patIndex = time pattern index ** Output: none ** Returns: error code ** Purpose: assigns a time pattern to a node's demand category **---------------------------------------------------------------- */ { Network *net = &p->network; Pdemand d; // Check for valid arguments if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > net->Nnodes) return 203; if (patIndex < 0 || patIndex > net->Npats) return 205; // Locate target demand in node's demand list d = finddemand(p->network.Node[nodeIndex].D, demandIndex); if (d == NULL) return 253; // Assign new time pattern to target demand d->Pat = patIndex; return 0; } /******************************************************************** Link Functions ********************************************************************/ int DLLEXPORT EN_addlink(EN_Project p, const char *id, int linkType, const char *fromNode, const char *toNode, int *index) /*---------------------------------------------------------------- ** Input: id = link ID name ** type = link type (see EN_LinkType) ** fromNode = name of link's starting node ** toNode = name of link's ending node ** Output: index = position of new link in Link array ** Returns: error code ** Purpose: adds a new link to a project **---------------------------------------------------------------- */ { Network *net = &p->network; Hydraul *hyd = &p->hydraul; int i, n, size, errcode; int n1, n2; Slink *link; Spump *pump; // Cannot modify network structure while solvers are active *index = 0; if (!p->Openflag) return 102; if (p->hydraul.OpenHflag || p->quality.OpenQflag) return 262; // Check if id name contains invalid characters if (!namevalid(id)) return 252; // Check if a link with same id already exists if (EN_getlinkindex(p, id, &i) == 0) return 215; // Check for valid link type if (linkType < CVPIPE || linkType > PCV) return 251; // Lookup the link's from and to nodes n1 = hashtable_find(net->NodeHashTable, fromNode); n2 = hashtable_find(net->NodeHashTable, toNode); if (n1 == 0 || n2 == 0) return 203; // Check that valve link has legal connections if (linkType > PUMP) { errcode = valvecheck(p, 0, linkType, n1, n2); if (errcode) return errcode; } // Grow link-related arrays to accomodate the new link net->Nlinks++; p->parser.MaxLinks = net->Nlinks; n = net->Nlinks; size = (n + 1) * sizeof(Slink); net->Link = (Slink *)realloc(net->Link, size); size = (n + 1) * sizeof(double); hyd->LinkFlow = (double *)realloc(hyd->LinkFlow, size); hyd->LinkSetting = (double *)realloc(hyd->LinkSetting, size); size = (n + 1) * sizeof(StatusType); hyd->LinkStatus = (StatusType *)realloc(hyd->LinkStatus, size); // Set properties for the new link link = &net->Link[n]; strncpy(link->ID, id, MAXID); if (linkType <= PIPE) net->Npipes++; else if (linkType == PUMP) { // Grow pump array to accomodate the new link net->Npumps++; size = (net->Npumps + 1) * sizeof(Spump); net->Pump = (Spump *)realloc(net->Pump, size); pump = &net->Pump[net->Npumps]; pump->Link = n; pump->Ptype = NOCURVE; pump->Q0 = 0; pump->Qmax = 0; pump->Hmax = 0; pump->H0 = 0; pump->R = 0; pump->N = 0; pump->Hcurve = 0; pump->Ecurve = 0; pump->Upat = 0; pump->Epat = 0; pump->Ecost = 0; pump->Energy.TotalCost = MISSING; } else { // Grow valve array to accomodate the new link net->Nvalves++; size = (net->Nvalves + 1) * sizeof(Svalve); net->Valve = (Svalve *)realloc(net->Valve, size); net->Valve[net->Nvalves].Link = n; net->Valve[net->Nvalves].Curve = 0; } link->Type = linkType; link->N1 = n1; link->N2 = n2; link->InitStatus = OPEN; if (linkType == PUMP) { link->Kc = 1.0; // Speed factor link->Km = 0.0; // Horsepower link->Len = 0.0; } else if (linkType <= PIPE) // pipe or cvpipe { // 10" diameter new ductile iron pipe with // length of average city block link->Diam = 10 / p->Ucf[DIAM]; switch (hyd->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; // Loss coeff link->Len = 330.0; } else // Valve { link->Diam = 10 / p->Ucf[DIAM]; link->Kc = 0.0; // Valve setting. link->Km = 0.0; // Loss coeff link->Len = 0.0; link->InitStatus = ACTIVE; } link->Kb = 0; link->Kw = 0; link->LeakArea = 0; link->LeakExpan = 0; link->InitSetting = link->Kc; link->R = 0; link->Rc = 0; link->Rpt = 0; link->ResultIndex = 0; link->Comment = NULL; link->Tag = NULL; link->Vertices = NULL; hashtable_insert(net->LinkHashTable, link->ID, n); *index = n; return 0; } int DLLEXPORT EN_deletelink(EN_Project p, int index, int actionCode) /*---------------------------------------------------------------- ** Input: index = index of the link to delete ** actionCode = how to treat controls that contain the link: ** EN_UNCONDITIONAL deletes all such controls plus the link, ** EN_CONDITIONAL does not delete the link if it appears ** in a control and returns an error code ** Output: none ** Returns: error code ** Purpose: deletes a link from a project **---------------------------------------------------------------- */ { Network *net = &p->network; int i; int pumpindex; int valveindex; int linkType; Slink *link; // Cannot modify network structure while solvers are active if (!p->Openflag) return 102; if (p->hydraul.OpenHflag || p->quality.OpenQflag) return 262; // Check that link exists if (index <= 0 || index > net->Nlinks) return 204; if (actionCode < EN_UNCONDITIONAL || actionCode > EN_CONDITIONAL) return 251; // Deletion will be cancelled if link appears in any controls if (actionCode == EN_CONDITIONAL) { actionCode = incontrols(p, LINK, index); if (actionCode > 0) return 261; } // Get references to the link and its type link = &net->Link[index]; EN_getlinktype(p, index, &linkType); // Remove link from its hash table hashtable_delete(net->LinkHashTable, link->ID); // Remove link's comment and vertices free(link->Comment); free(link->Tag); freelinkvertices(link); // Shift position of higher entries in Link array down one for (i = index; i <= net->Nlinks - 1; i++) { net->Link[i] = net->Link[i + 1]; // ... update link's entry in the hash table hashtable_update(net->LinkHashTable, net->Link[i].ID, i); } // Adjust references to higher numbered links for pumps & valves for (i = 1; i <= net->Npumps; i++) { if (net->Pump[i].Link > index) net->Pump[i].Link -= 1; } for (i = 1; i <= net->Nvalves; i++) { if (net->Valve[i].Link > index) net->Valve[i].Link -= 1; } // Reduce the number of pipes count by one if it is a pipe. if (linkType == PIPE) { net->Npipes--; } // Delete any pump associated with the deleted link if (linkType == PUMP) { pumpindex = findpump(net, index); for (i = pumpindex; i <= net->Npumps - 1; i++) { net->Pump[i] = net->Pump[i + 1]; } net->Npumps--; } // Delete any valve (linkType > PUMP) associated with the deleted link if (linkType > PUMP) { valveindex = findvalve(net, index); for (i = valveindex; i <= net->Nvalves - 1; i++) { net->Valve[i] = net->Valve[i + 1]; } net->Nvalves--; } // Delete any control containing the link for (i = net->Ncontrols; i >= 1; i--) { if (net->Control[i].Link == index) EN_deletecontrol(p, i); } // Adjust higher numbered link indices in remaining controls for (i = 1; i <= net->Ncontrols; i++) { if (net->Control[i].Link > index) net->Control[i].Link--; } // Make necessary adjustments to rule-based controls adjustrules(p, EN_R_LINK, index); // see RULES.C // Reduce link count by one net->Nlinks--; return 0; } int DLLEXPORT EN_getlinkindex(EN_Project p, const char *id, int *index) /*---------------------------------------------------------------- ** Input: id = link ID name ** Output: index = link index ** Returns: error code ** Purpose: retrieves the index of a link **---------------------------------------------------------------- */ { *index = 0; if (!p->Openflag) return 102; *index = findlink(&p->network, id); if (*index == 0) return 204; else return 0; } int DLLEXPORT EN_getlinkid(EN_Project p, int index, char *id) /*---------------------------------------------------------------- ** Input: index = link index ** Output: id = link ID name ** Returns: error code ** Purpose: retrieves the ID name of a link **---------------------------------------------------------------- */ { strcpy(id, ""); if (!p->Openflag) return 102; if (index < 1 || index > p->network.Nlinks) return 204; strcpy(id, p->network.Link[index].ID); return 0; } int DLLEXPORT EN_setlinkid(EN_Project p, int index, const char *newid) /*---------------------------------------------------------------- ** Input: index = link index ** id = link ID name ** Output: none ** Returns: error code ** Purpose: sets the ID name of a link **---------------------------------------------------------------- */ { Network *net = &p->network; // Check for valid arguments if (index <= 0 || index > net->Nlinks) return 204; if (!namevalid(newid)) return 252; // Check if another link with same name exists if (hashtable_find(net->LinkHashTable, newid) > 0) return 215; // Replace the existing link ID with the new value hashtable_delete(net->LinkHashTable, net->Link[index].ID); strncpy(net->Link[index].ID, newid, MAXID); hashtable_insert(net->LinkHashTable, net->Link[index].ID, index); return 0; } int DLLEXPORT EN_getlinktype(EN_Project p, int index, int *linkType) /*---------------------------------------------------------------- ** Input: index = link index ** Output: linkType = link type (see EN_LinkType) ** Returns: error code ** Purpose: retrieves the type code of a link **---------------------------------------------------------------- */ { *linkType = -1; if (!p->Openflag) return 102; if (index < 1 || index > p->network.Nlinks) return 204; *linkType = p->network.Link[index].Type; return 0; } int DLLEXPORT EN_setlinktype(EN_Project p, int *index, int linkType, int actionCode) /*---------------------------------------------------------------- ** Input: index = link index ** linkType = new link type (see EN_LinkType) ** actionCode = how to treat controls that contain the link: ** EN_UNCONDITIONAL deletes all such controls, ** EN_CONDITIONAL cancels the type change if the link appears ** in a control and returns an error code ** Output: none ** Returns: error code ** Purpose: changes the type of a particular link (e.g. pipe to pump) **---------------------------------------------------------------- */ { Network *net = &p->network; int i = *index, n1, n2; char id[MAXID + 1]; char id1[MAXID + 1]; char id2[MAXID + 1]; int errcode; int oldType; // Cannot modify network structure while solvers are active if (!p->Openflag) return 102; if (p->hydraul.OpenHflag || p->quality.OpenQflag) return 262; // Check for valid input parameters if (linkType < 0 || linkType > PCV || actionCode < EN_UNCONDITIONAL || actionCode > EN_CONDITIONAL) { return 251; } // Check for valid link index if (i <= 0 || i > net->Nlinks) return 204; // Check if current link type equals new type EN_getlinktype(p, i, &oldType); if (oldType == linkType) return 0; // Type change will be cancelled if link appears in any controls if (actionCode == EN_CONDITIONAL) { actionCode = incontrols(p, LINK, i); if (actionCode > 0) return 261; } // Pipe changing from or to having a check valve if (oldType <= PIPE && linkType <= PIPE) { net->Link[i].Type = linkType; if (linkType == CVPIPE) net->Link[i].InitStatus = OPEN; return 0; } // Get ID's of link & its end nodes EN_getlinkid(p, i, id); EN_getlinknodes(p, i, &n1, &n2); EN_getnodeid(p, n1, id1); EN_getnodeid(p, n2, id2); // Check for illegal valve connections errcode = valvecheck(p, i, linkType, n1, n2); if (errcode) return errcode; // Delete the original link (and any controls containing it) EN_deletelink(p, i, actionCode); // Create a new link of new type and old id errcode = EN_addlink(p, id, linkType, id1, id2, index); return errcode; } int DLLEXPORT EN_getlinknodes(EN_Project p, int index, int *node1, int *node2) /*---------------------------------------------------------------- ** Input: index = link index ** Output: node1 = index of link's starting node ** node2 = index of link's ending node ** Returns: error code ** Purpose: retrieves the start and end nodes of a link **---------------------------------------------------------------- */ { *node1 = 0; *node2 = 0; if (!p->Openflag) return 102; if (index < 1 || index > p->network.Nlinks) return 204; *node1 = p->network.Link[index].N1; *node2 = p->network.Link[index].N2; return 0; } int DLLEXPORT EN_setlinknodes(EN_Project p, int index, int node1, int node2) /*---------------------------------------------------------------- ** Input: index = link index ** node1 = index of link's new starting node ** node2 = index of link's new ending node ** Returns: error code ** Purpose: sets the start and end nodes of a link **---------------------------------------------------------------- */ { Network *net = &p->network; int type, errcode; // Cannot modify network structure while solvers are active if (p->hydraul.OpenHflag || p->quality.OpenQflag) return 262; // Check for valid link index if (index <= 0 || index > net->Nlinks) return 204; // Check that nodes exist if (node1 < 0 || node1 > net->Nnodes) return 203; if (node2 < 0 || node2 > net->Nnodes) return 203; // Check that nodes are not the same if (node1 == node2) return 222; // Do nothing if the new nodes are the same as the old ones if (node1 == net->Link[index].N1 && node2 == net->Link[index].N2) return 0; // Check for illegal valve connection type = net->Link[index].Type; if (type > PUMP) { errcode = valvecheck(p, index, type, node1, node2); if (errcode) return errcode; } // Assign new end nodes to link net->Link[index].N1 = node1; net->Link[index].N2 = node2; return 0; } int DLLEXPORT EN_getlinkvalue(EN_Project p, int index, int property, double *value) /*---------------------------------------------------------------- ** Input: index = link index ** property = link property code (see EN_LinkProperty) ** Output: value = link property value ** Returns: error code ** Purpose: retrieves a property value for a link **---------------------------------------------------------------- */ { Network *net = &p->network; Hydraul *hyd = &p->hydraul; double a, h, q, v = 0.0; int pmp; Slink *Link = net->Link; Spump *Pump = net->Pump; double *Ucf = p->Ucf; // Check for valid arguments *value = 0.0; if (!p->Openflag) return 102; if (index <= 0 || index > net->Nlinks) return 204; // Retrieve called-for property switch (property) { case EN_DIAMETER: if (Link[index].Type == PUMP) v = 0.0; else v = Link[index].Diam * Ucf[DIAM]; break; case EN_LENGTH: v = Link[index].Len * Ucf[ELEV]; break; case EN_ROUGHNESS: if (Link[index].Type <= PIPE) { if (hyd->Formflag == DW) v = Link[index].Kc * (1000.0 * Ucf[ELEV]); else v = Link[index].Kc; } else v = 0.0; break; case EN_MINORLOSS: if (Link[index].Type != PUMP) { v = Link[index].Km; v *= (SQR(Link[index].Diam) * SQR(Link[index].Diam) / 0.02517); } else v = 0.0; break; case EN_INITSTATUS: if (Link[index].InitStatus <= CLOSED) v = 0.0; else v = 1.0; if (Link[index].Type > PUMP && Link[index].InitStatus > OPEN) v = 2.0; break; case EN_INITSETTING: v = Link[index].InitSetting; switch (Link[index].Type) { case CVPIPE: case PIPE: if (hyd->Formflag == DW) v = v * (1000.0 * Ucf[ELEV]); break; case PRV: case PSV: case PBV: v *= Ucf[PRESSURE]; break; case FCV: v *= Ucf[FLOW]; default: break; } break; case EN_KBULK: v = Link[index].Kb * SECperDAY; break; case EN_KWALL: v = Link[index].Kw * SECperDAY; break; case EN_FLOW: if (hyd->LinkStatus[index] <= CLOSED) v = 0.0; else v = hyd->LinkFlow[index] * Ucf[FLOW]; break; case EN_VELOCITY: if (Link[index].Type == PUMP) v = 0.0; else if (hyd->LinkStatus[index] <= CLOSED) v = 0.0; else { q = ABS(hyd->LinkFlow[index]); a = PI * SQR(Link[index].Diam) / 4.0; v = q / a * Ucf[VELOCITY]; } break; case EN_HEADLOSS: if (hyd->LinkStatus[index] <= CLOSED) v = 0.0; else { h = hyd->NodeHead[Link[index].N1] - hyd->NodeHead[Link[index].N2]; if (Link[index].Type != PUMP) h = ABS(h); v = h * Ucf[HEADLOSS]; } break; case EN_STATUS: if (hyd->LinkStatus[index] <= CLOSED) v = 0.0; else v = 1.0; if (Link[index].Type > PUMP && hyd->LinkStatus[index] > OPEN) v = 2.0; break; case EN_SETTING: if (Link[index].Type == PIPE || Link[index].Type == CVPIPE) { return EN_getlinkvalue(p, index, EN_ROUGHNESS, value); } if (hyd->LinkSetting[index] == MISSING) v = 0.0; else v = hyd->LinkSetting[index]; switch (Link[index].Type) { case PRV: case PSV: case PBV: v *= Ucf[PRESSURE]; break; case FCV: v *= Ucf[FLOW]; default: break; } break; case EN_ENERGY: getenergy(p, index, &v, &a); break; case EN_LINKQUAL: v = avgqual(p, index) * Ucf[LINKQUAL]; break; case EN_LINKPATTERN: if (Link[index].Type == PUMP) { v = (double)Pump[findpump(&p->network, index)].Upat; } break; case EN_PUMP_STATE: v = hyd->LinkStatus[index]; if (Link[index].Type == PUMP) { pmp = findpump(net, index); if (hyd->LinkStatus[index] >= OPEN) { if (hyd->LinkFlow[index] > hyd->LinkSetting[index] * Pump[pmp].Qmax) { v = XFLOW; } if (hyd->LinkFlow[index] < 0.0) v = XHEAD; } } break; case EN_PUMP_EFFIC: getenergy(p, index, &a, &v); break; case EN_PUMP_POWER: v = 0; if (Link[index].Type == PUMP) { pmp = findpump(net, index); if (Pump[pmp].Ptype == CONST_HP) v = Link[index].Km; // Power in HP or KW } break; case EN_PUMP_HCURVE: if (Link[index].Type == PUMP) { v = (double)Pump[findpump(&p->network, index)].Hcurve; } break; case EN_PUMP_ECURVE: if (Link[index].Type == PUMP) { v = (double)Pump[findpump(&p->network, index)].Ecurve; } break; case EN_PUMP_ECOST: if (Link[index].Type == PUMP) { v = (double)Pump[findpump(&p->network, index)].Ecost; } break; case EN_PUMP_EPAT: if (Link[index].Type == PUMP) { v = (double)Pump[findpump(&p->network, index)].Epat; } break; case EN_PCV_CURVE: if (Link[index].Type == PCV) { v = net->Valve[findvalve(&p->network, index)].Curve; } break; case EN_GPV_CURVE: if (Link[index].Type == GPV) { v = Link[index].Kc; } break; case EN_LINK_INCONTROL: v = (double)incontrols(p, LINK, index); break; case EN_LEAK_AREA: v = Link[index].LeakArea * Ucf[LENGTH]; break; case EN_LEAK_EXPAN: v = Link[index].LeakExpan * Ucf[LENGTH]; break; case EN_LINK_LEAKAGE: v = findlinkleakage(p, index) * Ucf[FLOW]; break; default: return 251; } *value = (double)v; return 0; } int DLLEXPORT EN_getlinkvalues(EN_Project p, int property, double *values) /*---------------------------------------------------------------- ** Input: property = link property code (see EN_LinkProperty) ** Output: values = array of link property values ** Returns: error code ** Purpose: retrieves property values for all links **---------------------------------------------------------------- */ { int errcode = 0, i = 0; for(i = 1; i <= p->network.Nlinks; i++) { errcode = EN_getlinkvalue(p, i, property, &values[i-1]); if(errcode != 0) { return errcode; } } return 0; } int DLLEXPORT EN_setlinkvalue(EN_Project p, int index, int property, double value) /*---------------------------------------------------------------- ** Input: index = link index ** property = link property code (see EN_LinkProperty) ** value = property value ** Output: none ** Returns: error code ** Purpose: sets a property value for a link **---------------------------------------------------------------- */ { Network *net = &p->network; Hydraul *hyd = &p->hydraul; Quality *qual = &p->quality; Slink *Link = net->Link; double *Ucf = p->Ucf; char s; double r; int pumpIndex, patIndex, curveIndex; if (!p->Openflag) return 102; if (index <= 0 || index > net->Nlinks) return 204; switch (property) { case EN_DIAMETER: if (Link[index].Type != PUMP) { if (value <= 0.0) return 211; value /= Ucf[DIAM]; // Convert to feet r = Link[index].Diam / value; // Ratio of old to new diam Link[index].Km *= SQR(r) * SQR(r); // Adjust minor loss factor Link[index].Diam = value; // Update diameter resistcoeff(p, index); // Update resistance coeff. } break; case EN_LENGTH: if (Link[index].Type <= PIPE) { if (value <= 0.0) return 211; Link[index].Len = value / Ucf[ELEV]; resistcoeff(p, index); } break; case EN_ROUGHNESS: if (Link[index].Type <= PIPE) { if (value <= 0.0) return 211; Link[index].Kc = value; if (hyd->Formflag == DW) Link[index].Kc /= (1000.0 * Ucf[ELEV]); if (p->hydraul.OpenHflag) resistcoeff(p, index); else Link[index].InitSetting = Link[index].Kc; } break; case EN_MINORLOSS: if (Link[index].Type != PUMP) { if (value < 0.0) return 211; Link[index].Km = 0.02517 * value / SQR(Link[index].Diam) / SQR(Link[index].Diam); } break; case EN_INITSTATUS: case EN_STATUS: // Cannot set status for a check valve if (Link[index].Type == CVPIPE) return 207; s = (char)ROUND(value); if (s < 0 || s > 2) return 211; s = s + CLOSED; if (property == EN_INITSTATUS) { Link[index].InitStatus = s; } else { setlinkstatus(p, index, s, &hyd->LinkStatus[index], &hyd->LinkSetting[index]); } break; case EN_INITSETTING: case EN_SETTING: if (Link[index].Type == PIPE || Link[index].Type == CVPIPE) { EN_setlinkvalue(p, index, EN_ROUGHNESS, value); if (property == EN_INITSETTING) Link[index].InitSetting = Link[index].Kc; } else { switch (Link[index].Type) { case PUMP: if (value < 0.0) return 211; break; case PRV: case PSV: case PBV: value /= Ucf[PRESSURE]; break; case FCV: value /= Ucf[FLOW]; break; case TCV: case PCV: break; case GPV: return 207; // Cannot modify setting for GPV default: return 0; } if (property == EN_INITSETTING) { Link[index].Kc = value; Link[index].InitSetting = value; } else { setlinksetting(p, index, value, &hyd->LinkStatus[index], &hyd->LinkSetting[index]); } } break; case EN_KBULK: if (Link[index].Type <= PIPE) { Link[index].Kb = value / SECperDAY; qual->Reactflag = 1; } break; case EN_KWALL: if (Link[index].Type <= PIPE) { Link[index].Kw = value / SECperDAY; qual->Reactflag = 1; } break; case EN_LINKPATTERN: if (Link[index].Type == PUMP) { patIndex = ROUND(value); if (patIndex < 0 || patIndex > net->Npats) return 205; pumpIndex = findpump(&p->network, index); net->Pump[pumpIndex].Upat = patIndex; } break; case EN_PUMP_POWER: if (Link[index].Type == PUMP) { if (value <= 0.0) return 211; pumpIndex = findpump(&p->network, index); net->Pump[pumpIndex].Ptype = CONST_HP; net->Pump[pumpIndex].Hcurve = 0; net->Link[index].Km = value; } break; case EN_PUMP_HCURVE: if (Link[index].Type == PUMP) { return EN_setheadcurveindex(p, index, ROUND(value)); } break; case EN_PUMP_ECURVE: if (Link[index].Type == PUMP) { curveIndex = ROUND(value); if (curveIndex < 0 || curveIndex > net->Ncurves) return 205; pumpIndex = findpump(&p->network, index); net->Pump[pumpIndex].Ecurve = curveIndex; } break; case EN_PUMP_ECOST: if (Link[index].Type == PUMP) { if (value < 0.0) return 211; pumpIndex = findpump(&p->network, index); net->Pump[pumpIndex].Ecost = value; } break; case EN_PUMP_EPAT: if (Link[index].Type == PUMP) { patIndex = ROUND(value); if (patIndex < 0 || patIndex > net->Npats) return 205; pumpIndex = findpump(&p->network, index); net->Pump[pumpIndex].Epat = patIndex; } break; case EN_PCV_CURVE: if (Link[index].Type == PCV) { curveIndex = ROUND(value); if (curveIndex < 0 || curveIndex > net->Ncurves) return 206; net->Valve[findvalve(&p->network, index)].Curve = curveIndex; } break; case EN_GPV_CURVE: if (Link[index].Type == GPV) { curveIndex = ROUND(value); if (curveIndex < 0 || curveIndex > net->Ncurves) return 206; Link[index].Kc = curveIndex; if (hyd->OpenHflag == FALSE) Link[index].InitSetting = curveIndex; } break; case EN_LEAK_AREA: // leak area in sq mm per 100 pipe length units if (value < 0.0) return 211; Link[index].LeakArea = value / Ucf[LENGTH]; break; case EN_LEAK_EXPAN: // leak area expansion slope (sq mm per unit of head) if (value < 0.0) return 211; Link[index].LeakExpan = value / Ucf[LENGTH]; break; default: return 251; } return 0; } int DLLEXPORT EN_setpipedata(EN_Project p, int index, double length, double diam, double rough, double mloss) /*---------------------------------------------------------------- ** Input: index = pipe link index ** length = pipe length ** diam = pipe diameter ** rough = pipe roughness coefficient ** mloss = minor loss coefficient ** Output: none ** Returns: error code ** Purpose: sets several properties for a pipe link **---------------------------------------------------------------- */ { Network *net = &p->network; Slink *Link = net->Link; double *Ucf = p->Ucf; double diameter = diam; // Check that pipe exists if (!p->Openflag) return 102; if (index <= 0 || index > net->Nlinks) return 204; if (Link[index].Type > PIPE) return 0; // Check for valid parameters if (length <= 0.0 || diam <= 0.0 || rough <= 0.0 || mloss < 0.0) return 211; // Assign parameters to pipe Link[index].Len = length / Ucf[ELEV]; diameter /= Ucf[DIAM]; Link[index].Diam = diameter; Link[index].Kc = rough; if (p->hydraul.Formflag == DW) Link[index].Kc /= (1000.0 * Ucf[ELEV]); // Update minor loss factor & pipe flow resistance Link[index].Km = 0.02517 * mloss / SQR(Link[index].Diam) / SQR(Link[index].Diam); resistcoeff(p, index); return 0; } int DLLEXPORT EN_getvertexcount(EN_Project p, int index, int *count) /*---------------------------------------------------------------- ** Input: index = link index ** Output: count = number of link's vertex points ** Returns: error code ** Purpose: retrieves number of vertex points in a link **---------------------------------------------------------------- */ { Network *net = &p->network; Slink *Link = net->Link; Pvertices vertices; // Check that link exists *count = 0; if (!p->Openflag) return 102; if (index <= 0 || index > net->Nlinks) return 204; // Set count to number of vertices vertices = Link[index].Vertices; if (vertices) *count = vertices->Npts; return 0; } int DLLEXPORT EN_getvertex(EN_Project p, int index, int vertex, double *x, double *y) /*---------------------------------------------------------------- ** Input: index = link index ** vertex = index of a link vertex point ** Output: x = vertex point's X-coordinate ** y = vertex point's Y-coordinate ** Returns: error code ** Purpose: retrieves the coordinates of a vertex point in a link **---------------------------------------------------------------- */ { Network *net = &p->network; Slink *Link = net->Link; Pvertices vertices; // Check that link exists *x = MISSING; *y = MISSING; if (!p->Openflag) return 102; if (index <= 0 || index > net->Nlinks) return 204; // Check that vertex exists vertices = Link[index].Vertices; if (vertices == NULL) return 255; if (vertex <= 0 || vertex > vertices->Npts) return 255; *x = vertices->X[vertex - 1]; *y = vertices->Y[vertex - 1]; return 0; } int DLLEXPORT EN_setvertex(EN_Project p, int index, int vertex, double x, double y) /*---------------------------------------------------------------- ** Input: index = link index ** vertex = index of a link vertex point ** x = vertex point's X-coordinate ** y = vertex point's Y-coordinate ** Returns: error code ** Purpose: sets the coordinates of a vertex point in a link **---------------------------------------------------------------- */ { Network *net = &p->network; Slink *Link = net->Link; Pvertices vertices; // Check that link exists if (!p->Openflag) return 102; if (index <= 0 || index > net->Nlinks) return 204; // Check that vertex exists vertices = Link[index].Vertices; if (vertices == NULL) return 255; if (vertex <= 0 || vertex > vertices->Npts) return 255; vertices->X[vertex - 1] = x; vertices->Y[vertex - 1] = y; return 0; } int DLLEXPORT EN_setvertices(EN_Project p, int index, double *x, double *y, int count) /*---------------------------------------------------------------- ** Input: index = link index ** x = array of X-coordinates for vertex points ** y = array of Y-coordinates for vertex points ** count = number of vertex points ** Returns: error code ** Purpose: assigns a set of vertex points to a link **---------------------------------------------------------------- */ { Network *net = &p->network; Slink *link; int i; int err = 0; // Check that link exists if (!p->Openflag) return 102; if (index <= 0 || index > net->Nlinks) return 204; link = &net->Link[index]; // Delete existing set of vertices freelinkvertices(link); // Add each new vertex to the link for (i = 0; i < count; i++) { err = addlinkvertex(link, x[i], y[i]); if (err) break; } if (err) freelinkvertices(link); return err; } /******************************************************************** Pump Functions ********************************************************************/ int DLLEXPORT EN_getpumptype(EN_Project p, int linkIndex, int *pumpType) /*---------------------------------------------------------------- ** Input: linkIndex = index of a pump link ** Output: pumpType = type of pump head curve (see EN_PumpType) ** Returns: error code ** Purpose: retrieves the type of head curve used by a pump **---------------------------------------------------------------- */ { Network *net = &p->network; Slink *Link = net->Link; Spump *Pump = net->Pump; const int Nlinks = net->Nlinks; *pumpType = -1; if (!p->Openflag) return 102; if (linkIndex < 1 || linkIndex > Nlinks) return 204; if (PUMP != Link[linkIndex].Type) return 216; *pumpType = Pump[findpump(&p->network, linkIndex)].Ptype; return 0; } int DLLEXPORT EN_getheadcurveindex(EN_Project p, int linkIndex, int *curveIndex) /*---------------------------------------------------------------- ** Input: linkIndex = index of a pump link ** Output: curveIndex = index of a pump's head curve ** Returns: error code ** Purpose: retrieves the index of a pump's head curve **---------------------------------------------------------------- */ { Network *net = &p->network; Slink *Link = net->Link; Spump *Pump = net->Pump; const int Nlinks = net->Nlinks; *curveIndex = 0; if (!p->Openflag) return 102; if (linkIndex < 1 || linkIndex > Nlinks) return 204; if (PUMP != Link[linkIndex].Type) return 216; *curveIndex = Pump[findpump(net, linkIndex)].Hcurve; return 0; } int DLLEXPORT EN_setheadcurveindex(EN_Project p, int linkIndex, int curveIndex) /*---------------------------------------------------------------- ** Input: linkIndex = index of a pump link ** curveIndex = index of a curve ** Output: none ** Returns: error code ** Purpose: assigns a new head curve to a pump **---------------------------------------------------------------- */ { Network *net = &p->network; int pumpIndex; int err = 0; Spump *pump; // Check for valid parameters if (!p->Openflag) return 102; if (linkIndex < 1 || linkIndex > net->Nlinks) return 204; if (PUMP != net->Link[linkIndex].Type) return 0; if (curveIndex < 0 || curveIndex > net->Ncurves) return 206; // Assign the new curve to the pump pumpIndex = findpump(net, linkIndex); pump = &net->Pump[pumpIndex]; pump->Hcurve = curveIndex; net->Link[linkIndex].Km = 0.0; return 0; } /******************************************************************** Time Pattern Functions ********************************************************************/ int DLLEXPORT EN_addpattern(EN_Project p, const char *id) /*---------------------------------------------------------------- ** Input: id = time pattern ID name ** Output: none ** Returns: error code ** Purpose: adds a new time pattern to a project **---------------------------------------------------------------- */ { Network *net = &p->network; Parser *parser = &p->parser; int i, n, err = 0; Spattern *pat; // Check if a pattern with same id already exists if (!p->Openflag) return 102; if (EN_getpatternindex(p, id, &i) == 0) return 215; // Check if id name contains invalid characters if (!namevalid(id)) return 252; // Expand the project's array of patterns n = net->Npats + 1; net->Pattern = (Spattern *)realloc(net->Pattern, (n + 1) * sizeof(Spattern)); // Assign properties to the new pattern pat = &net->Pattern[n]; strcpy(pat->ID, id); pat->Comment = NULL; pat->Length = 1; pat->F = (double *)calloc(1, sizeof(double)); if (pat->F == NULL) err = 1; else pat->F[0] = 1.0; // Abort if memory allocation error if (err) { free(pat->F); return 101; } // Update the number of patterns net->Npats = n; parser->MaxPats = n; return 0; } int DLLEXPORT EN_loadpatternfile(EN_Project p, const char *filename, const char *id) /*---------------------------------------------------------------- ** Input: filename = name of the file containing pattern data ** id = ID for the new pattern ** Output: none ** Returns: error code ** Purpose: loads time patterns from a file into a project under a specific pattern ID **---------------------------------------------------------------- */ { FILE *file; char line[MAXLINE+1]; char *tok; int err = 0; int i; int len = 0; double value; double *values = NULL; int CHUNK = 50; if (!p->Openflag) return 102; file = fopen(filename, "r"); if (file == NULL) return 302; // Add a new pattern or use an existing pattern. err = EN_getpatternindex(p, id, &i); if (err == 205) { if ((err = EN_addpattern(p, id)) != 0) { fclose(file); return err; } i = p->network.Npats; } // Read pattern values while (fgets(line, sizeof(line), file) != NULL) { // Skip lines that don't contain valid numbers tok = strtok(line, SEPSTR); if (tok == NULL) continue; if (!getfloat(tok, &value)) continue; // Resize multiplier array if it's full if (len % CHUNK == 0) { values = (double *) realloc(values, (len + CHUNK) * sizeof(double)); // Abort if memory allocation error if (values == NULL) { fclose(file); return 101; } } values[len] = value; len++; } fclose(file); // Transfer multipliers to pattern err = EN_setpattern(p, i, values, len); free(values); return err; } int DLLEXPORT EN_deletepattern(EN_Project p, int index) /*---------------------------------------------------------------- ** Input: index = index of the pattern to delete ** Output: none ** Returns: error code ** Purpose: deletes a time pattern from a project **---------------------------------------------------------------- */ { int i; Network *net = &p->network; Parser *parser = &p->parser; Hydraul *hyd = &p->hydraul; // Can't delete a pattern while a solver is active if (!p->Openflag)return 102; if (p->hydraul.OpenHflag || p->quality.OpenQflag) return 262; // Check that pattern exists if (index < 1 || index > p->network.Npats) return 205; // Adjust references by other objects to patterns adjustpatterns(net, index); // Modify global energy price pattern if (hyd->Epat == index) hyd->Epat = 0; else if (hyd->Epat > index) hyd->Epat--; // Modify global default demand pattern if (hyd->DefPat == index) hyd->DefPat = 0; else if (hyd->DefPat > index) hyd->DefPat--; // Free the pattern's factor array FREE(net->Pattern[index].F); FREE(net->Pattern[index].Comment); // Shift the entries in the network's Pattern array for (i = index; i < net->Npats; i++) net->Pattern[i] = net->Pattern[i+1]; net->Npats--; parser->MaxPats--; return 0; } int DLLEXPORT EN_getpatternindex(EN_Project p, const char *id, int *index) /*---------------------------------------------------------------- ** Input: id = time pattern name ** Output: index = time pattern index ** Returns: error code ** Purpose: retrieves the index of a time pattern **---------------------------------------------------------------- */ { int i; *index = 0; if (!p->Openflag) return 102; for (i = 1; i <= p->network.Npats; i++) { if (strcmp(id, p->network.Pattern[i].ID) == 0) { *index = i; return 0; } } *index = 0; return 205; } int DLLEXPORT EN_getpatternid(EN_Project p, int index, char *id) /*---------------------------------------------------------------- ** Input: index = time pattern index ** Output: id = time pattern ID name ** Returns: error code ** Purpose: retrieves the ID name of a time pattern **---------------------------------------------------------------- */ { strcpy(id, ""); if (!p->Openflag)return 102; if (index < 1 || index > p->network.Npats) return 205; strcpy(id, p->network.Pattern[index].ID); return 0; } int DLLEXPORT EN_setpatternid(EN_Project p, int index, const char *id) /*---------------------------------------------------------------- ** Input: index = time pattern index ** id = time pattern ID name ** Returns: error code ** Purpose: changes the ID name of a time pattern **---------------------------------------------------------------- */ { int i; if (!p->Openflag) return 102; if (index < 1 || index > p->network.Npats) return 205; // Check if id name contains invalid characters if (!namevalid(id)) return 252; for (i = 1; i <= p->network.Npats; i++) { if (i != index && strcmp(id, p->network.Pattern[i].ID) == 0) return 215; } strcpy(p->network.Pattern[index].ID, id); return 0; } int DLLEXPORT EN_getpatternlen(EN_Project p, int index, int *len) /*---------------------------------------------------------------- ** Input: index = time pattern index ** Output: len = number of periods in a time pattern ** Returns: error code ** Purpose: retrieves the number of time periods in a time pattern **---------------------------------------------------------------- */ { if (!p->Openflag) return 102; if (index < 1 || index > p->network.Npats) return 205; *len = p->network.Pattern[index].Length; return 0; } int DLLEXPORT EN_getpatternvalue(EN_Project p, int index, int period, double *value) /*---------------------------------------------------------------- ** Input: index = time pattern index ** period = time pattern period ** Output: value = pattern factor for a particular time period ** Returns: error code ** Purpose: retrieves the pattern factor for a specific time period ** in a time pattern **---------------------------------------------------------------- */ { *value = 0.0; if (!p->Openflag) return 102; if (index < 1 || index > p->network.Npats) return 205; if (period < 1 || period > p->network.Pattern[index].Length) return 251; *value = (double)p->network.Pattern[index].F[period - 1]; return 0; } int DLLEXPORT EN_setpatternvalue(EN_Project p, int index, int period, double value) /*---------------------------------------------------------------- ** Input: index = time pattern index ** period = time pattern period ** value = pattern factor for a particular time period ** Output: none ** Returns: error code ** Purpose: sets the pattern factor for a specific time period ** in a time pattern **---------------------------------------------------------------- */ { Network *net = &p->network; Spattern *Pattern = net->Pattern; if (!p->Openflag) return 102; if (index <= 0 || index > net->Npats) return 205; if (period <= 0 || period > Pattern[index].Length) return 251; Pattern[index].F[period - 1] = value; return 0; } int DLLEXPORT EN_getaveragepatternvalue(EN_Project p, int index, double *value) /*---------------------------------------------------------------- ** Input: index = time pattern index ** Output: value = average of a time pattern's factors ** Returns: error code ** Purpose: retrieves the average of all pattern factors for a time pattern **---------------------------------------------------------------- */ { Network *net = &p->network; Spattern *Pattern = net->Pattern; int i; *value = 0.0; if (!p->Openflag) return 102; if (index < 0 || index > net->Npats) return 205; for (i = 0; i < Pattern[index].Length; i++) { *value += (double)Pattern[index].F[i]; } *value /= (double)Pattern[index].Length; return 0; } int DLLEXPORT EN_setpattern(EN_Project p, int index, double *values, int len) /*---------------------------------------------------------------- ** Input: index = time pattern index ** values = an array of pattern factor values ** len = number of time periods contained in f ** Output: none ** Returns: error code ** Purpose: replaces the pattern factors in a time pattern **---------------------------------------------------------------- */ { Network *net = &p->network; int j; Spattern *Pattern = net->Pattern; // Check for valid arguments if (!p->Openflag) return 102; if (index <= 0 || index > net->Npats) return 205; if (values == NULL) return 205; if (len <= 0) return 202; // Re-set number of time periods & reallocate memory for multipliers Pattern[index].Length = len; Pattern[index].F = (double *)realloc(Pattern[index].F, len * sizeof(double)); if (Pattern[index].F == NULL) return 101; // Load multipliers into pattern for (j = 0; j < len; j++) Pattern[index].F[j] = values[j]; return 0; } /******************************************************************** Data Curve Functions ********************************************************************/ int DLLEXPORT EN_addcurve(EN_Project p, const char *id) /*---------------------------------------------------------------- ** Input: id = data curve ID name ** Output: none ** Returns: error code ** Purpose: adds a new data curve to a project **---------------------------------------------------------------- */ { Network *net = &p->network; int i, n, err = 0; Scurve *curve; // Check if a curve with same id already exists if (!p->Openflag) return 102; if (EN_getcurveindex(p, id, &i) == 0) return 215; // Check if id name contains invalid characters if (!namevalid(id)) return 252; // Expand the array of curves n = net->Ncurves + 1; net->Curve = (Scurve *) realloc(net->Curve, (n + 1) * sizeof(Scurve)); // Set the properties of the new curve curve = &net->Curve[n]; strcpy(curve->ID, id); curve->Comment = NULL; curve->Capacity = 1; curve->Npts = 1; curve->Type = GENERIC_CURVE; curve->X = (double *)calloc(1, sizeof(double)); curve->Y = (double *)calloc(1, sizeof(double)); if (curve->X == NULL) err = 1; else if (curve->Y == NULL) err = 1; else { curve->X[0] = 1.0; curve->Y[0] = 1.0; } // Abort if memory allocation error if (err) { free(curve->X); free(curve->Y); return 101; } // Update the number of curves net->Ncurves = n; p->parser.MaxCurves = n; return 0; } int DLLEXPORT EN_deletecurve(EN_Project p, int index) /*---------------------------------------------------------------- ** Input: index = index of the curve to delete ** Output: none ** Returns: error code ** Purpose: deletes a data curve from a project **---------------------------------------------------------------- */ { int i; Network *net = &p->network; Parser *parser = &p->parser; // Can't delete a curve while a solver is active if (!p->Openflag)return 102; if (p->hydraul.OpenHflag || p->quality.OpenQflag) return 262; // Check that curve exists if (index < 1 || index > p->network.Ncurves) return 205; // Adjust references by other objects to curves adjustcurves(net, index); // Free the curve's data arrays FREE(net->Curve[index].X); FREE(net->Curve[index].Y); FREE(net->Curve[index].Comment); // Shift the entries in the network's Curve array for (i = index; i < net->Ncurves; i++) net->Curve[i] = net->Curve[i + 1]; net->Ncurves--; parser->MaxCurves--; return 0; } int DLLEXPORT EN_getcurveindex(EN_Project p, const char *id, int *index) /*---------------------------------------------------------------- ** Input: id = data curve name ** Output: index = data curve index ** Returns: error code ** Purpose: retrieves the index of a data curve **---------------------------------------------------------------- */ { *index = 0; if (!p->Openflag) return 102; *index = findcurve(&p->network, id); if (*index == 0) return 206; return 0; } int DLLEXPORT EN_getcurveid(EN_Project p, int index, char *id) /*---------------------------------------------------------------- ** Input: index = data curve index ** Output: id = data curve ID name ** Returns: error code ** Purpose: retrieves the name of a data curve **---------------------------------------------------------------- */ { strcpy(id, ""); if (!p->Openflag) return 102; if (index < 1 || index > p->network.Ncurves) return 206; strcpy(id, p->network.Curve[index].ID); return 0; } int DLLEXPORT EN_setcurveid(EN_Project p, int index, const char *id) /*---------------------------------------------------------------- ** Input: index = data curve index ** id = data curve ID name ** Returns: error code ** Purpose: changes the ID name of a data curve **---------------------------------------------------------------- */ { int i; if (!p->Openflag) return 102; if (index < 1 || index > p->network.Ncurves) return 205; // Check if id name contains invalid characters if (!namevalid(id)) return 252; for (i = 1; i <= p->network.Ncurves; i++) { if (i != index && strcmp(id, p->network.Curve[i].ID) == 0) return 215; } strcpy(p->network.Curve[index].ID, id); return 0; } int DLLEXPORT EN_getcurvelen(EN_Project p, int index, int *len) /*---------------------------------------------------------------- ** Input: index = data curve index ** Output: len = number of points in a data curve ** Returns: error code ** Purpose: retrieves the number of points in a data curve **---------------------------------------------------------------- */ { if (!p->Openflag) return 102; if (index < 1 || index > p->network.Ncurves) return 206; *len = p->network.Curve[index].Npts; return 0; } int DLLEXPORT EN_getcurvetype(EN_Project p, int index, int *type) /*---------------------------------------------------------------- ** Input: index = data curve index ** Output: type = type of data curve (see EN_CurveType) ** Returns: error code ** Purpose: retrieves the type assigned to a data curve **---------------------------------------------------------------- */ { Network *net = &p->network; if (!p->Openflag) return 102; if (index < 1 || index > net->Ncurves) return 206; *type = net->Curve[index].Type; return 0; } int DLLEXPORT EN_setcurvetype(EN_Project p, int index, int type) /*---------------------------------------------------------------- ** Input: index = data curve index ** type = type of data curve (see EN_CurveType) ** Returns: error code ** Purpose: sets the type assigned to a data curve **---------------------------------------------------------------- */ { Network *net = &p->network; if (!p->Openflag) return 102; if (index < 1 || index > net->Ncurves) return 206; if (type < 0 || type > EN_VALVE_CURVE) return 251; net->Curve[index].Type = type; return 0; } int DLLEXPORT EN_getcurvevalue(EN_Project p, int curveIndex, int pointIndex, double *x, double *y) /*---------------------------------------------------------------- ** Input: curveIndex = data curve index ** pointIndex = index of a data point on the curve ** Output: x = x-value of the point on the curve ** y = y-value of the point on the curve ** Returns: error code ** Purpose: retrieves the value of a specific point on a data curve **---------------------------------------------------------------- */ { *x = 0.0; *y = 0.0; if (!p->Openflag) return 102; if (curveIndex < 1 || curveIndex > p->network.Ncurves) return 206; if (pointIndex < 1 || pointIndex > p->network.Curve[curveIndex].Npts) return 251; *x = p->network.Curve[curveIndex].X[pointIndex - 1]; *y = p->network.Curve[curveIndex].Y[pointIndex - 1]; return 0; } int DLLEXPORT EN_setcurvevalue(EN_Project p, int curveIndex, int pointIndex, double x, double y) /*---------------------------------------------------------------- ** Input: curveIndex = data curve index ** pointIndex = index of a data point on the curve ** x = new x-value for the point on the curve ** y = new y-value for the point on the curve ** Output: none ** Returns: error code ** Purpose: sets the value of a specific point on a data curve ** Note: if pointIndex exceeds the curve's length a new point is added. **---------------------------------------------------------------- */ { Network *net = &p->network; Scurve *curve; double x1 = -1.e37, x2 = 1.e37; int n = pointIndex - 1; // Check for valid input if (!p->Openflag) return 102; if (curveIndex <= 0 || curveIndex > net->Ncurves) return 206; curve = &net->Curve[curveIndex]; if (pointIndex <= 0) return 251; // Check that new point maintains increasing x values if (n - 1 >= 0) x1 = curve->X[n-1]; if (n + 1 < curve->Npts) x2 = curve->X[n+1]; if (x <= x1 || x >= x2) return 230; // Expand curve if need be if (pointIndex > curve->Npts) pointIndex = curve->Npts + 1; if (pointIndex >= curve->Capacity) { if (resizecurve(curve, curve->Capacity + 10) > 0) return 101; } // Increase curve's number of points if need be if (pointIndex > curve->Npts) { curve->Npts++; n = curve->Npts - 1; } // Insert new point into curve curve->X[n] = x; curve->Y[n] = y; return 0; } int DLLEXPORT EN_getcurve(EN_Project p, int index, char *id, int *nPoints, double *xValues, double *yValues) /*---------------------------------------------------------------- ** Input: index = data curve index ** Output: id = ID name of data curve ** nPoints = number of data points on the curve ** xValues = array of x-values for each data point ** yValues = array of y-values for each data point ** Returns: error code ** Purpose: retrieves the data associated with a data curve ** ** The calling program is responsible for making xValues and ** yValues large enough to hold nPoints data points. **---------------------------------------------------------------- */ { int i; Scurve *curve; if (!p->Openflag) return 102; if (index <= 0 || index > p->network.Ncurves) return 206; if (xValues == NULL || yValues == NULL) return 206; curve = &p->network.Curve[index]; strncpy(id, curve->ID, MAXID); *nPoints = curve->Npts; for (i = 0; i < curve->Npts; i++) { xValues[i] = curve->X[i]; yValues[i] = curve->Y[i]; } return 0; } int DLLEXPORT EN_setcurve(EN_Project p, int index, double *xValues, double *yValues, int nPoints) /*---------------------------------------------------------------- ** Input: index = data curve index ** xValues = array of x-values ** yValues = array of y-values ** nPoints = number of data points in the x and y arrays ** Returns: error code ** Purpose: replaces a curve's set of data points **---------------------------------------------------------------- */ { Network *net = &p->network; Scurve *curve; int j; // Check for valid arguments if (!p->Openflag) return 102; if (index <= 0 || index > net->Ncurves) return 206; if (xValues == NULL || yValues == NULL) return 206; if (nPoints <= 0) return 202; // Check that x values are increasing for (j = 1; j < nPoints; j++) if (xValues[j-1] >= xValues[j]) return 230; // Expand size of curve's data arrays if need be curve = &net->Curve[index]; if (resizecurve(curve, nPoints) > 0) return 101; // Load values into curve curve->Npts = nPoints; for (j = 0; j < nPoints; j++) { curve->X[j] = xValues[j]; curve->Y[j] = yValues[j]; } return 0; } /******************************************************************** Simple Controls Functions ********************************************************************/ int DLLEXPORT EN_addcontrol(EN_Project p, int type, int linkIndex, double setting, int nodeIndex, double level, int *index) /*---------------------------------------------------------------- ** Input: type = type of control (see EN_ControlType) ** linkIndex = index of link being controlled ** setting = link control setting (e.g., pump speed) ** nodeIndex = index of node controlling a link (for level controls) ** level = control activation level (pressure for junction nodes, ** water level for tank nodes or time value for time-based ** control) ** Output: index = the index of the new control ** Returns: error code ** Purpose: adds a new simple control to a project **---------------------------------------------------------------- */ { Network *net = &p->network; int err, n; Scontrol ctrl; // Check that project exists if (!p->Openflag) return 102; // Check that controlled link exists if (linkIndex <= 0 || linkIndex > net->Nlinks) return 204; // Insert control properties into a temporary struct err = setcontrol(p, type, linkIndex, setting, nodeIndex, level, &ctrl); if (err > 0) return err; // Expand project's array of controls n = net->Ncontrols + 1; net->Control = (Scontrol *)realloc(net->Control, (n + 1) * sizeof(Scontrol)); // Set properties of the new control net->Control[n] = ctrl; // Update number of controls net->Ncontrols = n; p->parser.MaxControls = n; // Replace the control's index *index = n; return 0; } int DLLEXPORT EN_deletecontrol(EN_Project p, int index) /*---------------------------------------------------------------- ** Input: index = index of the control ** Output: none ** Returns: error code ** Purpose: deletes a simple control from a project **---------------------------------------------------------------- */ { Network *net = &p->network; int i; if (index <= 0 || index > net->Ncontrols) return 241; for (i = index; i <= net->Ncontrols - 1; i++) { net->Control[i] = net->Control[i + 1]; } net->Ncontrols--; return 0; } int DLLEXPORT EN_getcontrol(EN_Project p, int index, int *type, int *linkIndex, double *setting, int *nodeIndex, double *level) /*---------------------------------------------------------------- ** Input: index = index of the control ** Output: type = type of control (see EN_ControlType) ** linkIndex = index of link being controlled ** setting = link control setting (e.g., pump speed) ** nodeIndex = index of node that triggers a level control ** level = trigger level that activates the control (pressure for junction nodes, ** water level for tank nodes or time value for time-based control) ** Returns: error code ** Purpose: Retrieves the properties of a simple control **---------------------------------------------------------------- */ { Network *net = &p->network; double s, lvl; double *Ucf = p->Ucf; Scontrol *control; Snode *node; // Set default return values s = 0.0; lvl = 0.0; *type = 0; *linkIndex = 0; *nodeIndex = 0; // Check for valid arguments if (!p->Openflag) return 102; if (index <= 0 || index > net->Ncontrols) return 241; // Retrieve control's type and link index control = &net->Control[index]; *type = control->Type; *linkIndex = control->Link; // Retrieve control's setting s = control->Setting; if (control->Setting != MISSING) { switch (net->Link[*linkIndex].Type) { case PRV: case PSV: case PBV: s *= Ucf[PRESSURE]; break; case FCV: s *= Ucf[FLOW]; default: break; } } else if (control->Status == OPEN) s = SET_OPEN; else s = SET_CLOSED; // Retrieve level value for a node level control *nodeIndex = control->Node; if (*nodeIndex > 0) { node = &net->Node[*nodeIndex]; if (*nodeIndex > net->Njuncs) // Node is a tank { lvl = (control->Grade - node->El) * Ucf[ELEV]; } else // Node is a junction { lvl = (control->Grade - node->El) * Ucf[PRESSURE]; } } // Retrieve level value for a time-based control else { lvl = (double)control->Time; } *setting = s; *level = lvl; return 0; } int DLLEXPORT EN_setcontrol(EN_Project p, int index, int type, int linkIndex, double setting, int nodeIndex, double level) /*---------------------------------------------------------------- ** Input: index = index of the control ** type = type of control (see EN_ControlType) ** linkIndex = index of link being controlled ** setting = link control setting (e.g., pump speed) ** nodeIndex = index of node that triggers the control (for level controls) ** level = trigger level that activates the control (pressure for junction nodes, ** water level for tank nodes or time value for time-based control) ** Output: none ** Returns: error code ** Purpose: Sets the properties of a simple control **---------------------------------------------------------------- */ { Network *net = &p->network; int err; Scontrol ctrl; // Check that project exists if (!p->Openflag) return 102; // Check that control exists if (index <= 0 || index > net->Ncontrols) return 241; // Check that controlled link exists (0 index de-activates the control) if (linkIndex == 0) { net->Control[index].Link = 0; return 0; } if (linkIndex < 0 || linkIndex > net->Nlinks) return 204; // Assign new set of properties to control err = setcontrol(p, type, linkIndex, setting, nodeIndex, level, &ctrl); if (err > 0) return err; net->Control[index] = ctrl; return 0; } int DLLEXPORT EN_getcontrolenabled(EN_Project p, int index, int *enabled) { Network *net = &p->network; Scontrol *control; // Check for valid arguments if (!p->Openflag) return 102; if (index <= 0 || index > net->Ncontrols) return 241; control = &net->Control[index]; *enabled = control->isEnabled; return 0; } int DLLEXPORT EN_setcontrolenabled(EN_Project p, int index, int enabled) { Network *net = &p->network; Scontrol *control; // Check for valid arguments if (enabled != TRUE && enabled != FALSE) return 202; // illegal numeric value if (!p->Openflag) return 102; if (index <= 0 || index > net->Ncontrols) return 241; control = &net->Control[index]; control->isEnabled = enabled; return 0; } /******************************************************************** Rule-Based Controls Functions ********************************************************************/ int DLLEXPORT EN_addrule(EN_Project p, char *rule) /*---------------------------------------------------------------- ** Input: rule = text of rule being added in the format ** used for the [RULES] section of an EPANET input file ** Output: none ** Returns: error code ** Purpose: adds a new rule to a project **---------------------------------------------------------------- */ { Network *net = &p->network; Parser *parser = &p->parser; Rules *rules = &p->rules; char *line; char *nextline; char line2[MAXLINE+1]; // Resize rules array net->Rule = (Srule *)realloc(net->Rule, (net->Nrules + 2)*sizeof(Srule)); rules->Errcode = 0; rules->RuleState = 6; // = r_PRIORITY // Extract each line of the rule statement line = rule; while (line) { // Find where current line ends and next one begins nextline = strchr(line, '\n'); if (nextline) *nextline = '\0'; // Copy and tokenize the current line strcpy(line2, line); strcat(line2, "\n"); // Tokenizer won't work without this parser->Ntokens = gettokens(line2, parser->Tok, MAXTOKS, parser->Comment); // Process the line to build up the rule's contents if (parser->Ntokens > 0 && *parser->Tok[0] != ';') { ruledata(p); // Nrules gets updated in ruledata() if (rules->Errcode) break; } // Extract next line from the rule statement if (nextline) *nextline = '\n'; line = nextline ? (nextline + 1) : NULL; } // Delete new rule entry if there was an error if (rules->Errcode) deleterule(p, net->Nrules); // Re-assign error code 201 (syntax error) to 250 (invalid format) if (rules->Errcode == 201) rules->Errcode = 250; return rules->Errcode; } int DLLEXPORT EN_deleterule(EN_Project p, int index) /*---------------------------------------------------------------- ** Input: index = rule index ** Output: none ** Returns: error code ** Purpose: deletes a rule from a project **---------------------------------------------------------------- */ { if (index < 1 || index > p->network.Nrules) return 257; deleterule(p, index); return 0; } int DLLEXPORT EN_getrule(EN_Project p, int index, int *nPremises, int *nThenActions, int *nElseActions, double *priority) /*---------------------------------------------------------------- ** Input: index = rule index ** Output: nPremises = number of premises conditions (IF AND OR) ** nThenActions = number of actions in THEN portion of rule ** nElseActions = number of actions in ELSE portion of rule ** priority = rule priority ** Returns: error code ** Purpose: gets information about a particular rule **---------------------------------------------------------------- */ { Network *net = &p->network; int count; Spremise *premise; Saction *action; if (index < 1 || index > net->Nrules) return 257; *priority = (double)p->network.Rule[index].priority; count = 0; premise = net->Rule[index].Premises; while (premise != NULL) { count++; premise = premise->next; } *nPremises = count; count = 0; action = net->Rule[index].ThenActions; while (action != NULL) { count++; action = action->next; } *nThenActions = count; count = 0; action = net->Rule[index].ElseActions; while (action != NULL) { count++; action = action->next; } *nElseActions = count; return 0; } int DLLEXPORT EN_getruleID(EN_Project p, int index, char *id) /*---------------------------------------------------------------- ** Input: index = rule index ** Output: id = rule ID label ** Returns: error code ** Purpose: retrieves the ID label of a rule **---------------------------------------------------------------- */ { strcpy(id, ""); if (!p->Openflag) return 102; if (index < 1 || index > p->network.Nrules) return 257; strcpy(id, p->network.Rule[index].label); return 0; } int DLLEXPORT EN_getpremise(EN_Project p, int ruleIndex, int premiseIndex, int *logop, int *object, int *objIndex, int *variable, int *relop, int *status, double *value) /*---------------------------------------------------------------- ** Input: ruleIndex = rule index ** premiseIndex = premise index ** Output: logop = logical operator (IF = 1, AND = 2, OR = 3) ** object = type of object appearing in the premise (see EN_RuleObject) ** objIndex = object's index in Node or Link array ** variable = object variable being tested (see EN_RuleVariable) ** relop = relational operator (see EN_RuleOperator) ** status = object status being tested against (see EN_RuleStatus)) ** value = variable value being tested against ** Returns: error code ** Purpose: retrieves the properties of a rule's premise **---------------------------------------------------------------- */ { Spremise *premises; Spremise *premise; if (ruleIndex < 1 || ruleIndex > p->network.Nrules) return 257; premises = p->network.Rule[ruleIndex].Premises; premise = getpremise(premises, premiseIndex); if (premise == NULL) return 258; *logop = premise->logop; *object = premise->object; *objIndex = premise->index; *variable = premise->variable; *relop = premise->relop; *status = premise->status; *value = (double)premise->value; return 0; } int DLLEXPORT EN_setpremise(EN_Project p, int ruleIndex, int premiseIndex, int logop, int object, int objIndex, int variable, int relop, int status, double value) /*---------------------------------------------------------------- ** Input: ruleIndex = rule index ** premiseIndex = premise index ** logop = logical operator (IF = 1, AND = 2, OR = 3) ** object = type of object appearing in the premise (see EN_RuleObject) ** objIndex = object's index in Node or Link array ** variable = object variable being tested (see EN_RuleVariable) ** relop = relational operator (see EN_RuleOperator) ** status = object status being tested against (see EN_RuleStatus)) ** value = variable value being tested against ** Output: none ** Returns: error code ** Purpose: sets the properties of a rule's premise **---------------------------------------------------------------- */ { Spremise *premises; Spremise *premise; if (ruleIndex < 1 || ruleIndex > p->network.Nrules) return 257; premises = p->network.Rule[ruleIndex].Premises; premise = getpremise(premises, premiseIndex); if (premise == NULL) return 258; premise->logop = logop; premise->object = object; premise->index = objIndex; premise->variable = variable; premise->relop = relop; premise->status = status; premise->value = value; return 0; } int DLLEXPORT EN_setpremiseindex(EN_Project p, int ruleIndex, int premiseIndex, int objIndex) /*---------------------------------------------------------------- ** Input: ruleIndex = rule index ** premiseIndex = premise index ** objIndex = object's index in Node or Link array ** Output: none ** Returns: error code ** Purpose: sets the index of an object referred to in a rule's premise **---------------------------------------------------------------- */ { Spremise *premises; Spremise *premise; if (ruleIndex < 1 || ruleIndex > p->network.Nrules) return 257; premises = p->network.Rule[ruleIndex].Premises; premise = getpremise(premises, premiseIndex); if (premise == NULL) return 258; premise->index = objIndex; return 0; } int DLLEXPORT EN_setpremisestatus(EN_Project p, int ruleIndex, int premiseIndex, int status) /*---------------------------------------------------------------- ** Input: ruleIndex = rule index ** premiseIndex = premise index ** status = object status being tested against ** (see EN_RuleStatus)) ** Output: none ** Returns: error code ** Purpose: sets the status of an object being tested against ** in a rule's premise **---------------------------------------------------------------- */ { Spremise *premises; Spremise *premise; if (ruleIndex < 1 || ruleIndex > p->network.Nrules) return 257; premises = p->network.Rule[ruleIndex].Premises; premise = getpremise(premises, premiseIndex); if (premise == NULL) return 258; premise->status = status; return 0; } int DLLEXPORT EN_setpremisevalue(EN_Project p, int ruleIndex, int premiseIndex, double value) /*---------------------------------------------------------------- ** Input: ruleIndex = rule index ** premiseIndex = premise index ** value = value of object variable being tested against ** Output: none ** Returns: error code ** Purpose: sets the value of object's variable being tested against ** in a rule's premise **---------------------------------------------------------------- */ { Spremise *premises; Spremise *premise; if (ruleIndex < 1 || ruleIndex > p->network.Nrules) return 257; premises = p->network.Rule[ruleIndex].Premises; premise = getpremise(premises, premiseIndex); if (premise == NULL) return 258; premise->value = value; return 0; } int DLLEXPORT EN_getthenaction(EN_Project p, int ruleIndex, int actionIndex, int *linkIndex, int *status, double *setting) /*---------------------------------------------------------------- ** Input: ruleIndex = rule index ** actionIndex = index of a rule's THEN actions ** Output: linkIndex = index of link appearing in the action ** status = status assigned to the link (see EN_RuleStatus)) ** setting = setting assigned to the link ** Returns: error code ** Purpose: retrieves the properties of a rule's THEN action **---------------------------------------------------------------- */ { Saction *actions; Saction *action; if (ruleIndex < 1 || ruleIndex > p->network.Nrules) return 257; actions = p->network.Rule[ruleIndex].ThenActions; action = getaction(actions, actionIndex); if (action == NULL) return 258; *linkIndex = action->link; *status = action->status; *setting = (double)action->setting; return 0; } int DLLEXPORT EN_setthenaction(EN_Project p, int ruleIndex, int actionIndex, int linkIndex, int status, double setting) /*---------------------------------------------------------------- ** Input: ruleIndex = rule index ** actionIndex = index of a rule's THEN actions ** linkIndex = index of link appearing in the action ** status = status assigned to the link (see EN_RuleStatus)) ** setting = setting assigned to the link ** Returns: error code ** Purpose: sets the properties of a rule's THEN action **---------------------------------------------------------------- */ { Saction *actions; Saction *action; if (ruleIndex < 1 || ruleIndex > p->network.Nrules) return 257; actions = p->network.Rule[ruleIndex].ThenActions; action = getaction(actions, actionIndex); if (action == NULL) return 258; action->link = linkIndex; action->status = status; action->setting = setting; return 0; } int DLLEXPORT EN_getelseaction(EN_Project p, int ruleIndex, int actionIndex, int *linkIndex, int *status, double *setting) /*---------------------------------------------------------------- ** Input: ruleIndex = rule index ** actionIndex = index of a rule's ELSE actions ** Output: linkIndex = index of link appearing in the action ** status = status assigned to the link (see EN_RuleStatus)) ** setting = setting assigned to the link ** Returns: error code ** Purpose: retrieves the properties of a rule's ELSE action **---------------------------------------------------------------- */ { Saction *actions; Saction *action; if (ruleIndex < 1 || ruleIndex > p->network.Nrules) return 257; actions = p->network.Rule[ruleIndex].ElseActions; action = getaction(actions, actionIndex); if (action == NULL) return 258; *linkIndex = action->link; *status = action->status; *setting = (double)action->setting; return 0; } int DLLEXPORT EN_setelseaction(EN_Project p, int ruleIndex, int actionIndex, int linkIndex, int status, double setting) /*---------------------------------------------------------------- ** Input: ruleIndex = rule index ** actionIndex = index of a rule's ELSE actions ** linkIndex = index of link appearing in the action ** status = status assigned to the link (see EN_RuleStatus)) ** setting = setting assigned to the link ** Returns: error code ** Purpose: sets the properties of a rule's ELSE action **---------------------------------------------------------------- */ { Saction *actions; Saction *action; if (ruleIndex < 1 || ruleIndex > p->network.Nrules) return 257; actions = p->network.Rule[ruleIndex].ElseActions; action = getaction(actions, actionIndex); if (action == NULL) return 258; action->link = linkIndex; action->status = status; action->setting = setting; return 0; } int DLLEXPORT EN_setrulepriority(EN_Project p, int index, double priority) /*----------------------------------------------------------------------------- ** Input: index = rule index ** priority = rule priority level ** Output: none ** Returns: error code ** Purpose: sets the priority level for a rule **----------------------------------------------------------------------------- */ { if (index <= 0 || index > p->network.Nrules) return 257; p->network.Rule[index].priority = priority; return 0; } int DLLEXPORT EN_getruleenabled(EN_Project p, int index, int *enabled) { Network *net = &p->network; Srule *rule; // Check for valid arguments if (!p->Openflag) return 102; if (index <= 0 || index > net->Nrules) return 241; rule = &net->Rule[index]; *enabled = rule->isEnabled; return 0; } int DLLEXPORT EN_setruleenabled(EN_Project p, int index, int enabled) { Network *net = &p->network; Srule *rule; // Check for valid arguments if (enabled != TRUE && enabled != FALSE) return 202; // illegal numeric value if (!p->Openflag) return 102; if (index <= 0 || index > net->Nrules) return 241; rule = &net->Rule[index]; rule->isEnabled = enabled; return 0; }