From b5e3986e6bc43526a3e5f3b53c63dcf7ee268ada Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Thu, 9 Aug 2018 10:42:47 -0400 Subject: [PATCH] Pressure Dependent Demands added to address issue 163 --- ReleaseNotes2_2.md | 96 +++++ include/epanet2.h | 64 +-- src/enumstxt.h | 4 + src/epanet.c | 102 +++-- src/funcs.h | 22 +- src/hydcoeffs.c | 360 ++++++++++++---- src/hydraul.c | 26 +- src/hydsolver.c | 999 ++++++++++++++------------------------------- src/hydstatus.c | 462 +++++++++++++++++++++ src/inpfile.c | 20 +- src/input1.c | 12 +- src/input2.c | 2 - src/input3.c | 301 +++++++------- src/output.c | 4 +- src/quality.c | 4 +- src/report.c | 9 +- src/rules.c | 4 +- src/text.h | 10 + src/types.h | 24 +- 19 files changed, 1495 insertions(+), 1030 deletions(-) create mode 100644 ReleaseNotes2_2.md create mode 100644 src/hydstatus.c diff --git a/ReleaseNotes2_2.md b/ReleaseNotes2_2.md new file mode 100644 index 0000000..d09fbf8 --- /dev/null +++ b/ReleaseNotes2_2.md @@ -0,0 +1,96 @@ + + + +Release Notes for EPANET 2.2 (Draft) +============================ + +This document describes the changes and updates that have been made to version 2.2 of EPANET. + +## Thread-Safe API Functions + +A duplicate set of the version 2.1 API functions has been provided that allow multiple EPANET projects to be analyzed concurrently in a thread-safe manner. These functions maintain the same name as the original but use a `EN_` prefix instead of `EN`. In addition, the first argument to each of these functions is a pointer to an `EN_Project` structure that encapsulates the network data for the particular project being analyzed. For example, instead of writing: + +`ENgetnodevalue(nodeIndex, EN_ELEVATION, &elev)` + +one would use: + +`EN_getnodevalue(pr, nodeIndex, EN_ELEVATION, &elev)` + +where `pr` is the pointer to an `EN_Project`. + +Two new functions have been added to the API to manage the creation and deletion of project pointers. `EN_createproject` creates a new project along with a pointer to it, while `EN_deleteproject` deletes a project. An example of using the thread-safe version of the API is shown below: +``` +#include "epanet2.h" +int runEpanet(char *finp, char *frpt) +{ + EN_Project *pr = NULL; + int err; + err = EN_createproject(&pr); + if (err) return err; + err = EN_open(pr, finp, frpt, ""); + if (!err) err = EN_solveH(pr); + if (!err) err = EN_report(pr); + EN_close(pr); + EN_deleteproject(pr); + return err; +} +``` + +## Additional Convergence Parameters + +Two new analysis options have been added to provide more rigorous convergence criteria for EPANET's hydraulic solver. In the API they are named `EN_HEADERROR` and `EN_FLOWCHANGE` while in the `[OPTIONS]` section of an EPANET input file they are named `HEADERROR` and `FLOWCHANGE`, respectively. + +`EN_HEADERROR` is the maximum head loss error that any network link can have for hydraulic convergence to occur. A link's head loss error is the difference between the head loss found as a function of computed flow in the link (such as by the Hazen-Williams equation for a pipe) and the difference in computed heads for the link's end nodes. The units of this parameter are feet (or meters for SI units). The default value of 0 indicates that no head error limit applies. + +`EN_FLOWCHANGE` is the largest change in flow that any network element (link, emitter, or pressure-dependent demand) can have for hydraulic convergence to occur. It is specified in whatever flow units the project is using. The default value of 0 indicates that no flow change limit applies. + +These new parameters augment the current `EN_ACCURACY` option which always remains in effect. In addition, both `EN_HEADERROR` and `EN_FLOWCHANGE` can be used as parameters in the `ENgetstatistic` (or `EN_getstatistic`) function to retrieve their computed values (even when their option values are 0) after a hydraulic solution has been completed. + +## Improved Linear Solver Routine +EPANET's hydraulic solver requires solving a system of linear equations over a series of iterations until a set of convergence criteria are met. The coefficient matrix of this linear system is square and symmetric. It has a row for each network node and a non-zero off-diagonal coefficient for each link. The numerical effort needed to solve the linear system can be reduced if the nodes are re-ordered so that the non-zero coefficients cluster more tightly around the diagonal. + +EPANET's original node re-ordering scheme has been replaced by the more powerful **Multiple Minimum Degree (MMD)** algorithm. On a series of eight networks ranging in size from 7,700 to 100,000 nodes **MMD** reduced the solution time for a single period (steady state) hydraulic analysis by an average of more than 50%. + +## Pressure Dependent Demands +EPANET has always employed a Demand Driven Analysis (**DDA**) when modeling network hydraulics. Under this approach nodal demands at a given point in time are fixed values that must be delivered no matter what nodal heads and link flows are produced by a hydraulic solution. This can result in situations where required demands are satisfied at nodes that have negative pressures - a physical impossibility. + +To address this issue EPANET has been extended to use a Pressure Driven Analysis (**PDA**) if so desired. Under **PDA**, the demand *D* delivered at a node depends on the node's available pressure *P* according to: +$$D =D_f\left(\frac{P-P_{min}}{P_{req}-P_{min}}\right)^{P_{exp}} for P_{0}<=P<=P_{req}$$where *Df* is the full demand required, *Pmin* is the pressure below which demand is zero, *Preq* is the pressure required to deliver the full required demand and *Pexp* is an exponent. When *P < Pmin* demand is 0 and when *P > Preq* demand equals *Df*. + +To implement pressure dependent analysis four new parameters have been added to the [OPTIONS] section of the EPANET input file: + + +| Parameter | Description | Default | +|--|--|--| +| DEMAND MODEL | either DDA or PDA | DDA | +| MINIMUM PRESSURE | value for *Pmin* | 0 +| REQUIRED PRESSURE | value for *Preq* | 0 +| PRESSURE EXPONENT | value for *Pexp* | 0.5 | + +These parameters can also be set and retrieved in code using the following API functions +``` +int ENsetdemandmodel(int modelType, double pMin, double pReq, double pExp); +int ENgetdemandmodel(int *modelType, double *pMin, double *pReq, double *pExp); +``` +for the legacy API and +``` +int EN_setdemandmodel(EN_Project *pr, int modelType, double pMin, double pReq, double pExp); +int EN_getdemandmodel(EN_Project *pr, int *modelType, double *pMin, double *pReq, double *pExp); +``` +for the thread-safe API. Some additional points regarding the new **PDA** option are: + + - If no DEMAND MODEL and its parameters are specified then the analysis defaults to being demand driven (**DDA**). + - This implementation of **PDA** assumes that the same parameters apply to all nodes in the network. Extending the framework to allow different parameters for specific nodes is straightforward to do but is left as a future feature to implement. + - *P0* is allowed to equal to *Preq*. This condition can be used to find a solution that results in the smallest amount of demand reductions needed to insure that no node delivers positive demand at a pressure below *Pmin*. + +## Code Changes + + - The header file `vars.h` containing global variables has been eliminated. Instead a number of new structures incorporating these variables has been added to `types.h`. These structures have been incorporated into the new `EN_Project` structure, also defined in `types.h`, which gets passed into each of the thread-safe API functions as a pointer. + - Each of the legacy API functions now simply calls its thread-safe counterpart passing in a pointer to a default global`EN_Project` variable that is declared in `types.h`. + - Throughout all code modules, global variables that were previously accessed through `vars.h` are now accessed using the `EN_Project` pointer that is passed into the functions where the variables appear. + - The exceedingly long `hydraul.c` file has been split into four separate files: + - `hydraul.c` now contains just the code needed to initialize a hydraulic analysis, set demands and control actions at each time step, and determine the length of the next time step to take. + - `hydsolver.c` implements EPANET's hydraulic solver at a single point in time. + - `hydcoeffs.c` computes values of the matrix coefficients (derived from link head losses and their gradients) used by the hydraulic solver. + - `hydstatus.c` checks for status changes in valves and pumps as requested by the hydraulic solver. + - The Multiple Minimum Degree re-ordering algorithm appears in a new file named `genmmd.c`. This is 1990's legacy code that is readily available on the web and can be found in several linear equation solver libraries. diff --git a/include/epanet2.h b/include/epanet2.h index 81b469e..bfc1905 100644 --- a/include/epanet2.h +++ b/include/epanet2.h @@ -138,10 +138,11 @@ typedef enum { EN_NEXTEVENTIDX = 15 } EN_TimeProperty; - typedef enum { EN_ITERATIONS = 0, - EN_RELATIVEERROR = 1 + EN_RELATIVEERROR = 1, + EN_MAXHEADERROR = 2, + EN_MAXFLOWCHANGE = 3 } EN_AnalysisStatistic; typedef enum { @@ -150,7 +151,7 @@ typedef enum { EN_LINKCOUNT = 2, /**< Number of Links (Pipes + Pumps + Valves) */ EN_PATCOUNT = 3, /**< Number of Time Patterns */ EN_CURVECOUNT = 4, /**< Number of Curves */ - EN_CONTROLCOUNT = 5, /**< Number of Control Statements */ + EN_CONTROLCOUNT = 5, /**< Number of Control Statements */ EN_RULECOUNT = 6 /**< Number of Rule-based Control Statements */ } EN_CountType; @@ -205,6 +206,10 @@ typedef enum { EN_CMD = 9 } EN_FlowUnits; +typedef enum { /* Demand model types. */ + EN_DDA = 0, /**< Demand driven analysis */ + EN_PDA = 1 /**< Pressure driven analysis */ +} EN_DemandModel; /// Simulation Option codes typedef enum { @@ -224,8 +229,6 @@ typedef enum { EN_TIMEOFDAY = 3 } EN_ControlType; - - typedef enum { EN_AVERAGE = 1, /* Time statistic types. */ EN_MINIMUM = 2, /* See TstatType in TYPES.H */ @@ -233,8 +236,6 @@ typedef enum { EN_RANGE = 4 } EN_StatisticType; - - typedef enum { EN_MIX1 = 0, /* Tank mixing models */ EN_MIX2 = 1, @@ -242,8 +243,6 @@ typedef enum { EN_LIFO = 3 } EN_MixingModel; - - typedef enum { EN_NOSAVE = 0, EN_SAVE = 1, @@ -251,8 +250,6 @@ typedef enum { EN_SAVE_AND_INIT = 11 } EN_SaveOption; - - typedef enum { EN_CONST_HP = 0, /* constant horsepower */ EN_POWER_FUNC = 1, /* power function */ @@ -260,7 +257,6 @@ typedef enum { } EN_CurveType; - // --- Declare the EPANET toolkit functions #if defined(__cplusplus) extern "C" { @@ -270,8 +266,6 @@ extern "C" { @brief The EPANET Project wrapper object */ typedef struct EN_Project EN_Project; - typedef struct EN_Pattern EN_Pattern; - typedef struct EN_Curve EN_Curve; /** @brief runs a complete EPANET simulation @@ -522,7 +516,29 @@ extern "C" { @return Error code */ int DLLEXPORT ENsetflowunits(int code); - + + /** + @brief Retrieves the type of demand model in use and its parameters + @param[out] type Type of demand model (EN_DDA or EN_PDA) + @param[out] pmin Pressure below which there is no demand + @param[out] preq Pressure required to deliver full demand + @param[out] pexp Pressure exponent in demand function + @return Error code + */ + int DLLEXPORT ENgetdemandmodel(int *type, EN_API_FLOAT_TYPE *pmin, + EN_API_FLOAT_TYPE *preq, EN_API_FLOAT_TYPE *pexp); + + /** + @brief Sets the type of demand model to use and its parameters + @param type Type of demand model (EN_DDA or EN_PDA) + @param pmin Pressure below which there is no demand + @param preq Pressure required to deliver full demand + @param pexp Pressure exponent in demand function + @return Error code + */ + int DLLEXPORT ENsetdemandmodel(int type, EN_API_FLOAT_TYPE pmin, + EN_API_FLOAT_TYPE preq, EN_API_FLOAT_TYPE pexp); + /** @brief Retrieves the index of the time pattern with specified ID @param id String ID of the time pattern @@ -795,7 +811,7 @@ extern "C" { int DLLEXPORT ENsetnodevalue(int index, int code, EN_API_FLOAT_TYPE v); /** - @brief Set a proprty value for a link. + @brief Set a property value for a link. @param index The index of a link. First link is index 1. @param code The code for the property to set. @param v The value to set for this link and property. @@ -1120,15 +1136,14 @@ extern "C" { int DLLEXPORT ENdeletelink(int linkIndex); - - /*************************************************** Threadsafe versions of all epanet functions ***************************************************/ - int DLLEXPORT EN_alloc(EN_Project **p); - int DLLEXPORT EN_free(EN_Project *p); + int DLLEXPORT EN_createproject(EN_Project **p); + int DLLEXPORT EN_deleteproject(EN_Project *p); + int DLLEXPORT EN_epanet(char *inpFile, char *rptFile, char *binOutFile, void (*callback) (char *)); int DLLEXPORT EN_init(EN_Project *p, char *rptFile, char *binOutFile, EN_FlowUnits UnitsType, EN_FormType HeadlossFormula); int DLLEXPORT EN_open(EN_Project *p, char *inpFile, char *rptFile, char *binOutFile); @@ -1160,6 +1175,10 @@ extern "C" { int DLLEXPORT EN_gettimeparam(EN_Project *p, int code, long *value); int DLLEXPORT EN_getflowunits(EN_Project *p, int *code); int DLLEXPORT EN_setflowunits(EN_Project *p, int code); + int DLLEXPORT EN_getdemandmodel(EN_Project *p, int *type, EN_API_FLOAT_TYPE *pmin, EN_API_FLOAT_TYPE *preq, + EN_API_FLOAT_TYPE *pexp); + int DLLEXPORT EN_setdemandmodel(EN_Project *p, int type, EN_API_FLOAT_TYPE pmin, EN_API_FLOAT_TYPE preq, + EN_API_FLOAT_TYPE pexp); int DLLEXPORT EN_getpatternindex(EN_Project *p, char *id, int *index); int DLLEXPORT EN_getpatternid(EN_Project *p, int index, char *id); int DLLEXPORT EN_getpatternlen(EN_Project *p, int index, int *len); @@ -1224,11 +1243,6 @@ extern "C" { int DLLEXPORT EN_deletenode(EN_Project *p, int nodeIndex); int DLLEXPORT EN_deletelink(EN_Project *p, int linkIndex); - - - - - #if defined(__cplusplus) } #endif diff --git a/src/enumstxt.h b/src/enumstxt.h index c3abbc3..6688a1a 100755 --- a/src/enumstxt.h +++ b/src/enumstxt.h @@ -74,6 +74,10 @@ char *PressUnitsTxt[] = {w_PSI, w_KPA, w_METERS}; +char *DemandModelTxt[] = { w_DDA, + w_PDA, + NULL }; + char *QualTxt[] = {w_NONE, w_CHEM, w_AGE, diff --git a/src/epanet.c b/src/epanet.c index f0f0ff8..d89327d 100644 --- a/src/epanet.c +++ b/src/epanet.c @@ -129,9 +129,10 @@ execute function x and set the error code equal to its return value. #include "funcs.h" #include "text.h" #include "types.h" -#define EXTERN -////////////////////////////////////////////#include "epanet2.h" -#include "vars.h" + +// This single global variable is used only when the library is called +// in "legacy mode" with the 2.1-style API. +EN_Project *_defaultModel; /**************************************************************** @@ -159,7 +160,7 @@ execute function x and set the error code equal to its return value. int DLLEXPORT ENepanet(char *f1, char *f2, char *f3, void (*pviewprog)(char *)) { int errcode = 0; - ERRCODE(EN_alloc(&_defaultModel)); + ERRCODE(EN_createproject(&_defaultModel)); ERRCODE(EN_open(_defaultModel, f1, f2, f3)); _defaultModel->viewprog = pviewprog; if (_defaultModel->out_files.Hydflag != USE) { @@ -168,13 +169,13 @@ int DLLEXPORT ENepanet(char *f1, char *f2, char *f3, ERRCODE(EN_solveQ(_defaultModel)); ERRCODE(EN_report(_defaultModel)); EN_close(_defaultModel); - EN_free(_defaultModel); + EN_deleteproject(_defaultModel); return (errcode); } int DLLEXPORT ENopen(char *f1, char *f2, char *f3) { int errcode = 0; - ERRCODE(EN_alloc(&_defaultModel)); + ERRCODE(EN_createproject(&_defaultModel)); EN_open(_defaultModel, f1, f2, f3); return (errcode); } @@ -183,7 +184,10 @@ int DLLEXPORT ENsaveinpfile(char *filename) { return EN_saveinpfile(_defaultModel, filename); } -int DLLEXPORT ENclose() { return EN_close(_defaultModel); } +int DLLEXPORT ENclose() { + EN_close(_defaultModel); + return EN_deleteproject(_defaultModel); +} int DLLEXPORT ENsolveH() { return EN_solveH(_defaultModel); } @@ -262,6 +266,16 @@ int DLLEXPORT ENsetflowunits(int code) { return EN_setflowunits(_defaultModel, code); } +int DLLEXPORT ENgetdemandmodel(int *type, EN_API_FLOAT_TYPE *pmin, EN_API_FLOAT_TYPE *preq, + EN_API_FLOAT_TYPE *pexp) { + return EN_getdemandmodel(_defaultModel, type, pmin, preq, pexp); +} + +int DLLEXPORT ENsetdemandmodel(int type, EN_API_FLOAT_TYPE pmin, EN_API_FLOAT_TYPE preq, + EN_API_FLOAT_TYPE pexp) { + return EN_setdemandmodel(_defaultModel, type, pmin, preq, pexp); +} + int DLLEXPORT ENgetpatternindex(char *id, int *index) { return EN_getpatternindex(_defaultModel, id, index); } @@ -532,23 +546,32 @@ int DLLEXPORT ENdeletenode(int index) { return EN_deletenode(_defaultModel, index); } + +int DLLEXPORT EN_epanet(char *inpFile, char *rptFile, char *binOutFile, void(*callback) (char *)) +{ + return ENepanet(inpFile, rptFile, binOutFile, callback); +} + /* ---------------------------------------------------------------- Functions for opening & closing the EPANET system ---------------------------------------------------------------- */ -/// allocate a project pointer -int DLLEXPORT EN_alloc(EN_Project **p) +/// Create an EPANET project +int DLLEXPORT EN_createproject(EN_Project **p) { - EN_Project *project = calloc(1, sizeof(EN_Project)); - *p = project; - return 0; + EN_Project *project = calloc(1, sizeof(EN_Project)); + if (project == NULL) return 101; + *p = project; + return 0; } -int DLLEXPORT EN_free(EN_Project *p) +/// Delete an EPANET project +int DLLEXPORT EN_deleteproject(EN_Project *p) { - free(p); + if (p) free(p); + p = NULL; return 0; } @@ -558,9 +581,8 @@ int DLLEXPORT EN_init(EN_Project *pr, char *f2, char *f3, ** Input: ** f2 = pointer to name of report file ** f3 = pointer to name of binary output file - UnitsType = flow units flag - HeadlossFormula = headloss formula flag - + ** UnitsType = flow units flag + ** HeadlossFormula = headloss formula flag ** Output: none ** Returns: error code ** Purpose: opens EPANET @@ -1504,6 +1526,30 @@ int DLLEXPORT EN_setflowunits(EN_Project *p, int code) { return(0); } + +int DLLEXPORT EN_getdemandmodel(EN_Project *p, int *type, EN_API_FLOAT_TYPE *pmin, + EN_API_FLOAT_TYPE *preq, EN_API_FLOAT_TYPE *pexp) +{ + *type = p->hydraulics.DemandModel; + *pmin = (EN_API_FLOAT_TYPE)(p->hydraulics.Pmin * p->Ucf[PRESSURE]); + *preq = (EN_API_FLOAT_TYPE)(p->hydraulics.Preq * p->Ucf[PRESSURE]); + *pexp = (EN_API_FLOAT_TYPE)(p->hydraulics.Pexp); + return 0; +} + +int DLLEXPORT EN_setdemandmodel(EN_Project *p, int type, EN_API_FLOAT_TYPE pmin, + EN_API_FLOAT_TYPE preq, EN_API_FLOAT_TYPE pexp) +{ + if (type < 0 || type > EN_PDA) return 251; + if (pmin > preq || pexp <= 0.0) return 202; + p->hydraulics.DemandModel = type; + p->hydraulics.Pmin = pmin / p->Ucf[PRESSURE]; + p->hydraulics.Preq = preq / p->Ucf[PRESSURE]; + p->hydraulics.Pexp = pexp; + return 0; +} + + int DLLEXPORT EN_getpatternindex(EN_Project *p, char *id, int *index) { int i; *index = 0; @@ -1657,11 +1703,17 @@ int DLLEXPORT EN_geterror(int errcode, char *errmsg, int n) { int DLLEXPORT EN_getstatistic(EN_Project *p, int code, EN_API_FLOAT_TYPE *value) { switch (code) { case EN_ITERATIONS: - *value = (EN_API_FLOAT_TYPE)p->hydraulics.iterations; + *value = (EN_API_FLOAT_TYPE)p->hydraulics.Iterations; break; case EN_RELATIVEERROR: - *value = (EN_API_FLOAT_TYPE)p->hydraulics.relativeError; + *value = (EN_API_FLOAT_TYPE)p->hydraulics.RelativeError; break; + case EN_MAXHEADERROR: + *value = (EN_API_FLOAT_TYPE)(p->hydraulics.MaxHeadError * p->Ucf[HEAD]); + break; + case EN_MAXFLOWCHANGE: + *value = (EN_API_FLOAT_TYPE)(p->hydraulics.MaxFlowChange * p->Ucf[FLOW]); + break; default: break; } @@ -1725,8 +1777,8 @@ int DLLEXPORT EN_getcoord(EN_Project *p, int index, EN_API_FLOAT_TYPE *x, if (p->network.Coord[index].HaveCoords == FALSE) return (254); - *x = p->network.Coord[index].X; - *y = p->network.Coord[index].Y; + *x = (EN_API_FLOAT_TYPE)p->network.Coord[index].X; + *y = (EN_API_FLOAT_TYPE)p->network.Coord[index].Y; return 0; } @@ -4215,7 +4267,7 @@ int DLLEXPORT EN_getaveragepatternvalue(EN_Project *p, int index, EN_API_FLOAT_T return (205); // if (period < 1 || period > Pattern[index].Length) return(251); for (i = 0; i < Pattern[index].Length; i++) { - *value += Pattern[index].F[i]; + *value += (EN_API_FLOAT_TYPE)Pattern[index].F[i]; } *value /= (EN_API_FLOAT_TYPE)Pattern[index].Length; return (0); @@ -4686,7 +4738,7 @@ int DLLEXPORT EN_getpremise(EN_Project *pr, int indexRule, int idxPremise, int * *variable = p->variable; *relop = p->relop; *status = p->status; - *value = p[0].value; + *value = (EN_API_FLOAT_TYPE)p[0].value; return (0); } @@ -4820,7 +4872,7 @@ int DLLEXPORT EN_gettrueaction(EN_Project *pr, int indexRule, int indexAction, i } *indexLink = a->link; *status = a->status; - *setting = a->setting; + *setting = (EN_API_FLOAT_TYPE)a->setting; return (0); } @@ -4868,7 +4920,7 @@ int DLLEXPORT EN_getfalseaction(EN_Project *pr, int indexRule, int indexAction, } *indexLink = a->link; *status = a->status; - *setting = a->setting; + *setting = (EN_API_FLOAT_TYPE)a->setting; return (0); } diff --git a/src/funcs.h b/src/funcs.h index c8dfb05..e274ded 100755 --- a/src/funcs.h +++ b/src/funcs.h @@ -158,28 +158,13 @@ void inithyd(EN_Project *pr, int initFlags); /* Re-sets initial condition int runhyd(EN_Project *pr, long *); /* Solves 1-period hydraulics */ int nexthyd(EN_Project *pr, long *); /* Moves to next time period */ void closehyd(EN_Project *pr); /* Closes hydraulics solver */ -int allocmatrix(EN_Project *pr); /* Allocates matrix coeffs. */ -void freematrix(EN_Project *pr); /* Frees matrix coeffs. */ -void initlinkflow(EN_Project *pr, int, char, - double); /* Initializes link flow */ -void setlinkflow(EN_Project *pr, int, double); /* Sets link flow via headloss*/ void setlinkstatus(EN_Project *pr, int, char, StatType *, double *); /* Sets link status */ void setlinksetting(EN_Project *pr, int, double, StatType *, double *); /* Sets pump/valve setting */ - -void demands(EN_Project *pr); /* Computes current demands */ -int controls(EN_Project *pr); /* Controls link settings */ -long timestep(EN_Project *pr); /* Computes new time step */ int tanktimestep(EN_Project *pr, long *); /* Time till tanks fill/drain */ -void controltimestep(EN_Project *pr, long *); /* Time till control action */ -void ruletimestep(EN_Project *pr, long *); /* Time till rule action */ - -void addenergy(EN_Project *pr, long); /* Accumulates energy usage */ void getenergy(EN_Project *pr, int, double *, double *); /* Computes link energy use */ - -void tanklevels(EN_Project *pr, long); /* Computes new tank levels */ double tankvolume(EN_Project *pr, int,double); /* Finds tank vol. from grade */ double tankgrade(EN_Project *pr, int,double); /* Finds tank grade from vol. */ @@ -188,8 +173,13 @@ int hydsolve(EN_Project *pr, int *,double *); /* Solves network equations /* ----------- HYDCOEFFS.C --------------*/ void resistcoeff(EN_Project *pr, int k); /* Finds pipe flow resistance */ -void hlosscoeff(EN_Project *pr, int k); /* Finds link head loss coeff */ +void headlosscoeffs(EN_Project *pr); // Finds link head loss coeffs. void matrixcoeffs(EN_Project *pr); /* Finds hyd. matrix coeffs. */ +double emitflowchange(EN_Project *pr, int i); /* Change in emitter outflow */ +double demandflowchange(EN_Project *pr, int i, // Change in demand outflow + double dp, double n); +void demandparams(EN_Project *pr, double *dp, // PDA function parameters + double *n); /* ----------- SMATRIX.C ---------------*/ int createsparse(EN_Project *pr); /* Creates sparse matrix */ diff --git a/src/hydcoeffs.c b/src/hydcoeffs.c index 9b830d9..00b0b82 100644 --- a/src/hydcoeffs.c +++ b/src/hydcoeffs.c @@ -29,15 +29,21 @@ const double AB = 3.28895476345e-03; // 5.74/(4000^.9) const double AC = -5.14214965799e-03; // AA*AB // External functions -//void resistcoeff(EN_Project *pr, int k); -//void hlosscoeff(EN_Project *pr, int k); -//void matrixcoeffs(EN_Project *pr); +//void resistcoeff(EN_Project *pr, int k); +//void headlosscoeffs(EN_Project *pr); +//void matrixcoeffs(EN_Project *pr); +//double emitflowchange(EN_Project *pr, int i); +//double demandflowchange(EN_Project *pr, int i, double dp, double n); +//void demandparams(EN_Project *pr, double *dp, double *n); // Local functions static void linkcoeffs(EN_Project *pr); static void nodecoeffs(EN_Project *pr); static void valvecoeffs(EN_Project *pr); static void emittercoeffs(EN_Project *pr); +static void demandcoeffs(EN_Project *pr); +static void demandheadloss(double d, double dfull, double dp, + double n, double *hloss, double *hgrad); static void pipecoeff(EN_Project *pr, int k); static void DWpipecoeff(EN_Project *pr, int k); @@ -55,7 +61,6 @@ static void psvcoeff(EN_Project *pr, int k, int n1, int n2); static void fcvcoeff(EN_Project *pr, int k, int n1, int n2); - void resistcoeff(EN_Project *pr, int k) /* **-------------------------------------------------------------------- @@ -83,13 +88,14 @@ void resistcoeff(EN_Project *pr, int k) switch (hyd->Formflag) { case HW: - link->R = 4.727*L / pow(e, hyd->Hexp) / pow(d, 4.871); + link->R = 4.727 * L / pow(e, hyd->Hexp) / pow(d, 4.871); break; case DW: - link->R = L / 2.0 / 32.2 / d / SQR(PI*SQR(d) / 4.0); + link->R = L / 2.0 / 32.2 / d / SQR(PI * SQR(d) / 4.0); break; case CM: - link->R = SQR(4.0*e / (1.49*PI*d*d)) * pow((d / 4.0), -1.333)*L; + link->R = SQR(4.0 * e / (1.49 * PI * SQR(d))) * + pow((d / 4.0), -1.333) * L; } break; @@ -106,45 +112,46 @@ void resistcoeff(EN_Project *pr, int k) } -void hlosscoeff(EN_Project *pr, int k) +void headlosscoeffs(EN_Project *pr) /* **-------------------------------------------------------------- -** Input: k = link index +** Input: none ** Output: none -** Purpose: computes P and Y coefficients for a link +** Purpose: computes coefficients P (1 / head loss gradient) +** and Y (head loss / gradient) for all links. **-------------------------------------------------------------- */ { + int k; EN_Network *net = &pr->network; hydraulics_t *hyd = &pr->hydraulics; - solver_t *sol = &hyd->solver; - Slink *link = &net->Link[k]; - switch (link->Type) + for (k = 1; k <= net->Nlinks; k++) { - case EN_CVPIPE: - case EN_PIPE: - pipecoeff(pr, k); - break; - case EN_PUMP: - pumpcoeff(pr, k); - break; - case EN_PBV: - pbvcoeff(pr, k); - break; - case EN_TCV: - tcvcoeff(pr, k); - break; - case EN_GPV: - gpvcoeff(pr, k); - break; - case EN_FCV: - case EN_PRV: - case EN_PSV: - if (hyd->LinkSetting[k] == MISSING) { - valvecoeff(pr, k); + switch (net->Link[k].Type) + { + case EN_CVPIPE: + case EN_PIPE: + pipecoeff(pr, k); + break; + case EN_PUMP: + pumpcoeff(pr, k); + break; + case EN_PBV: + pbvcoeff(pr, k); + break; + case EN_TCV: + tcvcoeff(pr, k); + break; + case EN_GPV: + gpvcoeff(pr, k); + break; + case EN_FCV: + case EN_PRV: + case EN_PSV: + if (hyd->LinkSetting[k] == MISSING) valvecoeff(pr, k); + else hyd->solver.P[k] = 0.0; } - else sol->P[k] = 0.0; } } @@ -162,13 +169,23 @@ void matrixcoeffs(EN_Project *pr) solver_t *sol = &hyd->solver; EN_Network *net = &pr->network; + // Reset values of all diagonal coeffs. (Aii), off-diagonal + // coeffs. (Aij), r.h.s. coeffs. (F) and node flow balance (X_tmp) memset(sol->Aii, 0, (net->Nnodes + 1) * sizeof(double)); memset(sol->Aij, 0, (hyd->Ncoeffs + 1) * sizeof(double)); memset(sol->F, 0, (net->Nnodes + 1) * sizeof(double)); memset(hyd->X_tmp, 0, (net->Nnodes + 1) * sizeof(double)); + + // Compute matrix coeffs. from links, emitters, and nodal demands linkcoeffs(pr); emittercoeffs(pr); + demandcoeffs(pr); + + // Update nodal flow balances with demands and add onto r.h.s. coeffs. nodecoeffs(pr); + + // Finally, find coeffs. for PRV/PSV/FCV control valves whose + // status is not fixed to OPEN/CLOSED valvecoeffs(pr); } @@ -189,7 +206,7 @@ void linkcoeffs(EN_Project *pr) solver_t *sol = &hyd->solver; Slink *link; - // Examine each link of network */ + // Examine each link of network for (k = 1; k <= net->Nlinks; k++) { if (sol->P[k] == 0.0) continue; @@ -197,7 +214,7 @@ void linkcoeffs(EN_Project *pr) n1 = link->N1; // Start node of link n2 = link->N2; // End node of link - // Update net nodal inflows (X), solution matrix (A) and RHS array (F) + // Update net nodal inflows (X_tmp), solution matrix (A) and RHS array (F) // (Use covention that flow out of node is (-), flow into node is (+)) hyd->X_tmp[n1] -= hyd->LinkFlows[k]; hyd->X_tmp[n2] += hyd->LinkFlows[k]; @@ -212,21 +229,18 @@ void linkcoeffs(EN_Project *pr) sol->F[sol->Row[n1]] += sol->Y[k]; // RHS coeff. } - // Node n1 is a tank - else { - sol->F[sol->Row[n2]] += (sol->P[k] * hyd->NodeHead[n1]); - } + // Node n1 is a tank/reservoir + else sol->F[sol->Row[n2]] += (sol->P[k] * hyd->NodeHead[n1]); // Node n2 is junction - if (n2 <= net->Njuncs) { + if (n2 <= net->Njuncs) + { sol->Aii[sol->Row[n2]] += sol->P[k]; // Diagonal coeff. sol->F[sol->Row[n2]] -= sol->Y[k]; // RHS coeff. } - // Node n2 is a tank - else { - sol->F[sol->Row[n1]] += (sol->P[k] * hyd->NodeHead[n2]); - } + // Node n2 is a tank/reservoir + else sol->F[sol->Row[n1]] += (sol->P[k] * hyd->NodeHead[n2]); } } @@ -236,7 +250,7 @@ void nodecoeffs(EN_Project *pr) **---------------------------------------------------------------- ** Input: none ** Output: none -** Purpose: completes calculation of nodal flow imbalance (X) +** Purpose: completes calculation of nodal flow imbalance (X_tmp) ** & flow correction (F) arrays **---------------------------------------------------------------- */ @@ -250,7 +264,7 @@ void nodecoeffs(EN_Project *pr) // flow imbalance & add imbalance to RHS array F. for (i = 1; i <= net->Njuncs; i++) { - hyd->X_tmp[i] -= hyd->NodeDemand[i]; + hyd->X_tmp[i] -= hyd->DemandFlows[i]; sol->F[sol->Row[i]] += hyd->X_tmp[i]; } } @@ -276,15 +290,20 @@ void valvecoeffs(EN_Project *pr) // Examine each valve for (i = 1; i <= net->Nvalves; i++) { + // Find valve's link index valve = &net->Valve[i]; - k = valve->Link; // Link index of valve + k = valve->Link; + + // Coeffs. for fixed status valves have already been computed + if (hyd->LinkSetting[k] == MISSING) continue; + + // Start & end nodes of valve's link link = &net->Link[k]; - if (hyd->LinkSetting[k] == MISSING) { - continue; // Valve status fixed - } - n1 = link->N1; // Start & end nodes + n1 = link->N1; n2 = link->N2; - switch (link->Type) // Call valve-specific function + + // Call valve-specific function + switch (link->Type) { case EN_PRV: prvcoeff(pr, k, n1, n2); @@ -330,17 +349,17 @@ void emittercoeffs(EN_Project *pr) for (i = 1; i <= net->Njuncs; i++) { node = &net->Node[i]; - if (node->Ke == 0.0) { - continue; - } + if (node->Ke == 0.0) continue; ke = MAX(CSMALL, node->Ke); // emitter coeff. q = hyd->EmitterFlows[i]; // emitter flow z = ke * pow(ABS(q), hyd->Qexp); // emitter head loss p = hyd->Qexp * z / ABS(q); // head loss gradient - if (p < hyd->RQtol) { + if (p < hyd->RQtol) + { p = 1.0 / hyd->RQtol; } - else { + else + { p = 1.0 / p; // inverse head loss gradient } y = SGN(q)*z*p; // head loss / gradient @@ -351,6 +370,173 @@ void emittercoeffs(EN_Project *pr) } +double emitflowchange(EN_Project *pr, int i) +/* +**-------------------------------------------------------------- +** Input: i = node index +** Output: returns change in flow at an emitter node +** Purpose: computes flow change at an emitter node +**-------------------------------------------------------------- +*/ +{ + double ke, p; + hydraulics_t *hyd = &pr->hydraulics; + Snode *node = &pr->network.Node[i]; + + ke = MAX(CSMALL, node->Ke); + p = hyd->Qexp * ke * pow(ABS(hyd->EmitterFlows[i]), (hyd->Qexp - 1.0)); + if (p < hyd->RQtol) p = 1 / hyd->RQtol; + else p = 1.0 / p; + return(hyd->EmitterFlows[i] / hyd->Qexp - p * (hyd->NodeHead[i] - node->El)); +} + + +void demandparams(EN_Project *pr, double *dp, double *n) +/* +**-------------------------------------------------------------- +** Input: none +** Output: dp = pressure range over which demands can vary +** n = exponent in head loss v. demand function +** Purpose: retrieves parameters that define a pressure dependent +** demand function. +**-------------------------------------------------------------- +*/ +{ + hydraulics_t *hyd = &pr->hydraulics; + + // If required pressure equals minimum pressure, use a linear demand + // curve with a 0.01 PSI pressure range to approximate an all or + // nothing demand solution + if (hyd->Preq == hyd->Pmin) + { + *dp = 0.01 / PSIperFT; + *n = 1.0; + } + + // Otherwise use the user-supplied demand curve parameters + else + { + *dp = hyd->Preq - hyd->Pmin; + *n = 1.0 / hyd->Pexp; + } +} + + +void demandcoeffs(EN_Project *pr) +/* +**-------------------------------------------------------------- +** Input: none +** Output: none +** Purpose: computes matrix coeffs. for pressure dependent demands +** +** Note: Pressure dependent demands are modelled like emitters +** with Hloss = Pserv * (D / Dfull)^(1/Pexp) +** where D (actual demand) is zero for negative pressure +** and is Dfull above pressure Pserv. +**-------------------------------------------------------------- +*/ +{ + int i, row; + double dp, // pressure range over which demand can vary (ft) + n, // exponent in head loss v. demand function + hloss, // head loss in supplying demand (ft) + hgrad; // gradient of demand head loss (ft/cfs) + + hydraulics_t *hyd = &pr->hydraulics; + solver_t *sol = &hyd->solver; + EN_Network *net = &pr->network; + + // Get demand function parameters + if (hyd->DemandModel == DDA) return; + demandparams(pr, &dp, &n); + + // Examine each junction node + for (i = 1; i <= net->Njuncs; i++) + { + // Skip junctions with non-positive demands + if (hyd->NodeDemand[i] <= 0.0) continue; + + // Find head loss for demand outflow at node's elevation + demandheadloss(hyd->DemandFlows[i], hyd->NodeDemand[i], dp, n, + &hloss, &hgrad); + + // Update row of solution matrix A & its r.h.s. F + row = sol->Row[i]; + sol->Aii[row] += 1.0 / hgrad; + sol->F[row] += (hloss + net->Node[i].El + hyd->Pmin) / hgrad; + } +} + + +double demandflowchange(EN_Project *pr, int i, double dp, double n) +/* +**-------------------------------------------------------------- +** Input: i = node index +** dp = pressure range fro demand funtion (ft) +** n = exponent in head v. demand function +** Output: returns change in pressure dependent demand flow +** Purpose: computes change in outflow at at a node subject to +** pressure dependent demands +**-------------------------------------------------------------- +*/ +{ + double hloss, hgrad; + hydraulics_t *hyd = &pr->hydraulics; + + demandheadloss(hyd->DemandFlows[i], hyd->NodeDemand[i], dp, n, &hloss, &hgrad); + return (hloss - hyd->NodeHead[i] + pr->network.Node[i].El + hyd->Pmin) / hgrad; +} + + +void demandheadloss(double d, double dfull, double dp, double n, + double *hloss, double *hgrad) + /* + **-------------------------------------------------------------- + ** Input: d = actual junction demand (cfs) + ** dfull = full junction demand required (cfs) + ** dp = pressure range for demand function (ft) + ** n = exponent in head v. demand function + ** Output: hloss = head loss delivering demand d (ft) + ** hgrad = gradient of head loss (ft/cfs) + ** Purpose: computes head loss and its gradient for delivering + ** a pressure dependent demand flow. + **-------------------------------------------------------------- + */ +{ + const double RB = 1.0e9; + const double EPS = 0.001; + double r = d / dfull; + + // Use upper barrier function for excess demand above full value + if (r > 1.0) + { + *hgrad = RB; + *hloss = dp + RB * (d - dfull); + } + + // Use lower barrier function for negative demand + else if (r < 0) + { + *hgrad = RB; + *hloss = RB * d; + } + + // Use linear head loss function for near zero demand + else if (r < EPS) + { + *hgrad = dp * pow(EPS, n - 1.0) / dfull; + *hloss = (*hgrad) * d; + } + + // Otherwise use power head loss function + else + { + *hgrad = n * dp * pow(r, n - 1.0) / dfull; + *hloss = (*hgrad) * d / n; + } +} + + void pipecoeff(EN_Project *pr, int k) /* **-------------------------------------------------------------- @@ -382,8 +568,9 @@ void pipecoeff(EN_Project *pr, int k) return; } - // ... head loss formula is Darcy-Weisbach - if (hyd->Formflag == DW) { + // Use custom function for Darcy-Weisbach formula + if (hyd->Formflag == DW) + { DWpipecoeff(pr, k); return; } @@ -550,7 +737,8 @@ void pumpcoeff(EN_Project *pr, int k) Spump *pump; // Use high resistance pipe if pump closed or cannot deliver head - if (hyd->LinkStatus[k] <= CLOSED || setting == 0.0) { + if (hyd->LinkStatus[k] <= CLOSED || setting == 0.0) + { sol->P[k] = 1.0 / CBIG; sol->Y[k] = hyd->LinkFlows[k]; return; @@ -580,9 +768,7 @@ void pumpcoeff(EN_Project *pr, int k) h0 = SQR(setting) * pump->H0; n = pump->N; r = pump->R * pow(setting, 2.0 - n); - if (n != 1.0) { - r = n * r * pow(q, n - 1.0); - } + if (n != 1.0) r = n * r * pow(q, n - 1.0); // Compute inverse headloss gradient (P) and flow correction factor (Y) sol->P[k] = 1.0 / MAX(r, hyd->RQtol); @@ -615,15 +801,9 @@ void curvecoeff(EN_Project *pr, int i, double q, double *h0, double *r) // Find linear segment of curve that brackets flow q k2 = 0; - while (k2 < npts && x[k2] < q) - k2++; - - if (k2 == 0) - k2++; - - else if (k2 == npts) - k2--; - + while (k2 < npts && x[k2] < q) k2++; + if (k2 == 0) k2++; + else if (k2 == npts) k2--; k1 = k2 - 1; // Compute slope and intercept of this segment @@ -653,13 +833,12 @@ void gpvcoeff(EN_Project *pr, int k) solver_t *sol = &hyd->solver; // Treat as a pipe if valve closed - if (hyd->LinkStatus[k] == CLOSED) { - valvecoeff(pr, k); - } + if (hyd->LinkStatus[k] == CLOSED) valvecoeff(pr, k); // Otherwise utilize headloss curve // whose index is stored in K - else { + else + { // Find slope & intercept of headloss curve. q = ABS(hyd->LinkFlows[k]); q = MAX(q, TINY); @@ -687,18 +866,22 @@ void pbvcoeff(EN_Project *pr, int k) Slink *link = &pr->network.Link[k]; // If valve fixed OPEN or CLOSED then treat as a pipe - if (hyd->LinkSetting[k] == MISSING || hyd->LinkSetting[k] == 0.0) { + if (hyd->LinkSetting[k] == MISSING || hyd->LinkSetting[k] == 0.0) + { valvecoeff(pr, k); } // If valve is active - else { + else + { // Treat as a pipe if minor loss > valve setting - if (link->Km * SQR(hyd->LinkFlows[k]) > hyd->LinkSetting[k]) { + if (link->Km * SQR(hyd->LinkFlows[k]) > hyd->LinkSetting[k]) + { valvecoeff(pr, k); } // Otherwise force headloss across valve to be equal to setting - else { + else + { sol->P[k] = CBIG; sol->Y[k] = hyd->LinkSetting[k] * CBIG; } @@ -723,7 +906,8 @@ void tcvcoeff(EN_Project *pr, int k) km = link->Km; // If valve not fixed OPEN or CLOSED, compute its loss coeff. - if (hyd->LinkSetting[k] != MISSING) { + if (hyd->LinkSetting[k] != MISSING) + { link->Km = 0.02517 * hyd->LinkSetting[k] / (SQR(link->Diam)*SQR(link->Diam)); } @@ -768,7 +952,8 @@ void prvcoeff(EN_Project *pr, int k, int n1, int n2) sol->Y[k] = hyd->LinkFlows[k] + hyd->X_tmp[n2]; // Force flow balance sol->F[j] += (hset * CBIG); // Force head = hset sol->Aii[j] += CBIG; // at downstream node - if (hyd->X_tmp[n2] < 0.0) { + if (hyd->X_tmp[n2] < 0.0) + { sol->F[i] += hyd->X_tmp[n2]; } return; @@ -818,7 +1003,8 @@ void psvcoeff(EN_Project *pr, int k, int n1, int n2) sol->Y[k] = hyd->LinkFlows[k] - hyd->X_tmp[n1]; // Force flow balance sol->F[i] += (hset * CBIG); // Force head = hset sol->Aii[i] += CBIG; // at upstream node - if (hyd->X_tmp[n1] > 0.0) { + if (hyd->X_tmp[n1] > 0.0) + { sol->F[j] += hyd->X_tmp[n1]; } return; @@ -919,9 +1105,7 @@ void valvecoeff(EN_Project *pr, int k) if (link->Km > 0.0) { p = 2.0 * link->Km * fabs(flow); - if (p < hyd->RQtol) { - p = hyd->RQtol; - } + if (p < hyd->RQtol) p = hyd->RQtol; sol->P[k] = 1.0 / p; sol->Y[k] = flow / 2.0; } diff --git a/src/hydraul.c b/src/hydraul.c index 545ed37..3984ec0 100755 --- a/src/hydraul.c +++ b/src/hydraul.c @@ -60,6 +60,20 @@ AUTHOR: L. Rossman #define QZERO 1.e-6 /* Equivalent to zero flow */ +// Local functions +int allocmatrix(EN_Project *pr); +void freematrix(EN_Project *pr); +void initlinkflow(EN_Project *pr, int, char, double); +void setlinkflow(EN_Project *pr, int, double); +void demands(EN_Project *pr); +int controls(EN_Project *pr); +long timestep(EN_Project *pr); +void controltimestep(EN_Project *pr, long *); +void ruletimestep(EN_Project *pr, long *); +void addenergy(EN_Project *pr, long); +void tanklevels(EN_Project *pr, long); + + int openhyd(EN_Project *pr) /* *-------------------------------------------------------------- @@ -194,15 +208,12 @@ int runhyd(EN_Project *pr, long *t) /* Solve network hydraulic equations */ errcode = hydsolve(pr,&iter,&relerr); + if (!errcode) { /* Report new status & save results */ if (rep->Statflag) { writehydstat(pr,iter,relerr); } - - /* solution info */ - hyd->relativeError = relerr; - hyd->iterations = iter; /*** Updated 3/1/01 ***/ /* If system unbalanced and no extra trials */ @@ -316,6 +327,7 @@ int allocmatrix(EN_Project *pr) s->Aii = (double *) calloc(net->Nnodes+1,sizeof(double)); s->Aij = (double *) calloc(hyd->Ncoeffs+1,sizeof(double)); s->F = (double *) calloc(net->Nnodes+1,sizeof(double)); + hyd->DemandFlows = (double *)calloc(net->Nnodes + 1, sizeof(double)); hyd->EmitterFlows = (double *) calloc(net->Nnodes+1,sizeof(double)); s->P = (double *) calloc(net->Nlinks+1,sizeof(double)); s->Y = (double *) calloc(net->Nlinks+1,sizeof(double)); @@ -324,6 +336,7 @@ int allocmatrix(EN_Project *pr) ERRCODE(MEMCHECK(s->Aii)); ERRCODE(MEMCHECK(s->Aij)); ERRCODE(MEMCHECK(s->F)); + ERRCODE(MEMCHECK(hyd->DemandFlows)); ERRCODE(MEMCHECK(hyd->EmitterFlows)); ERRCODE(MEMCHECK(s->P)); ERRCODE(MEMCHECK(s->Y)); @@ -348,6 +361,7 @@ void freematrix(EN_Project *pr) free(s->Aii); free(s->Aij); free(s->F); + free(hyd->DemandFlows); free(hyd->EmitterFlows); free(s->P); free(s->Y); @@ -551,7 +565,6 @@ void setlinksetting(EN_Project *pr, int index, double value, StatType *s, doubl } - void demands(EN_Project *pr) /* **-------------------------------------------------------------------- @@ -591,6 +604,9 @@ void demands(EN_Project *pr) sum += djunc; } hyd->NodeDemand[i] = sum; + + // Initialize pressure dependent demand + hyd->DemandFlows[i] = sum; } /* Update head at fixed grade nodes with time patterns. */ diff --git a/src/hydsolver.c b/src/hydsolver.c index beeff1d..d08076a 100644 --- a/src/hydsolver.c +++ b/src/hydsolver.c @@ -22,7 +22,7 @@ The solver implements Todini's Global Gradient Algorithm. #include "types.h" #include "funcs.h" -// Error in network being hydraulically balanced +// Hydraulic balance error for network being analyzed typedef struct { double maxheaderror; double maxflowerror; @@ -33,22 +33,25 @@ typedef struct { } Hydbalance; // External functions -//int hydsolve(EN_Project *pr, int *iter, double *relerr); +//int hydsolve(EN_Project *pr, int *iter, double *relerr); +//void headlosscoeffs(EN_Project *pr); +//void matrixcoeffs(EN_Project *pr); + +extern int valvestatus(EN_Project *pr); //(see HYDSTATUS.C) +extern int linkstatus(EN_Project *pr); //(see HYDSTATUS.C) // Local functions static int badvalve(EN_Project *pr, int); -static int valvestatus(EN_Project *pr); -static int linkstatus(EN_Project *pr); -static StatType cvstatus(EN_Project *pr, StatType, double, double); -static StatType pumpstatus(EN_Project *pr, int, double); -static StatType prvstatus(EN_Project *pr, int, StatType, double, double, double); -static StatType psvstatus(EN_Project *pr, int, StatType, double, double, double); -static StatType fcvstatus(EN_Project *pr, int, StatType, double, double); -static void tankstatus(EN_Project *pr, int, int, int); static int pswitch(EN_Project *pr); static double newflows(EN_Project *pr, Hydbalance *hbal); -static double emitflowchange(EN_Project *pr, int i); +static void newlinkflows(EN_Project *pr, Hydbalance *hbal, double *qsum, + double *dqsum); +static void newemitterflows(EN_Project *pr, Hydbalance *hbal, double *qsum, + double *dqsum); +static void newdemandflows(EN_Project *pr, Hydbalance *hbal, double *qsum, + double *dqsum); + static void checkhydbalance(EN_Project *pr, Hydbalance *hbal); static int hasconverged(EN_Project *pr, double *relerr, Hydbalance *hbal); static void reporthydbal(EN_Project *pr, Hydbalance *hbal); @@ -75,122 +78,117 @@ int hydsolve(EN_Project *pr, int *iter, double *relerr) ** is > 0 then future computed flow changes are only 60% of ** their full value. A complete status check on all links ** is made when convergence is achieved. If convergence is -** not achieved in hyd->MaxIter trials and hyd->ExtraIter > 0 then -** another hyd->ExtraIter trials are made with no status changes +** not achieved in MaxIter trials and ExtraIter > 0 then +** another ExtraIter trials are made with no status changes ** made to any links and a warning message is generated. ** ** This procedure calls linsolve() which appears in SMATRIX.C. **------------------------------------------------------------------- */ { - int i; /* Node index */ - int errcode = 0; /* Node causing solution error */ - int nextcheck; /* Next status check trial */ - int maxtrials; /* Max. trials for convergence */ - double newerr; /* New convergence error */ - int valveChange; /* Valve status change flag */ - int statChange; /* Non-valve status change flag */ - Hydbalance hydbal; /* Hydraulic balance errors */ + int i; // Node index + int errcode = 0; // Node causing solution error + int nextcheck; // Next status check trial + int maxtrials; // Max. trials for convergence + double newerr; // New convergence error + int valveChange; // Valve status change flag + int statChange; // Non-valve status change flag + Hydbalance hydbal; // Hydraulic balance errors EN_Network *net = &pr->network; hydraulics_t *hyd = &pr->hydraulics; solver_t *sol = &hyd->solver; report_options_t *rep = &pr->report; - /* Initialize status checking & relaxation factor */ + // Initialize status checking & relaxation factor nextcheck = hyd->CheckFreq; hyd->RelaxFactor = 1.0; - /* Repeat iterations until convergence or trial limit is exceeded. */ - /* (hyd->ExtraIter used to increase trials in case of status cycling.) */ - if (pr->report.Statflag == FULL) { + // Repeat iterations until convergence or trial limit is exceeded. + // (ExtraIter used to increase trials in case of status cycling.) + if (pr->report.Statflag == FULL) + { writerelerr(pr, 0, 0); } maxtrials = hyd->MaxIter; - if (hyd->ExtraIter > 0) { + if (hyd->ExtraIter > 0) + { maxtrials += hyd->ExtraIter; } *iter = 1; - while (*iter <= maxtrials) { + while (*iter <= maxtrials) + { /* ** Compute coefficient matrices A & F and solve A*H = F ** where H = heads, A = Jacobian coeffs. derived from ** head loss gradients, & F = flow correction terms. ** Solution for H is returned in F from call to linsolve(). */ - for (i = 1; i <= net->Nlinks; i++) { - hlosscoeff(pr, i); - } + headlosscoeffs(pr); matrixcoeffs(pr); errcode = linsolve(pr, net->Njuncs); - /* Take action depending on error code */ - if (errcode < 0) { - break; /* Memory allocation problem */ - } - if (errcode > 0) { /* Ill-conditioning problem */ - /* If control valve causing problem, fix its status & continue, */ - /* otherwise end the iterations with no solution. */ - if (badvalve(pr, sol->Order[errcode])) { - continue; - } + // Quit if memory allocation error + if (errcode < 0) break; + + // Matrix ill-conditioning problem - if control valve causing problem, + // fix its status & continue, otherwise quit with no solution. + if (errcode > 0) + { + if (badvalve(pr, sol->Order[errcode])) continue; else break; } - /* Update current solution. */ - /* (Row[i] = row of solution matrix corresponding to node i). */ - for (i = 1; i <= net->Njuncs; i++) { - hyd->NodeHead[i] = sol->F[sol->Row[i]]; /* Update heads */ + // Update current solution. + // (Row[i] = row of solution matrix corresponding to node i) + for (i = 1; i <= net->Njuncs; i++) + { + hyd->NodeHead[i] = sol->F[sol->Row[i]]; // Update heads } - newerr = newflows(pr, &hydbal); /* Update flows */ + newerr = newflows(pr, &hydbal); // Update flows *relerr = newerr; - /* Write convergence error to status report if called for */ - if (rep->Statflag == FULL) { + // Write convergence error to status report if called for + if (rep->Statflag == FULL) + { writerelerr(pr, *iter, *relerr); } - /* Apply solution damping & check for change in valve status */ + // Apply solution damping & check for change in valve status hyd->RelaxFactor = 1.0; valveChange = FALSE; - if (hyd->DampLimit > 0.0) { - if (*relerr <= hyd->DampLimit) { + if (hyd->DampLimit > 0.0) + { + if (*relerr <= hyd->DampLimit) + { hyd->RelaxFactor = 0.6; valveChange = valvestatus(pr); } } - else { + else + { valveChange = valvestatus(pr); } - /* Check for convergence */ - if (hasconverged(pr, relerr, &hydbal)) { - /* We have convergence. Quit if we are into extra iterations. */ - if (*iter > hyd->MaxIter) { - break; - } + // Check for convergence + if (hasconverged(pr, relerr, &hydbal)) + { + // We have convergence - quit if we are into extra iterations + if (*iter > hyd->MaxIter) break; - /* Quit if no status changes occur. */ + // Quit if no status changes occur statChange = FALSE; - if (valveChange) { - statChange = TRUE; - } - if (linkstatus(pr)) { - statChange = TRUE; - } - if (pswitch(pr)) { - statChange = TRUE; - } - if (!statChange) { - break; - } + if (valveChange) statChange = TRUE; + if (linkstatus(pr)) statChange = TRUE; + if (pswitch(pr)) statChange = TRUE; + if (!statChange) break; - /* We have a status change so continue the iterations */ + // We have a status change so continue the iterations nextcheck = *iter + hyd->CheckFreq; } - /* No convergence yet. See if its time for a periodic status */ - /* check on pumps, CV's, and pipes connected to tanks. */ + // No convergence yet - see if its time for a periodic status + // check on pumps, CV's, and pipes connected to tank else if (*iter <= hyd->MaxCheck && *iter == nextcheck) { linkstatus(pr); @@ -199,20 +197,28 @@ int hydsolve(EN_Project *pr, int *iter, double *relerr) (*iter)++; } - /* Iterations ended. Report any errors. */ - if (errcode < 0) errcode = 101; /* Memory allocation error */ + // Iterations ended - report any errors. + if (errcode < 0) errcode = 101; // Memory allocation error else if (errcode > 0) { - writehyderr(pr, sol->Order[errcode]); /* Ill-conditioned eqns. error */ + writehyderr(pr, sol->Order[errcode]); // Ill-conditioned matrix error errcode = 110; } - /* Add any emitter flows to junction demands */ - for (i = 1; i <= net->Njuncs; i++) { - hyd->NodeDemand[i] += hyd->EmitterFlows[i]; + // Replace junction demands with total outflow values + for (i = 1; i <= net->Njuncs; i++) + { + hyd->NodeDemand[i] = hyd->DemandFlows[i] + hyd->EmitterFlows[i]; } + + // Save convergence info + hyd->RelativeError = *relerr; + hyd->MaxHeadError = hydbal.maxheaderror; + hyd->MaxFlowChange = hydbal.maxflowchange; + hyd->Iterations = *iter; + return(errcode); -} /* End of netsolve */ +} int badvalve(EN_Project *pr, int n) @@ -235,513 +241,44 @@ int badvalve(EN_Project *pr, int n) report_options_t *rep = &pr->report; time_options_t *time = &pr->time_options; Slink *link; + EN_LinkType t; - for (i = 1; i <= net->Nvalves; i++) { + for (i = 1; i <= net->Nvalves; i++) + { k = net->Valve[i].Link; link = &net->Link[k]; n1 = link->N1; n2 = link->N2; - if (n == n1 || n == n2) { - EN_LinkType t = link->Type; - if (t == EN_PRV || t == EN_PSV || t == EN_FCV) { - if (hyd->LinkStatus[k] == ACTIVE) { - if (rep->Statflag == FULL) { + if (n == n1 || n == n2) + { + t = link->Type; + if (t == EN_PRV || t == EN_PSV || t == EN_FCV) + { + if (hyd->LinkStatus[k] == ACTIVE) + { + if (rep->Statflag == FULL) + { sprintf(pr->Msg, FMT61, clocktime(rep->Atime, time->Htime), link->ID); writeline(pr, pr->Msg); } - if (link->Type == EN_FCV) { + if (link->Type == EN_FCV) + { hyd->LinkStatus[k] = XFCV; } - else { + else + { hyd->LinkStatus[k] = XPRESSURE; } - return(1); + return 1; } } - return(0); + return 0; } } - return(0); + return 0; } -int valvestatus(EN_Project *pr) -/* -**----------------------------------------------------------------- -** Input: none -** Output: returns 1 if any pressure or flow control valve -** changes status, 0 otherwise -** Purpose: updates status for PRVs & PSVs whose status -** is not fixed to OPEN/CLOSED -**----------------------------------------------------------------- -*/ -{ - int change = FALSE, /* Status change flag */ - i, k, /* Valve & link indexes */ - n1, n2; /* Start & end nodes */ - StatType status; /* Valve status settings */ - double hset; /* Valve head setting */ - Slink *link; - - EN_Network *net = &pr->network; - hydraulics_t *hyd = &pr->hydraulics; - report_options_t *rep = &pr->report; - - for (i = 1; i <= net->Nvalves; i++) /* Examine each valve */ - { - k = net->Valve[i].Link; /* Link index of valve */ - link = &net->Link[k]; - if (hyd->LinkSetting[k] == MISSING) { - continue; /* Valve status fixed */ - } - n1 = link->N1; /* Start & end nodes */ - n2 = link->N2; - status = hyd->LinkStatus[k]; /* Save current status */ - -// if (s != CLOSED /* No change if flow is */ -// && ABS(hyd->LinkFlows[k]) < hyd->Qtol) continue; /* negligible. */ - - switch (link->Type) /* Evaluate new status: */ - { - case EN_PRV: - hset = net->Node[n2].El + hyd->LinkSetting[k]; - hyd->LinkStatus[k] = prvstatus(pr, k, status, hset, hyd->NodeHead[n1], hyd->NodeHead[n2]); - break; - case EN_PSV: - hset = net->Node[n1].El + hyd->LinkSetting[k]; - hyd->LinkStatus[k] = psvstatus(pr, k, status, hset, hyd->NodeHead[n1], hyd->NodeHead[n2]); - break; - -//// FCV status checks moved back into the linkstatus() function //// -// case FCV: S[k] = fcvstatus(k,s,hyd->NodeHead[n1],hyd->NodeHead[n2]); -// break; - - default: - continue; - } - - /*** Updated 9/7/00 ***/ - /* Do not reset flow in valve if its status changes. */ - /* This strategy improves convergence. */ - - /* Check for status change */ - if (status != hyd->LinkStatus[k]) - { - if (rep->Statflag == FULL) { - writestatchange(pr, k, status, hyd->LinkStatus[k]); - } - change = TRUE; - } - } - return(change); -} /* End of valvestatus() */ - - -/*** Updated 9/7/00 ***/ -int linkstatus(EN_Project *pr) -/* -**-------------------------------------------------------------- -** Input: none -** Output: returns 1 if any link changes status, 0 otherwise -** Purpose: determines new status for pumps, CVs, FCVs & pipes -** to tanks. -**-------------------------------------------------------------- -*/ -{ - int change = FALSE, /* Status change flag */ - k, /* Link index */ - n1, /* Start node index */ - n2; /* End node index */ - double dh; /* Head difference */ - StatType status; /* Current status */ - - EN_Network *net = &pr->network; - hydraulics_t *hyd = &pr->hydraulics; - report_options_t *rep = &pr->report; - Slink *link; - - /* Examine each Slink */ - for (k = 1; k <= net->Nlinks; k++) - { - link = &net->Link[k]; - n1 = link->N1; - n2 = link->N2; - dh = hyd->NodeHead[n1] - hyd->NodeHead[n2]; - - /* Re-open temporarily closed links (status = XHEAD or TEMPCLOSED) */ - status = hyd->LinkStatus[k]; - if (status == XHEAD || status == TEMPCLOSED) { - hyd->LinkStatus[k] = OPEN; - } - - /* Check for status changes in CVs and pumps */ - if (link->Type == EN_CVPIPE) { - hyd->LinkStatus[k] = cvstatus(pr, hyd->LinkStatus[k], dh, hyd->LinkFlows[k]); - } - if (link->Type == EN_PUMP && hyd->LinkStatus[k] >= OPEN && hyd->LinkSetting[k] > 0.0) { - hyd->LinkStatus[k] = pumpstatus(pr, k, -dh); - } - - /* Check for status changes in non-fixed FCVs */ - if (link->Type == EN_FCV && hyd->LinkSetting[k] != MISSING) { - hyd->LinkStatus[k] = fcvstatus(pr, k, status, hyd->NodeHead[n1], hyd->NodeHead[n2]); // - } - - /* Check for flow into (out of) full (empty) tanks */ - if (n1 > net->Njuncs || n2 > net->Njuncs) { - tankstatus(pr, k, n1, n2); - } - - /* Note change in link status; do not revise link flow */ - if (status != hyd->LinkStatus[k]) { - change = TRUE; - if (rep->Statflag == FULL) { - writestatchange(pr, k, status, hyd->LinkStatus[k]); - } - - //if (S[k] <= CLOSED) hyd->LinkFlows[k] = QZERO; - //else setlinkflow(k, dh); - } - } - return(change); -} /* End of linkstatus */ - - -StatType cvstatus(EN_Project *pr, StatType s, double dh, double q) -/* -**-------------------------------------------------- -** Input: s = current status -** dh = headloss -** q = flow -** Output: returns new link status -** Purpose: updates status of a check valve. -**-------------------------------------------------- -*/ -{ - hydraulics_t *hyd = &pr->hydraulics; - - /* Prevent reverse flow through CVs */ - if (ABS(dh) > hyd->Htol) - { - if (dh < -hyd->Htol) { - return(CLOSED); - } - else if (q < -hyd->Qtol) { - return(CLOSED); - } - else { - return(OPEN); - } - } - else - { - if (q < -hyd->Qtol) { - return(CLOSED); - } - else { - return(s); - } - } -} - - -StatType pumpstatus(EN_Project *pr, int k, double dh) -/* -**-------------------------------------------------- -** Input: k = link index -** dh = head gain -** Output: returns new pump status -** Purpose: updates status of an open pump. -**-------------------------------------------------- -*/ -{ - int p; - double hmax; - hydraulics_t *hyd = &pr->hydraulics; - EN_Network *net = &pr->network; - - /* Prevent reverse flow through pump */ - p = findpump(net, k); - if (net->Pump[p].Ptype == CONST_HP) { - hmax = BIG; - } - else { - hmax = SQR(hyd->LinkSetting[k]) * net->Pump[p].Hmax; - } - if (dh > hmax + hyd->Htol) { - return(XHEAD); - } - - /*** Flow higher than pump curve no longer results in a status change ***/ - /* Check if pump cannot deliver flow */ - //if (hyd->LinkFlows[k] > K[k]*Pump[p].Qmax + hyd->Qtol) return(XFLOW); - return(OPEN); -} - - -StatType prvstatus(EN_Project *pr, int k, StatType s, double hset, double h1, double h2) -/* -**----------------------------------------------------------- -** Input: k = link index -** s = current status -** hset = valve head setting -** h1 = head at upstream node -** h2 = head at downstream node -** Output: returns new valve status -** Purpose: updates status of a pressure reducing valve. -**----------------------------------------------------------- -*/ -{ - StatType status; /* New valve status */ - double hml; /* Minor headloss */ - hydraulics_t *hyd = &pr->hydraulics; - - double htol = hyd->Htol; - Slink *link = &pr->network.Link[k]; - - status = s; - if (hyd->LinkSetting[k] == MISSING) - return(status); /* Status fixed by user */ - hml = link->Km*SQR(hyd->LinkFlows[k]); /* Head loss when open */ - - /*** Status rules below have changed. ***/ - - switch (s) - { - case ACTIVE: - if (hyd->LinkFlows[k] < -hyd->Qtol) { - status = CLOSED; - } - else if (h1 - hml < hset - htol) { - status = OPEN; - } - else { - status = ACTIVE; - } - break; - case OPEN: - if (hyd->LinkFlows[k] < -hyd->Qtol) { - status = CLOSED; - } - else if (h2 >= hset + htol) { - status = ACTIVE; - } - else { - status = OPEN; - } - break; - case CLOSED: - if (h1 >= hset + htol && h2 < hset - htol) { - status = ACTIVE; - } - else if (h1 < hset - htol && h1 > h2 + htol) { - status = OPEN; - } - else { - status = CLOSED; - } - break; - case XPRESSURE: - if (hyd->LinkFlows[k] < -hyd->Qtol) { - status = CLOSED; - } - break; - default: - break; - } - return(status); -} - - -StatType psvstatus(EN_Project *pr, int k, StatType s, double hset, double h1, double h2) -/* -**----------------------------------------------------------- -** Input: k = link index -** s = current status -** hset = valve head setting -** h1 = head at upstream node -** h2 = head at downstream node -** Output: returns new valve status -** Purpose: updates status of a pressure sustaining valve. -**----------------------------------------------------------- -*/ -{ - StatType status; /* New valve status */ - double hml; /* Minor headloss */ - hydraulics_t *hyd = &pr->hydraulics; - - double htol = hyd->Htol; - - Slink *link = &pr->network.Link[k]; - - status = s; - if (hyd->LinkSetting[k] == MISSING) { - return(status); /* Status fixed by user */ - } - hml = link->Km*SQR(hyd->LinkFlows[k]); /* Head loss when open */ - -/*** Status rules below have changed. ***/ - - switch (s) - { - case ACTIVE: - if (hyd->LinkFlows[k] < -hyd->Qtol) { - status = CLOSED; - } - else if (h2 + hml > hset + htol) { - status = OPEN; - } - else { - status = ACTIVE; - } - break; - case OPEN: - if (hyd->LinkFlows[k] < -hyd->Qtol) { - status = CLOSED; - } - else if (h1 < hset - htol) { - status = ACTIVE; - } - else { - status = OPEN; - } - break; - case CLOSED: - if (h2 > hset + htol && h1 > h2 + htol) { - status = OPEN; - } - else if (h1 >= hset + htol && h1 > h2 + htol) { - status = ACTIVE; - } - else { - status = CLOSED; - } - break; - case XPRESSURE: - if (hyd->LinkFlows[k] < -hyd->Qtol) { - status = CLOSED; - } - break; - default: - break; - } - return(status); -} - - -StatType fcvstatus(EN_Project *pr, int k, StatType s, double h1, double h2) -/* -**----------------------------------------------------------- -** Input: k = link index -** s = current status -** h1 = head at upstream node -** h2 = head at downstream node -** Output: returns new valve status -** Purpose: updates status of a flow control valve. -** -** Valve status changes to XFCV if flow reversal. -** If current status is XFCV and current flow is -** above setting, then valve becomes active. -** If current status is XFCV, and current flow -** positive but still below valve setting, then -** status remains same. -**----------------------------------------------------------- -*/ -{ - StatType status; /* New valve status */ - hydraulics_t *hyd = &pr->hydraulics; - - status = s; - if (h1 - h2 < -hyd->Htol) { - status = XFCV; - } - else if (hyd->LinkFlows[k] < -hyd->Qtol) { - status = XFCV; - } - else if (s == XFCV && hyd->LinkFlows[k] >= hyd->LinkSetting[k]) { - status = ACTIVE; - } - return(status); -} - - -/*** Updated 9/7/00 ***/ -/*** Updated 11/19/01 ***/ -void tankstatus(EN_Project *pr, int k, int n1, int n2) -/* -**---------------------------------------------------------------- -** Input: k = link index -** n1 = start node of link -** n2 = end node of link -** Output: none -** Purpose: closes link flowing into full or out of empty tank -**---------------------------------------------------------------- -*/ -{ - int i, n; - double h, q; - Stank *tank; - - hydraulics_t *hyd = &pr->hydraulics; - EN_Network *net = &pr->network; - Slink *link = &net->Link[k]; - - /* Make node n1 be the tank */ - q = hyd->LinkFlows[k]; - i = n1 - net->Njuncs; - if (i <= 0) { - i = n2 - net->Njuncs; - if (i <= 0) { - return; - } - n = n1; - n1 = n2; - n2 = n; - q = -q; - } - h = hyd->NodeHead[n1] - hyd->NodeHead[n2]; - tank = &net->Tank[i]; - /* Skip reservoirs & closed links */ - if (tank->A == 0.0 || hyd->LinkStatus[k] <= CLOSED) { - return; - } - - /* If tank full, then prevent flow into it */ - if (hyd->NodeHead[n1] >= tank->Hmax - hyd->Htol) - { - - /* Case 1: Link is a pump discharging into tank */ - if (link->Type == EN_PUMP) { - if (link->N2 == n1) - hyd->LinkStatus[k] = TEMPCLOSED; - } - - /* Case 2: Downstream head > tank head */ - /* (i.e., an open outflow check valve would close) */ - else if (cvstatus(pr, OPEN, h, q) == CLOSED) { - hyd->LinkStatus[k] = TEMPCLOSED; - } - } - - /* If tank empty, then prevent flow out of it */ - if (hyd->NodeHead[n1] <= tank->Hmin + hyd->Htol) { - - /* Case 1: Link is a pump discharging from tank */ - if (link->Type == EN_PUMP) { - if (link->N1 == n1) { - hyd->LinkStatus[k] = TEMPCLOSED; - } - } - - /* Case 2: Tank head > downstream head */ - /* (i.e., a closed outflow check valve would open) */ - else if (cvstatus(pr, CLOSED, h, q) == OPEN) { - hyd->LinkStatus[k] = TEMPCLOSED; - } - } -} /* End of tankstatus */ - - int pswitch(EN_Project *pr) /* **-------------------------------------------------------------- @@ -752,98 +289,133 @@ int pswitch(EN_Project *pr) **-------------------------------------------------------------- */ { - int i, /* Control statement index */ - k, /* Link being controlled */ - n, /* Node controlling Slink */ - reset, /* Flag on control conditions */ - change, /* Flag for status or setting change */ - anychange = 0; /* Flag for 1 or more changes */ - char s; /* Current link status */ + int i, // Control statement index + k, // Index of link being controlled + n, // Node controlling link k + reset, // Flag on control conditions + change, // Flag for status or setting change + anychange = 0; // Flag for 1 or more control actions + char s; // Current link status EN_Network *net = &pr->network; hydraulics_t *hyd = &pr->hydraulics; report_options_t *rep = &pr->report; Slink *link; - /* Check each control statement */ + // Check each control statement for (i = 1; i <= net->Ncontrols; i++) { reset = 0; - if ((k = net->Control[i].Link) <= 0) { - continue; - } + k = net->Control[i].Link; + if (k <= 0) continue; - /* Determine if control based on a junction, not a tank */ - if ((n = net->Control[i].Node) > 0 && n <= net->Njuncs) { - /* Determine if control conditions are satisfied */ - if (net->Control[i].Type == LOWLEVEL - && hyd->NodeHead[n] <= net->Control[i].Grade + hyd->Htol) { + // Determine if control based on a junction, not a tank + n = net->Control[i].Node; + if (n > 0 && n <= net->Njuncs) + { + // Determine if control conditions are satisfied + if (net->Control[i].Type == LOWLEVEL && + hyd->NodeHead[n] <= net->Control[i].Grade + hyd->Htol) + { reset = 1; } - if (net->Control[i].Type == HILEVEL - && hyd->NodeHead[n] >= net->Control[i].Grade - hyd->Htol) { + if (net->Control[i].Type == HILEVEL && + hyd->NodeHead[n] >= net->Control[i].Grade - hyd->Htol) + { reset = 1; } } - /* Determine if control forces a status or setting change */ + // Determine if control forces a status or setting change if (reset == 1) { link = &net->Link[k]; change = 0; s = hyd->LinkStatus[k]; - if (link->Type == EN_PIPE) { - if (s != net->Control[i].Status) { - change = 1; - } + if (link->Type == EN_PIPE) + { + if (s != net->Control[i].Status) change = 1; } - if (link->Type == EN_PUMP) { - if (hyd->LinkSetting[k] != net->Control[i].Setting) { - change = 1; - } + if (link->Type == EN_PUMP) + { + if (hyd->LinkSetting[k] != net->Control[i].Setting) change = 1; } - if (link->Type >= EN_PRV) { - if (hyd->LinkSetting[k] != net->Control[i].Setting) { - change = 1; - } - else if (hyd->LinkSetting[k] == MISSING && s != net->Control[i].Status) { + if (link->Type >= EN_PRV) + { + if (hyd->LinkSetting[k] != net->Control[i].Setting) change = 1; + else if (hyd->LinkSetting[k] == MISSING && s != net->Control[i].Status) + { change = 1; } } - /* If a change occurs, update status & setting */ - if (change) { + // If a change occurs, update status & setting + if (change) + { hyd->LinkStatus[k] = net->Control[i].Status; - if (link->Type > EN_PIPE) { + if (link->Type > EN_PIPE) + { hyd->LinkSetting[k] = net->Control[i].Setting; } - if (rep->Statflag == FULL) { + if (rep->Statflag == FULL) + { writestatchange(pr, k, s, hyd->LinkStatus[k]); } - - /* Re-set flow if status has changed */ - // if (S[k] != s) initlinkflow(k, S[k], K[k]); anychange = 1; } } } return(anychange); -} /* End of pswitch */ +} double newflows(EN_Project *pr, Hydbalance *hbal) /* **---------------------------------------------------------------- -** Input: none +** Input: hbal = ptr. to hydraulic balance information ** Output: returns solution convergence error +** Purpose: updates link, emitter & demand flows after new +** nodal heads are computed. +**---------------------------------------------------------------- +*/ +{ + double dqsum, // Network flow change + qsum; // Network total flow + + EN_Network *net = &pr->network; + hydraulics_t *hyd = &pr->hydraulics; + + // Initialize sum of flows & corrections + qsum = 0.0; + dqsum = 0.0; + hbal->maxflowchange = 0.0; + hbal->maxflowlink = 1; + hbal->maxflownode = -1; + + // Update flows in all real and virtual links + newlinkflows(pr, hbal, &qsum, &dqsum); + newemitterflows(pr, hbal, &qsum, &dqsum); + newdemandflows(pr, hbal, &qsum, &dqsum); + + // Return ratio of total flow corrections to total flow + if (qsum > hyd->Hacc) return (dqsum / qsum); + else return dqsum; +} + + +void newlinkflows(EN_Project *pr, Hydbalance *hbal, double *qsum, double *dqsum) +/* +**---------------------------------------------------------------- +** Input: hbal = ptr. to hydraulic balance information +** qsum = sum of current system flows +** dqsum = sum of system flow changes +** Output: updates hbal, qsum and dqsum ** Purpose: updates link flows after new nodal heads computed **---------------------------------------------------------------- */ { double dh, /* Link head loss */ dq; /* Link flow change */ - double dqsum, /* Network flow change */ - qsum; /* Network total flow */ int k, n, n1, n2; EN_Network *net = &pr->network; @@ -851,114 +423,152 @@ double newflows(EN_Project *pr, Hydbalance *hbal) solver_t *sol = &hyd->solver; Slink *link; - /* Initialize net inflows (i.e., demands) at tanks */ - for (n = net->Njuncs + 1; n <= net->Nnodes; n++) { + // Initialize net inflows (i.e., demands) at fixed grade nodes + for (n = net->Njuncs + 1; n <= net->Nnodes; n++) + { hyd->NodeDemand[n] = 0.0; } - /* Initialize sum of flows & corrections */ - qsum = 0.0; - dqsum = 0.0; - - hbal->maxflowchange = 0.0; - hbal->maxflowlink = 1; - - /* Update flows in all links */ + // Examine each link for (k = 1; k <= net->Nlinks; k++) { + // Get link and its end nodes link = &net->Link[k]; - /* - ** Apply flow update formula: - ** dq = Y - P*(new head loss) - ** P = 1/(dh/dq) - ** Y = P*(head loss based on current flow) - ** where P & Y were computed in newcoeffs(). - */ - n1 = link->N1; n2 = link->N2; + + // Apply flow update formula: + // dq = Y - P * (new head loss) + // P = 1 / (previous head loss gradient) + // Y = P * (previous head loss) + // where P & Y were computed in hlosscoeff() in hydcoeffs.c + dh = hyd->NodeHead[n1] - hyd->NodeHead[n2]; dq = sol->Y[k] - sol->P[k] * dh; - /* Adjust flow change by the relaxation factor */ + // Adjust flow change by the relaxation factor dq *= hyd->RelaxFactor; - /* Prevent flow in constant HP pumps from going negative */ - if (link->Type == EN_PUMP) { + // Prevent flow in constant HP pumps from going negative + if (link->Type == EN_PUMP) + { n = findpump(net, k); - if (net->Pump[n].Ptype == CONST_HP && dq > hyd->LinkFlows[k]) { + if (net->Pump[n].Ptype == CONST_HP && dq > hyd->LinkFlows[k]) + { dq = hyd->LinkFlows[k] / 2.0; } } + + // Update link flow and system flow summation hyd->LinkFlows[k] -= dq; + *qsum += ABS(hyd->LinkFlows[k]); + *dqsum += ABS(dq); - /* Update sum of absolute flows & flow corrections */ - qsum += ABS(hyd->LinkFlows[k]); - dqsum += ABS(dq); - - /* Update identity of link with max. flow change */ - if (ABS(dq) > hbal->maxflowchange) { + // Update identity of element with max. flow change + if (ABS(dq) > hbal->maxflowchange) + { hbal->maxflowchange = ABS(dq); hbal->maxflowlink = k; - } + hbal->maxflownode = -1; + } - /* Update net flows to tanks */ + // Update net flows to fixed grade nodes if (hyd->LinkStatus[k] > CLOSED) { - if (n1 > net->Njuncs) { - hyd->NodeDemand[n1] -= hyd->LinkFlows[k]; - } - if (n2 > net->Njuncs) { - hyd->NodeDemand[n2] += hyd->LinkFlows[k]; - } + if (n1 > net->Njuncs) hyd->NodeDemand[n1] -= hyd->LinkFlows[k]; + if (n2 > net->Njuncs) hyd->NodeDemand[n2] += hyd->LinkFlows[k]; } } - - /* Update emitter flows */ - for (k = 1; k <= net->Njuncs; k++) - { - if (net->Node[k].Ke == 0.0) { - continue; - } - dq = emitflowchange(pr, k); - hyd->EmitterFlows[k] -= dq; - qsum += ABS(hyd->EmitterFlows[k]); - dqsum += ABS(dq); - } - - /* Return ratio of total flow corrections to total flow */ - if (qsum > hyd->Hacc) { - return(dqsum / qsum); - } - else { - return(dqsum); - } -} /* End of newflows */ +} -double emitflowchange(EN_Project *pr, int i) +void newemitterflows(EN_Project *pr, Hydbalance *hbal, double *qsum, + double *dqsum) /* -**-------------------------------------------------------------- -** Input: i = node index -** Output: returns change in flow at an emitter node -** Purpose: computes flow change at an emitter node -**-------------------------------------------------------------- +**---------------------------------------------------------------- +** Input: hbal = ptr. to hydraulic balance information +** qsum = sum of current system flows +** dqsum = sum of system flow changes +** Output: updates hbal, qsum and dqsum +** Purpose: updates nodal emitter flows after new nodal heads computed +**---------------------------------------------------------------- */ { - double ke, p; + double dq; + int k; + EN_Network *net = &pr->network; hydraulics_t *hyd = &pr->hydraulics; - EN_Network *n = &pr->network; - Snode *node = &n->Node[i]; - ke = MAX(CSMALL, node->Ke); - p = hyd->Qexp * ke * pow(ABS(hyd->EmitterFlows[i]), (hyd->Qexp - 1.0)); - if (p < hyd->RQtol) { - p = 1 / hyd->RQtol; + // Examine each network junction + for (k = 1; k <= net->Njuncs; k++) + { + // Skip junction if it does not have an emitter + if (net->Node[k].Ke == 0.0) continue; + + // Find emitter flow change (see hydcoeffs.c) + dq = emitflowchange(pr, k); + hyd->EmitterFlows[k] -= dq; + + // Update system flow summation + *qsum += ABS(hyd->EmitterFlows[k]); + *dqsum += ABS(dq); + + // Update identity of element with max. flow change + if (ABS(dq) > hbal->maxflowchange) + { + hbal->maxflowchange = ABS(dq); + hbal->maxflownode = k; + hbal->maxflowlink = -1; + } } - else { - p = 1.0 / p; +} + + +void newdemandflows(EN_Project *pr, Hydbalance *hbal, double *qsum, double *dqsum) +/* +**---------------------------------------------------------------- +** Input: hbal = ptr. to hydraulic balance information +** qsum = sum of current system flows +** dqsum = sum of system flow changes +** Output: updates hbal, qsum and dqsum +** Purpose: updates nodal pressure dependent demand flows after +** new nodal heads computed +**---------------------------------------------------------------- +*/ +{ + double dp, // pressure range over which demand can vary (ft) + dq, // change in demand flow (cfs) + n; // exponent in head loss v. demand function + int k; + EN_Network *net = &pr->network; + hydraulics_t *hyd = &pr->hydraulics; + + // Get demand function parameters + if (hyd->DemandModel == DDA) return; + demandparams(pr, &dp, &n); + + // Examine each junction + for (k = 1; k <= net->Njuncs; k++) + { + // Skip junctions with no positive demand + if (hyd->NodeDemand[k] <= 0.0) continue; + + // Find change in demand flow (see hydcoeffs.c) + dq = demandflowchange(pr, k, dp, n); + hyd->DemandFlows[k] -= dq; + + // Update system flow summation + *qsum += ABS(hyd->DemandFlows[k]); + *dqsum += ABS(dq); + + // Update identity of element with max. flow change + if (ABS(dq) > hbal->maxflowchange) + { + hbal->maxflowchange = ABS(dq); + hbal->maxflownode = k; + hbal->maxflowlink = -1; + } } - return(hyd->EmitterFlows[i] / hyd->Qexp - p * (hyd->NodeHead[i] - node->El)); } @@ -980,9 +590,10 @@ void checkhydbalance(EN_Project *pr, Hydbalance *hbal) Slink *link; hbal->maxheaderror = 0.0; hbal->maxheadlink = 1; - for (k = 1; k <= net->Nlinks; k++) { + headlosscoeffs(pr); + for (k = 1; k <= net->Nlinks; k++) + { if (hyd->LinkStatus[k] <= CLOSED) continue; - hlosscoeff(pr, k); if (sol->P[k] == 0.0) continue; link = &net->Link[k]; n1 = link->N1; @@ -990,7 +601,8 @@ void checkhydbalance(EN_Project *pr, Hydbalance *hbal) dh = hyd->NodeHead[n1] - hyd->NodeHead[n2]; headloss = sol->Y[k] / sol->P[k]; headerror = ABS(dh - headloss); - if (headerror > hbal->maxheaderror) { + if (headerror > hbal->maxheaderror) + { hbal->maxheaderror = headerror; hbal->maxheadlink = k; } @@ -1013,13 +625,14 @@ int hasconverged(EN_Project *pr, double *relerr, Hydbalance *hbal) if (*relerr > hyd->Hacc) return 0; checkhydbalance(pr, hbal); - if (pr->report.Statflag == FULL) { + if (pr->report.Statflag == FULL) + { reporthydbal(pr, hbal); } if (hyd->HeadErrorLimit > 0.0 && - hbal->maxheaderror > hyd->HeadErrorLimit) return 0; + hbal->maxheaderror > hyd->HeadErrorLimit) return 0; if (hyd->FlowChangeLimit > 0.0 && - hbal->maxflowchange > hyd->FlowChangeLimit) return 0; + hbal->maxflowchange > hyd->FlowChangeLimit) return 0; return 1; } @@ -1038,11 +651,13 @@ void reporthydbal(EN_Project *pr, Hydbalance *hbal) double herror = hbal->maxheaderror * pr->Ucf[HEAD]; int qlink = hbal->maxflowlink; int hlink = hbal->maxheadlink; - if (qlink >= 1) { + if (qlink >= 1) + { sprintf(pr->Msg, FMT66, qchange, pr->network.Link[qlink].ID); writeline(pr, pr->Msg); } - if (hlink >= 1) { + if (hlink >= 1) + { sprintf(pr->Msg, FMT67, herror, pr->network.Link[hlink].ID); writeline(pr, pr->Msg); } diff --git a/src/hydstatus.c b/src/hydstatus.c new file mode 100644 index 0000000..f5041c6 --- /dev/null +++ b/src/hydstatus.c @@ -0,0 +1,462 @@ +/* +********************************************************************* + +HYDSTATUS.C -- Hydraulic status updating for the EPANET Program + +******************************************************************* +*/ + +#include +#include "types.h" +#include "funcs.h" + +// External functions +int valvestatus(EN_Project *pr); +int linkstatus(EN_Project *pr); + +// Local functions +static StatType cvstatus(EN_Project *pr, StatType, double, double); +static StatType pumpstatus(EN_Project *pr, int, double); +static StatType prvstatus(EN_Project *pr, int, StatType, double, double, double); +static StatType psvstatus(EN_Project *pr, int, StatType, double, double, double); +static StatType fcvstatus(EN_Project *pr, int, StatType, double, double); +static void tankstatus(EN_Project *pr, int, int, int); + + +int valvestatus(EN_Project *pr) +/* +**----------------------------------------------------------------- +** Input: none +** Output: returns 1 if any pressure or flow control valve +** changes status, 0 otherwise +** Purpose: updates status for PRVs & PSVs whose status +** is not fixed to OPEN/CLOSED +**----------------------------------------------------------------- +*/ +{ + int change = FALSE, // Status change flag + i, k, // Valve & link indexes + n1, n2; // Start & end nodes + StatType status; // Valve status settings + double hset; // Valve head setting + Slink *link; + + EN_Network *net = &pr->network; + hydraulics_t *hyd = &pr->hydraulics; + report_options_t *rep = &pr->report; + + // Examine each valve + for (i = 1; i <= net->Nvalves; i++) + { + // Get valve's link and its index + k = net->Valve[i].Link; + link = &net->Link[k]; + + // Ignore valve if its status is fixed to OPEN/CLOSED + if (hyd->LinkSetting[k] == MISSING) continue; + + // Get start/end node indexes & save current status + n1 = link->N1; + n2 = link->N2; + status = hyd->LinkStatus[k]; + + // Evaluate valve's new status + switch (link->Type) + { + case EN_PRV: + hset = net->Node[n2].El + hyd->LinkSetting[k]; + hyd->LinkStatus[k] = prvstatus(pr, k, status, hset, + hyd->NodeHead[n1], hyd->NodeHead[n2]); + break; + case EN_PSV: + hset = net->Node[n1].El + hyd->LinkSetting[k]; + hyd->LinkStatus[k] = psvstatus(pr, k, status, hset, + hyd->NodeHead[n1], hyd->NodeHead[n2]); + break; + default: + continue; + } + + // Check for a status change + if (status != hyd->LinkStatus[k]) + { + if (rep->Statflag == FULL) + { + writestatchange(pr, k, status, hyd->LinkStatus[k]); + } + change = TRUE; + } + } + return change; +} /* End of valvestatus() */ + + +int linkstatus(EN_Project *pr) +/* +**-------------------------------------------------------------- +** Input: none +** Output: returns 1 if any link changes status, 0 otherwise +** Purpose: determines new status for pumps, CVs, FCVs & pipes +** to tanks. +**-------------------------------------------------------------- +*/ +{ + int change = FALSE, // Status change flag + k, // Link index + n1, // Start node index + n2; // End node index + double dh; // Head difference across link + StatType status; // Current status + + EN_Network *net = &pr->network; + hydraulics_t *hyd = &pr->hydraulics; + report_options_t *rep = &pr->report; + Slink *link; + + // Examine each link + for (k = 1; k <= net->Nlinks; k++) + { + link = &net->Link[k]; + n1 = link->N1; + n2 = link->N2; + dh = hyd->NodeHead[n1] - hyd->NodeHead[n2]; + + // Re-open temporarily closed links (status = XHEAD or TEMPCLOSED) + status = hyd->LinkStatus[k]; + if (status == XHEAD || status == TEMPCLOSED) + { + hyd->LinkStatus[k] = OPEN; + } + + // Check for status changes in CVs and pumps + if (link->Type == EN_CVPIPE) + { + hyd->LinkStatus[k] = cvstatus(pr, hyd->LinkStatus[k], dh, + hyd->LinkFlows[k]); + } + if (link->Type == EN_PUMP && hyd->LinkStatus[k] >= OPEN && + hyd->LinkSetting[k] > 0.0) + { + hyd->LinkStatus[k] = pumpstatus(pr, k, -dh); + } + + // Check for status changes in non-fixed FCVs + if (link->Type == EN_FCV && hyd->LinkSetting[k] != MISSING) + { + hyd->LinkStatus[k] = fcvstatus(pr, k, status, hyd->NodeHead[n1], + hyd->NodeHead[n2]); + } + + // Check for flow into (out of) full (empty) tanks + if (n1 > net->Njuncs || n2 > net->Njuncs) + { + tankstatus(pr, k, n1, n2); + } + + // Note any change in link status; do not revise link flow + if (status != hyd->LinkStatus[k]) + { + change = TRUE; + if (rep->Statflag == FULL) + { + writestatchange(pr, k, status, hyd->LinkStatus[k]); + } + } + } + return change; +} + + +StatType cvstatus(EN_Project *pr, StatType s, double dh, double q) +/* +**-------------------------------------------------- +** Input: s = current link status +** dh = head loss across link +** q = link flow +** Output: returns new link status +** Purpose: updates status of a check valve link. +**-------------------------------------------------- +*/ +{ + hydraulics_t *hyd = &pr->hydraulics; + + // Prevent reverse flow through CVs + if (ABS(dh) > hyd->Htol) + { + if (dh < -hyd->Htol) return CLOSED; + else if (q < -hyd->Qtol) return CLOSED; + else return OPEN; + } + else + { + if (q < -hyd->Qtol) return CLOSED; + else return s; + } +} + + +StatType pumpstatus(EN_Project *pr, int k, double dh) +/* +**-------------------------------------------------- +** Input: k = link index +** dh = head gain across link +** Output: returns new pump status +** Purpose: updates status of an open pump. +**-------------------------------------------------- +*/ +{ + int p; + double hmax; + hydraulics_t *hyd = &pr->hydraulics; + EN_Network *net = &pr->network; + + // Find maximum head (hmax) pump can deliver + p = findpump(net, k); + if (net->Pump[p].Ptype == CONST_HP) + { + // Use huge value for constant HP pump + hmax = BIG; + } + else + { + // Use speed-adjusted shut-off head for other pumps + hmax = SQR(hyd->LinkSetting[k]) * net->Pump[p].Hmax; + } + + // Check if currrent head gain exceeds pump's max. head + if (dh > hmax + hyd->Htol) return XHEAD; + + // No check is made to see if flow exceeds pump's max. flow + return OPEN; +} + + +StatType prvstatus(EN_Project *pr, int k, StatType s, double hset, + double h1, double h2) +/* +**----------------------------------------------------------- +** Input: k = link index +** s = current status +** hset = valve head setting +** h1 = head at upstream node +** h2 = head at downstream node +** Output: returns new valve status +** Purpose: updates status of a pressure reducing valve. +**----------------------------------------------------------- +*/ +{ + StatType status; // Valve's new status + double hml; // Head loss when fully opened + hydraulics_t *hyd = &pr->hydraulics; + + double htol = hyd->Htol; + Slink *link = &pr->network.Link[k]; + + // Head loss when fully open + hml = link->Km * SQR(hyd->LinkFlows[k]); + + // Rules for updating valve's status from current value s + status = s; + switch (s) + { + case ACTIVE: + if (hyd->LinkFlows[k] < -hyd->Qtol) status = CLOSED; + else if (h1 - hml < hset - htol) status = OPEN; + else status = ACTIVE; + break; + + case OPEN: + if (hyd->LinkFlows[k] < -hyd->Qtol) status = CLOSED; + else if (h2 >= hset + htol) status = ACTIVE; + else status = OPEN; + break; + + case CLOSED: + if (h1 >= hset + htol && h2 < hset - htol) status = ACTIVE; + else if (h1 < hset - htol && h1 > h2 + htol) status = OPEN; + else status = CLOSED; + break; + + case XPRESSURE: + if (hyd->LinkFlows[k] < -hyd->Qtol) status = CLOSED; + break; + + default: + break; + } + return status; +} + + +StatType psvstatus(EN_Project *pr, int k, StatType s, double hset, + double h1, double h2) +/* +**----------------------------------------------------------- +** Input: k = link index +** s = current status +** hset = valve head setting +** h1 = head at upstream node +** h2 = head at downstream node +** Output: returns new valve status +** Purpose: updates status of a pressure sustaining valve. +**----------------------------------------------------------- +*/ +{ + StatType status; // Valve's new status + double hml; // Head loss when fully opened + hydraulics_t *hyd = &pr->hydraulics; + + double htol = hyd->Htol; + Slink *link = &pr->network.Link[k]; + + // Head loss when fully open + hml = link->Km * SQR(hyd->LinkFlows[k]); + + // Rules for updating valve's status from current value s + status = s; + switch (s) + { + case ACTIVE: + if (hyd->LinkFlows[k] < -hyd->Qtol) status = CLOSED; + else if (h2 + hml > hset + htol) status = OPEN; + else status = ACTIVE; + break; + + case OPEN: + if (hyd->LinkFlows[k] < -hyd->Qtol) status = CLOSED; + else if (h1 < hset - htol) status = ACTIVE; + else status = OPEN; + break; + + case CLOSED: + if (h2 > hset + htol && h1 > h2 + htol) status = OPEN; + else if (h1 >= hset + htol && h1 > h2 + htol) status = ACTIVE; + else status = CLOSED; + break; + + case XPRESSURE: + if (hyd->LinkFlows[k] < -hyd->Qtol) status = CLOSED; + break; + + default: + break; + } + return status; +} + + +StatType fcvstatus(EN_Project *pr, int k, StatType s, double h1, double h2) +/* +**----------------------------------------------------------- +** Input: k = link index +** s = current status +** h1 = head at upstream node +** h2 = head at downstream node +** Output: returns new valve status +** Purpose: updates status of a flow control valve. +** +** Valve status changes to XFCV if flow reversal. +** If current status is XFCV and current flow is +** above setting, then valve becomes active. +** If current status is XFCV, and current flow +** positive but still below valve setting, then +** status remains same. +**----------------------------------------------------------- +*/ +{ + StatType status; // New valve status + hydraulics_t *hyd = &pr->hydraulics; + + status = s; + if (h1 - h2 < -hyd->Htol) + { + status = XFCV; + } + else if (hyd->LinkFlows[k] < -hyd->Qtol) + { + status = XFCV; + } + else if (s == XFCV && hyd->LinkFlows[k] >= hyd->LinkSetting[k]) + { + status = ACTIVE; + } + return status; +} + + +void tankstatus(EN_Project *pr, int k, int n1, int n2) +/* +**---------------------------------------------------------------- +** Input: k = link index +** n1 = start node of link +** n2 = end node of link +** Output: none +** Purpose: closes link flowing into full or out of empty tank +**---------------------------------------------------------------- +*/ +{ + int i, n; + double h, q; + Stank *tank; + + hydraulics_t *hyd = &pr->hydraulics; + EN_Network *net = &pr->network; + Slink *link = &net->Link[k]; + + // Return if link is closed + if (hyd->LinkStatus[k] <= CLOSED) return; + + // Make node n1 be the tank, reversing flow (q) if need be + q = hyd->LinkFlows[k]; + i = n1 - net->Njuncs; + if (i <= 0) + { + i = n2 - net->Njuncs; + if (i <= 0) return; + n = n1; + n1 = n2; + n2 = n; + q = -q; + } + + // Ignore reservoirs + tank = &net->Tank[i]; + if (tank->A == 0.0) return; + + // Find head difference across link + h = hyd->NodeHead[n1] - hyd->NodeHead[n2]; + + // If tank is full, then prevent flow into it + if (hyd->NodeHead[n1] >= tank->Hmax - hyd->Htol) + { + // Case 1: Link is a pump discharging into tank + if (link->Type == EN_PUMP) + { + if (link->N2 == n1) hyd->LinkStatus[k] = TEMPCLOSED; + } + + // Case 2: Downstream head > tank head + // (e.g., an open outflow check valve would close) + else if (cvstatus(pr, OPEN, h, q) == CLOSED) + { + hyd->LinkStatus[k] = TEMPCLOSED; + } + } + + // If tank is empty, then prevent flow out of it + if (hyd->NodeHead[n1] <= tank->Hmin + hyd->Htol) + { + // Case 1: Link is a pump discharging from tank + if (link->Type == EN_PUMP) + { + if (link->N1 == n1) hyd->LinkStatus[k] = TEMPCLOSED; + } + + // Case 2: Tank head > downstream head + // (e.g., a closed outflow check valve would open) + else if (cvstatus(pr, CLOSED, h, q) == OPEN) + { + hyd->LinkStatus[k] = TEMPCLOSED; + } + } +} diff --git a/src/inpfile.c b/src/inpfile.c index 8bf894d..f5ef6b8 100644 --- a/src/inpfile.c +++ b/src/inpfile.c @@ -26,14 +26,14 @@ data describing a piping network to a file in EPANET's text format. #else #include #endif + #include "hash.h" #include "text.h" #include "types.h" -#include "epanet2.h" #include "funcs.h" #include -#define EXTERN extern -#include "vars.h" +//#define EXTERN extern +//#include "vars.h" /* Defined in enumstxt.h in EPANET.C */ extern char *LinkTxt[]; @@ -618,11 +618,15 @@ int saveinpfile(EN_Project *pr, char *fname) fprintf(f, "\n CHECKFREQ %-d", hyd->CheckFreq); fprintf(f, "\n MAXCHECK %-d", hyd->MaxCheck); fprintf(f, "\n DAMPLIMIT %-.8f", hyd->DampLimit); - if (hyd->HeadErrorLimit > 0.0) { - fprintf(f, "\n HEADERROR %-.8f", hyd->HeadErrorLimit * pr->Ucf[HEAD]); - } - if (hyd->FlowChangeLimit > 0.0) { - fprintf(f, "\n FLOWCHANGE %-.8f", hyd->FlowChangeLimit * pr->Ucf[FLOW]); + fprintf(f, "\n HEADERROR %-.8f", hyd->HeadErrorLimit * pr->Ucf[HEAD]); + fprintf(f, "\n FLOWCHANGE %-.8f", hyd->FlowChangeLimit * pr->Ucf[FLOW]); + + if (hyd->DemandModel == PDA) + { + fprintf(f, "\n DEMAND MODEL PDA"); + fprintf(f, "\n MINIMUM PRESSURE %-.4f", hyd->Pmin * pr->Ucf[PRESSURE]); + fprintf(f, "\n REQUIRED PRESSURE %-.4f", hyd->Preq * pr->Ucf[PRESSURE]); + fprintf(f, "\n PRESSURE EXPONENT %-.4f", hyd->Pexp); } /* Write [REPORT] section */ diff --git a/src/input1.c b/src/input1.c index b59096a..46da66a 100644 --- a/src/input1.c +++ b/src/input1.c @@ -33,8 +33,6 @@ AUTHOR: L. Rossman #include "epanet2.h" #include "funcs.h" #include -#define EXTERN extern -#include "vars.h" /* --------------------- Module Global Variables ---------------------- @@ -128,8 +126,12 @@ void setdefaults(EN_Project *pr) hyd->Qtol = QTOL; /* Default flow tolerance */ hyd->Hacc = HACC; /* Default hydraulic accuracy */ - hyd->FlowChangeLimit = 0.0; /* Default flow change limit */ - hyd->HeadErrorLimit = 0.0; /* Default head error limit */ + hyd->FlowChangeLimit = 0.0; // Default flow change limit + hyd->HeadErrorLimit = 0.0; // Default head error limit + hyd->DemandModel = DDA; // Demand driven analysis + hyd->Pmin = 0.0; // Minimum demand pressure (ft) + hyd->Preq = 0.0; // Required demand pressure (ft) + hyd->Pexp = 0.5; // Pressure function exponent qu->Ctol = MISSING; /* No pre-set quality tolerance */ hyd->MaxIter = MAXITER; /* Default max. hydraulic trials */ @@ -581,6 +583,8 @@ void convertunits(EN_Project *pr) demand->Base /= pr->Ucf[DEMAND]; } } + hyd->Pmin /= pr->Ucf[PRESSURE]; + hyd->Preq /= pr->Ucf[PRESSURE]; /* Convert emitter discharge coeffs. to head loss coeff. */ ucf = pow(pr->Ucf[FLOW], hyd->Qexp) / pr->Ucf[PRESSURE]; diff --git a/src/input2.c b/src/input2.c index b0a1b13..e4836b5 100644 --- a/src/input2.c +++ b/src/input2.c @@ -37,8 +37,6 @@ The following utility functions are all called from INPUT3.C #include "epanet2.h" #include "funcs.h" #include -#define EXTERN extern -#include "vars.h" #define MAXERRS 10 /* Max. input errors reported */ diff --git a/src/input3.c b/src/input3.c index 9c66f63..58f9ce2 100644 --- a/src/input3.c +++ b/src/input3.c @@ -32,12 +32,11 @@ All functions in this module are called from newline() in INPUT2.C. #include "text.h" #include "types.h" #include -#define EXTERN extern -#include "vars.h" /* Defined in enumstxt.h in EPANET.C */ extern char *MixTxt[]; extern char *Fldname[]; +extern char *DemandModelTxt[]; /* Defined in INPUT2.C */ @@ -1763,6 +1762,7 @@ int optionchoice(EN_Project *pr, int n) ** VERIFY filename ** UNBALANCED STOP/CONTINUE {Niter} ** PATTERN id +** DEMAND MODEL DDA/PDA/PPA **-------------------------------------------------------------- */ { @@ -1771,131 +1771,125 @@ int optionchoice(EN_Project *pr, int n) quality_t *qu = &pr->quality; parser_data_t *par = &pr->parser; out_file_t *out = &pr->out_files; - + int choice; /* Check if 1st token matches a parameter name and */ /* process the input for the matched parameter */ - if (n < 0) - return (201); - if (match(par->Tok[0], w_UNITS)) { - if (n < 1) - return (0); - else if (match(par->Tok[1], w_CFS)) - par->Flowflag = CFS; - else if (match(par->Tok[1], w_GPM)) - par->Flowflag = GPM; - else if (match(par->Tok[1], w_AFD)) - par->Flowflag = AFD; - else if (match(par->Tok[1], w_MGD)) - par->Flowflag = MGD; - else if (match(par->Tok[1], w_IMGD)) - par->Flowflag = IMGD; - else if (match(par->Tok[1], w_LPS)) - par->Flowflag = LPS; - else if (match(par->Tok[1], w_LPM)) - par->Flowflag = LPM; - else if (match(par->Tok[1], w_CMH)) - par->Flowflag = CMH; - else if (match(par->Tok[1], w_CMD)) - par->Flowflag = CMD; - else if (match(par->Tok[1], w_MLD)) - par->Flowflag = MLD; - else if (match(par->Tok[1], w_SI)) - par->Flowflag = LPS; - else - return (201); - } else if (match(par->Tok[0], w_PRESSURE)) { - if (n < 1) - return (0); - else if (match(par->Tok[1], w_PSI)) - par->Pressflag = PSI; - else if (match(par->Tok[1], w_KPA)) - par->Pressflag = KPA; - else if (match(par->Tok[1], w_METERS)) - par->Pressflag = METERS; - else - return (201); - } else if (match(par->Tok[0], w_HEADLOSS)) { - if (n < 1) - return (0); - else if (match(par->Tok[1], w_HW)) - hyd->Formflag = HW; - else if (match(par->Tok[1], w_DW)) - hyd->Formflag = DW; - else if (match(par->Tok[1], w_CM)) - hyd->Formflag = CM; - else - return (201); - } else if (match(par->Tok[0], w_HYDRAULIC)) { - if (n < 2) - return (0); - else if (match(par->Tok[1], w_USE)) - out->Hydflag = USE; - else if (match(par->Tok[1], w_SAVE)) - out->Hydflag = SAVE; - else - return (201); + if (n < 0) return (201); + if (match(par->Tok[0], w_UNITS)) + { + if (n < 1) return (0); + else if (match(par->Tok[1], w_CFS)) par->Flowflag = CFS; + else if (match(par->Tok[1], w_GPM)) par->Flowflag = GPM; + else if (match(par->Tok[1], w_AFD)) par->Flowflag = AFD; + else if (match(par->Tok[1], w_MGD)) par->Flowflag = MGD; + else if (match(par->Tok[1], w_IMGD)) par->Flowflag = IMGD; + else if (match(par->Tok[1], w_LPS)) par->Flowflag = LPS; + else if (match(par->Tok[1], w_LPM)) par->Flowflag = LPM; + else if (match(par->Tok[1], w_CMH)) par->Flowflag = CMH; + else if (match(par->Tok[1], w_CMD)) par->Flowflag = CMD; + else if (match(par->Tok[1], w_MLD)) par->Flowflag = MLD; + else if (match(par->Tok[1], w_SI)) par->Flowflag = LPS; + else return (201); + } + + else if (match(par->Tok[0], w_PRESSURE)) + { + if (n < 1) return (0); + else if (match(par->Tok[1], w_EXPONENT)) return -1; + else if (match(par->Tok[1], w_PSI)) par->Pressflag = PSI; + else if (match(par->Tok[1], w_KPA)) par->Pressflag = KPA; + else if (match(par->Tok[1], w_METERS)) par->Pressflag = METERS; + else return (201); + } + + else if (match(par->Tok[0], w_HEADLOSS)) + { + if (n < 1) return (0); + else if (match(par->Tok[1], w_HW)) hyd->Formflag = HW; + else if (match(par->Tok[1], w_DW)) hyd->Formflag = DW; + else if (match(par->Tok[1], w_CM)) hyd->Formflag = CM; + else return (201); + } + + else if (match(par->Tok[0], w_HYDRAULIC)) + { + if (n < 2) return (0); + else if (match(par->Tok[1], w_USE)) out->Hydflag = USE; + else if (match(par->Tok[1], w_SAVE)) out->Hydflag = SAVE; + else return (201); strncpy(out->HydFname, par->Tok[2], MAXFNAME); - } else if (match(par->Tok[0], w_QUALITY)) { - if (n < 1) - return (0); - else if (match(par->Tok[1], w_NONE)) - qu->Qualflag = NONE; - else if (match(par->Tok[1], w_CHEM)) - qu->Qualflag = CHEM; - else if (match(par->Tok[1], w_AGE)) - qu->Qualflag = AGE; - else if (match(par->Tok[1], w_TRACE)) - qu->Qualflag = TRACE; - else { - qu->Qualflag = CHEM; - strncpy(qu->ChemName, par->Tok[1], MAXID); - if (n >= 2) - strncpy(qu->ChemUnits, par->Tok[2], MAXID); + } + + else if (match(par->Tok[0], w_QUALITY)) + { + if (n < 1) return (0); + else if (match(par->Tok[1], w_NONE)) qu->Qualflag = NONE; + else if (match(par->Tok[1], w_CHEM)) qu->Qualflag = CHEM; + else if (match(par->Tok[1], w_AGE)) qu->Qualflag = AGE; + else if (match(par->Tok[1], w_TRACE)) qu->Qualflag = TRACE; + else + { + qu->Qualflag = CHEM; + strncpy(qu->ChemName, par->Tok[1], MAXID); + if (n >= 2) strncpy(qu->ChemUnits, par->Tok[2], MAXID); } if (qu->Qualflag == TRACE) /* Source tracing option */ { /* Copy Trace Node ID to par->Tok[0] for error reporting */ strcpy(par->Tok[0], ""); - if (n < 2) - return (212); + if (n < 2) return (212); strcpy(par->Tok[0], par->Tok[2]); qu->TraceNode = findnode(net,par->Tok[2]); - if (qu->TraceNode == 0) - return (212); + if (qu->TraceNode == 0) return (212); strncpy(qu->ChemName, u_PERCENT, MAXID); strncpy(qu->ChemUnits, par->Tok[2], MAXID); } - if (qu->Qualflag == AGE) { + if (qu->Qualflag == AGE) + { strncpy(qu->ChemName, w_AGE, MAXID); strncpy(qu->ChemUnits, u_HOURS, MAXID); } - } else if (match(par->Tok[0], w_MAP)) { - if (n < 1) - return (0); + } + + else if (match(par->Tok[0], w_MAP)) + { + if (n < 1) return (0); strncpy(pr->MapFname, par->Tok[1], MAXFNAME); /* Map file name */ - } else if (match(par->Tok[0], w_VERIFY)) { + } + + else if (match(par->Tok[0], w_VERIFY)) + { /* Backward compatibility for verification file */ - } else if (match(par->Tok[0], w_UNBALANCED)) /* Unbalanced option */ + } + + else if (match(par->Tok[0], w_UNBALANCED)) /* Unbalanced option */ { - if (n < 1) - return (0); - if (match(par->Tok[1], w_STOP)) - hyd->ExtraIter = -1; - else if (match(par->Tok[1], w_CONTINUE)) { - if (n >= 2) - hyd->ExtraIter = atoi(par->Tok[2]); - else - hyd->ExtraIter = 0; - } else - return (201); - } else if (match(par->Tok[0], w_PATTERN)) /* Pattern option */ + if (n < 1) return (0); + if (match(par->Tok[1], w_STOP)) hyd->ExtraIter = -1; + else if (match(par->Tok[1], w_CONTINUE)) + { + if (n >= 2) hyd->ExtraIter = atoi(par->Tok[2]); + else hyd->ExtraIter = 0; + } + else return (201); + } + + else if (match(par->Tok[0], w_PATTERN)) /* Pattern option */ { - if (n < 1) - return (0); + if (n < 1) return (0); strncpy(par->DefPatID, par->Tok[1], MAXID); - } else - return (-1); + } + + else if (match(par->Tok[0], w_DEMAND)) + { + if (n < 2) return 0; + if (!match(par->Tok[1], w_MODEL)) return -1; + choice = findmatch(par->Tok[2], DemandModelTxt); + if (choice < 0) return 201; + hyd->DemandModel = choice; + } + else return (-1); return (0); } /* end of optionchoice */ @@ -1916,6 +1910,9 @@ int optionvalue(EN_Project *pr, int n) ** HEADLIMIT value ** FLOWLIMIT value +** MINIMUM PRESSURE value +** REQUIRED PRESSURE value +** PRESSURE EXPONENT value ** TOLERANCE value ** SEGMENTS value (not used) @@ -1939,39 +1936,40 @@ int optionvalue(EN_Project *pr, int n) double y; /* Check for obsolete SEGMENTS keyword */ - //if (match(par->Tok[0], w_SEGMENTS)) - if (match(tok0, w_SEGMENTS)) - return (0); + if (match(tok0, w_SEGMENTS)) return (0); /* Check for missing value (which is permissible) */ if (match(tok0, w_SPECGRAV) || match(tok0, w_EMITTER) || - match(tok0, w_DEMAND)) + match(tok0, w_DEMAND) || match(tok0, w_MINIMUM) || + match(tok0, w_REQUIRED) || match(tok0, w_PRESSURE) || + match(tok0, w_PRECISION)) + { nvalue = 2; - if (n < nvalue) - return (0); + } + if (n < nvalue) return (0); /* Check for valid numerical input */ - if (!getfloat(par->Tok[nvalue], &y)) - return (213); + if (!getfloat(par->Tok[nvalue], &y)) return (213); /* Check for WQ tolerance option (which can be 0) */ - if (match(tok0, w_TOLERANCE)) { - if (y < 0.0) - return (213); + if (match(tok0, w_TOLERANCE)) + { + if (y < 0.0) return (213); qu->Ctol = y; /* Quality tolerance*/ return (0); } /* Check for Diffusivity option */ - if (match(tok0, w_DIFFUSIVITY)) { - if (y < 0.0) - return (213); + if (match(tok0, w_DIFFUSIVITY)) + { + if (y < 0.0) return (213); qu->Diffus = y; return (0); } /* Check for Damping Limit option */ - if (match(tok0, w_DAMPLIMIT)) { + if (match(tok0, w_DAMPLIMIT)) + { hyd->DampLimit = y; return (0); } @@ -1992,41 +1990,51 @@ int optionvalue(EN_Project *pr, int n) return 0; } + /* Check for pressure dependent demand params */ + else if (match(tok0, w_MINIMUM)) + { + if (y < 0.0) return 213; + hyd->Pmin = y; + return 0; + } + else if (match(tok0, w_REQUIRED)) + { + if (y < 0.0) return 213; + hyd->Preq = y; + return 0; + } + else if (match(tok0, w_PRESSURE)) + { + if (y < 0.0) return 213; + hyd->Pexp = y; + return 0; + } + /* All other options must be > 0 */ - if (y <= 0.0) - return (213); + if (y <= 0.0) return (213); /* Assign value to specified option */ - if (match(tok0, w_VISCOSITY)) - hyd->Viscos = y; /* Viscosity */ - else if (match(tok0, w_SPECGRAV)) - hyd->SpGrav = y; /* Spec. gravity */ - else if (match(tok0, w_TRIALS)) - hyd->MaxIter = (int)y; /* Max. trials */ + if (match(tok0, w_VISCOSITY)) hyd->Viscos = y; /* Viscosity */ + else if (match(tok0, w_SPECGRAV)) hyd->SpGrav = y; /* Spec. gravity */ + else if (match(tok0, w_TRIALS)) hyd->MaxIter = (int)y; /* Max. trials */ else if (match(tok0, w_ACCURACY)) /* Accuracy */ { y = MAX(y, 1.e-5); y = MIN(y, 1.e-1); hyd->Hacc = y; } - else if (match(tok0, w_HTOL)) - hyd->Htol = y; - else if (match(tok0, w_QTOL)) - hyd->Qtol = y; - else if (match(tok0, w_RQTOL)) { - if (y >= 1.0) - return (213); + else if (match(tok0, w_HTOL)) hyd->Htol = y; + else if (match(tok0, w_QTOL)) hyd->Qtol = y; + else if (match(tok0, w_RQTOL)) + { + if (y >= 1.0) return (213); hyd->RQtol = y; - } else if (match(tok0, w_CHECKFREQ)) - hyd->CheckFreq = (int)y; - else if (match(tok0, w_MAXCHECK)) - hyd->MaxCheck = (int)y; - else if (match(tok0, w_EMITTER)) - hyd->Qexp = 1.0 / y; - else if (match(tok0, w_DEMAND)) - hyd->Dmult = y; - else - return (201); + } + else if (match(tok0, w_CHECKFREQ)) hyd->CheckFreq = (int)y; + else if (match(tok0, w_MAXCHECK)) hyd->MaxCheck = (int)y; + else if (match(tok0, w_EMITTER)) hyd->Qexp = 1.0 / y; + else if (match(tok0, w_DEMAND)) hyd->Dmult = y; + else return (201); return (0); } /* end of optionvalue */ @@ -2091,7 +2099,8 @@ int getpumpcurve(EN_Project *pr, int n) return (0); } -int powercurve(double h0, double h1, double h2, double q1, double q2, double *a, double *b, double *c) +int powercurve(double h0, double h1, double h2, double q1, double q2, + double *a, double *b, double *c) /* **--------------------------------------------------------- ** Input: h0 = shutoff head diff --git a/src/output.c b/src/output.c index 39687cf..d9ec660 100644 --- a/src/output.c +++ b/src/output.c @@ -19,14 +19,12 @@ AUTHOR: L. Rossman #else #include #endif -#include "epanet2.h" + #include "funcs.h" #include "text.h" #include "types.h" #include -#define EXTERN extern #include "hash.h" -#include "vars.h" /* write x[1] to x[n] to file */ size_t f_save(REAL4 *x, int n, FILE *file) { diff --git a/src/quality.c b/src/quality.c index 03e29b6..a7286db 100644 --- a/src/quality.c +++ b/src/quality.c @@ -54,15 +54,13 @@ AUTHOR: L. Rossman #else #include #endif + #include "hash.h" #include "text.h" #include "types.h" -#include "epanet2.h" #include "funcs.h" #include -#define EXTERN extern #include "mempool.h" -#include "vars.h" /* ** Macros to identify upstream & downstream nodes of a link diff --git a/src/report.c b/src/report.c index 8d2101e..80eb45a 100644 --- a/src/report.c +++ b/src/report.c @@ -32,15 +32,13 @@ formatted string S to the report file. #else #include #endif -#include "epanet2.h" + #include "funcs.h" #include "hash.h" #include "text.h" #include "types.h" #include #include -#define EXTERN extern -#include "vars.h" #undef WINDOWS #ifdef _WIN32 @@ -50,12 +48,13 @@ formatted string S to the report file. #define MAXCOUNT 10 /* Max. # of disconnected nodes listed */ -/* Defined in enumstxt.h in EPANET.C */ +/* Defined in enumstxt.h */ extern char *NodeTxt[]; extern char *LinkTxt[]; extern char *StatTxt[]; extern char *TstatTxt[]; extern char *RptFormTxt[]; +extern char *DemandModelTxt[]; typedef REAL4 *Pfloat; void writenodetable(EN_Project *pr, Pfloat *); @@ -224,6 +223,8 @@ void writesummary(EN_Project *pr) writeline(pr, s); sprintf(s, FMT25, RptFormTxt[hyd->Formflag]); writeline(pr, s); + sprintf(s, FMT25a, DemandModelTxt[hyd->DemandModel]); + writeline(pr, s); sprintf(s, FMT26, time->Hstep * pr->Ucf[TIME], rep->Field[TIME].Units); writeline(pr, s); sprintf(s, FMT27, hyd->Hacc); diff --git a/src/rules.c b/src/rules.c index 39d6a6a..d5da392 100644 --- a/src/rules.c +++ b/src/rules.c @@ -29,13 +29,11 @@ AUTHOR: L. Rossman #else #include #endif -#include "epanet2.h" + #include "funcs.h" #include "hash.h" #include "text.h" #include "types.h" -#define EXTERN extern -#include "vars.h" enum Rulewords { r_RULE, diff --git a/src/text.h b/src/text.h index 85159a9..4684fc8 100755 --- a/src/text.h +++ b/src/text.h @@ -145,6 +145,12 @@ AUTHOR: L. Rossman #define w_FLOWCHANGE "FLOWCHANGE" #define w_HEADERROR "HEADERROR" +#define w_MODEL "MODEL" +#define w_DDA "DDA" +#define w_PDA "PDA" +#define w_REQUIRED "REQ" +#define w_EXPONENT "EXP" + #define w_SECONDS "SEC" #define w_MINUTES "MIN" #define w_HOURS "HOU" @@ -323,6 +329,9 @@ AUTHOR: L. Rossman #define t_perM3 " /m3" #define t_perMGAL "/Mgal" #define t_DIFFER "DIFFERENTIAL" +#define t_FIXED "Fixed Demands" +#define t_POWER "Power Function" +#define t_ORIFICE "Orifice Flow" /* ------------------ Format Messages ------------------*/ @@ -362,6 +371,7 @@ AUTHOR: L. Rossman #define FMT23 " Number of Pumps ................... %-d" #define FMT24 " Number of Valves .................. %-d" #define FMT25 " Headloss Formula .................. %s" +#define FMT25a " Nodal Demand Model ................ %s" #define FMT26 " Hydraulic Timestep ................ %-.2f %s" #define FMT27 " Hydraulic Accuracy ................ %-.6f" diff --git a/src/types.h b/src/types.h index a039d8c..f3ac921 100755 --- a/src/types.h +++ b/src/types.h @@ -298,6 +298,11 @@ typedef enum { MAX_ENERGY_STATS } EnergyStats; +typedef enum { + DDA, // Demand Driven Analysis + PDA // Pressure Driven Analysis +} DemandModelType; + /* ------------------------------------------------------ Global Data Structures @@ -759,7 +764,8 @@ typedef struct { typedef struct { double - *NodeDemand, /* Node actual demand */ + *NodeDemand, // Node actual total outflow + *DemandFlows, // Demand outflows *EmitterFlows, /* Emitter flows */ *LinkSetting, /* Link settings */ *LinkFlows, /* Link flows */ @@ -768,10 +774,13 @@ typedef struct { Qtol, /* Flow rate tolerance */ RQtol, /* Flow resistance tolerance */ Hexp, /* Exponent in headloss formula */ - Qexp, /* Exponent in orifice formula */ + Qexp, /* Exponent in emitter formula */ + Pexp, // Exponent in demand formula + Pmin, // Pressure needed for any demand + Preq, // Pressure needed for full demand Dmult, /* Demand multiplier */ - Hacc, /* Hydraulics solution accuracy */ + Hacc, /* Hydraulics solution accuracy */ FlowChangeLimit, /* Hydraulics flow change limit */ HeadErrorLimit, /* Hydraulics head error limit */ @@ -787,7 +796,8 @@ typedef struct { int DefPat, /* Default demand pattern */ - Epat; /* Energy cost time pattern */ + Epat, /* Energy cost time pattern */ + DemandModel; // Fixed or pressure dependent StatType *LinkStatus, /* Link status */ @@ -805,8 +815,10 @@ typedef struct { Formflag; /* Hydraulic formula flag */ /* Info about hydraulic solution */ - double relativeError; - int iterations; + double RelativeError; + double MaxHeadError; + double MaxFlowChange; + int Iterations; /* Flag used to halt taking further time steps */ int Haltflag;