/* ****************************************************************************** Project: OWA EPANET Version: 2.2 Module: epanet.c Description: implementation of EPANET's API functions Authors: see AUTHORS Copyright: see AUTHORS License: see LICENSE Last Updated: 01/11/2019 ****************************************************************************** */ #include #include #include #ifndef __APPLE__ #include #endif #include #include #include "epanet2_2.h" #include "types.h" #include "funcs.h" #include "text.h" #include "enumstxt.h" #ifdef WINDOWS #define snprintf _snprintf #endif /******************************************************************** System 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); *p = NULL; 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 > CMD) 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); // Initialize the default demand pattern p->parser.MaxPats = 0; getpatterns(p); 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: opens an EPANET input file & reads in network data **---------------------------------------------------------------- */ { int errcode = 0; // Set system flags p->Openflag = FALSE; p->hydraul.OpenHflag = FALSE; p->quality.OpenQflag = FALSE; p->outfile.SaveHflag = FALSE; p->outfile.SaveQflag = FALSE; p->Warnflag = FALSE; p->report.Messageflag = TRUE; p->report.Rptflag = 1; // Initialize data arrays to NULL initpointers(p); // Open input & report files ERRCODE(openfiles(p, inpFile, rptFile, outFile)); if (errcode > 0) { errmsg(p, errcode); return errcode; } // Allocate memory for project's data arrays writewin(p->viewprog, FMT100); ERRCODE(netsize(p)); ERRCODE(allocdata(p)); // Read input data ERRCODE(getdata(p)); // Free temporary linked lists used for Patterns & Curves freeTmplist(p->parser.Patlist); freeTmplist(p->parser.Curvelist); // If using previously saved hydraulics file then open it if (p->outfile.Hydflag == USE) ERRCODE(openhydfile(p)); // Write input summary to report file if (!errcode) { if (p->report.Summaryflag) writesummary(p); writetime(p, FMT104); p->Openflag = TRUE; } else errmsg(p, errcode); return errcode; } int DLLEXPORT EN_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 **---------------------------------------------------------------- */ { Outfile *out; // Free all project data if (p->Openflag) writetime(p, FMT105); freedata(p); // Close output file out = &p->outfile; if (out->TmpOutFile != out->OutFile) { if (out->TmpOutFile != NULL) fclose(out->TmpOutFile); out->TmpOutFile = NULL; } if (out->OutFile != NULL) { fclose(out->OutFile); out->OutFile = NULL; } // 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 (out->HydFile != NULL) { fclose(out->HydFile); out->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; 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; } } // 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) closehyd(p); p->hydraul.OpenHflag = FALSE; return 0; } int DLLEXPORT EN_savehydfile(EN_Project p, 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, 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; return 0; } /******************************************************************** Reporting Functions ********************************************************************/ int DLLEXPORT EN_writeline(EN_Project p, 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_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, 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_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_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 = (double)p->hydraul.Iterations; break; case EN_RELATIVEERROR: *value = (double)p->hydraul.RelativeError; break; case EN_MAXHEADERROR: *value = (double)(p->hydraul.MaxHeadError * p->Ucf[HEAD]); break; case EN_MAXFLOWCHANGE: *value = (double)(p->hydraul.MaxFlowChange * p->Ucf[FLOW]); break; case EN_MASSBALANCE: *value = (double)(p->quality.MassBalance.ratio); break; default: *value = 0.0; break; } return 0; } int DLLEXPORT EN_gettitle(EN_Project p, char *titleline1, char *titleline2, char *titleline3) /*---------------------------------------------------------------- ** Input: None ** Output: titleline1-3 = project's title lines ** Returns: error code ** Purpose: retrieves the title lines of the project **---------------------------------------------------------------- */ { if (!p->Openflag) return 102; strcpy(titleline1, p->Title[0]); strcpy(titleline2, p->Title[1]); strcpy(titleline3, p->Title[2]); return 0; } int DLLEXPORT EN_settitle(EN_Project p, char *titleline1, char *titleline2, char *titleline3) /*---------------------------------------------------------------- ** Input: titleline1-3 = project's title lines ** Returns: error code ** Purpose: sets the title lines of the project **---------------------------------------------------------------- */ { if (!p->Openflag) return 102; strncpy(p->Title[0], titleline1, TITLELEN); strncpy(p->Title[1], titleline2, TITLELEN); strncpy(p->Title[2], titleline3, TITLELEN); return 123; } /******************************************************************** 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_DEFDEMANDPAT: v = hyd->DefPat; 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; 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; Snode *node; Pdemand demand; const int Njuncs = net->Njuncs; double *Ucf = p->Ucf; int i, j; int tmpPat, pat, error; char tmpId[MAXID + 1]; double Ke, n, ucf; if (!p->Openflag) return 102; switch (option) { case EN_TRIALS: if (value < 1.0) return 213; hyd->MaxIter = (int)value; break; case EN_ACCURACY: if (value < 1.e-5 || value > 1.e-1) return 213; hyd->Hacc = value; break; case EN_TOLERANCE: if (value < 0.0) return 213; 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: if (value <= 0.0) return 213; hyd->Dmult = value; break; case EN_HEADERROR: if (value < 0.0) return 213; hyd->HeadErrorLimit = value / Ucf[HEAD]; break; case EN_FLOWCHANGE: if (value < 0.0) return 213; hyd->FlowChangeLimit = value / Ucf[FLOW]; break; case EN_DEFDEMANDPAT: //check that the pattern exists or is set to zero to delete the default pattern pat = ROUND(value); if (pat < 0 || pat > net->Npats) return 205; tmpPat = hyd->DefPat; //get the new pattern ID if (pat == 0) { strncpy(tmpId, p->parser.DefPatID, MAXID); } else { error = EN_getpatternid(p, pat, tmpId); if (error != 0) return error; } // replace node patterns with default pattern for (i = 1; i <= net->Nnodes; i++) { node = &net->Node[i]; for (demand = node->D; demand != NULL; demand = demand->next) { if (demand->Pat == tmpPat) { demand->Pat = pat; strcpy(demand->Name, ""); } } } strncpy(p->parser.DefPatID, tmpId, MAXID); hyd->DefPat = pat; break; case EN_GLOBALEFFIC: if (value <= 0.0 || value > 100.0) return 213; hyd->Epump = value; break; case EN_GLOBALPRICE: if (value < 0.0) return 213; 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: if (value < 0.0) return 213; hyd->Dcost = value; 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, xfactor, yfactor; 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]; p->parser.Flowflag = units; switch (units) { case LPS: case LPM: case MLD: case CMH: case CMD: 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 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_STATISTIC: *value = rpt->Tstatflag; break; case EN_RULESTEP: *value = time->Rulestep; break; case EN_PERIODS: *value = rpt->Nperiods; break; case EN_STARTTIME: *value = time->Tstart; break; case EN_HTIME: *value = time->Htime; 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; } 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; default: return 251; } 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, char *chemName, char *chemUnits, 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; double ccf = 1.0; if (!p->Openflag) return 102; if (qualType < EN_NONE || qualType > EN_TRACE) return 251; qual->Qualflag = (char)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) & (Ucf[QUALITY] != 1)) { 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, char *id, int nodeType) /*---------------------------------------------------------------- ** Input: id = node ID name ** nodeType = type of node (see EN_NodeType) ** Output: none ** 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; int index; int size; struct Sdemand *demand; Stank *tank; Snode *node; Scontrol *control; // Cannot modify network structure while solvers are active if (!p->Openflag) return 102; if (hyd->OpenHflag || qual->OpenQflag) return 262; // Check if a node with same id already exists if (EN_getnodeindex(p, id, &i) == 0) return 215; // Check that id name is not too long if (strlen(id) > MAXID) return 250; // 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); // Actions taken when a new Junction is added if (nodeType == EN_JUNCTION) { net->Njuncs++; nIdx = net->Njuncs; node = &net->Node[nIdx]; demand = (struct Sdemand *)malloc(sizeof(struct Sdemand)); demand->Base = 0.0; demand->Pat = hyd->DefPat; // Use default pattern strcpy(demand->Name, ""); demand->next = NULL; node->D = demand; // shift rest of Node array for (index = net->Nnodes; index >= net->Njuncs; index--) { hashtable_update(net->NodeHashTable, net->Node[index].ID, index + 1); net->Node[index + 1] = net->Node[index]; } // shift indices of Tank array for (index = 1; index <= net->Ntanks; index++) { net->Tank[index].Node += 1; } // shift indices of Links, if necessary for (index = 1; index <= net->Nlinks; index++) { if (net->Link[index].N1 > net->Njuncs - 1) net->Link[index].N1 += 1; if (net->Link[index].N2 > net->Njuncs - 1) net->Link[index].N2 += 1; } // shift indices of tanks/reservoir nodes in controls for (index = 1; index <= net->Ncontrols; ++index) { control = &net->Control[index]; if (control->Node > net->Njuncs - 1) control->Node += 1; } // adjust indices of tanks/reservoirs in Rule premises (see RULES.C) adjusttankrules(p); } // Actions taken when a new Tank/Reservoir is added else { nIdx = net->Nnodes + 1; node = &net->Node[nIdx]; 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->V1max = 10000; } net->Nnodes++; strncpy(node->ID, id, MAXID); // set default values for new node node->El = 0; node->S = NULL; node->C0 = 0; node->Ke = 0; node->Rpt = 0; node->X = MISSING; node->Y = MISSING; strcpy(node->Comment, ""); // Insert new node into hash table hashtable_insert(net->NodeHashTable, node->ID, 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; Pdemand demand, nextdemand; Psource source; // 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; // Count number of simple & rule-based controls that contain the node if (actionCode == EN_CONDITIONAL) { actionCode = incontrols(p, NODE, index); for (i = 1; i <= net->Nlinks; i++) { if (net->Link[i].N1 == index || net->Link[i].N2 == index) actionCode += incontrols(p, LINK, i); } if (actionCode > 0) return 261; } // 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 demand = node->D; while (demand != NULL) { nextdemand = demand->next; free(demand); demand = nextdemand; } source = node->S; if (source != NULL) free(source); // 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); } // Remove references to demands & source in last (inactive) Node array entry net->Node[net->Nnodes].D = NULL; net->Node[net->Nnodes].S = NULL; // 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, 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, 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; size_t n; // Check for valid arguments if (index <= 0 || index > net->Nnodes) return 203; n = strlen(newid); if (n < 1 || n > MAXID) return 209; if (strcspn(newid, " ;") < n) return 209; // 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; Pdemand demand; Psource source; Snode *Node = net->Node; Stank *Tank = net->Tank; int nJuncs = net->Njuncs; double *Ucf = p->Ucf; double *NodeDemand = hyd->NodeDemand; 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: v = 0.0; // NOTE: primary demand category is last on demand list if (index <= nJuncs) { for (demand = Node[index].D; demand != NULL; demand = demand->next) { v = (demand->Base); } } v *= Ucf[FLOW]; break; case EN_PATTERN: v = 0.0; // NOTE: primary demand category is last on demand list if (index <= nJuncs) { for (demand = Node[index].D; demand != NULL; demand = demand->next) { v = (double)(demand->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].V1max * Ucf[VOLUME]; break; case EN_DEMAND: v = 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 && Tank[index - nJuncs].Vmax > 0.0) { v = Tank[index - nJuncs].V1max / Tank[index - nJuncs].Vmax; } 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; default: return 251; } *value = v; 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; Pdemand demand; 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 last on demand list if (index <= nJuncs) { for (demand = Node[index].D; demand != NULL; demand = demand->next) { if (demand->next == NULL) demand->Base = value / Ucf[FLOW]; } } break; case EN_PATTERN: // NOTE: primary demand category is last on demand list j = ROUND(value); if (j < 0 || j > nPats) return 205; if (index <= nJuncs) { for (demand = Node[index].D; demand != NULL; demand = demand->next) { if (demand->next == NULL) demand->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; 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 0; 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; if (index <= nJuncs) return 0; j = index - nJuncs; if (Tank[j].A > 0.0) { value /= Ucf[ELEV]; Tank[j].A = PI * SQR(value) / 4.0; 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); Tank[j].Vcurve = 0; } break; case EN_MINVOLUME: if (value < 0.0) return 209; if (index <= nJuncs) return 0; j = index - nJuncs; if (Tank[j].A > 0.0) { Tank[j].Vmin = value / Ucf[VOLUME]; Tank[j].V0 = tankvolume(p, j, Tank[j].H0); Tank[j].Vmax = tankvolume(p, j, Tank[j].Hmax); } break; case EN_VOLCURVE: if (index < nJuncs) return 0; i = ROUND(value); if (i < 0 || i > net->Ncurves) return 205; curve = &net->Curve[i]; j = index - nJuncs; if (Tank[j].A == 0.0) return 0; n = curve->Npts - 1; if (Tank[j].Vmin * Ucf[VOLUME] < curve->Y[0] || Tank[j].Vmax * Ucf[VOLUME] > curve->Y[n]) return 225; Tank[j].Vcurve = i; 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); Tank[j].A = (curve->Y[n] - curve->Y[0]) / (curve->X[n] - curve->X[0]); break; case EN_MINLEVEL: if (value < 0.0) return 209; if (index <= nJuncs) return 0; // not a tank or reservoir j = index - nJuncs; if (Tank[j].A == 0.0) return 0; // node is a reservoir hTmp = value / Ucf[ELEV] + Node[index].El; if (hTmp < Tank[j].Hmax && hTmp <= Tank[j].H0) { if (Tank[j].Vcurve > 0) return 0; Tank[j].Hmin = hTmp; Tank[j].Vmin = (Tank[j].Hmin - Node[index].El) * Tank[j].A; } else return 225; break; case EN_MAXLEVEL: if (value < 0.0) return 209; if (index <= nJuncs) return 0; // not a tank or reservoir j = index - nJuncs; if (Tank[j].A == 0.0) return 0; // node is a reservoir hTmp = value / Ucf[ELEV] + Node[index].El; if (hTmp > Tank[j].Hmin && hTmp >= Tank[j].H0) { if (Tank[j].Vcurve > 0) return 0; Tank[j].Hmax = hTmp; Tank[j].Vmax = tankvolume(p, j, Tank[j].Hmax); } else return 225; break; case EN_MIXMODEL: j = ROUND(value); if (index <= nJuncs) return 0; 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 0; if (value < 0.0 || value > 1.0) return 209; j = index - nJuncs; if (Tank[j].A > 0.0) { Tank[j].V1max = value * Tank[j].Vmax; } break; case EN_TANK_KBULK: if (index <= nJuncs) return 0; j = index - nJuncs; if (Tank[j].A > 0.0) { Tank[j].Kb = value / SECperDAY; qual->Reactflag = 1; } break; default: return 251; } return 0; } int DLLEXPORT EN_setjuncdata(EN_Project p, int index, double elev, double dmnd, 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 **---------------------------------------------------------------- */ { Network *net = &p->network; int i, patIndex = 0; Snode *Node = net->Node; Pdemand demand; // Check that junction exists if (!p->Openflag) return 102; if (index <= 0 || index > net->Njuncs) return 203; // Check that demand pattern exists if (strlen(dmndpat) > 0) { for (i = 1; i <= net->Npats; i++) { if (strcmp(dmndpat, net->Pattern[i].ID) == 0) { patIndex = i; break; } } if (patIndex == 0) return 205; } // Assign values to junction's parameters Node[index].El = elev / p->Ucf[ELEV]; for (demand = Node[index].D; demand != NULL; demand = demand->next) { if (demand->next == NULL) { demand->Base = dmnd / p->Ucf[FLOW]; demand->Pat = patIndex; } } return 0; } int DLLEXPORT EN_settankdata(EN_Project p, int index, double elev, double initlvl, double minlvl, double maxlvl, double diam, double minvol, 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 area, elevation = elev; double *Ucf = p->Ucf; Stank *Tank = net->Tank; Scurve *curve; // Check that tank exists if (!p->Openflag) return 102; if (index <= net->Njuncs || index > net->Nnodes) return 203; j = index - net->Njuncs; if (Tank[j].A == 0) return 0; // 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 = elevation; Tank[j].A = area / Ucf[ELEV] / Ucf[ELEV]; Tank[j].H0 = elevation + initlvl / Ucf[ELEV]; Tank[j].Hmin = elevation + minlvl / Ucf[ELEV]; Tank[j].Hmax = elevation + maxlvl / Ucf[ELEV]; Tank[j].Vcurve = curveIndex; 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 = (double)(p->hydraul.Pmin * p->Ucf[PRESSURE]); *preq = (double)(p->hydraul.Preq * p->Ucf[PRESSURE]); *pexp = (double)(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 (pmin > preq || pexp <= 0.0) return 209; 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_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 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; int n = 1; // Check for valid arguments if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203; // Retrieve demand for specified category if (nodeIndex <= p->network.Njuncs) { for (d = p->network.Node[nodeIndex].D; n < demandIndex && d->next != NULL; d = d->next) n++; if (n != demandIndex) return 253; *baseDemand = (double)(d->Base * p->Ucf[FLOW]); } else *baseDemand = (double)(0.0); 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; int n = 1; // Check for valid arguments if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203; // Set baseline demand for specified category if (nodeIndex <= p->network.Njuncs) { for (d = p->network.Node[nodeIndex].D; n < demandIndex && d->next != NULL; d = d->next) n++; if (n != demandIndex) return 253; 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; int n = 1; strcpy(demandName, ""); // Check for valid arguments if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > p->network.Njuncs) return 203; // Locate demand category record and retrieve its name for (d = p->network.Node[nodeIndex].D; n < demandIndex && d->next != NULL; d = d->next) n++; if (n != demandIndex) return 253; strcpy(demandName, d->Name); return 0; } int DLLEXPORT EN_setdemandname(EN_Project p, int nodeIndex, int demandIndex, 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; int n = 1; // Check for valid arguments if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > p->network.Njuncs) return 203; // Locate demand category record and assign demandName to it for (d = p->network.Node[nodeIndex].D; n < demandIndex && d->next != NULL; d = d->next) n++; if (n != demandIndex) return 253; strncpy(d->Name, demandName, MAXMSG); 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; int n = 1; // Check for valid arguments if (!p->Openflag) return 102; if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203; for (d = p->network.Node[nodeIndex].D; n < demandIndex && d->next != NULL; d = d->next) n++; if (n != demandIndex) return 253; *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; int n = 1; // 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 demand category record and assign time pattern to it if (nodeIndex <= net->Njuncs) { for (d = net->Node[nodeIndex].D; n < demandIndex && d->next != NULL; d = d->next) n++; if (n != demandIndex) return 253; d->Pat = patIndex; } return 0; } /******************************************************************** Link Functions ********************************************************************/ int DLLEXPORT EN_addlink(EN_Project p, char *id, int linkType, char *fromNode, char *toNode) /*---------------------------------------------------------------- ** 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: none ** 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 if (!p->Openflag) return 102; if (p->hydraul.OpenHflag || p->quality.OpenQflag) return 262; // 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 > GPV) 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 id name is not too long if (strlen(id) > MAXID) return 250; // Check that valve link has legal connections if (linkType > PUMP) { errcode = valvecheck(p, linkType, n1, n2); if (errcode) return errcode; } // Grow link-related arrays to accomodate the new link 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; } link->Type = linkType; link->N1 = n1; link->N2 = n2; link->Status = 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 { link->Diam = 10 / p->Ucf[DIAM]; link->Kc = 100; // Rough. coeff link->Km = 0.0; // Loss coeff link->Len = 1000; } 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->Status = ACTIVE; } link->Kb = 0; link->Kw = 0; link->R = 0; link->Rc = 0; link->Rpt = 0; strcpy(link->Comment, ""); hashtable_insert(net->LinkHashTable, link->ID, 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); // 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; } // 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, 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, 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; size_t n; // Check for valid arguments if (index <= 0 || index > net->Nlinks) return 204; n = strlen(newid); if (n < 1 || n > MAXID) return 211; if (strcspn(newid, " ;") < n) return 211; // 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 > GPV || 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].Status = 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, 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); // Find the index of this new link EN_getlinkindex(p, id, 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 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; // Check for illegal valve connection type = net->Link[index].Type; if (type > PUMP) { errcode = valvecheck(p, 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; double *LinkFlow = hyd->LinkFlow; double *LinkSetting = hyd->LinkSetting; // 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].Status <= CLOSED) v = 0.0; else v = 1.0; break; case EN_INITSETTING: if (Link[index].Type == PIPE || Link[index].Type == CVPIPE) { return EN_getlinkvalue(p, index, EN_ROUGHNESS, value); } v = Link[index].Kc; switch (Link[index].Type) { 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 = 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(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; break; case EN_SETTING: if (Link[index].Type == PIPE || Link[index].Type == CVPIPE) { return EN_getlinkvalue(p, index, EN_ROUGHNESS, value); } if (LinkSetting[index] == MISSING) v = 0.0; else v = 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; default: return 251; } *value = (double)v; 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; double *LinkSetting = hyd->LinkSetting; 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]); resistcoeff(p, index); } 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 > 1) return 211; if (property == EN_INITSTATUS) { setlinkstatus(p, index, s, &Link[index].Status, &Link[index].Kc); } else { setlinkstatus(p, index, s, &hyd->LinkStatus[index], &LinkSetting[index]); } break; case EN_INITSETTING: case EN_SETTING: if (value < 0.0) return 211; if (Link[index].Type == PIPE || Link[index].Type == CVPIPE) { return EN_setlinkvalue(p, index, EN_ROUGHNESS, value); } else { switch (Link[index].Type) { case PUMP: break; case PRV: case PSV: case PBV: value /= Ucf[PRESSURE]; break; case FCV: value /= Ucf[FLOW]; break; case TCV: break; case GPV: return 207; // Cannot modify setting for GPV default: return 0; } if (property == EN_INITSETTING) { setlinksetting(p, index, value, &Link[index].Status, &Link[index].Kc); } else { setlinksetting(p, index, value, &hyd->LinkStatus[index], &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; updatepumpparams(p, pumpIndex); net->Pump[pumpIndex].R /= Ucf[POWER]; net->Pump[pumpIndex].Q0 /= Ucf[FLOW]; net->Pump[pumpIndex].Qmax /= Ucf[FLOW]; net->Pump[pumpIndex].Hmax /= Ucf[HEAD]; } 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; 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; } /******************************************************************** 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; double *Ucf = p->Ucf; int pumpIndex; 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 = &p->network.Pump[pumpIndex]; pump->Ptype = NOCURVE; pump->Hcurve = curveIndex; // Update the pump curve's parameters and convert their units updatepumpparams(p, pumpIndex); if (pump->Ptype == POWER_FUNC) { pump->H0 /= Ucf[HEAD]; pump->R *= (pow(Ucf[FLOW], pump->N) / Ucf[HEAD]); } pump->Q0 /= Ucf[FLOW]; pump->Qmax /= Ucf[FLOW]; pump->Hmax /= Ucf[HEAD]; // Designate the newly assigned curve as being a Pump Curve p->network.Curve[curveIndex].Type = PUMP_CURVE; return 0; } /******************************************************************** Time Pattern Functions ********************************************************************/ int DLLEXPORT EN_addpattern(EN_Project p, 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; Hydraul *hyd = &p->hydraul; 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 that id name is not too long if (strlen(id) > MAXID) return 250; // 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->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; // Make new pattern be default demand pattern if name matches if (strcmp(id, parser->DefPatID) == 0) hyd->DefPat = n; return 0; } int DLLEXPORT EN_getpatternindex(EN_Project p, 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_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 < 1 || 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 (len <= 0) return 251; // 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, 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 that id name is not too long if (strlen(id) > MAXID) return 250; // 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->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_getcurveindex(EN_Project p, 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 **---------------------------------------------------------------- */ { int i; *index = 0; if (!p->Openflag) return 102; for (i = 1; i <= p->network.Ncurves; i++) { if (strcmp(id, p->network.Curve[i].ID) == 0) { *index = i; return 0; } } *index = 0; return 206; } 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_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_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 = (double)p->network.Curve[curveIndex].X[pointIndex - 1]; *y = (double)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 **---------------------------------------------------------------- */ { Network *net = &p->network; Scurve *curve; if (!p->Openflag) return 102; if (curveIndex <= 0 || curveIndex > net->Ncurves) return 206; curve = &net->Curve[curveIndex]; if (pointIndex <= 0 || pointIndex > curve->Npts) return 251; curve->X[pointIndex - 1] = x; curve->Y[pointIndex - 1] = 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; 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 (nPoints <= 0) return 202; // Check that x values are increasing for (j = 1; j < nPoints; j++) if (xValues[j-1] >= xValues[j]) return 230; // Re-set number of points & reallocate memory for values curve = &net->Curve[index]; curve->Npts = nPoints; curve->X = (double *)realloc(curve->X, nPoints * sizeof(double)); curve->Y = (double *)realloc(curve->Y, nPoints * sizeof(double)); if (curve->X == NULL) return 101; if (curve->Y == NULL) return 101; // Load values into curve 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; Parser *parser = &p->parser; char status = ACTIVE; int n; long t = 0; double s = setting, lvl = level; double *Ucf = p->Ucf; Scontrol *control; // Check that project exists if (!p->Openflag) return 102; // Check that controlled link exists if (linkIndex <= 0 || linkIndex > net->Nlinks) return 204; // Cannot control check valve if (net->Link[linkIndex].Type == CVPIPE) return 207; // Check for valid parameters if (type < 0 || type > EN_TIMEOFDAY) return 251; if (type == EN_LOWLEVEL || type == EN_HILEVEL) { if (nodeIndex < 1 || nodeIndex > net->Nnodes) return 203; } else nodeIndex = 0; if (s < 0.0 || lvl < 0.0) return 202; // Adjust units of control parameters switch (net->Link[linkIndex].Type) { case PRV: case PSV: case PBV: s /= Ucf[PRESSURE]; break; case FCV: s /= Ucf[FLOW]; break; case GPV: if (s == 0.0) status = CLOSED; else if (s == 1.0) status = OPEN; else return 202; s = net->Link[linkIndex].Kc; break; case PIPE: case PUMP: status = OPEN; if (s == 0.0) status = CLOSED; default: break; } if (type == LOWLEVEL || type == HILEVEL) { if (nodeIndex > net->Njuncs) lvl = net->Node[nodeIndex].El + level / Ucf[ELEV]; else lvl = net->Node[nodeIndex].El + level / Ucf[PRESSURE]; } if (type == TIMER) t = (long)ROUND(lvl); if (type == TIMEOFDAY) t = (long)ROUND(lvl) % SECperDAY; // 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 control = &net->Control[n]; control->Type = (char)type; control->Link = linkIndex; control->Node = nodeIndex; control->Status = status; control->Setting = s; control->Grade = lvl; control->Time = t; // Update number of controls net->Ncontrols = n; 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 = 1.0; else s = 0.0; // 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 = (double)s; *level = (double)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; char status = ACTIVE; long t = 0; double s = setting, lvl = level; double *Ucf = p->Ucf; Slink *link; Scontrol *control; // Check that project exists if (!p->Openflag) return 102; // Check that control exists if (index <= 0 || index > net->Ncontrols) return 241; control = &net->Control[index]; // Check that controlled link exists (0 index de-activates the control) if (linkIndex == 0) { control->Link = 0; return 0; } if (linkIndex < 0 || linkIndex > net->Nlinks) return 204; // Cannot control check valve if (net->Link[linkIndex].Type == CVPIPE) return 207; // Check for valid control properties if (type < 0 || type > EN_TIMEOFDAY) return 251; if (type == EN_LOWLEVEL || type == EN_HILEVEL) { if (nodeIndex < 1 || nodeIndex > net->Nnodes) return 203; } else nodeIndex = 0; if (s < 0.0 || lvl < 0.0) return 202; // Adjust units of control's properties link = &net->Link[linkIndex]; switch (link->Type) { case PRV: case PSV: case PBV: s /= Ucf[PRESSURE]; break; case FCV: s /= Ucf[FLOW]; break; case GPV: if (s == 0.0) status = CLOSED; else if (s == 1.0) status = OPEN; else return 202; s = link->Kc; break; case PIPE: case PUMP: status = OPEN; if (s == 0.0) status = CLOSED; default: break; } if (type == LOWLEVEL || type == HILEVEL) { if (nodeIndex > net->Njuncs) lvl = net->Node[nodeIndex].El + level / Ucf[ELEV]; else lvl = net->Node[nodeIndex].El + level / Ucf[PRESSURE]; } if (type == TIMER) t = (long)ROUND(lvl); if (type == TIMEOFDAY) t = (long)ROUND(lvl) % SECperDAY; /* Reset control's parameters */ control->Type = (char)type; control->Link = linkIndex; control->Node = nodeIndex; control->Status = status; control->Setting = s; control->Grade = lvl; control->Time = t; 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 = 1; premise = net->Rule[index].Premises; while (premise->next != NULL) { count++; premise = premise->next; } *nPremises = count; count = 1; action = net->Rule[index].ThenActions; while (action->next != NULL) { count++; action = action->next; } *nThenActions = count; action = net->Rule[index].ElseActions; count = 0; if (action != NULL) { count = 1; while (action->next != 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].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_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].ThenActions; 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; }