/*
|
******************************************************************************
|
Project: OWA HYDRAULIC
|
Version: 2.2
|
Module: epanet.c
|
Description: implementation of HYDRAULIC's API functions
|
Authors: see AUTHORS
|
Copyright: see AUTHORS
|
License: see LICENSE
|
Last Updated: 11/15/2019
|
******************************************************************************
|
*/
|
|
#include <stdlib.h>
|
#include <stdio.h>
|
#include <string.h>
|
#include <float.h>
|
#include <math.h>
|
|
#include "epanet2_2.h"
|
#include "types.h"
|
#define EXTERN extern
|
#include "funcs.h"
|
#include "text.h"
|
#include "enumstxt.h"
|
|
#if W30
|
#include "vars.h"
|
#define snprintf _snprintf
|
#endif
|
|
/********************************************************************
|
|
Project Functions
|
|
********************************************************************/
|
|
int DLLEXPORT EN_createproject(EN_Project *p)
|
/*----------------------------------------------------------------
|
** Input: none
|
** Output: p = pointer to a new HYDRAULIC project
|
** Returns: error code
|
** Purpose: creates a new HYDRAULIC 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 HYDRAULIC project
|
**----------------------------------------------------------------
|
*/
|
{
|
if (p == NULL) return -1;
|
if (p->Openflag) {
|
EN_close(p);
|
}
|
remove(p->TmpHydFname);
|
remove(p->TmpOutFname);
|
remove(p->TmpStatFname);
|
free(p);
|
return 0;
|
}
|
|
|
|
int DLLEXPORT EN_runproject(EN_Project p, const char *inpFile,
|
const char *rptFile, const char *outFile,
|
void (*pviewprog)(char *))
|
/*------------------------------------------------------------------------
|
** Input: inpFile = name of HYDRAULIC 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 HYDRAULIC 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 HYDRAULIC 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 HYDRAULIC 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);
|
p->parser.MaxPats = 0;
|
p->Openflag = TRUE;
|
return errcode;
|
}
|
|
int DLLEXPORT EN_open(EN_Project p, const char *inpFile, const char *rptFile,
|
const char *outFile)
|
/*----------------------------------------------------------------
|
** Input: inpFile = name of input file
|
** rptFile = name of report file
|
** outFile = name of binary output file
|
** Output: none
|
** Returns: error code
|
** Purpose: opens an HYDRAULIC 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));
|
|
// Close input file
|
if (p->parser.InFile != NULL)
|
{
|
fclose(p->parser.InFile);
|
p->parser.InFile = NULL;
|
}
|
|
// If using previously saved hydraulics file then open it
|
if (p->outfile.Hydflag == USE) ERRCODE(openhydfile(p));
|
|
// Write input summary to report file
|
if (!errcode)
|
{
|
if (p->report.Summaryflag) writesummary(p);
|
writetime(p, FMT104);
|
p->Openflag = TRUE;
|
}
|
else errmsg(p, errcode);
|
return errcode;
|
}
|
|
int DLLEXPORT EN_gettitle(EN_Project p, char *line1, char *line2, char *line3)
|
/*----------------------------------------------------------------
|
** Input: None
|
** Output: line1, line2, line3 = project's title lines
|
** Returns: error code
|
** Purpose: retrieves the title lines of the project
|
**----------------------------------------------------------------
|
*/
|
{
|
if (!p->Openflag) return 102;
|
strncpy(line1, p->Title[0], TITLELEN);
|
strncpy(line2, p->Title[1], TITLELEN);
|
strncpy(line3, p->Title[2], TITLELEN);
|
return 0;
|
}
|
|
int DLLEXPORT EN_settitle(EN_Project p, char *line1, char *line2, char *line3)
|
/*----------------------------------------------------------------
|
** Input: line1, line2, line3 = project's title lines
|
** Returns: error code
|
** Purpose: sets the title lines of the project
|
**----------------------------------------------------------------
|
*/
|
{
|
if (!p->Openflag) return 102;
|
strncpy(p->Title[0], line1, TITLELEN);
|
strncpy(p->Title[1], line2, TITLELEN);
|
strncpy(p->Title[2], line3, TITLELEN);
|
return 0;
|
}
|
|
/********************************************************************
|
|
Project Functions
|
|
********************************************************************/
|
|
int DLLEXPORT EN_setprojectreport(EN_Project p, int report)
|
/*----------------------------------------------------------------
|
** Input: none
|
** Output: p = pointer to a new HYDRAULIC project
|
** Returns: error code
|
** Purpose: creates a new HYDRAULIC project
|
**----------------------------------------------------------------
|
*/
|
{
|
if (!p->Openflag) return 102;
|
p->isReport = report;
|
return 0;
|
}
|
|
int DLLEXPORT EN_getcomment(EN_Project p, int object, int index, char *comment)
|
/*----------------------------------------------------------------
|
** Input: object = a type of object (see EN_ObjectType)
|
** index = the object's index
|
** Output: comment = the object's descriptive comment
|
** Returns: error code
|
** Purpose: Retrieves an object's descriptive comment
|
**----------------------------------------------------------------
|
*/
|
{
|
return getcomment(&p->network, object, index, comment);
|
}
|
|
int DLLEXPORT EN_setcomment(EN_Project p, int object, int index, char *comment)
|
/*----------------------------------------------------------------
|
** Input: object = a type of object (see EN_ObjectType)
|
** index = the object's index
|
** comment = a descriptive comment to assign
|
** Returns: error code
|
** Purpose: Assigns a descriptive comment to an object
|
**----------------------------------------------------------------
|
*/
|
{
|
return setcomment(&p->network, object, index, comment);
|
}
|
|
int DLLEXPORT EN_getcount(EN_Project p, int object, int *count)
|
/*----------------------------------------------------------------
|
** Input: object = type of object to count (see EN_CountType)
|
** Output: count = number of objects of the specified type
|
** Returns: error code
|
** Purpose: Retrieves number of network objects of a given type
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
|
*count = 0;
|
if (!p->Openflag) return 102;
|
switch (object)
|
{
|
case EN_NODECOUNT:
|
*count = net->Nnodes;
|
break;
|
case EN_TANKCOUNT:
|
*count = net->Ntanks;
|
break;
|
case EN_LINKCOUNT:
|
*count = net->Nlinks;
|
break;
|
case EN_PATCOUNT:
|
*count = net->Npats;
|
break;
|
case EN_CURVECOUNT:
|
*count = net->Ncurves;
|
break;
|
case EN_CONTROLCOUNT:
|
*count = net->Ncontrols;
|
break;
|
case EN_RULECOUNT:
|
*count = net->Nrules;
|
break;
|
default:
|
return 251;
|
}
|
return 0;
|
}
|
|
int DLLEXPORT EN_saveinpfile(EN_Project p, const char *filename)
|
/*----------------------------------------------------------------
|
** Input: filename = name of file to which project is saved
|
** Output: none
|
** Returns: error code
|
** Purpose: saves project to an HYDRAULIC formatted file
|
**----------------------------------------------------------------
|
*/
|
{
|
if (!p->Openflag) return 102;
|
return saveinpfile(p, filename);
|
}
|
|
int DLLEXPORT EN_close(EN_Project p)
|
/*----------------------------------------------------------------
|
** Input: none
|
** Output: none
|
** Returns: error code
|
** Purpose: frees all memory & files used by a project
|
**----------------------------------------------------------------
|
*/
|
{
|
// Free all project data
|
//[CloudflightÐÞ¸Ä]2023-11-17
|
//if (p->Openflag) writetime(p, FMT105);
|
freedata(p);
|
|
// Close output file
|
closeoutfile(p);
|
|
// Close input file
|
if (p->parser.InFile != NULL)
|
{
|
fclose(p->parser.InFile);
|
p->parser.InFile = NULL;
|
}
|
|
// Close report file
|
if (p->report.RptFile != NULL && p->report.RptFile != stdout)
|
{
|
fclose(p->report.RptFile);
|
p->report.RptFile = NULL;
|
}
|
|
// Close hydraulics file
|
if (p->outfile.HydFile != NULL)
|
{
|
fclose(p->outfile.HydFile);
|
p->outfile.HydFile = NULL;
|
}
|
|
// Reset system flags
|
p->Openflag = FALSE;
|
p->hydraul.OpenHflag = FALSE;
|
p->outfile.SaveHflag = FALSE;
|
p->quality.OpenQflag = FALSE;
|
p->outfile.SaveQflag = FALSE;
|
return 0;
|
}
|
|
/********************************************************************
|
|
Hydraulic Analysis Functions
|
|
********************************************************************/
|
|
int DLLEXPORT EN_solveH(EN_Project p)
|
/*----------------------------------------------------------------
|
** Input: none
|
** Output: none
|
** Returns: error code
|
** Purpose: solves for network hydraulics in all time periods
|
**----------------------------------------------------------------
|
*/
|
{
|
int errcode;
|
long t, tstep;
|
|
// Open hydraulics solver
|
errcode = EN_openH(p);
|
if (!errcode)
|
{
|
// Initialize hydraulics
|
errcode = EN_initH(p, EN_SAVE);
|
|
// Analyze each hydraulic time period
|
if (!errcode) do
|
{
|
// Display progress message
|
sprintf(p->Msg, "%-10s",
|
clocktime(p->report.Atime, p->times.Htime));
|
sprintf(p->Msg, FMT101, p->report.Atime);
|
writewin(p->viewprog, p->Msg);
|
|
// Solve for hydraulics & advance to next time period
|
tstep = 0;
|
ERRCODE(EN_runH(p, &t));
|
ERRCODE(EN_nextH(p, &tstep));
|
} while (tstep > 0);
|
}
|
|
// Close hydraulics solver
|
EN_closeH(p);
|
errcode = MAX(errcode, p->Warnflag);
|
return errcode;
|
}
|
|
int DLLEXPORT EN_saveH(EN_Project p)
|
/*----------------------------------------------------------------
|
** Input: none
|
** Output: none
|
** Returns: error code
|
** Purpose: saves hydraulic results to binary file
|
**
|
** Must be called before EN_report() if no WQ simulation made.
|
** Should not be called if EN_solveQ() will be used.
|
**----------------------------------------------------------------
|
*/
|
{
|
int tmpflag;
|
int errcode;
|
|
// Check if hydraulic results exist
|
if (!p->outfile.SaveHflag) return 104;
|
|
// Temporarily turn off WQ analysis
|
tmpflag = p->quality.Qualflag;
|
p->quality.Qualflag = NONE;
|
|
// Call WQ solver to simply transfer results from Hydraulics file
|
// to Output file at fixed length reporting time intervals
|
errcode = EN_solveQ(p);
|
|
// Restore WQ analysis option
|
p->quality.Qualflag = tmpflag;
|
if (errcode) errmsg(p, errcode);
|
return errcode;
|
}
|
|
int DLLEXPORT EN_openH(EN_Project p)
|
/*----------------------------------------------------------------
|
** Input: none
|
** Output: none
|
** Returns: error code
|
** Purpose: opens a project's hydraulic solver
|
**----------------------------------------------------------------
|
*/
|
{
|
int errcode = 0;
|
|
// Check that input data exists
|
p->hydraul.OpenHflag = FALSE;
|
p->outfile.SaveHflag = FALSE;
|
if (!p->Openflag) return 102;
|
|
// Check that previously saved hydraulics file not in use
|
if (p->outfile.Hydflag == USE) return 107;
|
|
// Open hydraulics solver
|
ERRCODE(openhyd(p));
|
if (!errcode) p->hydraul.OpenHflag = TRUE;
|
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, const char *filename)
|
/*----------------------------------------------------------------
|
** Input: filename = name of file to which hydraulic results are saved
|
** Output: none
|
** Returns: error code
|
** Purpose: saves results from a scratch hydraulics file to a
|
** permanent one
|
**----------------------------------------------------------------
|
*/
|
{
|
FILE *f;
|
FILE *HydFile;
|
int c;
|
|
// Check that hydraulics results exist
|
if (p->outfile.HydFile == NULL || !p->outfile.SaveHflag) return 104;
|
|
// Open the permanent hydraulics file
|
if ((f = fopen(filename, "w+b")) == NULL) return 305;
|
|
// Copy from the scratch file to f
|
HydFile = p->outfile.HydFile;
|
fseek(HydFile, 0, SEEK_SET);
|
while ((c = fgetc(HydFile)) != EOF) fputc(c, f);
|
fclose(f);
|
return 0;
|
}
|
|
int DLLEXPORT EN_usehydfile(EN_Project p, const char *filename)
|
/*----------------------------------------------------------------
|
** Input: filename = name of previously saved hydraulics file
|
** Output: none
|
** Returns: error code
|
** Purpose: uses contents of a previously saved hydraulics file to
|
** run a water quality analysis
|
**----------------------------------------------------------------
|
*/
|
{
|
int errcode;
|
|
// Check that project was opened & hydraulic solver is closed
|
if (!p->Openflag) return 102;
|
if (p->hydraul.OpenHflag) return 108;
|
|
// Try to open hydraulics file
|
strncpy(p->outfile.HydFname, filename, MAXFNAME);
|
p->outfile.Hydflag = USE;
|
p->outfile.SaveHflag = TRUE;
|
errcode = openhydfile(p);
|
|
// If error, then reset flags
|
if (errcode)
|
{
|
strcpy(p->outfile.HydFname, "");
|
p->outfile.Hydflag = SCRATCH;
|
p->outfile.SaveHflag = FALSE;
|
}
|
return errcode;
|
}
|
|
/********************************************************************
|
|
Water Quality Analysis Functions
|
|
********************************************************************/
|
|
int DLLEXPORT EN_solveQ(EN_Project p)
|
/*----------------------------------------------------------------
|
** Input: none
|
** Output: none
|
** Returns: error code
|
** Purpose: solves for network water quality in all time periods
|
**----------------------------------------------------------------
|
*/
|
{
|
int errcode;
|
long t, tstep;
|
|
// Open WQ solver
|
errcode = EN_openQ(p);
|
if (!errcode)
|
{
|
// Initialize WQ solver
|
errcode = EN_initQ(p, EN_SAVE);
|
if (!p->quality.Qualflag) writewin(p->viewprog, FMT106);
|
|
// Analyze each hydraulic period
|
if (!errcode) do
|
{
|
// Display progress message
|
sprintf(p->Msg, "%-10s",
|
clocktime(p->report.Atime, p->times.Htime));
|
if (p->quality.Qualflag)
|
{
|
sprintf(p->Msg, FMT102, p->report.Atime);
|
writewin(p->viewprog, p->Msg);
|
}
|
|
// Retrieve current hydraulic results & update water quality
|
// to start of next time period
|
tstep = 0;
|
ERRCODE(EN_runQ(p, &t));
|
ERRCODE(EN_nextQ(p, &tstep));
|
} while (tstep > 0);
|
}
|
|
// Close WQ solver
|
EN_closeQ(p);
|
return errcode;
|
}
|
|
int DLLEXPORT EN_openQ(EN_Project p)
|
/*----------------------------------------------------------------
|
** Input: none
|
** Output: none
|
** Returns: error code
|
** Purpose: opens a project's water quality solver
|
**----------------------------------------------------------------
|
*/
|
{
|
int errcode = 0;
|
|
// Check that hydraulics results exist
|
p->quality.OpenQflag = FALSE;
|
p->outfile.SaveQflag = FALSE;
|
if (!p->Openflag) return 102;
|
if (!p->hydraul.OpenHflag && !p->outfile.SaveHflag) return 104;
|
|
// Open water quality solver
|
ERRCODE(openqual(p));
|
if (!errcode) p->quality.OpenQflag = TRUE;
|
else errmsg(p, errcode);
|
return errcode;
|
}
|
|
int DLLEXPORT EN_initQ(EN_Project p, int saveFlag)
|
/*----------------------------------------------------------------
|
** Input: saveFlag = flag indicating if results should be saved
|
** to the binary output file or not
|
** Output: none
|
** Returns: error code
|
** Purpose: initializes the water quality solver
|
**----------------------------------------------------------------
|
*/
|
{
|
int errcode = 0;
|
|
if (!p->quality.OpenQflag) return 105;
|
initqual(p);
|
p->outfile.SaveQflag = FALSE;
|
p->outfile.Saveflag = FALSE;
|
if (saveFlag)
|
{
|
errcode = openoutfile(p);
|
if (!errcode) p->outfile.Saveflag = TRUE;
|
}
|
return errcode;
|
}
|
|
int DLLEXPORT EN_runQ(EN_Project p, long *currentTime)
|
/*----------------------------------------------------------------
|
** Input: none
|
** Output: currentTime = current simulation time (sec)
|
** Returns: error code
|
** Purpose: retrieves current hydraulic results and saves current
|
** results to file.
|
**----------------------------------------------------------------
|
*/
|
{
|
int errcode;
|
|
*currentTime = 0;
|
if (!p->quality.OpenQflag) return 105;
|
errcode = runqual(p, currentTime);
|
if (errcode) errmsg(p, errcode);
|
return errcode;
|
}
|
|
int DLLEXPORT EN_nextQ(EN_Project p, long *tStep)
|
/*----------------------------------------------------------------
|
** Input: none
|
** Output: tStep = time step over which water quality is updated (sec)
|
** Returns: error code
|
** Purpose: updates water quality throughout the network until
|
** next hydraulic event occurs Hydraulic Status:
|
**----------------------------------------------------------------
|
*/
|
{
|
int errcode;
|
|
*tStep = 0;
|
if (!p->quality.OpenQflag) return 105;
|
errcode = nextqual(p, tStep);
|
if (!errcode && p->outfile.Saveflag && *tStep == 0)
|
{
|
p->outfile.SaveQflag = TRUE;
|
}
|
if (errcode) errmsg(p, errcode);
|
return errcode;
|
}
|
|
int DLLEXPORT EN_stepQ(EN_Project p, long *timeLeft)
|
/*----------------------------------------------------------------
|
** Input: none
|
** Output: timeLeft = amount of simulation time remaining (sec)
|
** Returns: error code
|
** Purpose: updates water quality throughout the network over
|
** fixed time step
|
**----------------------------------------------------------------
|
*/
|
{
|
int errcode;
|
|
*timeLeft = 0;
|
if (!p->quality.OpenQflag) return 105;
|
errcode = stepqual(p, timeLeft);
|
if (!errcode && p->outfile.Saveflag && *timeLeft == 0)
|
{
|
p->outfile.SaveQflag = TRUE;
|
}
|
if (errcode) errmsg(p, errcode);
|
return errcode;
|
}
|
|
int DLLEXPORT EN_closeQ(EN_Project p)
|
/*----------------------------------------------------------------
|
** Input: none
|
** Output: none
|
** Returns: error code
|
** Purpose: closes a project's water quality solver
|
**----------------------------------------------------------------
|
*/
|
{
|
if (!p->Openflag) return 102;
|
closequal(p);
|
p->quality.OpenQflag = FALSE;
|
closeoutfile(p);
|
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
|
**----------------------------------------------------------------
|
*/
|
{
|
//°×ÔÆ·ÉÐÞ¸Ä
|
return 0;
|
|
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
|
**----------------------------------------------------------------
|
*/
|
{
|
//°×ÔÆ·ÉÐÞ¸Ä
|
return 0;
|
|
|
|
int errcode;
|
|
// Check if results have been saved to binary output file
|
if (!p->outfile.SaveQflag) return 106;
|
writewin(p->viewprog, FMT103);
|
|
// Write the formatted report
|
errcode = writereport(p);
|
if (errcode) errmsg(p, errcode);
|
return errcode;
|
}
|
|
int DLLEXPORT EN_copyreport(EN_Project p, char *filename)
|
/*----------------------------------------------------------------
|
** Input: filename = name of file to receive copy of report
|
** Output: none
|
** Returns: error code
|
** Purpose: copies the contents of a project's report file to
|
** another file
|
**----------------------------------------------------------------
|
*/
|
{
|
return copyreport(p, filename);
|
}
|
|
int DLLEXPORT EN_clearreport(EN_Project p)
|
/*----------------------------------------------------------------
|
** Input: none
|
** Output: none
|
** Returns: error code
|
** Purpose: clears the contents of a project's report file
|
**----------------------------------------------------------------
|
*/
|
{
|
return clearreport(p);
|
}
|
|
int DLLEXPORT EN_resetreport(EN_Project p)
|
/*----------------------------------------------------------------
|
** Input: none
|
** Output: none
|
** Returns: error code
|
** Purpose: resets reporting options to their default values
|
**----------------------------------------------------------------
|
*/
|
{
|
int i;
|
|
if (!p->Openflag) return 102;
|
initreport(&p->report);
|
for (i = 1; i <= p->network.Nnodes; i++)
|
{
|
p->network.Node[i].Rpt = 0;
|
}
|
for (i = 1; i <= p->network.Nlinks; i++)
|
{
|
p->network.Link[i].Rpt = 0;
|
}
|
return 0;
|
}
|
|
int DLLEXPORT EN_setreport(EN_Project p, char *format)
|
/*----------------------------------------------------------------
|
** Input: format = a report formatting command
|
** Output: none
|
** Returns: error code
|
** Purpose: sets a specific set of reporting options
|
**----------------------------------------------------------------
|
*/
|
{
|
char s1[MAXLINE + 1];
|
|
if (!p->Openflag) return 102;
|
if (strlen(format) >= MAXLINE) return 250;
|
strcpy(s1, format);
|
strcat(s1, "\n");
|
if (setreport(p, s1) > 0) return 250;
|
else return 0;
|
}
|
|
int DLLEXPORT EN_setstatusreport(EN_Project p, int level)
|
/*----------------------------------------------------------------
|
** Input: level = level of reporting to use (see EN_StatusReport)
|
** Output: none
|
** Returns: error code
|
** Purpose: sets the level of hydraulic status reporting
|
**----------------------------------------------------------------
|
*/
|
{
|
int errcode = 0;
|
|
if (level >= EN_NO_REPORT && level <= EN_FULL_REPORT)
|
{
|
p->report.Statflag = (char)level;
|
}
|
else errcode = 251;
|
return errcode;
|
}
|
|
int DLLEXPORT EN_getversion(int *version)
|
/*----------------------------------------------------------------
|
** Input: none
|
** Output: version = version number of the source code
|
** Returns: error code (should always be 0)
|
** Purpose: retrieves the toolkit API version number
|
**
|
** The version number is set by the constant CODEVERSION found in
|
** TYPES.H and is to be interpreted with implied decimals, i.e.,
|
** "20100" == "2(.)01(.)00".
|
**----------------------------------------------------------------
|
*/
|
{
|
*version = CODEVERSION;
|
return 0;
|
}
|
|
int DLLEXPORT EN_geterror(int errcode, char *errmsg, int maxLen)
|
/*----------------------------------------------------------------
|
** Input: errcode = an error or warnng code
|
** maxLen = maximum characters that errmsg can hold
|
** Output: errmsg = text of error/warning message
|
** Returns: error code
|
** Purpose: retrieves the text of the message associated with
|
** a particular error/warning code
|
**----------------------------------------------------------------
|
*/
|
{
|
char msg1[MAXMSG+1] = "";
|
char msg2[MAXMSG+1] = "";
|
|
switch (errcode)
|
{
|
case 1:
|
strncpy(errmsg, WARN1, maxLen);
|
break;
|
case 2:
|
strncpy(errmsg, WARN2, maxLen);
|
break;
|
case 3:
|
strncpy(errmsg, WARN3, maxLen);
|
break;
|
case 4:
|
strncpy(errmsg, WARN4, maxLen);
|
break;
|
case 5:
|
strncpy(errmsg, WARN5, maxLen);
|
break;
|
case 6:
|
strncpy(errmsg, WARN6, maxLen);
|
break;
|
default:
|
sprintf(msg1, "Error %d: ", errcode);
|
if ((errcode >= 202 && errcode <= 222) ||
|
(errcode >= 240 && errcode <= 261)) strcat(msg1, "function call contains ");
|
snprintf(errmsg, maxLen, "%s%s", msg1, geterrmsg(errcode, msg2));
|
}
|
if (strlen(errmsg) == 0)
|
return 251;
|
else
|
return 0;
|
}
|
|
|
int DLLEXPORT EN_geterrormsg(EN_Project pr, 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
|
**----------------------------------------------------------------
|
*/
|
{
|
// ¼ì²éerrmsgÊÇ·ñΪNULL
|
if (errmsg == NULL) {
|
return 252; // ·µ»Ø´íÎóÂ룬±íʾerrmsgΪNULL
|
}
|
|
// »ñÈ¡pr->MsgTextµÄ³¤¶È
|
int msgLen = strlen(pr->MsgText);
|
|
// È·±£errmsgÓÐ×ã¹»µÄ¿Õ¼äÀ´´æ´¢pr->MsgText
|
if (msgLen >= maxLen) {
|
return 253; // ·µ»Ø´íÎóÂ룬±íʾerrmsg¿Õ¼ä²»×ã
|
}
|
|
//Ϊerrmsg·ÖÅä×ã¹»µÄ¿Õ¼ä
|
/*errmsg = (char*)malloc(msgLen + 1);*/
|
|
|
// ³õʼ»¯errmsgΪ¿Õ×Ö·û´®
|
errmsg[0] = '\0';
|
|
// ½«pr->MsgTextµÄÄÚÈÝ·ÅÈëerrmsgÖÐ
|
strncpy(errmsg, pr->MsgText, msgLen);
|
|
|
|
// È·±£errmsgÊÇÒÔNULL½áβµÄ×Ö·û´®
|
errmsg[msgLen] = '\0';
|
|
if (strlen(errmsg) == 0)
|
return 251;
|
else
|
return 0;
|
}
|
|
int DLLEXPORT EN_getstatistic(EN_Project p, int type, double *value)
|
/*----------------------------------------------------------------
|
** Input: type = type of simulation statistic (see EN_AnalysisStatistic)
|
** Output: value = simulation analysis statistic value
|
** Returns: error code
|
** Purpose: retrieves the value of a simulation analysis statistic
|
**----------------------------------------------------------------
|
*/
|
{
|
switch (type)
|
{
|
case EN_ITERATIONS:
|
*value = p->hydraul.Iterations;
|
break;
|
case EN_RELATIVEERROR:
|
*value = p->hydraul.RelativeError;
|
break;
|
case EN_MAXHEADERROR:
|
*value = p->hydraul.MaxHeadError * p->Ucf[HEAD];
|
break;
|
case EN_MAXFLOWCHANGE:
|
*value = p->hydraul.MaxFlowChange * p->Ucf[FLOW];
|
break;
|
case EN_DEFICIENTNODES:
|
*value = p->hydraul.DeficientNodes;
|
break;
|
case EN_DEMANDREDUCTION:
|
*value = p->hydraul.DemandReduction;
|
break;
|
case EN_MASSBALANCE:
|
*value = p->quality.MassBalance.ratio;
|
break;
|
default:
|
*value = 0.0;
|
return 251;
|
}
|
return 0;
|
}
|
|
int DLLEXPORT EN_getresultindex(EN_Project p, int type, int index, int *value)
|
/*----------------------------------------------------------------
|
** Input: type = type of object (either EN_NODE or EN_LINK)
|
** index = the object's index
|
** Output: value = the order in which the object's results were saved
|
** Returns: error code
|
** Purpose: retrieves the order in which a node's or link's results
|
** were saved to an output file.
|
**----------------------------------------------------------------
|
*/
|
{
|
*value = 0;
|
if (!p->Openflag) return 102;
|
if (type == EN_NODE)
|
{
|
if (index <= 0 || index > p->network.Nnodes) return 203;
|
*value = p->network.Node[index].ResultIndex;
|
}
|
else if (type == EN_LINK)
|
{
|
if (index <= 0 || index > p->network.Nlinks) return 204;
|
*value = p->network.Link[index].ResultIndex;
|
}
|
else return 251;
|
return 0;
|
}
|
|
/********************************************************************
|
|
Analysis Options Functions
|
|
********************************************************************/
|
|
int DLLEXPORT EN_getoption(EN_Project p, int option, double *value)
|
/*----------------------------------------------------------------
|
** Input: option = an analysis option code (see EN_Option)
|
** Output: value = analysis option value
|
** Returns: error code
|
** Purpose: retrieves the value of an analysis option
|
**----------------------------------------------------------------
|
*/
|
{
|
Hydraul *hyd = &p->hydraul;
|
Quality *qual = &p->quality;
|
|
double *Ucf = p->Ucf;
|
double v = 0.0;
|
|
*value = 0.0;
|
if (!p->Openflag) return 102;
|
switch (option)
|
{
|
case EN_TRIALS:
|
v = (double)hyd->MaxIter;
|
break;
|
case EN_ACCURACY:
|
v = hyd->Hacc;
|
break;
|
case EN_TOLERANCE:
|
v = qual->Ctol * Ucf[QUALITY];
|
break;
|
case EN_EMITEXPON:
|
if (hyd->Qexp > 0.0) v = 1.0 / hyd->Qexp;
|
break;
|
case EN_DEMANDMULT:
|
v = hyd->Dmult;
|
break;
|
case EN_HEADERROR:
|
v = hyd->HeadErrorLimit * Ucf[HEAD];
|
break;
|
case EN_FLOWCHANGE:
|
v = hyd->FlowChangeLimit * Ucf[FLOW];
|
break;
|
case EN_HEADLOSSFORM:
|
v = hyd->Formflag;
|
break;
|
case EN_GLOBALEFFIC:
|
v = hyd->Epump;
|
break;
|
case EN_GLOBALPRICE:
|
v = hyd->Ecost;
|
break;
|
case EN_GLOBALPATTERN:
|
v = hyd->Epat;
|
break;
|
case EN_DEMANDCHARGE:
|
v = hyd->Dcost;
|
break;
|
case EN_SP_GRAVITY:
|
v = hyd->SpGrav;
|
break;
|
case EN_SP_VISCOS:
|
v = hyd->Viscos / VISCOS;
|
break;
|
case EN_UNBALANCED:
|
v = hyd->ExtraIter;
|
break;
|
case EN_CHECKFREQ:
|
v = hyd->CheckFreq;
|
break;
|
case EN_MAXCHECK:
|
v = hyd->MaxCheck;
|
break;
|
case EN_DAMPLIMIT:
|
v = hyd->DampLimit;
|
break;
|
case EN_SP_DIFFUS:
|
v = qual->Diffus / DIFFUS;
|
break;
|
case EN_BULKORDER:
|
v = qual->BulkOrder;
|
break;
|
case EN_WALLORDER:
|
v = qual->WallOrder;
|
break;
|
case EN_TANKORDER:
|
v = qual->TankOrder;
|
break;
|
case EN_CONCENLIMIT:
|
v = qual->Climit * p->Ucf[QUALITY];
|
break;
|
|
default:
|
return 251;
|
}
|
*value = (double)v;
|
return 0;
|
}
|
|
int DLLEXPORT EN_setoption(EN_Project p, int option, double value)
|
/*----------------------------------------------------------------
|
** Input: option = analysis option code (see EN_Option)
|
** value = analysis option value
|
** Output: none
|
** Returns: error code
|
** Purpose: sets the value for an analysis option
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
Hydraul *hyd = &p->hydraul;
|
Quality *qual = &p->quality;
|
|
int Njuncs = net->Njuncs;
|
double *Ucf = p->Ucf;
|
int i, j, pat;
|
double Ke, n, ucf;
|
|
if (!p->Openflag) return 102;
|
|
// The EN_UNBALANCED option can be < 0 indicating that the simulation
|
// should be halted if no convergence is reached in EN_TRIALS. Other
|
// values set the number of additional trials to use with no more
|
// link status changes to achieve convergence.
|
if (option == EN_UNBALANCED)
|
{
|
hyd->ExtraIter = (int)value;
|
if (hyd->ExtraIter < 0) hyd->ExtraIter = -1;
|
return 0;
|
}
|
|
// All other option values must be non-negative
|
if (value < 0.0) return 213;
|
|
// Process the speficied option
|
switch (option)
|
{
|
case EN_TRIALS:
|
if (value < 1.0) return 213;
|
hyd->MaxIter = (int)value;
|
break;
|
|
case EN_ACCURACY:
|
if (value < 1.e-8 || value > 1.e-1) return 213;
|
hyd->Hacc = value;
|
break;
|
|
case EN_TOLERANCE:
|
qual->Ctol = value / Ucf[QUALITY];
|
break;
|
|
case EN_EMITEXPON:
|
if (value <= 0.0) return 213;
|
n = 1.0 / value;
|
ucf = pow(Ucf[FLOW], n) / Ucf[PRESSURE];
|
for (i = 1; i <= Njuncs; i++)
|
{
|
j = EN_getnodevalue(p, i, EN_EMITTER, &Ke);
|
if (j == 0 && Ke > 0.0) net->Node[i].Ke = ucf / pow(Ke, n);
|
}
|
hyd->Qexp = n;
|
break;
|
|
case EN_DEMANDMULT:
|
hyd->Dmult = value;
|
break;
|
|
case EN_HEADERROR:
|
hyd->HeadErrorLimit = value / Ucf[HEAD];
|
break;
|
|
case EN_FLOWCHANGE:
|
hyd->FlowChangeLimit = value / Ucf[FLOW];
|
break;
|
|
case EN_HEADLOSSFORM:
|
// Can't change if hydraulic solver is open
|
if (p->hydraul.OpenHflag) return 262;
|
i = ROUND(value);
|
if (i < HW || i > CM) return 213;
|
hyd->Formflag = i;
|
if (hyd->Formflag == HW) hyd->Hexp = 1.852;
|
else hyd->Hexp = 2.0;
|
break;
|
|
case EN_GLOBALEFFIC:
|
if (value <= 1.0 || value > 100.0) return 213;
|
hyd->Epump = value;
|
break;
|
|
case EN_GLOBALPRICE:
|
hyd->Ecost = value;
|
break;
|
|
case EN_GLOBALPATTERN:
|
pat = ROUND(value);
|
if (pat < 0 || pat > net->Npats) return 205;
|
hyd->Epat = pat;
|
break;
|
|
case EN_DEMANDCHARGE:
|
hyd->Dcost = value;
|
break;
|
|
case EN_SP_GRAVITY:
|
if (value <= 0.0) return 213;
|
Ucf[PRESSURE] *= (value / hyd->SpGrav);
|
hyd->SpGrav = value;
|
break;
|
|
case EN_SP_VISCOS:
|
if (value <= 0.0) return 213;
|
hyd->Viscos = value * VISCOS;
|
break;
|
|
case EN_CHECKFREQ:
|
hyd->CheckFreq = (int)value;
|
break;
|
|
case EN_MAXCHECK:
|
hyd->MaxCheck = (int)value;
|
break;
|
|
case EN_DAMPLIMIT:
|
hyd->DampLimit = value;
|
break;
|
|
case EN_SP_DIFFUS:
|
qual->Diffus = value * DIFFUS;
|
break;
|
|
case EN_BULKORDER:
|
qual->BulkOrder = value;
|
break;
|
|
case EN_WALLORDER:
|
if (value == 0.0 || value == 1.0) qual->WallOrder = value;
|
else return 213;
|
break;
|
|
case EN_TANKORDER:
|
qual->TankOrder = value;
|
break;
|
|
case EN_CONCENLIMIT:
|
qual->Climit = value / p->Ucf[QUALITY];
|
break;
|
|
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_RULESTEP:
|
*value = time->Rulestep;
|
break;
|
case EN_STATISTIC:
|
*value = rpt->Tstatflag;
|
break;
|
case EN_PERIODS:
|
*value = rpt->Nperiods;
|
break;
|
case EN_STARTTIME:
|
*value = time->Tstart;
|
break;
|
case EN_HTIME:
|
*value = time->Htime;
|
break;
|
case EN_QTIME:
|
*value = time->Qtime;
|
case EN_HALTFLAG:
|
break;
|
case EN_NEXTEVENT:
|
*value = time->Hstep; // find the lesser of the hydraulic time step length,
|
// or the time to next full/empty tank
|
tanktimestep(p, value);
|
break;
|
case EN_NEXTEVENTTANK:
|
*value = time->Hstep;
|
i = tanktimestep(p, value);
|
*value = i;
|
break;
|
default:
|
return 251;
|
}
|
return 0;
|
}
|
|
int DLLEXPORT EN_settimeparam(EN_Project p, int param, long value)
|
/*----------------------------------------------------------------
|
** Input: param = time parameter code (see EN_TimeParameter)
|
** value = time parameter value
|
** Output: none
|
** Returns: error code
|
** Purpose: sets the value of a time parameter
|
**----------------------------------------------------------------
|
*/
|
{
|
Report *rpt = &p->report;
|
Times *time = &p->times;
|
|
if (!p->Openflag) return 102;
|
if (value < 0) return 213;
|
switch (param)
|
{
|
case EN_DURATION:
|
time->Dur = value;
|
if (time->Rstart > time->Dur) time->Rstart = 0;
|
break;
|
|
case EN_HYDSTEP:
|
if (value == 0) return 213;
|
time->Hstep = value;
|
time->Hstep = MIN(time->Pstep, time->Hstep);
|
time->Hstep = MIN(time->Rstep, time->Hstep);
|
time->Qstep = MIN(time->Qstep, time->Hstep);
|
break;
|
|
case EN_QUALSTEP:
|
if (value == 0) return 213;
|
time->Qstep = value;
|
time->Qstep = MIN(time->Qstep, time->Hstep);
|
break;
|
|
case EN_PATTERNSTEP:
|
if (value == 0) return 213;
|
time->Pstep = value;
|
if (time->Hstep > time->Pstep) time->Hstep = time->Pstep;
|
break;
|
|
case EN_PATTERNSTART:
|
time->Pstart = value;
|
break;
|
|
case EN_REPORTSTEP:
|
if (value == 0) return 213;
|
time->Rstep = value;
|
if (time->Hstep > time->Rstep) time->Hstep = time->Rstep;
|
break;
|
|
case EN_REPORTSTART:
|
if (time->Rstart > time->Dur) return 213;
|
time->Rstart = value;
|
break;
|
|
case EN_RULESTEP:
|
if (value == 0) return 213;
|
time->Rulestep = value;
|
time->Rulestep = MIN(time->Rulestep, time->Hstep);
|
break;
|
|
case EN_STATISTIC:
|
if (value > RANGE) return 213;
|
rpt->Tstatflag = (char)value;
|
break;
|
|
case EN_HTIME:
|
time->Htime = value;
|
break;
|
|
case EN_QTIME:
|
time->Qtime = value;
|
break;
|
|
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, oldQualFlag, traceNodeIndex;
|
double ccf = 1.0;
|
|
if (!p->Openflag) return 102;
|
if (qual->OpenQflag) return 262;
|
if (qualType < NONE || qualType > TRACE) return 251;
|
if (qualType == TRACE)
|
{
|
traceNodeIndex = findnode(net, traceNode);
|
if (traceNodeIndex == 0) return 212;
|
}
|
|
oldQualFlag = qual->Qualflag;
|
qual->Qualflag = qualType;
|
qual->Ctol *= Ucf[QUALITY];
|
if (qual->Qualflag == CHEM) // Chemical analysis
|
{
|
strncpy(qual->ChemName, chemName, MAXID);
|
strncpy(qual->ChemUnits, chemUnits, MAXID);
|
strncpy(rpt->Field[QUALITY].Units, qual->ChemUnits, MAXID);
|
strncpy(rpt->Field[REACTRATE].Units, qual->ChemUnits, MAXID);
|
strcat(rpt->Field[REACTRATE].Units, t_PERDAY);
|
ccf = 1.0 / LperFT3;
|
}
|
if (qual->Qualflag == TRACE) // Source trace analysis
|
{
|
qual->TraceNode = findnode(net, traceNode);
|
if (qual->TraceNode == 0) return 212;
|
strncpy(qual->ChemName, w_TRACE, MAXID);
|
strncpy(qual->ChemUnits, u_PERCENT, MAXID);
|
strcpy(rpt->Field[QUALITY].Units, u_PERCENT);
|
}
|
if (qual->Qualflag == AGE) // Water age analysis
|
{
|
strncpy(qual->ChemName, w_AGE, MAXID);
|
strncpy(qual->ChemUnits, u_HOURS, MAXID);
|
strcpy(rpt->Field[QUALITY].Units, u_HOURS);
|
}
|
|
// When changing from CHEM to AGE or TRACE, nodes initial quality
|
// values must be returned to their original ones
|
if ((qual->Qualflag == AGE || qual->Qualflag == TRACE) && oldQualFlag == CHEM)
|
{
|
for (i = 1; i <= p->network.Nnodes; i++)
|
{
|
p->network.Node[i].C0 *= Ucf[QUALITY];
|
}
|
}
|
|
Ucf[QUALITY] = ccf;
|
Ucf[LINKQUAL] = ccf;
|
Ucf[REACTRATE] = ccf;
|
qual->Ctol /= Ucf[QUALITY];
|
return 0;
|
}
|
|
/********************************************************************
|
|
Node Functions
|
|
********************************************************************/
|
|
int DLLEXPORT EN_addnode(EN_Project p, char *id, int nodeType, int *index)
|
/*----------------------------------------------------------------
|
** Input: id = node ID name
|
** nodeType = type of node (see EN_NodeType)
|
** Output: index = index of newly added node
|
** Returns: error code
|
** Purpose: adds a new node to a project
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
Hydraul *hyd = &p->hydraul;
|
Quality *qual = &p->quality;
|
|
int i, nIdx, size;
|
Stank *tank;
|
Snode *node;
|
Scontrol *control;
|
|
// Cannot modify network structure while solvers are active
|
*index = 0;
|
if (!p->Openflag) return 102;
|
if (hyd->OpenHflag || qual->OpenQflag) return 262;
|
|
// Check if id name contains invalid characters
|
if (!namevalid(id)) return 252;
|
|
// Check if a node with same id already exists
|
if (EN_getnodeindex(p, id, &i) == 0) return 215;
|
|
// Check for valid node type
|
if (nodeType < EN_JUNCTION || nodeType > EN_TANK) return 251;
|
|
// Grow node-related arrays to accomodate the new node
|
size = (net->Nnodes + 2) * sizeof(Snode);
|
net->Node = (Snode *)realloc(net->Node, size);
|
size = (net->Nnodes + 2) * sizeof(double);
|
hyd->NodeDemand = (double *)realloc(hyd->NodeDemand, size);
|
qual->NodeQual = (double *)realloc(qual->NodeQual, size);
|
hyd->NodeHead = (double *)realloc(hyd->NodeHead, size);
|
|
// Actions taken when a new Junction is added
|
if (nodeType == EN_JUNCTION)
|
{
|
// shift indices of non-Junction nodes at end of Node array
|
for (i = net->Nnodes; i > net->Njuncs; i--)
|
{
|
hashtable_update(net->NodeHashTable, net->Node[i].ID, i + 1);
|
net->Node[i + 1] = net->Node[i];
|
}
|
|
// set index of new Junction node
|
net->Njuncs++;
|
nIdx = net->Njuncs;
|
node = &net->Node[nIdx];
|
node->D = NULL;
|
adddemand(node, 0.0, 0, NULL);
|
|
// shift indices of Tank array
|
for (i = 1; i <= net->Ntanks; i++)
|
{
|
net->Tank[i].Node += 1;
|
}
|
// shift indices of Links, if necessary
|
for (i = 1; i <= net->Nlinks; i++)
|
{
|
if (net->Link[i].N1 > net->Njuncs - 1) net->Link[i].N1 += 1;
|
if (net->Link[i].N2 > net->Njuncs - 1) net->Link[i].N2 += 1;
|
}
|
// shift indices of tanks/reservoir nodes in controls
|
for (i = 1; i <= net->Ncontrols; ++i)
|
{
|
control = &net->Control[i];
|
if (control->Node > net->Njuncs - 1) control->Node += 1;
|
}
|
// adjust indices of tanks/reservoirs in Rule premises (see RULES.C)
|
adjusttankrules(p);
|
}
|
|
// Actions taken when a new Tank/Reservoir is added
|
else
|
{
|
nIdx = net->Nnodes + 1;
|
node = &net->Node[nIdx];
|
node->D = NULL;
|
net->Ntanks++;
|
|
// resize tanks array
|
net->Tank = (Stank *)realloc(net->Tank, (net->Ntanks + 1) * sizeof(Stank));
|
tank = &net->Tank[net->Ntanks];
|
|
// set default values for new tank or reservoir
|
tank->Node = nIdx;
|
tank->Pat = 0;
|
if (nodeType == EN_TANK) tank->A = 1.0;
|
else tank->A = 0;
|
tank->Hmin = 0;
|
tank->Hmax = 0;
|
tank->H0 = 0;
|
tank->Vmin = 0;
|
tank->Vmax = 0;
|
tank->V0 = 0;
|
tank->Kb = 0;
|
tank->V = 0;
|
tank->C = 0;
|
tank->Pat = 0;
|
tank->Vcurve = 0;
|
tank->MixModel = (MixType)0;
|
tank->V1max = 10000;
|
tank->CanOverflow = FALSE;
|
}
|
net->Nnodes++;
|
p->parser.MaxNodes = net->Nnodes;
|
strncpy(node->ID, id, MAXID);
|
|
// set default values for new node
|
node->Type = (NodeType)nodeType;
|
node->El = 0;
|
node->S = NULL;
|
node->C0 = 0;
|
node->Ke = 0;
|
node->Rpt = 0;
|
node->ResultIndex = 0;
|
node->X = MISSING;
|
node->Y = MISSING;
|
node->Comment = NULL;
|
|
// Insert new node into hash table
|
hashtable_insert(net->NodeHashTable, node->ID, nIdx);
|
*index = nIdx;
|
return 0;
|
}
|
|
int DLLEXPORT EN_deletenode(EN_Project p, int index, int actionCode)
|
/*----------------------------------------------------------------
|
** Input: index = index of the node to delete
|
** actionCode = how to treat controls that contain the link
|
** or its incident links:
|
** EN_UNCONDITIONAL deletes all such controls plus the node,
|
** EN_CONDITIONAL does not delete the node if it or any of
|
** its links appear in a control and returns an error code
|
** Output: none
|
** Returns: error code
|
** Purpose: deletes a node from a project
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
|
int i, nodeType, tankindex;
|
Snode *node;
|
|
// Cannot modify network structure while solvers are active
|
if (!p->Openflag) return 102;
|
if (p->hydraul.OpenHflag || p->quality.OpenQflag) return 262;
|
|
// Check that node exists
|
if (index <= 0 || index > net->Nnodes) return 203;
|
if (actionCode < EN_UNCONDITIONAL || actionCode > EN_CONDITIONAL) return 251;
|
|
// Can't delete a water quality trace node
|
if (index == p->quality.TraceNode) return 260;
|
|
// Do not delete a node contained in a control or is connected to a link
|
if (actionCode == EN_CONDITIONAL)
|
{
|
if (incontrols(p, NODE, index)) return 261;
|
for (i = 1; i <= net->Nlinks; i++)
|
{
|
if (net->Link[i].N1 == index ||
|
net->Link[i].N2 == index) return 259;
|
}
|
}
|
|
// Get a reference to the node & its type
|
node = &net->Node[index];
|
EN_getnodetype(p, index, &nodeType);
|
|
// Remove node from its hash table
|
hashtable_delete(net->NodeHashTable, node->ID);
|
|
// Free memory allocated to node's demands, WQ source & comment
|
freedemands(node);
|
free(node->S);
|
free(node->Comment);
|
|
// Shift position of higher entries in Node & Coord arrays down one
|
for (i = index; i <= net->Nnodes - 1; i++)
|
{
|
net->Node[i] = net->Node[i + 1];
|
// ... update node's entry in the hash table
|
hashtable_update(net->NodeHashTable, net->Node[i].ID, i);
|
}
|
|
// If deleted node is a tank, remove it from the Tank array
|
if (nodeType != EN_JUNCTION)
|
{
|
tankindex = findtank(net, index);
|
for (i = tankindex; i <= net->Ntanks - 1; i++)
|
{
|
net->Tank[i] = net->Tank[i + 1];
|
}
|
}
|
|
// Shift higher node indices in Tank array down one
|
for (i = 1; i <= net->Ntanks; i++)
|
{
|
if (net->Tank[i].Node > index) net->Tank[i].Node -= 1;
|
}
|
|
// Delete any links connected to the deleted node
|
// (Process links in reverse order to maintain their indexing)
|
for (i = net->Nlinks; i >= 1; i--)
|
{
|
if (net->Link[i].N1 == index ||
|
net->Link[i].N2 == index) EN_deletelink(p, i, EN_UNCONDITIONAL);
|
}
|
|
// Adjust indices of all link end nodes
|
for (i = 1; i <= net->Nlinks; i++)
|
{
|
if (net->Link[i].N1 > index) net->Link[i].N1 -= 1;
|
if (net->Link[i].N2 > index) net->Link[i].N2 -= 1;
|
}
|
|
// Delete any control containing the node
|
for (i = net->Ncontrols; i >= 1; i--)
|
{
|
if (net->Control[i].Node == index) EN_deletecontrol(p, i);
|
}
|
|
// Adjust higher numbered link indices in remaining controls
|
for (i = 1; i <= net->Ncontrols; i++)
|
{
|
if (net->Control[i].Node > index) net->Control[i].Node--;
|
}
|
|
// Make necessary adjustments to rule-based controls
|
adjustrules(p, EN_R_NODE, index);
|
|
// Reduce counts of node types
|
if (nodeType == EN_JUNCTION) net->Njuncs--;
|
else net->Ntanks--;
|
net->Nnodes--;
|
return 0;
|
}
|
|
int DLLEXPORT EN_getnodeindex(EN_Project p, 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_getnodecomment(EN_Project p, int index, char* id)
|
/*----------------------------------------------------------------
|
** Input: index = node index
|
** Output: id = node Comment
|
** 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;
|
if(p->network.Node[index].Comment!=NULL) strcpy(id, p->network.Node[index].Comment);
|
|
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;
|
|
// Check for valid arguments
|
if (index <= 0 || index > net->Nnodes) return 203;
|
if (!namevalid(newid)) return 252;
|
|
// Check if another node with same name exists
|
if (hashtable_find(net->NodeHashTable, newid) > 0)
|
return 215;
|
|
// Replace the existing node ID with the new value
|
hashtable_delete(net->NodeHashTable, net->Node[index].ID);
|
strncpy(net->Node[index].ID, newid, MAXID);
|
hashtable_insert(net->NodeHashTable, net->Node[index].ID, index);
|
return 0;
|
}
|
|
int DLLEXPORT EN_getnodetype(EN_Project p, int index, int *nodeType)
|
/*----------------------------------------------------------------
|
** Input: index = node index
|
** Output: nodeType = node type (see EN_NodeType)
|
** Returns: error code
|
** Purpose: retrieves the type of a node
|
**----------------------------------------------------------------
|
*/
|
{
|
*nodeType = -1;
|
if (!p->Openflag) return 102;
|
if (index < 1 || index > p->network.Nnodes) return 203;
|
if (index <= p->network.Njuncs) *nodeType = EN_JUNCTION;
|
else
|
{
|
if (p->network.Tank[index - p->network.Njuncs].A == 0.0)
|
{
|
*nodeType = EN_RESERVOIR;
|
}
|
else *nodeType = EN_TANK;
|
}
|
return 0;
|
}
|
|
int DLLEXPORT EN_getnodevalue(EN_Project p, int index, int property, double *value)
|
/*----------------------------------------------------------------
|
** Input: index = node index
|
** property = node property code (see EN_NodeProperty)
|
** Output: value = node property value
|
** Returns: error code
|
** Purpose: retrieves a property value for a node
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
Hydraul *hyd = &p->hydraul;
|
Quality *qual = &p->quality;
|
|
double v = 0.0;
|
Psource source;
|
|
Snode *Node = net->Node;
|
Stank *Tank = net->Tank;
|
|
int nJuncs = net->Njuncs;
|
|
double *Ucf = p->Ucf;
|
double *NodeHead = hyd->NodeHead;
|
double *NodeQual = qual->NodeQual;
|
|
// Check for valid arguments
|
*value = 0.0;
|
if (!p->Openflag) return 102;
|
if (index <= 0 || index > net->Nnodes) return 203;
|
|
// Retrieve requested property
|
switch (property)
|
{
|
case EN_ELEVATION:
|
v = Node[index].El * Ucf[ELEV];
|
break;
|
|
case EN_BASEDEMAND:
|
// NOTE: primary demand category is first on demand list
|
if (index <= nJuncs)
|
{
|
if (Node[index].D) v = Node[index].D->Base * Ucf[FLOW];
|
}
|
break;
|
|
case EN_PATTERN:
|
// NOTE: primary demand category is first on demand list
|
if (index <= nJuncs)
|
{
|
if (Node[index].D) v = (double)(Node[index].D->Pat);
|
}
|
else v = (double)(Tank[index - nJuncs].Pat);
|
break;
|
|
case EN_EMITTER:
|
v = 0.0;
|
if (Node[index].Ke > 0.0)
|
{
|
v = Ucf[FLOW] / pow((Ucf[PRESSURE] * Node[index].Ke), (1.0 / hyd->Qexp));
|
}
|
break;
|
|
case EN_INITQUAL:
|
v = Node[index].C0 * Ucf[QUALITY];
|
break;
|
|
case EN_SOURCEQUAL:
|
case EN_SOURCETYPE:
|
case EN_SOURCEMASS:
|
case EN_SOURCEPAT:
|
source = Node[index].S;
|
if (source == NULL) return 240;
|
if (property == EN_SOURCEQUAL) v = source->C0;
|
else if (property == EN_SOURCEMASS) v = source->Smass * 60.0;
|
else if (property == EN_SOURCEPAT) v = source->Pat;
|
else v = source->Type;
|
break;
|
|
case EN_TANKLEVEL:
|
if (index <= nJuncs) return 0;
|
v = (Tank[index - nJuncs].H0 - Node[index].El) * Ucf[ELEV];
|
break;
|
|
case EN_INITVOLUME:
|
v = 0.0;
|
if (index > nJuncs) v = Tank[index - nJuncs].V0 * Ucf[VOLUME];
|
break;
|
|
case EN_MIXMODEL:
|
v = MIX1;
|
if (index > nJuncs) v = Tank[index - nJuncs].MixModel;
|
break;
|
|
case EN_MIXZONEVOL:
|
v = 0.0;
|
if (index > nJuncs) v = Tank[index - nJuncs].V1max * Ucf[VOLUME];
|
break;
|
|
case EN_DEMAND:
|
v = hyd->NodeDemand[index] * Ucf[FLOW];
|
break;
|
|
case EN_HEAD:
|
v = NodeHead[index] * Ucf[HEAD];
|
break;
|
|
case EN_PRESSURE:
|
v = (NodeHead[index] - Node[index].El) * Ucf[PRESSURE];
|
break;
|
|
case EN_QUALITY:
|
v = NodeQual[index] * Ucf[QUALITY];
|
break;
|
|
case EN_TANKDIAM:
|
v = 0.0;
|
if (index > nJuncs)
|
{
|
v = sqrt(4.0 / PI * Tank[index - nJuncs].A) * Ucf[ELEV];
|
}
|
break;
|
|
case EN_MINVOLUME:
|
v = 0.0;
|
if (index > nJuncs) v = Tank[index - nJuncs].Vmin * Ucf[VOLUME];
|
break;
|
|
case EN_MAXVOLUME:
|
v = 0.0;
|
if (index > nJuncs) v = Tank[index - nJuncs].Vmax * Ucf[VOLUME];
|
break;
|
|
case EN_VOLCURVE:
|
v = 0.0;
|
if (index > nJuncs) v = Tank[index - nJuncs].Vcurve;
|
break;
|
|
case EN_MINLEVEL:
|
v = 0.0;
|
if (index > nJuncs)
|
{
|
v = (Tank[index - nJuncs].Hmin - Node[index].El) * Ucf[ELEV];
|
}
|
break;
|
|
case EN_MAXLEVEL:
|
v = 0.0;
|
if (index > nJuncs)
|
{
|
v = (Tank[index - nJuncs].Hmax - Node[index].El) * Ucf[ELEV];
|
}
|
break;
|
|
case EN_MIXFRACTION:
|
v = 1.0;
|
if (index > nJuncs && 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;
|
|
case EN_CANOVERFLOW:
|
if (Node[index].Type != TANK) return 0;
|
v = Tank[index - nJuncs].CanOverflow;
|
break;
|
|
case EN_DEMANDDEFICIT:
|
if (index > nJuncs) return 0;
|
// After an analysis, DemandFlow contains node's required demand
|
// while NodeDemand contains delivered demand + emitter flow
|
if (hyd->DemandFlow[index] < 0.0) return 0;
|
v = (hyd->DemandFlow[index] -
|
(hyd->NodeDemand[index] - hyd->EmitterFlow[index])) * Ucf[FLOW];
|
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;
|
Psource source;
|
double hTmp;
|
double vTmp;
|
|
if (!p->Openflag) return 102;
|
if (index <= 0 || index > nNodes) return 203;
|
switch (property)
|
{
|
case EN_ELEVATION:
|
if (index <= nJuncs) Node[index].El = value / Ucf[ELEV];
|
else
|
{
|
value = (value / Ucf[ELEV]) - Node[index].El;
|
j = index - nJuncs;
|
Tank[j].H0 += value;
|
Tank[j].Hmin += value;
|
Tank[j].Hmax += value;
|
Node[index].El += value;
|
hyd->NodeHead[index] += value;
|
}
|
break;
|
|
case EN_BASEDEMAND:
|
// NOTE: primary demand category is first on demand list
|
if (index <= nJuncs)
|
{
|
if (Node[index].D) Node[index].D->Base = value / Ucf[FLOW];
|
}
|
break;
|
|
case EN_PATTERN:
|
// NOTE: primary demand category is first on demand list
|
j = ROUND(value);
|
if (j < 0 || j > nPats) return 205;
|
if (index <= nJuncs)
|
{
|
if (Node[index].D) Node[index].D->Pat = j;
|
}
|
else Tank[index - nJuncs].Pat = j;
|
break;
|
|
case EN_EMITTER:
|
if (index > nJuncs) return 0;
|
if (value < 0.0) return 209;
|
if (value > 0.0) value = pow((Ucf[FLOW] / value), hyd->Qexp) / Ucf[PRESSURE];
|
Node[index].Ke = value;
|
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 = (SourceType)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; // invalid diameter
|
if (index <= nJuncs) return 0; // node is not a tank
|
j = index - nJuncs; // tank index
|
if (Tank[j].A == 0.0) return 0; // tank is a reservoir
|
value /= Ucf[ELEV]; // diameter in feet
|
Tank[j].A = PI * SQR(value) / 4.0; // new tank area
|
if (Tank[j].Vcurve > 0) // tank has a volume curve
|
{
|
Tank[j].Vcurve = 0; // remove volume curve
|
|
// Since the volume curve no longer applies we assume that the tank's
|
// shape below Hmin is cylindrical and Vmin equals area times Hmin
|
Tank[j].Vmin = Tank[j].A * Tank[j].Hmin;
|
}
|
// Since tank's area has changed its volumes must be updated
|
// NOTE: For a non-volume curve tank we can't change the Vmin
|
// associated with a Hmin since we don't know the tank's
|
// shape below Hmin. Vmin can always be changed by setting
|
// EN_MINVOLUME in a subsequent function call.
|
Tank[j].V0 = tankvolume(p, j, Tank[j].H0); // new init. volume
|
vTmp = Tank[j].Vmax; // old max. volume
|
Tank[j].Vmax = tankvolume(p, j, Tank[j].Hmax); // new max. volume
|
Tank[j].V1max *= Tank[j].Vmax / vTmp; // new mix zone volume
|
break;
|
|
case EN_MINVOLUME:
|
if (value < 0.0) return 209; // invalid volume
|
if (index <= nJuncs) return 0; // node is not a tank
|
j = index - nJuncs; // tank index
|
if (Tank[j].A == 0.0) return 0; // tank is a reservoir
|
i = Tank[j].Vcurve; // volume curve index
|
if (i > 0) // tank has a volume curve
|
{
|
curve = &net->Curve[i]; // curve object
|
if (value < curve->Y[0]) return 225; // volume off of curve
|
value /= Ucf[VOLUME]; // volume in ft3
|
hTmp = tankgrade(p, j, value); // head at given volume
|
if (hTmp > Tank[j].H0 ||
|
hTmp > Tank[j].Hmax) return 225; // invalid water levels
|
Tank[j].Hmin = hTmp; // new min. head
|
Tank[j].Vmin = value; // new min. volume
|
}
|
else // tank has no volume curve
|
{
|
// If the volume supplied by the function is 0 then the tank shape
|
// below Hmin is assumed to be cylindrical and a new Vmin value is
|
// computed. Otherwise Vmin is set to the supplied value.
|
if (value == 0.0) Tank[j].Vmin = Tank[j].A * Tank[j].Hmin;
|
else Tank[j].Vmin = value / Ucf[VOLUME];
|
|
// Since Vmin changes the other volumes need updating
|
Tank[j].V0 = tankvolume(p, j, Tank[j].H0); // new init. volume
|
vTmp = Tank[j].Vmax; // old max. volume
|
Tank[j].Vmax = tankvolume(p, j, Tank[j].Hmax); // new max. volume
|
Tank[j].V1max *= Tank[j].Vmax / vTmp; // new mix zone volume
|
}
|
break;
|
|
case EN_VOLCURVE:
|
// NOTE: Setting EN_VOLCURVE to 0 to remove a volume curve is not valid.
|
// One should instead set a value for EN_TANKDIAM.
|
i = ROUND(value); // curve index
|
if (i <= 0 ||
|
i > net->Ncurves) return 205; // invalid curve index
|
if (index <= nJuncs) return 0; // node not a tank
|
j = index - nJuncs; // tank index
|
if (Tank[j].A == 0.0) return 0; // tank is a reservoir
|
curve = &net->Curve[i]; // curve object
|
|
// Check that tank's min/max levels lie within curve
|
value = (Tank[j].Hmin - Node[index].El) * Ucf[ELEV];
|
if (value < curve->X[0]) return 225;
|
value = (Tank[j].Hmax - Node[index].El) * Ucf[ELEV];
|
n = curve->Npts - 1;
|
if (value > curve->X[n]) return 225;
|
|
Tank[j].Vcurve = i; // assign curve to tank
|
Tank[j].Vmin = tankvolume(p, j, Tank[j].Hmin); // new min. volume
|
Tank[j].V0 = tankvolume(p, j, Tank[j].H0); // new init. volume
|
vTmp = Tank[j].Vmax; // old max. volume
|
Tank[j].Vmax = tankvolume(p, j, Tank[j].Hmax); // new max. volume
|
Tank[j].V1max *= Tank[j].Vmax / vTmp; // new mix zone volume
|
Tank[j].A = (curve->Y[n] - curve->Y[0]) / // nominal area
|
(curve->X[n] - curve->X[0]);
|
break;
|
|
case EN_MINLEVEL:
|
if (value < 0.0) return 209; // invalid water level
|
if (index <= nJuncs) return 0; // node not a tank
|
j = index - nJuncs; // tank index
|
if (Tank[j].A == 0.0) return 0; // tank is a reservoir
|
hTmp = value / Ucf[ELEV] + Node[index].El; // convert level to head
|
if (hTmp >= Tank[j].Hmax ||
|
hTmp > Tank[j].H0) return 225; // invalid water levels
|
i = Tank[j].Vcurve; // volume curve index
|
if (i > 0) // tank has a volume curve
|
{
|
curve = &net->Curve[i];
|
if (value < curve->X[0]) return 225; // new level is off curve
|
Tank[j].Vmin = tankvolume(p, j, hTmp); // new min. volume
|
}
|
Tank[j].Hmin = hTmp;
|
// NOTE: We assume that for non-volume curve tanks Vmin doesn't change
|
// with Hmin. If not the case then a subsequent call setting
|
// EN_MINVOLUME must be made.
|
break;
|
|
case EN_MAXLEVEL:
|
if (value <= 0.0) return 209; // invalid water level
|
if (index <= nJuncs) return 0; // node not a tank
|
j = index - nJuncs; // tank index
|
if (Tank[j].A == 0.0) return 0; // tank is a reservoir
|
hTmp = value / Ucf[ELEV] + Node[index].El; // convert level to head
|
if (hTmp < Tank[j].Hmin ||
|
hTmp < Tank[j].H0) return 225; // invalid water levels
|
i = Tank[j].Vcurve; // volume curve index
|
if (i > 0) // tank has a volume curve
|
{
|
curve = &net->Curve[i];
|
n = curve->Npts - 1; // last point on curve
|
if (value > curve->X[n]) return 225; // new level is off curve
|
}
|
Tank[j].Hmax = hTmp; // new max. head
|
vTmp = Tank[j].Vmax; // old max. volume
|
Tank[j].Vmax = tankvolume(p, j, hTmp); // new max. volume
|
Tank[j].V1max *= Tank[j].Vmax / vTmp; // new mix zone volume
|
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 = (MixType)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;
|
|
case EN_CANOVERFLOW:
|
if (Node[index].Type != TANK) return 0;
|
Tank[index - nJuncs].CanOverflow = (value != 0.0);
|
break;
|
|
default:
|
return 251;
|
}
|
return 0;
|
}
|
|
int DLLEXPORT EN_setjuncdata(EN_Project p, int index, double elev,
|
double dmnd, char *dmndpat)
|
/*----------------------------------------------------------------
|
** Input: index = junction node index
|
** elev = junction elevation
|
** dmnd = junction primary base demand
|
** dmndpat = name of primary demand time pattern
|
** Output: none
|
** Returns: error code
|
** Purpose: sets several properties for a junction node
|
**----------------------------------------------------------------
|
*/
|
{
|
int patIndex = 0;
|
Snode *node;
|
|
// Check that junction exists
|
if (!p->Openflag) return 102;
|
if (index <= 0 || index > p->network.Njuncs) return 203;
|
|
// Check that demand pattern exists
|
if (dmndpat && strlen(dmndpat) > 0)
|
{
|
if (EN_getpatternindex(p, dmndpat, &patIndex) > 0) return 205;
|
}
|
|
// Assign demand parameters to junction's primary demand category
|
node = &(p->network.Node[index]);
|
dmnd /= p->Ucf[FLOW];
|
// Category exists - update its properties
|
if (node->D)
|
{
|
(node->D)->Base = dmnd;
|
(node->D)->Pat = patIndex;
|
}
|
// No demand categories exist -- create a new one
|
else if (!adddemand(node, dmnd, patIndex, NULL)) return 101;
|
|
// Assign new elevation value to junction
|
node->El = elev / p->Ucf[ELEV];
|
return 0;
|
}
|
|
int DLLEXPORT EN_settankdata(EN_Project p, int index, double elev,
|
double initlvl, double minlvl,
|
double maxlvl, double diam,
|
double minvol, 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;
|
if (curveIndex == 0)
|
{
|
if (minvol > 0.0) Tank[j].Vmin = minvol / Ucf[VOLUME];
|
else Tank[j].Vmin = Tank[j].A * Tank[j].Hmin;
|
}
|
else Tank[j].Vmin = tankvolume(p, j, Tank[j].Hmin);
|
Tank[j].V0 = tankvolume(p, j, Tank[j].H0);
|
Tank[j].Vmax = tankvolume(p, j, Tank[j].Hmax);
|
return 0;
|
}
|
|
int DLLEXPORT EN_getcoord(EN_Project p, int index, double *x, double *y)
|
/*----------------------------------------------------------------
|
** Input: index = node index
|
** Output: x = node x-coordinate
|
** y = node y-coordinate
|
** Returns: error code
|
** Purpose: retrieves the coordinates of a node
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
Snode *node;
|
|
if (!p->Openflag) return 102;
|
if (index < 1 || index > p->network.Nnodes) return 203;
|
|
// check if node has coords
|
node = &net->Node[index];
|
if (node->X == MISSING ||
|
node->Y == MISSING) return 254;
|
|
*x = (double)(node->X);
|
*y = (double)(node->Y);
|
return 0;
|
}
|
|
int DLLEXPORT EN_setcoord(EN_Project p, int index, double x, double y)
|
/*----------------------------------------------------------------
|
** Input: index = node index
|
** x = node x-coordinate
|
** y = node y-coordinate
|
** Output: none
|
** Returns: error code
|
** Purpose: sets the coordinates of a node
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
Snode *node;
|
|
if (!p->Openflag) return 102;
|
if (index < 1 || index > p->network.Nnodes) return 203;
|
node = &net->Node[index];
|
node->X = x;
|
node->Y = y;
|
return 0;
|
}
|
|
/********************************************************************
|
|
Nodal Demand Functions
|
|
********************************************************************/
|
|
int DLLEXPORT EN_getdemandmodel(EN_Project p, int *model, double *pmin,
|
double *preq, double *pexp)
|
/*----------------------------------------------------------------
|
** Input: none
|
** Output: model = type of demand model (see EN_DemandModel)
|
** pmin = minimum pressure for any demand
|
** preq = required pressure for full demand
|
** pexp = exponent in pressure dependent demand formula
|
** Returns: error code
|
** Purpose: retrieves the parameters of a project's demand model
|
**----------------------------------------------------------------
|
*/
|
{
|
*model = p->hydraul.DemandModel;
|
*pmin = p->hydraul.Pmin * p->Ucf[PRESSURE];
|
*preq = p->hydraul.Preq * p->Ucf[PRESSURE];
|
*pexp = p->hydraul.Pexp;
|
return 0;
|
}
|
|
int DLLEXPORT EN_setdemandmodel(EN_Project p, int model, double pmin,
|
double preq, double pexp)
|
/*----------------------------------------------------------------
|
** Input: model = type of demand model (see EN_DemandModel)
|
** pmin = minimum pressure for any demand
|
** preq = required pressure for full demand
|
** pexp = exponent in pressure dependent demand formula
|
** Output: none
|
** Returns: error code
|
** Purpose: sets the parameters of a project's demand model
|
**----------------------------------------------------------------
|
*/
|
{
|
if (model < 0 || model > EN_PDA) return 251;
|
if (model == EN_PDA)
|
{
|
if (pexp <= 0.0) return 208;
|
if (pmin < 0.0) return 208;
|
if (preq - pmin < MINPDIFF) return 208;
|
}
|
p->hydraul.DemandModel = model;
|
p->hydraul.Pmin = pmin / p->Ucf[PRESSURE];
|
p->hydraul.Preq = preq / p->Ucf[PRESSURE];
|
p->hydraul.Pexp = pexp;
|
return 0;
|
}
|
|
int DLLEXPORT EN_adddemand(EN_Project p, int nodeIndex, double baseDemand,
|
char *demandPattern, char *demandName)
|
/*----------------------------------------------------------------
|
** Input: nodeIndex = node index
|
** baseDemand = baseline demand value
|
** demandPattern = name of demand's time pattern (can be NULL or empty)
|
** demandName = name of demand's category (can be NULL or empty)
|
** Returns: error code
|
** Purpose: adds a new demand category to a junction node
|
**----------------------------------------------------------------
|
*/
|
{
|
int patIndex = 0;
|
Snode *node;
|
|
// Check for valid arguments
|
if (!p->Openflag) return 102;
|
if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203;
|
if (demandPattern && strlen(demandPattern) > 0)
|
{
|
if (EN_getpatternindex(p, demandPattern, &patIndex) > 0) return 205;
|
}
|
|
// Do nothing if node is not a junction
|
if (nodeIndex > p->network.Njuncs) return 0;
|
|
// Add the new demand to the node's demands list
|
node = &(p->network.Node[nodeIndex]);
|
if (!adddemand(node, baseDemand / p->Ucf[FLOW], patIndex, demandName)) return 101;
|
return 0;
|
}
|
|
int DLLEXPORT EN_deletedemand(EN_Project p, int nodeIndex, int demandIndex)
|
/*----------------------------------------------------------------
|
** Input: nodeIndex = node index
|
** demandIndex = index of node's demand to be deleted
|
** Returns: error code
|
** Purpose: deletes an existing demand category from a junction node
|
**----------------------------------------------------------------
|
*/
|
{
|
Pdemand d, dprev;
|
Snode *node;
|
int n = 1;
|
|
// Check for valid arguments
|
if (!p->Openflag) return 102;
|
if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203;
|
|
// Only junctions have demands
|
if (nodeIndex <= p->network.Njuncs)
|
{
|
// Find head of node's list of demands
|
node = &p->network.Node[nodeIndex];
|
d = node->D;
|
if (d == NULL) return 253;
|
dprev = d;
|
|
// Check if target demand is head of demand list
|
if (demandIndex == 1)
|
{
|
node->D = d->next;
|
free(d->Name);
|
free(d);
|
return 0;
|
}
|
|
// Otherwise locate target demand in demand list
|
while (d != NULL && n < demandIndex)
|
{
|
dprev = d;
|
d = d->next;
|
n++;
|
}
|
|
// Return error if target demand not found
|
if (d == NULL) return 253;
|
|
// Link the demands that precede and follow the target
|
dprev->next = d->next;
|
|
// Delete the target demand
|
free(d->Name);
|
free(d);
|
}
|
return 0;
|
}
|
|
int DLLEXPORT EN_getdemandindex(EN_Project p, int nodeIndex, char *demandName,
|
int *demandIndex)
|
/*----------------------------------------------------------------
|
** Input: nodeIndex = node index
|
** demandName = name of demand being sought
|
** Output: demandIndex = index of demand being sought
|
** Returns: error code
|
** Purpose: retrieves the position of a named demand category
|
** in a node's list of demands
|
**----------------------------------------------------------------
|
*/
|
{
|
Pdemand d;
|
int n = 0;
|
int nameEmpty = FALSE;
|
int found = FALSE;
|
|
// Check for valid arguments
|
*demandIndex = 0;
|
if (!p->Openflag) return 102;
|
if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203;
|
if (demandName == NULL) return 253;
|
|
// Check if target name is empty
|
if (strlen(demandName) == 0) nameEmpty = TRUE;
|
|
// Locate target demand in node's demands list
|
for (d = p->network.Node[nodeIndex].D; d != NULL; d = d->next)
|
{
|
n++;
|
if (d->Name == NULL)
|
{
|
if (nameEmpty) found = TRUE;;
|
}
|
else if (strcmp(d->Name, demandName) == 0) found = TRUE;
|
if (found) break;
|
}
|
|
// Return target demand's index
|
if (!found) return 253;
|
*demandIndex = n;
|
return 0;
|
}
|
|
int DLLEXPORT EN_getnumdemands(EN_Project p, int nodeIndex, int *numDemands)
|
/*----------------------------------------------------------------
|
** Input: nodeIndex = node index
|
** Output: numDemands = number of demand categories
|
** Returns: error code
|
** Purpose: retrieves the number of demand categories for a node
|
**----------------------------------------------------------------
|
*/
|
{
|
Pdemand d;
|
int n = 0;
|
|
// Check for valid arguments
|
if (!p->Openflag) return 102;
|
if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203;
|
|
// Count the number of demand categories assigned to node
|
for (d = p->network.Node[nodeIndex].D; d != NULL; d = d->next) n++;
|
*numDemands = n;
|
return 0;
|
}
|
|
//int DLLEXPORT EN_getTotalDemand(EN_Project pr, int p, double *value)
|
//{
|
// Network* net = &pr->network;
|
// Hydraul* hyd = &pr->hydraul;
|
// Times* time = &pr->times;
|
//
|
// int i, j, n;
|
// long k;
|
// double djunc, sum,totaldemand;
|
// Pdemand demand;
|
//
|
// totaldemand = 0;
|
// // Determine total elapsed number of pattern periods
|
// //p = (time->Htime + time->Pstart) / time->Pstep;
|
//
|
// // Update demand at each node according to its assigned pattern
|
// //hyd->Dsystem = 0.0; // System-wide demand
|
// for (i = 1; i <= net->Njuncs; i++)
|
// {
|
// sum = 0.0;
|
// for (demand = net->Node[i].D; demand != NULL; demand = demand->next)
|
// {
|
// if (net->Node[i].Type == JUNCTION)
|
// {
|
// // pattern period (k) = (elapsed periods) modulus (periods per pattern)
|
// j = demand->Pat;
|
// k = p % (long)net->Pattern[j].Length;
|
// djunc = (demand->Base) * net->Pattern[j].F[k];
|
// //if (djunc > 0.0) hyd->Dsystem += djunc;
|
// sum += djunc;
|
// }
|
// }
|
// //hyd->NodeDemand[i] = sum;
|
// // Initialize pressure dependent demand
|
// // hyd->DemandFlow[i] = sum;
|
//
|
// totaldemand += sum;
|
// }
|
// *value = totaldemand * pr->Ucf[FLOW];
|
// return 0;
|
//}
|
//ÄâÐ޸ģ¨Ôö¼Ó±£»¤£©
|
int DLLEXPORT EN_getTotalDemand(EN_Project pr, int p, double* value)
|
{
|
Network* net = &pr->network;
|
Hydraul* hyd = &pr->hydraul;
|
Times* time = &pr->times;
|
|
int i, j, n;
|
long k;
|
double djunc, sum, totaldemand;
|
Pdemand demand;
|
if (p < 0) return 0;//Ôö¼Ó±£»¤
|
totaldemand = 0;
|
|
for (i = 1; i <= net->Njuncs; i++)
|
{
|
sum = 0.0;
|
if (net->Node[i].Type == JUNCTION) { // ¼ì²é½ÚµãÀàÐÍ
|
for (demand = net->Node[i].D; demand != NULL; demand = demand->next)
|
{
|
j = demand->Pat;
|
if (j >= 0 && j < net->Npats) { // ¼ì²éģʽË÷ÒýÊÇ·ñÔ½½ç
|
k = p % (long)net->Pattern[j].Length;
|
djunc = (demand->Base) * net->Pattern[j].F[k];
|
sum += djunc;
|
}
|
}
|
}
|
|
totaldemand += sum;
|
}
|
*value = totaldemand * pr->Ucf[FLOW];
|
return 0;
|
}
|
int DLLEXPORT EN_getbasedemand(EN_Project p, int nodeIndex, int demandIndex,
|
double *baseDemand)
|
/*----------------------------------------------------------------
|
** Input: nodeIndex = node index
|
** demandIndex = demand category index
|
** Output: baseDemand = baseline demand value
|
** Returns: error code
|
** Purpose: retrieves the baseline value for a node's demand category
|
**----------------------------------------------------------------
|
*/
|
{
|
Pdemand d;
|
|
// Check for valid arguments
|
*baseDemand = 0.0;
|
if (!p->Openflag) return 102;
|
if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203;
|
|
// Locate target demand in node's demands list
|
d = finddemand(p->network.Node[nodeIndex].D, demandIndex);
|
if (d == NULL) return 253;
|
|
// Retrieve target demand's base value
|
*baseDemand = d->Base * p->Ucf[FLOW];
|
return 0;
|
}
|
|
int DLLEXPORT EN_setbasedemand(EN_Project p, int nodeIndex, int demandIndex,
|
double baseDemand)
|
/*----------------------------------------------------------------
|
** Input: nodeIndex = node index
|
** demandIndex = demand category index
|
** baseDemand = baseline demand value
|
** Output: none
|
** Returns: error code
|
** Purpose: sets the baseline value for a node's demand category
|
**----------------------------------------------------------------
|
*/
|
{
|
Pdemand d;
|
|
// Check for valid arguments
|
if (!p->Openflag) return 102;
|
if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203;
|
|
// Locate target demand in node's demands list
|
d = finddemand(p->network.Node[nodeIndex].D, demandIndex);
|
if (d == NULL) return 253;
|
|
// Assign new base value to target demand
|
d->Base = baseDemand / p->Ucf[FLOW];
|
return 0;
|
}
|
|
int DLLEXPORT EN_getdemandname(EN_Project p, int nodeIndex, int demandIndex,
|
char *demandName)
|
/*----------------------------------------------------------------
|
** Input: nodeIndex = node index
|
** demandIndex = demand category index
|
** Output: demandname = demand category name
|
** Returns: error code
|
** Purpose: retrieves the name assigned to a node's demand category
|
**----------------------------------------------------------------
|
*/
|
{
|
Pdemand d;
|
|
strcpy(demandName, "");
|
|
// Check for valid arguments
|
if (!p->Openflag) return 102;
|
if (nodeIndex <= 0 || nodeIndex > p->network.Njuncs) return 203;
|
|
// Locate target demand in node's demands list
|
d = finddemand(p->network.Node[nodeIndex].D, demandIndex);
|
if (d == NULL) return 253;
|
|
// Retrieve target demand's category name
|
if (d->Name) strcpy(demandName, d->Name);
|
return 0;
|
}
|
|
int DLLEXPORT EN_setdemandname(EN_Project p, int nodeIndex, int demandIndex,
|
char *demandName)
|
/*----------------------------------------------------------------
|
** Input: nodeIndex = node index
|
** demandIndex = demand category index
|
** demandName = name of demand category
|
** Output: none
|
** Returns: error code
|
** Purpose: assigns a name to a node's demand category
|
**----------------------------------------------------------------
|
*/
|
{
|
Pdemand d;
|
|
// Check for valid arguments
|
if (!p->Openflag) return 102;
|
if (nodeIndex <= 0 || nodeIndex > p->network.Njuncs) return 203;
|
|
// Locate target demand in node's demands list
|
d = finddemand(p->network.Node[nodeIndex].D, demandIndex);
|
if (d == NULL) return 253;
|
|
// Assign category name to target demand
|
d->Name = xstrcpy(&d->Name, demandName, MAXID);
|
return 0;
|
}
|
|
int DLLEXPORT EN_getdemandpattern(EN_Project p, int nodeIndex, int demandIndex,
|
int *patIndex)
|
/*----------------------------------------------------------------
|
** Input: nodeIndex = node index
|
** demandIndex = demand category index
|
** Output: patIndex = time pattern index
|
** Returns: error code
|
** Purpose: retrieves the time pattern assigned to a node's
|
** demand category
|
**----------------------------------------------------------------
|
*/
|
{
|
Pdemand d;
|
|
// Check for valid arguments
|
*patIndex = 0;
|
if (!p->Openflag) return 102;
|
if (nodeIndex <= 0 || nodeIndex > p->network.Nnodes) return 203;
|
|
// Locate target demand in node's demand list
|
d = finddemand(p->network.Node[nodeIndex].D, demandIndex);
|
if (d == NULL) return 253;
|
|
// Retrieve that demand's pattern index
|
*patIndex = d->Pat;
|
return 0;
|
}
|
|
int DLLEXPORT EN_setdemandpattern(EN_Project p, int nodeIndex, int demandIndex,
|
int patIndex)
|
/*----------------------------------------------------------------
|
** Input: nodeIndex = node index
|
** demandIndex = demand category index
|
** patIndex = time pattern index
|
** Output: none
|
** Returns: error code
|
** Purpose: assigns a time pattern to a node's demand category
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
|
Pdemand d;
|
|
// Check for valid arguments
|
if (!p->Openflag) return 102;
|
if (nodeIndex <= 0 || nodeIndex > net->Nnodes) return 203;
|
if (patIndex < 0 || patIndex > net->Npats) return 205;
|
|
// Locate target demand in node's demand list
|
d = finddemand(p->network.Node[nodeIndex].D, demandIndex);
|
if (d == NULL) return 253;
|
|
// Assign new time pattern to target demand
|
d->Pat = patIndex;
|
return 0;
|
}
|
|
/********************************************************************
|
|
Link Functions
|
|
********************************************************************/
|
|
int DLLEXPORT EN_addlink(EN_Project p, char *id, int linkType,
|
char *fromNode, char *toNode, int *index)
|
/*----------------------------------------------------------------
|
** Input: id = link ID name
|
** type = link type (see EN_LinkType)
|
** fromNode = name of link's starting node
|
** toNode = name of link's ending node
|
** Output: index = position of new link in Link array
|
** Returns: error code
|
** Purpose: adds a new link to a project
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
Hydraul *hyd = &p->hydraul;
|
|
int i, n, size, errcode;
|
int n1, n2;
|
Slink *link;
|
Spump *pump;
|
|
// Cannot modify network structure while solvers are active
|
*index = 0;
|
if (!p->Openflag) return 102;
|
if (p->hydraul.OpenHflag || p->quality.OpenQflag) return 262;
|
|
// Check if id name contains invalid characters
|
if (!namevalid(id)) return 252;
|
|
// Check if a link with same id already exists
|
if (EN_getlinkindex(p, id, &i) == 0) return 215;
|
|
// Check for valid link type
|
if (linkType < CVPIPE || linkType > 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 valve link has legal connections
|
if (linkType > PUMP)
|
{
|
errcode = valvecheck(p, 0, linkType, n1, n2);
|
if (errcode) return errcode;
|
}
|
|
// Grow link-related arrays to accomodate the new link
|
net->Nlinks++;
|
p->parser.MaxLinks = net->Nlinks;
|
n = net->Nlinks;
|
size = (n + 1) * sizeof(Slink);
|
net->Link = (Slink *)realloc(net->Link, size);
|
size = (n + 1) * sizeof(double);
|
hyd->LinkFlow = (double *)realloc(hyd->LinkFlow, size);
|
hyd->LinkSetting = (double *)realloc(hyd->LinkSetting, size);
|
size = (n + 1) * sizeof(StatusType);
|
hyd->LinkStatus = (StatusType *)realloc(hyd->LinkStatus, size);
|
|
// Set properties for the new link
|
link = &net->Link[n];
|
strncpy(link->ID, id, MAXID);
|
|
if (linkType <= PIPE) net->Npipes++;
|
else if (linkType == PUMP)
|
{
|
// Grow pump array to accomodate the new link
|
net->Npumps++;
|
size = (net->Npumps + 1) * sizeof(Spump);
|
net->Pump = (Spump *)realloc(net->Pump, size);
|
pump = &net->Pump[net->Npumps];
|
pump->Link = n;
|
pump->Ptype = NOCURVE;
|
pump->Q0 = 0;
|
pump->Qmax = 0;
|
pump->Hmax = 0;
|
pump->H0 = 0;
|
pump->R = 0;
|
pump->N = 0;
|
pump->Hcurve = 0;
|
pump->Ecurve = 0;
|
pump->Upat = 0;
|
pump->Epat = 0;
|
pump->Ecost = 0;
|
pump->Energy.TotalCost = MISSING;
|
}
|
else
|
{
|
// Grow valve array to accomodate the new link
|
net->Nvalves++;
|
size = (net->Nvalves + 1) * sizeof(Svalve);
|
net->Valve = (Svalve *)realloc(net->Valve, size);
|
net->Valve[net->Nvalves].Link = n;
|
}
|
|
link->Type =(LinkType) 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
|
{
|
// 10" diameter new ductile iron pipe with
|
// length of average city block
|
link->Diam = 10 / p->Ucf[DIAM];
|
switch (hyd->Formflag)
|
{
|
case HW: link->Kc = 130; break;
|
case DW: link->Kc = 0.0005; break;
|
case CM: link->Kc = 0.01; break;
|
default: link->Kc = 1.0;
|
}
|
link->Km = 0.0; // Loss coeff
|
link->Len = 330.0;
|
}
|
else // Valve
|
{
|
link->Diam = 10 / p->Ucf[DIAM];
|
link->Kc = 0.0; // Valve setting.
|
link->Km = 0.0; // Loss coeff
|
link->Len = 0.0;
|
link->Status = ACTIVE;
|
}
|
link->Kb = 0;
|
link->Kw = 0;
|
link->R = 0;
|
link->Rc = 0;
|
link->Rpt = 0;
|
link->ResultIndex = 0;
|
link->Comment = NULL;
|
link->Vertices = NULL;
|
|
hashtable_insert(net->LinkHashTable, link->ID, n);
|
*index = n;
|
return 0;
|
}
|
|
int DLLEXPORT EN_deletelink(EN_Project p, int index, int actionCode)
|
/*----------------------------------------------------------------
|
** Input: index = index of the link to delete
|
** actionCode = how to treat controls that contain the link:
|
** EN_UNCONDITIONAL deletes all such controls plus the link,
|
** EN_CONDITIONAL does not delete the link if it appears
|
** in a control and returns an error code
|
** Output: none
|
** Returns: error code
|
** Purpose: deletes a link from a project
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
|
int i;
|
int pumpindex;
|
int valveindex;
|
int linkType;
|
Slink *link;
|
|
// Cannot modify network structure while solvers are active
|
if (!p->Openflag) return 102;
|
if (p->hydraul.OpenHflag || p->quality.OpenQflag) return 262;
|
|
// Check that link exists
|
if (index <= 0 || index > net->Nlinks) return 204;
|
if (actionCode < EN_UNCONDITIONAL || actionCode > EN_CONDITIONAL) return 251;
|
|
// Deletion will be cancelled if link appears in any controls
|
if (actionCode == EN_CONDITIONAL)
|
{
|
actionCode = incontrols(p, LINK, index);
|
if (actionCode > 0) return 261;
|
}
|
|
// Get references to the link and its type
|
link = &net->Link[index];
|
EN_getlinktype(p, index, &linkType);
|
|
// Remove link from its hash table
|
hashtable_delete(net->LinkHashTable, link->ID);
|
|
// Remove link's comment and vertices
|
free(link->Comment);
|
freelinkvertices(link);
|
|
// Shift position of higher entries in Link array down one
|
for (i = index; i <= net->Nlinks - 1; i++)
|
{
|
net->Link[i] = net->Link[i + 1];
|
// ... update link's entry in the hash table
|
hashtable_update(net->LinkHashTable, net->Link[i].ID, i);
|
}
|
|
// Adjust references to higher numbered links for pumps & valves
|
for (i = 1; i <= net->Npumps; i++)
|
{
|
if (net->Pump[i].Link > index) net->Pump[i].Link -= 1;
|
}
|
for (i = 1; i <= net->Nvalves; i++)
|
{
|
if (net->Valve[i].Link > index) net->Valve[i].Link -= 1;
|
}
|
|
// 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_getlinkcomment(EN_Project p, int index, char* id)
|
/*----------------------------------------------------------------
|
** Input: index = link index
|
** Output: id = link Comment
|
** 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].Comment);
|
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;
|
|
// Check for valid arguments
|
if (index <= 0 || index > net->Nlinks) return 204;
|
if (!namevalid(newid)) return 252;
|
|
// Check if another link with same name exists
|
if (hashtable_find(net->LinkHashTable, newid) > 0) return 215;
|
|
// Replace the existing link ID with the new value
|
hashtable_delete(net->LinkHashTable, net->Link[index].ID);
|
strncpy(net->Link[index].ID, newid, MAXID);
|
hashtable_insert(net->LinkHashTable, net->Link[index].ID, index);
|
return 0;
|
}
|
|
int DLLEXPORT EN_getlinktype(EN_Project p, int index, int *linkType)
|
/*----------------------------------------------------------------
|
** Input: index = link index
|
** Output: linkType = link type (see EN_LinkType)
|
** Returns: error code
|
** Purpose: retrieves the type code of a link
|
**----------------------------------------------------------------
|
*/
|
{
|
*linkType = -1;
|
if (!p->Openflag) return 102;
|
if (index < 1 || index > p->network.Nlinks) return 204;
|
*linkType = p->network.Link[index].Type;
|
return 0;
|
}
|
|
int DLLEXPORT EN_setlinktype(EN_Project p, int *index, int linkType, int actionCode)
|
/*----------------------------------------------------------------
|
** Input: index = link index
|
** linkType = new link type (see EN_LinkType)
|
** actionCode = how to treat controls that contain the link:
|
** EN_UNCONDITIONAL deletes all such controls,
|
** EN_CONDITIONAL cancels the type change if the link appears
|
** in a control and returns an error code
|
** Output: none
|
** Returns: error code
|
** Purpose: changes the type of a particular link (e.g. pipe to pump)
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
|
int i = *index, n1, n2;
|
char id[MAXID + 1];
|
char id1[MAXID + 1];
|
char id2[MAXID + 1];
|
int errcode;
|
int oldType;
|
|
// Cannot modify network structure while solvers are active
|
if (!p->Openflag) return 102;
|
if (p->hydraul.OpenHflag || p->quality.OpenQflag) return 262;
|
|
// Check for valid input parameters
|
if (linkType < 0 || linkType > 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)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, i, linkType, n1, n2);
|
if (errcode) return errcode;
|
|
// Delete the original link (and any controls containing it)
|
EN_deletelink(p, i, actionCode);
|
|
// Create a new link of new type and old id
|
errcode = EN_addlink(p, id, linkType, id1, id2, index);
|
return errcode;
|
}
|
|
int DLLEXPORT EN_getlinknodes(EN_Project p, int index, int *node1, int *node2)
|
/*----------------------------------------------------------------
|
** Input: index = link index
|
** Output: node1 = index of link's starting node
|
** node2 = index of link's ending node
|
** Returns: error code
|
** Purpose: retrieves the start and end nodes of a link
|
**----------------------------------------------------------------
|
*/
|
{
|
*node1 = 0;
|
*node2 = 0;
|
if (!p->Openflag) return 102;
|
if (index < 1 || index > p->network.Nlinks) return 204;
|
*node1 = p->network.Link[index].N1;
|
*node2 = p->network.Link[index].N2;
|
return 0;
|
}
|
|
int DLLEXPORT EN_setlinknodes(EN_Project p, int index, int node1, int node2)
|
/*----------------------------------------------------------------
|
** Input: index = link index
|
** node1 = index of link's new starting node
|
** node2 = index of link's new ending node
|
** Returns: error code
|
** Purpose: sets the start and end nodes of a link
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
int type, errcode;
|
|
// Cannot modify network structure while solvers are active
|
if (p->hydraul.OpenHflag || p->quality.OpenQflag) return 262;
|
|
// Check for valid link index
|
if (index <= 0 || index > net->Nlinks) return 204;
|
|
// Check that nodes exist
|
if (node1 < 0 || node1 > net->Nnodes) return 203;
|
if (node2 < 0 || node2 > net->Nnodes) return 203;
|
|
// Check that nodes are not the same
|
if (node1 == node2) return 222;
|
|
// Do nothing if the new nodes are the same as the old ones
|
if (node1 == net->Link[index].N1 && node2 == net->Link[index].N2)
|
return 0;
|
|
// Check for illegal valve connection
|
type = net->Link[index].Type;
|
if (type > PUMP)
|
{
|
errcode = valvecheck(p, index, type, node1, node2);
|
if (errcode) return errcode;
|
}
|
|
// Assign new end nodes to link
|
net->Link[index].N1 = node1;
|
net->Link[index].N2 = node2;
|
return 0;
|
}
|
|
int DLLEXPORT EN_getlinkvalue(EN_Project p, int index, int property, double *value)
|
/*----------------------------------------------------------------
|
** Input: index = link index
|
** property = link property code (see EN_LinkProperty)
|
** Output: value = link property value
|
** Returns: error code
|
** Purpose: retrieves a property value for a link
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
Hydraul *hyd = &p->hydraul;
|
|
double a, h, q, v = 0.0;
|
int pmp;
|
Slink *Link = net->Link;
|
Spump *Pump = net->Pump;
|
double *Ucf = p->Ucf;
|
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;
|
/*----------begin sh3h-------*/
|
#if W30
|
case EN_MINORHEAD:
|
{
|
double q = ABS(hyd->LinkFlow[index]); /* Absolute flow */
|
double ml = Link[index].Km; /* Minor loss coeff. */
|
double hpipe = ml*SQR(q);
|
v = hpipe*Ucf[HEADLOSS];
|
break ;
|
}
|
case EN_FRICTIONHEAD:
|
{
|
double q = ABS(hyd->LinkFlow[index]); /* Absolute flow */
|
double r = Link[index].R; /* Resistance coeff. */
|
double hpipe = 0;
|
double f = 1.0;
|
double r1; /* D-W friction factor */
|
if (hyd->Formflag == DW)
|
{
|
hpipe = r * pow(q, hyd->Hexp);
|
}
|
else
|
{
|
hpipe = r * pow(q, hyd->Hexp);
|
}
|
v = hpipe * Ucf[HEADLOSS];
|
break;
|
}
|
case EN_VOLUMN:
|
v = 0.0;
|
if ( index > net->Njuncs ) v =net->Tank[index-net->Njuncs].V*Ucf[VOLUME];
|
|
break;
|
case EN_SPEED:
|
if (Link[index].Type == PUMP)
|
{
|
v = Link[index].Kc;
|
}
|
break;
|
case EN_POWER:
|
double v1, v2;
|
EN_getnodevalue(p, Link[index].N1, EN_PRESSURE, &v1);
|
EN_getnodevalue(p,Link[index].N2, EN_PRESSURE, &v2);
|
if (hyd->LinkStatus[index] <= CLOSED) v = 0.0;
|
else v = hyd->LinkFlow[index]*Ucf[FLOW];
|
v = 1000 * v / 1000 * ABS(v1 - v2);
|
break;
|
|
#endif
|
/*----------end sh3h-------*/
|
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;
|
/*----------begin sh3h-------*/
|
#if W30
|
case EN_SPEED:
|
if (Link[index].Type == PUMP)
|
{
|
Link[index].Kc = value;
|
hyd->LinkSetting[index] = value;
|
}
|
break;
|
#endif
|
/*----------end sh3h-------*/
|
default:
|
return 251;
|
}
|
return 0;
|
}
|
|
int DLLEXPORT EN_setpipedata(EN_Project p, int index, double length,
|
double diam, double rough, double mloss)
|
/*----------------------------------------------------------------
|
** Input: index = pipe link index
|
** length = pipe length
|
** diam = pipe diameter
|
** rough = pipe roughness coefficient
|
** mloss = minor loss coefficient
|
** Output: none
|
** Returns: error code
|
** Purpose: sets several properties for a pipe link
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
|
Slink *Link = net->Link;
|
double *Ucf = p->Ucf;
|
double diameter = diam;
|
|
// Check that pipe exists
|
if (!p->Openflag) return 102;
|
if (index <= 0 || index > net->Nlinks) return 204;
|
if (Link[index].Type > PIPE) return 0;
|
|
// Check for valid parameters
|
if (length <= 0.0 || diam <= 0.0 || rough <= 0.0 || mloss < 0.0) return 211;
|
|
// Assign parameters to pipe
|
Link[index].Len = length / Ucf[ELEV];
|
diameter /= Ucf[DIAM];
|
Link[index].Diam = diameter;
|
Link[index].Kc = rough;
|
if (p->hydraul.Formflag == DW) Link[index].Kc /= (1000.0 * Ucf[ELEV]);
|
|
// Update minor loss factor & pipe flow resistance
|
Link[index].Km = 0.02517 * mloss / SQR(Link[index].Diam) / SQR(Link[index].Diam);
|
resistcoeff(p, index);
|
return 0;
|
}
|
|
int DLLEXPORT EN_getvertexcount(EN_Project p, int index, int *count)
|
/*----------------------------------------------------------------
|
** Input: index = link index
|
** Output: count = number of link's vertex points
|
** Returns: error code
|
** Purpose: retrieves number of vertex points in a link
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
|
Slink *Link = net->Link;
|
Pvertices vertices;
|
|
// Check that link exists
|
*count = 0;
|
if (!p->Openflag) return 102;
|
if (index <= 0 || index > net->Nlinks) return 204;
|
|
// Set count to number of vertices
|
vertices = Link[index].Vertices;
|
if (vertices) *count = vertices->Npts;
|
return 0;
|
}
|
|
int DLLEXPORT EN_getvertex(EN_Project p, int index, int vertex, double *x, double *y)
|
/*----------------------------------------------------------------
|
** Input: index = link index
|
** vertex = index of a link vertex point
|
** Output: x = vertex point's X-coordinate
|
** y = vertex point's Y-coordinate
|
** Returns: error code
|
** Purpose: retrieves the coordinates of a vertex point in a link
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
|
Slink *Link = net->Link;
|
Pvertices vertices;
|
|
// Check that link exists
|
*x = MISSING;
|
*y = MISSING;
|
if (!p->Openflag) return 102;
|
if (index <= 0 || index > net->Nlinks) return 204;
|
|
// Check that vertex exists
|
vertices = Link[index].Vertices;
|
if (vertices == NULL) return 255;
|
if (vertex <= 0 || vertex > vertices->Npts) return 255;
|
*x = vertices->X[vertex - 1];
|
*y = vertices->Y[vertex - 1];
|
return 0;
|
}
|
|
int DLLEXPORT EN_setvertices(EN_Project p, int index, double *x, double *y, int count)
|
/*----------------------------------------------------------------
|
** Input: index = link index
|
** x = array of X-coordinates for vertex points
|
** y = array of Y-coordinates for vertex points
|
** count = number of vertex points
|
** Returns: error code
|
** Purpose: assigns a set of vertex points to a link
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
|
Slink *link;
|
int i;
|
int err = 0;
|
|
// Check that link exists
|
if (!p->Openflag) return 102;
|
if (index <= 0 || index > net->Nlinks) return 204;
|
link = &net->Link[index];
|
|
// Delete existing set of vertices
|
freelinkvertices(link);
|
|
// Add each new vertex to the link
|
for (i = 0; i < count; i++)
|
{
|
err = addlinkvertex(link, x[i], y[i]);
|
if (err) break;
|
}
|
if (err) freelinkvertices(link);
|
return err;
|
}
|
|
/********************************************************************
|
|
Pump Functions
|
|
********************************************************************/
|
|
int DLLEXPORT EN_getpumptype(EN_Project p, int linkIndex, int *pumpType)
|
/*----------------------------------------------------------------
|
** Input: linkIndex = index of a pump link
|
** Output: pumpType = type of pump head curve (see EN_PumpType)
|
** Returns: error code
|
** Purpose: retrieves the type of head curve used by a pump
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
|
Slink *Link = net->Link;
|
Spump *Pump = net->Pump;
|
const int Nlinks = net->Nlinks;
|
|
*pumpType = -1;
|
if (!p->Openflag) return 102;
|
if (linkIndex < 1 || linkIndex > Nlinks) return 204;
|
if (PUMP != Link[linkIndex].Type) return 216;
|
*pumpType = Pump[findpump(&p->network, linkIndex)].Ptype;
|
return 0;
|
}
|
|
int DLLEXPORT EN_getheadcurveindex(EN_Project p, int linkIndex, int *curveIndex)
|
/*----------------------------------------------------------------
|
** Input: linkIndex = index of a pump link
|
** Output: curveIndex = index of a pump's head curve
|
** Returns: error code
|
** Purpose: retrieves the index of a pump's head curve
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
|
Slink *Link = net->Link;
|
Spump *Pump = net->Pump;
|
const int Nlinks = net->Nlinks;
|
|
*curveIndex = 0;
|
if (!p->Openflag) return 102;
|
if (linkIndex < 1 || linkIndex > Nlinks) return 204;
|
if (PUMP != Link[linkIndex].Type) return 216;
|
*curveIndex = Pump[findpump(net, linkIndex)].Hcurve;
|
return 0;
|
}
|
|
int DLLEXPORT EN_setheadcurveindex(EN_Project p, int linkIndex, int curveIndex)
|
/*----------------------------------------------------------------
|
** Input: linkIndex = index of a pump link
|
** curveIndex = index of a curve
|
** Output: none
|
** Returns: error code
|
** Purpose: assigns a new head curve to a pump
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
|
double *Ucf = p->Ucf;
|
int pumpIndex;
|
int oldCurveIndex;
|
int newCurveType;
|
int err = 0;
|
Spump *pump;
|
|
// Check for valid parameters
|
if (!p->Openflag) return 102;
|
if (linkIndex < 1 || linkIndex > net->Nlinks) return 204;
|
if (PUMP != net->Link[linkIndex].Type) return 0;
|
if (curveIndex < 0 || curveIndex > net->Ncurves) return 206;
|
|
// Save values that need to be restored in case new curve is invalid
|
pumpIndex = findpump(net, linkIndex);
|
pump = &p->network.Pump[pumpIndex];
|
oldCurveIndex = pump->Hcurve;
|
newCurveType = p->network.Curve[curveIndex].Type;
|
|
// Assign the new curve to the pump
|
pump->Ptype = NOCURVE;
|
pump->Hcurve = curveIndex;
|
if (curveIndex == 0) return 0;
|
|
// Update the pump's head curve parameters (which also changes
|
// the new curve's Type to PUMP_CURVE)
|
err = updatepumpparams(p, pumpIndex);
|
|
// If the parameter updating failed (new curve was not a valid pump curve)
|
// restore the pump's original curve and its parameters
|
if (err > 0)
|
{
|
p->network.Curve[curveIndex].Type =(CurveType) newCurveType;
|
pump->Ptype = NOCURVE;
|
pump->Hcurve = oldCurveIndex;
|
if (oldCurveIndex == 0) return err;
|
updatepumpparams(p, pumpIndex);
|
}
|
|
// Convert the units of the updated pump parameters to feet and cfs
|
if (pump->Ptype == POWER_FUNC)
|
{
|
pump->H0 /= Ucf[HEAD];
|
pump->R *= (pow(Ucf[FLOW], pump->N) / Ucf[HEAD]);
|
}
|
pump->Q0 /= Ucf[FLOW];
|
pump->Qmax /= Ucf[FLOW];
|
pump->Hmax /= Ucf[HEAD];
|
|
return err;
|
}
|
|
/********************************************************************
|
|
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;
|
|
int i, n, err = 0;
|
Spattern *pat;
|
|
// Check if a pattern with same id already exists
|
if (!p->Openflag) return 102;
|
if (EN_getpatternindex(p, id, &i) == 0) return 215;
|
|
// Check if id name contains invalid characters
|
if (!namevalid(id)) return 252;
|
|
// Expand the project's array of patterns
|
n = net->Npats + 1;
|
net->Pattern = (Spattern *)realloc(net->Pattern, (n + 1) * sizeof(Spattern));
|
|
// Assign properties to the new pattern
|
pat = &net->Pattern[n];
|
strcpy(pat->ID, id);
|
pat->Comment = NULL;
|
pat->Length = 1;
|
pat->F = (double *)calloc(1, sizeof(double));
|
if (pat->F == NULL) err = 1;
|
else pat->F[0] = 1.0;
|
|
// Abort if memory allocation error
|
if (err)
|
{
|
free(pat->F);
|
return 101;
|
}
|
|
// Update the number of patterns
|
net->Npats = n;
|
parser->MaxPats = n;
|
return 0;
|
}
|
|
int DLLEXPORT EN_deletepattern(EN_Project p, int index)
|
/*----------------------------------------------------------------
|
** Input: index = index of the pattern to delete
|
** Output: none
|
** Returns: error code
|
** Purpose: deletes a time pattern from a project
|
**----------------------------------------------------------------
|
*/
|
{
|
int i;
|
|
Network *net = &p->network;
|
Parser *parser = &p->parser;
|
Hydraul *hyd = &p->hydraul;
|
|
// Can't delete a pattern while a solver is active
|
if (!p->Openflag)return 102;
|
if (p->hydraul.OpenHflag || p->quality.OpenQflag) return 262;
|
|
// Check that pattern exists
|
if (index < 1 || index > p->network.Npats) return 205;
|
|
// Adjust references by other objects to patterns
|
adjustpatterns(net, index);
|
|
// Modify global energy price pattern
|
if (hyd->Epat == index) hyd->Epat = 0;
|
else if (hyd->Epat > index) hyd->Epat--;
|
|
// Free the pattern's factor array
|
FREE(net->Pattern[index].F);
|
FREE(net->Pattern[index].Comment);
|
|
// Shift the entries in the network's Pattern array
|
for (i = index; i < net->Npats; i++) net->Pattern[i] = net->Pattern[i+1];
|
net->Npats--;
|
parser->MaxPats--;
|
return 0;
|
}
|
|
int DLLEXPORT EN_getpatternindex(EN_Project p, char *id, int *index)
|
/*----------------------------------------------------------------
|
** Input: id = time pattern name
|
** Output: index = time pattern index
|
** Returns: error code
|
** Purpose: retrieves the index of a time pattern
|
**----------------------------------------------------------------
|
*/
|
{
|
int i;
|
|
*index = 0;
|
if (!p->Openflag) return 102;
|
for (i = 1; i <= p->network.Npats; i++)
|
{
|
if (strcmp(id, p->network.Pattern[i].ID) == 0)
|
{
|
*index = i;
|
return 0;
|
}
|
}
|
*index = 0;
|
return 205;
|
}
|
|
int DLLEXPORT EN_getpatternid(EN_Project p, int index, char *id)
|
/*----------------------------------------------------------------
|
** Input: index = time pattern index
|
** Output: id = time pattern ID name
|
** Returns: error code
|
** Purpose: retrieves the ID name of a time pattern
|
**----------------------------------------------------------------
|
*/
|
{
|
strcpy(id, "");
|
if (!p->Openflag)return 102;
|
if (index < 1 || index > p->network.Npats) return 205;
|
strcpy(id, p->network.Pattern[index].ID);
|
return 0;
|
}
|
|
int DLLEXPORT EN_setpatternid(EN_Project p, int index, char *id)
|
/*----------------------------------------------------------------
|
** Input: index = time pattern index
|
** id = time pattern ID name
|
** Returns: error code
|
** Purpose: changes the ID name of a time pattern
|
**----------------------------------------------------------------
|
*/
|
{
|
int i;
|
|
if (!p->Openflag) return 102;
|
if (index < 1 || index > p->network.Npats) return 205;
|
|
// Check if id name contains invalid characters
|
if (!namevalid(id)) return 252;
|
|
for (i = 1; i <= p->network.Npats; i++)
|
{
|
if (i != index && strcmp(id, p->network.Pattern[i].ID) == 0) return 215;
|
}
|
strcpy(p->network.Pattern[index].ID, id);
|
return 0;
|
}
|
|
int DLLEXPORT EN_getpatternlen(EN_Project p, int index, int *len)
|
/*----------------------------------------------------------------
|
** Input: index = time pattern index
|
** Output: len = number of periods in a time pattern
|
** Returns: error code
|
** Purpose: retrieves the number of time periods in a time pattern
|
**----------------------------------------------------------------
|
*/
|
{
|
if (!p->Openflag) return 102;
|
if (index < 1 || index > p->network.Npats) return 205;
|
*len = p->network.Pattern[index].Length;
|
return 0;
|
}
|
|
int DLLEXPORT EN_getpatternvalue(EN_Project p, int index, int period, double *value)
|
/*----------------------------------------------------------------
|
** Input: index = time pattern index
|
** period = time pattern period
|
** Output: value = pattern factor for a particular time period
|
** Returns: error code
|
** Purpose: retrieves the pattern factor for a specific time period
|
** in a time pattern
|
**----------------------------------------------------------------
|
*/
|
{
|
*value = 0.0;
|
if (!p->Openflag) return 102;
|
if (index < 1 || index > p->network.Npats) return 205;
|
if (period < 1 || period > p->network.Pattern[index].Length) return 251;
|
*value = (double)p->network.Pattern[index].F[period - 1];
|
return 0;
|
}
|
|
int DLLEXPORT EN_setpatternvalue(EN_Project p, int index, int period, double value)
|
/*----------------------------------------------------------------
|
** Input: index = time pattern index
|
** period = time pattern period
|
** value = pattern factor for a particular time period
|
** Output: none
|
** Returns: error code
|
** Purpose: sets the pattern factor for a specific time period
|
** in a time pattern
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
Spattern *Pattern = net->Pattern;
|
|
if (!p->Openflag) return 102;
|
if (index <= 0 || index > net->Npats) return 205;
|
if (period <= 0 || period > Pattern[index].Length) return 251;
|
Pattern[index].F[period - 1] = value;
|
return 0;
|
}
|
|
int DLLEXPORT EN_getaveragepatternvalue(EN_Project p, int index, double *value)
|
/*----------------------------------------------------------------
|
** Input: index = time pattern index
|
** Output: value = average of a time pattern's factors
|
** Returns: error code
|
** Purpose: retrieves the average of all pattern factors for a time pattern
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
|
Spattern *Pattern = net->Pattern;
|
int i;
|
|
*value = 0.0;
|
if (!p->Openflag) return 102;
|
if (index < 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 (values == NULL) return 205;
|
if (len <= 0) return 202;
|
|
// Re-set number of time periods & reallocate memory for multipliers
|
Pattern[index].Length = len;
|
Pattern[index].F = (double *)realloc(Pattern[index].F, len * sizeof(double));
|
if (Pattern[index].F == NULL) return 101;
|
|
// Load multipliers into pattern
|
for (j = 0; j < len; j++) Pattern[index].F[j] = values[j];
|
return 0;
|
}
|
|
/********************************************************************
|
|
Data Curve Functions
|
|
********************************************************************/
|
|
int DLLEXPORT EN_addcurve(EN_Project p, char *id)
|
/*----------------------------------------------------------------
|
** Input: id = data curve ID name
|
** Output: none
|
** Returns: error code
|
** Purpose: adds a new data curve to a project
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
|
int i, n, err = 0;
|
Scurve *curve;
|
|
// Check if a curve with same id already exists
|
if (!p->Openflag) return 102;
|
if (EN_getcurveindex(p, id, &i) == 0) return 215;
|
|
// Check if id name contains invalid characters
|
if (!namevalid(id)) return 252;
|
|
// Expand the array of curves
|
n = net->Ncurves + 1;
|
net->Curve = (Scurve *) realloc(net->Curve, (n + 1) * sizeof(Scurve));
|
|
// Set the properties of the new curve
|
curve = &net->Curve[n];
|
strcpy(curve->ID, id);
|
curve->Comment = NULL;
|
curve->Capacity = 1;
|
curve->Npts = 1;
|
curve->Type = GENERIC_CURVE;
|
curve->X = (double *)calloc(1, sizeof(double));
|
curve->Y = (double *)calloc(1, sizeof(double));
|
if (curve->X == NULL) err = 1;
|
else if (curve->Y == NULL) err = 1;
|
else
|
{
|
curve->X[0] = 1.0;
|
curve->Y[0] = 1.0;
|
}
|
|
// Abort if memory allocation error
|
if (err)
|
{
|
free(curve->X);
|
free(curve->Y);
|
return 101;
|
}
|
|
// Update the number of curves
|
net->Ncurves = n;
|
p->parser.MaxCurves = n;
|
return 0;
|
}
|
|
int DLLEXPORT EN_deletecurve(EN_Project p, int index)
|
/*----------------------------------------------------------------
|
** Input: index = index of the curve to delete
|
** Output: none
|
** Returns: error code
|
** Purpose: deletes a data curve from a project
|
**----------------------------------------------------------------
|
*/
|
{
|
int i;
|
|
Network *net = &p->network;
|
Parser *parser = &p->parser;
|
|
// Can't delete a curve while a solver is active
|
if (!p->Openflag)return 102;
|
if (p->hydraul.OpenHflag || p->quality.OpenQflag) return 262;
|
|
// Check that curve exists
|
if (index < 1 || index > p->network.Ncurves) return 205;
|
|
// Adjust references by other objects to curves
|
adjustcurves(net, index);
|
|
// Free the curve's data arrays
|
FREE(net->Curve[index].X);
|
FREE(net->Curve[index].Y);
|
FREE(net->Curve[index].Comment);
|
|
// Shift the entries in the network's Curve array
|
for (i = index; i < net->Ncurves; i++) net->Curve[i] = net->Curve[i + 1];
|
net->Ncurves--;
|
parser->MaxCurves--;
|
return 0;
|
}
|
|
int DLLEXPORT EN_getcurveindex(EN_Project p, char *id, int *index)
|
/*----------------------------------------------------------------
|
** Input: id = data curve name
|
** Output: index = data curve index
|
** Returns: error code
|
** Purpose: retrieves the index of a data curve
|
**----------------------------------------------------------------
|
*/
|
{
|
*index = 0;
|
if (!p->Openflag) return 102;
|
*index = findcurve(&p->network, id);
|
if (*index == 0) return 206;
|
return 0;
|
}
|
|
int DLLEXPORT EN_getcurveid(EN_Project p, int index, char *id)
|
/*----------------------------------------------------------------
|
** Input: index = data curve index
|
** Output: id = data curve ID name
|
** Returns: error code
|
** Purpose: retrieves the name of a data curve
|
**----------------------------------------------------------------
|
*/
|
{
|
strcpy(id, "");
|
if (!p->Openflag) return 102;
|
if (index < 1 || index > p->network.Ncurves) return 206;
|
strcpy(id, p->network.Curve[index].ID);
|
return 0;
|
}
|
|
int DLLEXPORT EN_setcurveid(EN_Project p, int index, char *id)
|
/*----------------------------------------------------------------
|
** Input: index = data curve index
|
** id = data curve ID name
|
** Returns: error code
|
** Purpose: changes the ID name of a data curve
|
**----------------------------------------------------------------
|
*/
|
{
|
int i;
|
|
if (!p->Openflag) return 102;
|
if (index < 1 || index > p->network.Ncurves) return 205;
|
|
// Check if id name contains invalid characters
|
if (!namevalid(id)) return 252;
|
|
for (i = 1; i <= p->network.Ncurves; i++)
|
{
|
if (i != index && strcmp(id, p->network.Curve[i].ID) == 0) return 215;
|
}
|
strcpy(p->network.Curve[index].ID, id);
|
return 0;
|
}
|
|
int DLLEXPORT EN_getcurvelen(EN_Project p, int index, int *len)
|
/*----------------------------------------------------------------
|
** Input: index = data curve index
|
** Output: len = number of points in a data curve
|
** Returns: error code
|
** Purpose: retrieves the number of points in a data curve
|
**----------------------------------------------------------------
|
*/
|
{
|
if (!p->Openflag) return 102;
|
if (index < 1 || index > p->network.Ncurves) return 206;
|
*len = p->network.Curve[index].Npts;
|
return 0;
|
}
|
|
int DLLEXPORT EN_getcurvetype(EN_Project p, int index, int *type)
|
/*----------------------------------------------------------------
|
** Input: index = data curve index
|
** Output: type = type of data curve (see EN_CurveType)
|
** Returns: error code
|
** Purpose: retrieves the type assigned to a data curve
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
if (!p->Openflag) return 102;
|
if (index < 1 || index > net->Ncurves) return 206;
|
*type = net->Curve[index].Type;
|
return 0;
|
}
|
|
int DLLEXPORT EN_getcurvevalue(EN_Project p, int curveIndex, int pointIndex,
|
double *x, double *y)
|
/*----------------------------------------------------------------
|
** Input: curveIndex = data curve index
|
** pointIndex = index of a data point on the curve
|
** Output: x = x-value of the point on the curve
|
** y = y-value of the point on the curve
|
** Returns: error code
|
** Purpose: retrieves the value of a specific point on a data curve
|
**----------------------------------------------------------------
|
*/
|
{
|
*x = 0.0;
|
*y = 0.0;
|
if (!p->Openflag) return 102;
|
if (curveIndex < 1 || curveIndex > p->network.Ncurves) return 206;
|
if (pointIndex < 1 || pointIndex > p->network.Curve[curveIndex].Npts) return 251;
|
*x = p->network.Curve[curveIndex].X[pointIndex - 1];
|
*y = p->network.Curve[curveIndex].Y[pointIndex - 1];
|
return 0;
|
}
|
|
int DLLEXPORT EN_setcurvevalue(EN_Project p, int curveIndex, int pointIndex,
|
double x, double y)
|
/*----------------------------------------------------------------
|
** Input: curveIndex = data curve index
|
** pointIndex = index of a data point on the curve
|
** x = new x-value for the point on the curve
|
** y = new y-value for the point on the curve
|
** Output: none
|
** Returns: error code
|
** Purpose: sets the value of a specific point on a data curve
|
** Note: if pointIndex exceeds the curve's length a new point is added.
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
Scurve *curve;
|
double x1 = -1.e37, x2 = 1.e37;
|
int n = pointIndex - 1;
|
|
// Check for valid input
|
if (!p->Openflag) return 102;
|
if (curveIndex <= 0 || curveIndex > net->Ncurves) return 206;
|
curve = &net->Curve[curveIndex];
|
if (pointIndex <= 0) return 251;
|
|
// Check that new point maintains increasing x values
|
if (n - 1 >= 0) x1 = curve->X[n-1];
|
if (n + 1 < curve->Npts) x2 = curve->X[n+1];
|
if (x <= x1 || x >= x2) return 230;
|
|
// Expand curve if need be
|
if (pointIndex > curve->Npts) pointIndex = curve->Npts + 1;
|
if (pointIndex >= curve->Capacity)
|
{
|
if (resizecurve(curve, curve->Capacity + 10) > 0) return 101;
|
}
|
|
// Increase curve's number of points if need be
|
if (pointIndex > curve->Npts)
|
{
|
curve->Npts++;
|
n = curve->Npts - 1;
|
}
|
|
// Insert new point into curve
|
curve->X[n] = x;
|
curve->Y[n] = y;
|
|
// Adjust parameters for pumps using curve as a head curve
|
return adjustpumpparams(p, curveIndex);
|
}
|
|
int DLLEXPORT EN_getcurve(EN_Project p, int index, char *id, int *nPoints,
|
double *xValues, double *yValues)
|
/*----------------------------------------------------------------
|
** Input: index = data curve index
|
** Output: id = ID name of data curve
|
** nPoints = number of data points on the curve
|
** xValues = array of x-values for each data point
|
** yValues = array of y-values for each data point
|
** Returns: error code
|
** Purpose: retrieves the data associated with a data curve
|
**
|
** The calling program is responsible for making xValues and
|
** yValues large enough to hold nPoints data points.
|
**----------------------------------------------------------------
|
*/
|
{
|
int i;
|
Scurve *curve;
|
|
if (!p->Openflag) return 102;
|
if (index <= 0 || index > p->network.Ncurves) return 206;
|
if (xValues == NULL || yValues == NULL) return 206;
|
curve = &p->network.Curve[index];
|
strncpy(id, curve->ID, MAXID);
|
*nPoints = curve->Npts;
|
for (i = 0; i < curve->Npts; i++)
|
{
|
xValues[i] = curve->X[i];
|
yValues[i] = curve->Y[i];
|
}
|
return 0;
|
}
|
|
int DLLEXPORT EN_setcurve(EN_Project p, int index, double *xValues,
|
double *yValues, int nPoints)
|
/*----------------------------------------------------------------
|
** Input: index = data curve index
|
** xValues = array of x-values
|
** yValues = array of y-values
|
** nPoints = number of data points in the x and y arrays
|
** Returns: error code
|
** Purpose: replaces a curve's set of data points
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
Scurve *curve;
|
int j;
|
|
// Check for valid arguments
|
if (!p->Openflag) return 102;
|
if (index <= 0 || index > net->Ncurves) return 206;
|
if (xValues == NULL || yValues == NULL) return 206;
|
if (nPoints <= 0) return 202;
|
|
// Check that x values are increasing
|
for (j = 1; j < nPoints; j++) if (xValues[j-1] >= xValues[j]) return 230;
|
|
// Expand size of curve's data arrays if need be
|
curve = &net->Curve[index];
|
if (resizecurve(curve, nPoints) > 0) return 101;
|
|
// Load values into curve
|
curve->Npts = nPoints;
|
for (j = 0; j < nPoints; j++)
|
{
|
curve->X[j] = xValues[j];
|
curve->Y[j] = yValues[j];
|
}
|
|
// Adjust parameters for pumps using curve as a head curve
|
return adjustpumpparams(p, index);
|
}
|
|
/********************************************************************
|
|
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;
|
|
StatusType 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 = (ControlType)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;
|
|
StatusType 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 = (ControlType)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 HYDRAULIC input file
|
** Output: none
|
** Returns: error code
|
** Purpose: adds a new rule to a project
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
Parser *parser = &p->parser;
|
Rules *rules = &p->rules;
|
|
char *line;
|
char *nextline;
|
char line2[MAXLINE+1];
|
|
// Resize rules array
|
net->Rule = (Srule *)realloc(net->Rule, (net->Nrules + 2)*sizeof(Srule));
|
rules->Errcode = 0;
|
rules->RuleState = 6; // = r_PRIORITY
|
|
// Extract each line of the rule statement
|
line = rule;
|
while (line)
|
{
|
// Find where current line ends and next one begins
|
nextline = strchr(line, '\n');
|
if (nextline) *nextline = '\0';
|
|
// Copy and tokenize the current line
|
strcpy(line2, line);
|
strcat(line2, "\n"); // Tokenizer won't work without this
|
parser->Ntokens = gettokens(line2, parser->Tok, MAXTOKS, parser->Comment);
|
|
// Process the line to build up the rule's contents
|
if (parser->Ntokens > 0 && *parser->Tok[0] != ';')
|
{
|
ruledata(p); // Nrules gets updated in ruledata()
|
if (rules->Errcode) break;
|
}
|
|
// Extract next line from the rule statement
|
if (nextline) *nextline = '\n';
|
line = nextline ? (nextline + 1) : NULL;
|
}
|
|
// Delete new rule entry if there was an error
|
if (rules->Errcode) deleterule(p, net->Nrules);
|
|
// Re-assign error code 201 (syntax error) to 250 (invalid format)
|
if (rules->Errcode == 201) rules->Errcode = 250;
|
return rules->Errcode;
|
}
|
|
int DLLEXPORT EN_deleterule(EN_Project p, int index)
|
/*----------------------------------------------------------------
|
** Input: index = rule index
|
** Output: none
|
** Returns: error code
|
** Purpose: deletes a rule from a project
|
**----------------------------------------------------------------
|
*/
|
{
|
if (index < 1 || index > p->network.Nrules) return 257;
|
deleterule(p, index);
|
return 0;
|
}
|
|
int DLLEXPORT EN_getrule(EN_Project p, int index, int *nPremises,
|
int *nThenActions, int *nElseActions,
|
double *priority)
|
/*----------------------------------------------------------------
|
** Input: index = rule index
|
** Output: nPremises = number of premises conditions (IF AND OR)
|
** nThenActions = number of actions in THEN portion of rule
|
** nElseActions = number of actions in ELSE portion of rule
|
** priority = rule priority
|
** Returns: error code
|
** Purpose: gets information about a particular rule
|
**----------------------------------------------------------------
|
*/
|
{
|
Network *net = &p->network;
|
|
int count;
|
Spremise *premise;
|
Saction *action;
|
|
if (index < 1 || index > net->Nrules) return 257;
|
*priority = (double)p->network.Rule[index].priority;
|
|
count = 0;
|
premise = net->Rule[index].Premises;
|
while (premise != NULL)
|
{
|
count++;
|
premise = premise->next;
|
}
|
*nPremises = count;
|
|
count = 0;
|
action = net->Rule[index].ThenActions;
|
while (action != NULL)
|
{
|
count++;
|
action = action->next;
|
}
|
*nThenActions = count;
|
|
count = 0;
|
action = net->Rule[index].ElseActions;
|
while (action != NULL)
|
{
|
count++;
|
action = action->next;
|
}
|
*nElseActions = count;
|
return 0;
|
}
|
|
int DLLEXPORT EN_getruleID(EN_Project p, int index, char *id)
|
/*----------------------------------------------------------------
|
** Input: index = rule index
|
** Output: id = rule ID label
|
** Returns: error code
|
** Purpose: retrieves the ID label of a rule
|
**----------------------------------------------------------------
|
*/
|
{
|
strcpy(id, "");
|
if (!p->Openflag) return 102;
|
if (index < 1 || index > p->network.Nrules) return 257;
|
strcpy(id, p->network.Rule[index].label);
|
return 0;
|
}
|
|
int DLLEXPORT EN_getpremise(EN_Project p, int ruleIndex, int premiseIndex,
|
int *logop, int *object, int *objIndex, int *variable,
|
int *relop, int *status, double *value)
|
/*----------------------------------------------------------------
|
** Input: ruleIndex = rule index
|
** premiseIndex = premise index
|
** Output: logop = logical operator (IF = 1, AND = 2, OR = 3)
|
** object = type of object appearing in the premise (see EN_RuleObject)
|
** objIndex = object's index in Node or Link array
|
** variable = object variable being tested (see EN_RuleVariable)
|
** relop = relational operator (see EN_RuleOperator)
|
** status = object status being tested against (see EN_RuleStatus))
|
** value = variable value being tested against
|
** Returns: error code
|
** Purpose: retrieves the properties of a rule's premise
|
**----------------------------------------------------------------
|
*/
|
{
|
Spremise *premises;
|
Spremise *premise;
|
|
if (ruleIndex < 1 || ruleIndex > p->network.Nrules) return 257;
|
|
premises = p->network.Rule[ruleIndex].Premises;
|
premise = getpremise(premises, premiseIndex);
|
if (premise == NULL) return 258;
|
|
*logop = premise->logop;
|
*object = premise->object;
|
*objIndex = premise->index;
|
*variable = premise->variable;
|
*relop = premise->relop;
|
*status = premise->status;
|
*value = (double)premise->value;
|
return 0;
|
}
|
|
|
int DLLEXPORT EN_setpremise(EN_Project p, int ruleIndex, int premiseIndex,
|
int logop, int object, int objIndex, int variable,
|
int relop, int status, double value)
|
/*----------------------------------------------------------------
|
** Input: ruleIndex = rule index
|
** premiseIndex = premise index
|
** logop = logical operator (IF = 1, AND = 2, OR = 3)
|
** object = type of object appearing in the premise (see EN_RuleObject)
|
** objIndex = object's index in Node or Link array
|
** variable = object variable being tested (see EN_RuleVariable)
|
** relop = relational operator (see EN_RuleOperator)
|
** status = object status being tested against (see EN_RuleStatus))
|
** value = variable value being tested against
|
** Output: none
|
** Returns: error code
|
** Purpose: sets the properties of a rule's premise
|
**----------------------------------------------------------------
|
*/
|
{
|
Spremise *premises;
|
Spremise *premise;
|
|
if (ruleIndex < 1 || ruleIndex > p->network.Nrules) return 257;
|
|
premises = p->network.Rule[ruleIndex].Premises;
|
premise = getpremise(premises, premiseIndex);
|
if (premise == NULL) return 258;
|
|
premise->logop = logop;
|
premise->object = object;
|
premise->index = objIndex;
|
premise->variable = variable;
|
premise->relop = relop;
|
premise->status = status;
|
premise->value = value;
|
return 0;
|
}
|
|
int DLLEXPORT EN_setpremiseindex(EN_Project p, int ruleIndex, int premiseIndex,
|
int objIndex)
|
/*----------------------------------------------------------------
|
** Input: ruleIndex = rule index
|
** premiseIndex = premise index
|
** objIndex = object's index in Node or Link array
|
** Output: none
|
** Returns: error code
|
** Purpose: sets the index of an object referred to in a rule's premise
|
**----------------------------------------------------------------
|
*/
|
{
|
Spremise *premises;
|
Spremise *premise;
|
|
if (ruleIndex < 1 || ruleIndex > p->network.Nrules) return 257;
|
|
premises = p->network.Rule[ruleIndex].Premises;
|
premise = getpremise(premises, premiseIndex);
|
if (premise == NULL) return 258;
|
|
premise->index = objIndex;
|
return 0;
|
}
|
|
int DLLEXPORT EN_setpremisestatus(EN_Project p, int ruleIndex, int premiseIndex, int status)
|
/*----------------------------------------------------------------
|
** Input: ruleIndex = rule index
|
** premiseIndex = premise index
|
** status = object status being tested against
|
** (see EN_RuleStatus))
|
** Output: none
|
** Returns: error code
|
** Purpose: sets the status of an object being tested against
|
** in a rule's premise
|
**----------------------------------------------------------------
|
*/
|
{
|
Spremise *premises;
|
Spremise *premise;
|
|
if (ruleIndex < 1 || ruleIndex > p->network.Nrules) return 257;
|
|
premises = p->network.Rule[ruleIndex].Premises;
|
premise = getpremise(premises, premiseIndex);
|
if (premise == NULL) return 258;
|
|
premise->status = status;
|
return 0;
|
}
|
|
int DLLEXPORT EN_setpremisevalue(EN_Project p, int ruleIndex, int premiseIndex, double value)
|
/*----------------------------------------------------------------
|
** Input: ruleIndex = rule index
|
** premiseIndex = premise index
|
** value = value of object variable being tested against
|
** Output: none
|
** Returns: error code
|
** Purpose: sets the value of object's variable being tested against
|
** in a rule's premise
|
**----------------------------------------------------------------
|
*/
|
{
|
Spremise *premises;
|
Spremise *premise;
|
|
if (ruleIndex < 1 || ruleIndex > p->network.Nrules) return 257;
|
|
premises = p->network.Rule[ruleIndex].Premises;
|
premise = getpremise(premises, premiseIndex);
|
if (premise == NULL) return 258;
|
|
premise->value = value;
|
return 0;
|
}
|
|
int DLLEXPORT EN_getthenaction(EN_Project p, int ruleIndex, int actionIndex,
|
int *linkIndex, int *status, double *setting)
|
/*----------------------------------------------------------------
|
** Input: ruleIndex = rule index
|
** actionIndex = index of a rule's THEN actions
|
** Output: linkIndex = index of link appearing in the action
|
** status = status assigned to the link (see EN_RuleStatus))
|
** setting = setting assigned to the link
|
** Returns: error code
|
** Purpose: retrieves the properties of a rule's THEN action
|
**----------------------------------------------------------------
|
*/
|
{
|
Saction *actions;
|
Saction *action;
|
|
if (ruleIndex < 1 || ruleIndex > p->network.Nrules) return 257;
|
|
actions = p->network.Rule[ruleIndex].ThenActions;
|
action = getaction(actions, actionIndex);
|
if (action == NULL) return 258;
|
|
*linkIndex = action->link;
|
*status = action->status;
|
*setting = (double)action->setting;
|
return 0;
|
}
|
|
int DLLEXPORT EN_setthenaction(EN_Project p, int ruleIndex, int actionIndex,
|
int linkIndex, int status, double setting)
|
/*----------------------------------------------------------------
|
** Input: ruleIndex = rule index
|
** actionIndex = index of a rule's THEN actions
|
** linkIndex = index of link appearing in the action
|
** status = status assigned to the link (see EN_RuleStatus))
|
** setting = setting assigned to the link
|
** Returns: error code
|
** Purpose: sets the properties of a rule's THEN action
|
**----------------------------------------------------------------
|
*/
|
{
|
Saction *actions;
|
Saction *action;
|
|
if (ruleIndex < 1 || ruleIndex > p->network.Nrules) return 257;
|
|
actions = p->network.Rule[ruleIndex].ThenActions;
|
action = getaction(actions, actionIndex);
|
if (action == NULL) return 258;
|
|
action->link = linkIndex;
|
action->status = status;
|
action->setting = setting;
|
return 0;
|
}
|
|
int DLLEXPORT EN_getelseaction(EN_Project p, int ruleIndex, int actionIndex,
|
int *linkIndex, int *status, double *setting)
|
/*----------------------------------------------------------------
|
** Input: ruleIndex = rule index
|
** actionIndex = index of a rule's ELSE actions
|
** Output: linkIndex = index of link appearing in the action
|
** status = status assigned to the link (see EN_RuleStatus))
|
** setting = setting assigned to the link
|
** Returns: error code
|
** Purpose: retrieves the properties of a rule's ELSE action
|
**----------------------------------------------------------------
|
*/
|
{
|
Saction *actions;
|
Saction *action;
|
|
if (ruleIndex < 1 || ruleIndex > p->network.Nrules) return 257;
|
|
actions = p->network.Rule[ruleIndex].ElseActions;
|
action = getaction(actions, actionIndex);
|
if (action == NULL) return 258;
|
|
*linkIndex = action->link;
|
*status = action->status;
|
*setting = (double)action->setting;
|
return 0;
|
}
|
|
int DLLEXPORT EN_setelseaction(EN_Project p, int ruleIndex, int actionIndex,
|
int linkIndex, int status, double setting)
|
/*----------------------------------------------------------------
|
** Input: ruleIndex = rule index
|
** actionIndex = index of a rule's ELSE actions
|
** linkIndex = index of link appearing in the action
|
** status = status assigned to the link (see EN_RuleStatus))
|
** setting = setting assigned to the link
|
** Returns: error code
|
** Purpose: sets the properties of a rule's ELSE action
|
**----------------------------------------------------------------
|
*/
|
{
|
Saction *actions;
|
Saction *action;
|
|
if (ruleIndex < 1 || ruleIndex > p->network.Nrules) return 257;
|
|
actions = p->network.Rule[ruleIndex].ElseActions;
|
action = getaction(actions, actionIndex);
|
if (action == NULL) return 258;
|
|
action->link = linkIndex;
|
action->status = status;
|
action->setting = setting;
|
return 0;
|
}
|
|
int DLLEXPORT EN_setrulepriority(EN_Project p, int index, double priority)
|
/*-----------------------------------------------------------------------------
|
** Input: index = rule index
|
** priority = rule priority level
|
** Output: none
|
** Returns: error code
|
** Purpose: sets the priority level for a rule
|
**-----------------------------------------------------------------------------
|
*/
|
{
|
if (index <= 0 || index > p->network.Nrules) return 257;
|
p->network.Rule[index].priority = priority;
|
return 0;
|
}
|
|
|
|
|
int DLLEXPORT EN_setinistatus(EN_Project p, char* id, char* value)
|
{
|
if (setinistatus(p,id, value)) {
|
return(200);
|
}
|
return (0);
|
}
|
|
|
int DLLEXPORT EN_test(Sdemand d)
|
{
|
return 0;
|
}
|
|
//
|
//int DLLEXPORT ENaddjunc(EN_Project p, char* line)
|
//{
|
// int errcode = 0;
|
// ERRCODE(addjunc(p, line));
|
// return errcode;
|
//}
|
//
|
//int DLLEXPORT ENaddtank(EN_Project p, char* line)
|
//{
|
// int errcode = 0;
|
// ERRCODE(addtank(p, line));
|
// return errcode;
|
//}
|
//
|
//int DLLEXPORT ENaddpipe(EN_Project p, char* line, char* node1, char* node2)
|
//{
|
// int errcode = 0;
|
// ERRCODE(addpipe(p, line, node1, node2));
|
// return errcode;
|
//}
|
//
|
//int DLLEXPORT ENaddvalve(EN_Project p, char* line)
|
//{
|
// int errcode = 0;
|
// ERRCODE(addvalve(p, line));
|
// return(errcode);
|
//}
|
//
|
//int DLLEXPORT ENaddpump(EN_Project p, char* line)
|
//{
|
// int errcode = 0;
|
// ERRCODE(addpump(p, line));
|
// return(errcode);
|
//}
|
//
|
//int DLLEXPORT ENopenStart(EN_Project p,char* f1, char* f2, char* f3)
|
///*----------------------------------------------------------------
|
//** Input: f1 = pointer to name of input file
|
//** f2 = pointer to name of report file
|
//** f3 = pointer to name of binary output file
|
//** Output: none
|
//** Returns: error code
|
//** Purpose: opens HYDRAULIC input file & reads in network data
|
//**----------------------------------------------------------------
|
//*/
|
//{
|
// int errcode = 0;
|
//
|
// /*** Updated 9/7/00 ***/
|
// /* Reset math coprocessor */
|
//#ifdef DLL
|
// _fpreset();
|
//#endif
|
//
|
// /* 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;
|
//
|
// /*** Updated 9/7/00 ***/
|
// p->report.Messageflag = TRUE;
|
//
|
// /* If binary output file being used, then */
|
// /* do not write full results to Report file */
|
// /* (use it only for status reports). */
|
// p->report.Rptflag = 0;
|
// if (strlen(f3) == 0) p->report.Rptflag = 1;
|
//
|
// /*** Updated 9/7/00 ***/
|
// /*** Previous code segment ignored. ***/
|
// /*** Rptflag now always set to 1. ***/
|
// p->report.Rptflag = 1;
|
//
|
// /* Initialize global pointers to NULL. */
|
// initpointers(p);
|
//
|
// /* Open input & report files */
|
// ERRCODE(openfiles(p,f1, f2, f3));
|
// if (errcode > 0)
|
// {
|
// errmsg(p, errcode);
|
// return(errcode);
|
// }
|
// writelogo(p);
|
//
|
// /* Find network size & allocate memory for data */
|
// //writecon(p->viewprog,FMT02);
|
// writewin(p->viewprog, FMT100);
|
// ERRCODE(netsize(p));
|
// ERRCODE(allocdata(p));
|
//
|
// /* Retrieve input data */
|
// ERRCODE(getdataonly(p));
|
//
|
// return errcode;
|
//}
|
//
|
//int DLLEXPORT ENopenEnd(EN_Project p) {
|
// int errcode = 0;
|
//
|
// ERRCODE(adjustdataonly(p));
|
//
|
// /* Free temporary linked lists used for Patterns & Curves */
|
// /*freeTmplist(Patlist);
|
// freeTmplist(Curvelist);*/
|
//
|
// /* If using previously saved hydraulics then open its file */
|
// 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 ENsetlinknodes(EN_Project p,char* linkID, char* node1, char* node2)
|
//{
|
// int errcode = 0;
|
// ERRCODE(setlinknodes(p,linkID, node1, node2));
|
// return(errcode);
|
//}
|
//
|
//int DLLEXPORT ENsetlinklength(EN_Project p, char* linkID, float length) {
|
// int errcode = 0;
|
// ERRCODE(setlinklength(p,linkID, length));
|
// return(errcode);
|
//}
|