Files
EPANET/src/rules.c
T
Sam Hatchett f97d837231 Feature wrapper (#136)
this quite sizable commit does several things, but is primarily
focussed on building a toolkit that can run simultaneous
simulations/analyses within a shared memory space. Versions <=2.1 use a
long list of global variables that prevent multiple instantiations on
linux systems without resorting to compilation tricks (like duplicate
binaries or similar via static linkage). This version uses a single "Project" pointer to encapsulate
the network and analysis data. There are no changes to existing algo
implementations other than to accomodate dereferencing of the passed-in
pointers. A more detailed list of major changes below:

- mirrors all “ENxxxx” function calls with “EN_xxxx” versions (note the
underscore) that take an extra first parameter: a pointer to an
EN_Project struct, which contains all network, hydraulic, quality, and
associated data.
- tweaks some code formatting to make it more readable
- removes some deprecated/commented code that was sufficiently old
- fixes implicit type-cast warnings

* Added ENaddnode and ENaddlink functions

* More memory reallocations

* Added ENInit, ENsetheadcurveindex

* Added ENdeletelink and ENdeletenode

* restored default behavior for float types

* fixed type

* Added docstrings for ENinit

* cleanup change

* moves global rule variables to vars.h

* migrates rule structs to typedefs

for better readability

* char types to proper enums

fixes #93

* Change some variable declarations for compatibility

Changes to keep compatibility with C89 compilers: variables must be
declared at the top of the functions. Remove the use of EN_LinkType in
function call as it is not compatible with ENgetnodetype.

* Moved declaration of idstodelete to top of function

* Updated ENinit function and headers

Updated header files with new functions
Updated def file with new functions
For ENinit changes names of parameters #98
Added enum for headloss formula

* Missed these files in 1a033fc

* migrates char types to enums fixes #93,

supports unified link/node type enums, rather than public/private
redefinitions

* removing links in reverse-index order maintains proper indexing fixes #96

* style

* clarifies curve getter units issue (dox)

closes #95

* fixes link/node confusion in ENsetlinktype

partially reverts a3bce95dc330a5a297634a303d438e2e1bc41cc9

* partial compilation fix

* fixes dox issue

* fixes allocation issues with enums

- updates style in various places
- introduces FlowDirection enum
- use snprintf to prevent overflow

* fixes enum type cast

* updated mac project settings

* Use of _snprintf on Windows and remove DLLEXPORT from mempool.h

snprintf it not compatible on Windows so we use _snprintf
mempool gave starnge compilation errors while removing DLLEXPORT worked.
Not sure why these functions needed to be exposed in the DLL?

* Revert "Use of _snprintf on Windows and remove DLLEXPORT from mempool.h"

This reverts commit 6238f77d47fa0feaabe5836043c006937de433a2.

* use of _snprintf instead of snprintf on Windows and removed DLLEXPORT from mempool.h

Had compilation errors on mempool.h. Removed DLLEXPORT so solve it. Not
sure why there was a need to expose these functions?

* Shift indices for Links in ENaddnode

Need to shift indices for Links not just Pipes since a pump could be
connected directly to a reservoir. Also set the defult base demand to
zero (was 5).

* Set defualts for madatory link properties in ENaddlink and small fix for ENsetheadcurveindex

Relates to #102 and closes #103

* wraps globals into structs, duplicates api functions with objective versions



* parse and serialize Comment field for network elements

related to #47

* adds getter for head/efficiency curve

in EN_getlinkvalue

* adds getter for event node index

… to return the index of the junction (tank) that triggered the event.

* fixes edge case in parsing

… where inp files without demands in [JUNCTIONS] and without any
[DEMAND] categories will fail.

* adds freeing function for project pointer

* removes redundant string literals, fixes overrun issue in error message getter function

* check for hydraulics already closed

* moving error definitions to data file

* deprecates ENR err message getter (unused)

* updates location of errors data file

also begins to expose blind structs to curves and patterns,
anticipating buildout of APIs for those.

* updates CLI output to reflect executable name as invoked

relates to #109

* Feature nrtest (#131)

* Initial commit EPANET testing tools.

* Initial commit for epanet-nrtestsuite

* SWIG wrapper for EPANET outputapi (#118)

* Removed pervious version of outputapi and wrapper

* SWIG wrapper for EPANET outputapi

* Patching cmake build script fixed target for outputapi

* Build failing on deprecated test script

* Minor changes. Responding to review comments.

* Feature nrtest (#121)

* Configured python setup to automatically build nrtest tools.

* Working on build / testing automation

* Adding EPANET 2.0.12 benchmark

* Updated Travis yml to run nrtest

* Fixing InsecurePlatformWarning

* Fixing InsecurePlatformWarning again

* Fixing InsecurePlatformWarning

* Fixing InsecurePlatformWarning

* Fixing InsecurePlatformWarning

* Update .travis.yml

* Update .travis.yml

* Update .travis.yml

* Update .travis.yml

* Working on configuring python environment and building test tools under Travis CI.

* Making gen-config.sh and run-nrtest.sh executable

* Debugging .travis.yml

* Debugging .travis.yml

* Debugging .travis.yml again

* Debugging .travis.yml again

* debugging travis setup

* debugging Travis setup

* debugging Travis setup

* debugging Travis setup

* debugging Travis setup

* debugging Travis setup

* debugging Travis setup

* debugging Travis setup

* debugging Travis setup

* debugging Travis setup

* debugging Travis setup

* Fixing bug with __strncpy_chk destlen < len

* nrtesting clean up

* re-implements fixes from:

5eead5ae40
3c788567a4

* removes extraneous build files, moves cmake and updates travis

* mirror of 9b37035560f9683f1514b439f7586a5c17bca5bf

* Move some variable declarations

* More variable declarations

* Fix TmpDir

* Allocate _defaultModel

* Fix EN_addcurve funcrion

* Fix for inpfile

* Fix writeRuleinInp call

* Set MAXMSG to 79 chars

* Fix for flow direction

* Refactoring testing related python packages and SWIG wrapper bug fix (#139)

* Eliminated epanet-reader package. Removed numpy dependency from epanet-output. Fixed reference counting bug in SWIG wrapper. Added error checking to run_nrtest.sh. Added nrtest package to requirements file.

* changing buildhome directory

* Fixing bug related to preprocessor definition of PI
2018-01-09 16:56:42 -05:00

1420 lines
38 KiB
C

/*
**********************************************************************
RULES.C -- Rule processor module for EPANET
VERSION: 2.00
DATE: 5/8/00
9/7/00
10/25/00
3/1/01
8/15/07 (2.00.11)
AUTHOR: L. Rossman
US EPA - NRMRL
The entry points for this module are:
initrules() -- called from ENopen() in EPANET.C
addrule() -- called from netsize() in INPUT2.C
allocrules() -- called from allocdata() in EPANET.C
ruledata() -- called from newline() in INPUT2.C
freerules() -- called from freedata() in EPANET.C
checkrules() -- called from ruletimestep() in HYDRAUL.C
**********************************************************************
*/
#include <stdio.h>
#include <string.h>
#ifndef __APPLE__
#include <malloc.h>
#else
#include <stdlib.h>
#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,
r_IF,
r_AND,
r_OR,
r_THEN,
r_ELSE,
r_PRIORITY,
r_ERROR
};
char *Ruleword[] = {w_RULE, w_IF, w_AND, w_OR,
w_THEN, w_ELSE, w_PRIORITY, NULL};
enum Varwords {
r_DEMAND,
r_HEAD,
r_GRADE,
r_LEVEL,
r_PRESSURE,
r_FLOW,
r_STATUS,
r_SETTING,
r_POWER,
r_TIME,
r_CLOCKTIME,
r_FILLTIME,
r_DRAINTIME
};
char *Varword[] = {w_DEMAND, w_HEAD, w_GRADE, w_LEVEL, w_PRESSURE,
w_FLOW, w_STATUS, w_SETTING, w_POWER, w_TIME,
w_CLOCKTIME, w_FILLTIME, w_DRAINTIME, NULL};
enum Objects {
r_JUNC,
r_RESERV,
r_TANK,
r_PIPE,
r_PUMP,
r_VALVE,
r_NODE,
r_LINK,
r_SYSTEM
};
char *Object[] = {w_JUNC, w_RESERV, w_TANK, w_PIPE, w_PUMP,
w_VALVE, w_NODE, w_LINK, w_SYSTEM, NULL};
/* NOTE: place "<=" & ">=" before "<" & ">" so that findmatch() works correctly.
*/
enum Operators { EQ, NE, LE, GE, LT, GT, IS, NOT, BELOW, ABOVE };
char *Operator[] = {"=", "<>", "<=", ">=", "<", ">",
w_IS, w_NOT, w_BELOW, w_ABOVE, NULL};
enum Values { IS_NUMBER, IS_OPEN, IS_CLOSED, IS_ACTIVE };
char *Value[] = {"XXXX", w_OPEN, w_CLOSED, w_ACTIVE, NULL};
/* External variables declared in INPUT2.C */
/*
** Local function prototypes are defined here and not in FUNCS.H
** because some of them utilize the Premise and Action structures
** defined locally in this module.
*/
void newrule(EN_Project *pr);
int newpremise(EN_Project *pr, int);
int newaction(EN_Project *pr);
int newpriority(EN_Project *pr);
int evalpremises(EN_Project *pr, int);
void updateactlist(rules_t *rules, int, Action *);
int checkaction(rules_t *rules, int, Action *);
int checkpremise(EN_Project *pr, Premise *);
int checktime(EN_Project *pr, Premise *);
int checkstatus(EN_Project *pr, Premise *);
int checkvalue(EN_Project *pr, Premise *);
int takeactions(EN_Project *pr);
void clearactlist(rules_t *rules);
void clearrules(EN_Project *pr);
void ruleerrmsg(EN_Project *pr, int);
int writeRuleinInp(EN_Project *pr, FILE *f, int RuleIdx);
void initrules(rules_t *rules)
/*
**--------------------------------------------------------------
** Initializes rule base.
** Called by ENopen() in EPANET.C module
**--------------------------------------------------------------
*/
{
rules->RuleState = r_PRIORITY;
rules->Rule = NULL;
}
void addrule(parser_data_t *par, char *tok)
/*
**--------------------------------------------------------------
** Updates rule count if RULE keyword found in line of input.
** Called by netsize() in INPUT2.C module.
**--------------------------------------------------------------
*/
{
if (match(tok, w_RULE)) {
par->MaxRules++;
}
}
int allocrules(EN_Project *pr)
/*
**--------------------------------------------------------------
** Allocates memory for rule-based controls.
** Called by allocdata() in EPANET.C module.
**--------------------------------------------------------------
*/
{
rules_t *rules = &pr->rules;
parser_data_t *par = &pr->parser;
rules->Rule = (aRule *)calloc(par->MaxRules + 1, sizeof(aRule));
if (rules->Rule == NULL) {
return (101);
} else {
return (0);
}
}
void freerules(EN_Project *pr)
/*
**--------------------------------------------------------------
** Frees memory used for rule-based controls.
** Called by freedata() in EPANET.C module.
**--------------------------------------------------------------
*/
{
clearrules(pr);
free(pr->rules.Rule);
}
int checkrules(EN_Project *pr, long dt)
/*
**-----------------------------------------------------
** Checks which rules should fire at current time.
** Called by ruletimestep() in HYDRAUL.C.
**-----------------------------------------------------
*/
{
EN_Network *net = &pr->network;
time_options_t *time = &pr->time_options;
rules_t *rules = &pr->rules;
int i, r; /* Number of actions actually taken */
/* Start of rule evaluation time interval */
rules->Time1 = time->Htime - dt + 1;
/* Iterate through each rule */
rules->ActList = NULL;
r = 0;
for (i = 1; i <= net->Nrules; i++) {
/* If premises true, add THEN clauses to action list. */
if (evalpremises(pr, i) == TRUE) {
updateactlist(rules, i, rules->Rule[i].Tchain);
}
/* If premises false, add ELSE actions to list. */
else {
if (rules->Rule[i].Fchain != NULL) {
updateactlist(rules, i, rules->Rule[i].Fchain);
}
}
}
/* Execute actions then clear list. */
if (rules->ActList != NULL) {
r = takeactions(pr);
}
clearactlist(rules);
return (r);
}
int ruledata(EN_Project *pr)
/*
**--------------------------------------------------------------
** Parses a line from [RULES] section of input.
** Called by newline() in INPUT2.C module.
** Tok[] is global array of tokens parsed from input line.
**--------------------------------------------------------------
*/
{
EN_Network *net = &pr->network;
parser_data_t *par = &pr->parser;
rules_t *rules = &pr->rules;
char **Tok = par->Tok;
int key, /* Keyword code */
err;
/* Exit if current rule has an error */
if (rules->RuleState == r_ERROR)
return (0);
/* Find the key word that begins the rule statement */
err = 0;
key = findmatch(Tok[0], Ruleword);
switch (key) {
case -1:
err = 201; /* Unrecognized keyword */
break;
case r_RULE:
net->Nrules++;
newrule(pr);
rules->RuleState = r_RULE;
break;
case r_IF:
if (rules->RuleState != r_RULE) {
err = 221; /* Mis-placed IF clause */
break;
}
rules->RuleState = r_IF;
err = newpremise(pr,r_AND);
break;
case r_AND:
if (rules->RuleState == r_IF)
err = newpremise(pr, r_AND);
else if (rules->RuleState == r_THEN || rules->RuleState == r_ELSE)
err = newaction(pr);
else
err = 221;
break;
case r_OR:
if (rules->RuleState == r_IF)
err = newpremise(pr, r_OR);
else
err = 221;
break;
case r_THEN:
if (rules->RuleState != r_IF) {
err = 221; /* Mis-placed THEN clause */
break;
}
rules->RuleState = r_THEN;
err = newaction(pr);
break;
case r_ELSE:
if (rules->RuleState != r_THEN) {
err = 221; /* Mis-placed ELSE clause */
break;
}
rules->RuleState = r_ELSE;
err = newaction(pr);
break;
case r_PRIORITY:
if (rules->RuleState != r_THEN && rules->RuleState != r_ELSE) {
err = 221;
break;
}
rules->RuleState = r_PRIORITY;
err = newpriority(pr);
break;
default:
err = 201;
}
/* Set RuleState to r_ERROR if errors found */
if (err) {
rules->RuleState = r_ERROR;
ruleerrmsg(pr,err);
err = 200;
}
return (err);
}
void clearactlist(rules_t *rules)
/*
**----------------------------------------------------------
** Clears memory used for action list
**----------------------------------------------------------
*/
{
ActItem *a;
ActItem *anext;
a = rules->ActList;
while (a != NULL) {
anext = a->next;
free(a);
a = anext;
}
}
void clearrules(EN_Project *pr)
/*
**-----------------------------------------------------------
** Clears memory used for premises & actions for all rules
**-----------------------------------------------------------
*/
{
EN_Network *net = &pr->network;
rules_t *rules = &pr->rules;
Premise *p;
Premise *pnext;
Action *a;
Action *anext;
int i;
for (i = 1; i <= net->Nrules; i++) {
p = rules->Rule[i].Pchain;
while (p != NULL) {
pnext = p->next;
free(p);
p = pnext;
}
a = rules->Rule[i].Tchain;
while (a != NULL) {
anext = a->next;
free(a);
a = anext;
}
a = rules->Rule[i].Fchain;
while (a != NULL) {
anext = a->next;
free(a);
a = anext;
}
}
}
void newrule(EN_Project *pr)
/*
**----------------------------------------------------------
** Adds new rule to rule base
**----------------------------------------------------------
*/
{
EN_Network *net = &pr->network;
parser_data_t *par = &pr->parser;
rules_t *rules = &pr->rules;
char **Tok = par->Tok;
strncpy(rules->Rule[net->Nrules].label, Tok[1], MAXID);
rules->Rule[net->Nrules].Pchain = NULL;
rules->Rule[net->Nrules].Tchain = NULL;
rules->Rule[net->Nrules].Fchain = NULL;
rules->Rule[net->Nrules].priority = 0.0;
rules->Plast = NULL;
rules->Tlast = NULL;
rules->Flast = NULL;
}
int newpremise(EN_Project *pr, int logop)
/*
**--------------------------------------------------------------------
** Adds new premise to current rule.
** Formats are:
** IF/AND/OR <object> <id> <variable> <operator> <value>
** IF/AND/OR SYSTEM <variable> <operator> <value> (units)
**
** Calls findmatch() and hour() in INPUT2.C.
** Calls findnode() and findlink() in EPANET.C.
**---------------------------------------------------------------------
*/
{
EN_Network *net = &pr->network;
parser_data_t *par = &pr->parser;
rules_t *rules = &pr->rules;
char **Tok = par->Tok;
int i, j, k, m, r, s, v;
double x;
Premise *p;
/* Check for correct number of tokens */
if (par->Ntokens != 5 && par->Ntokens != 6)
return (201);
/* Find network object & id if present */
i = findmatch(Tok[1], Object);
if (i == r_SYSTEM) {
j = 0;
v = findmatch(Tok[2], Varword);
if (v != r_DEMAND && v != r_TIME && v != r_CLOCKTIME) {
return (201);
}
} else {
v = findmatch(Tok[3], Varword);
if (v < 0) {
return (201);
}
switch (i) {
case r_NODE:
case r_JUNC:
case r_RESERV:
case r_TANK:
k = r_NODE;
break;
case r_LINK:
case r_PIPE:
case r_PUMP:
case r_VALVE:
k = r_LINK;
break;
default:
return (201);
}
i = k;
if (i == r_NODE) {
j = findnode(net, Tok[2]);
if (j == 0)
return (203);
switch (v) {
case r_DEMAND:
case r_HEAD:
case r_GRADE:
case r_LEVEL:
case r_PRESSURE:
break;
case r_FILLTIME:
case r_DRAINTIME:
if (j <= net->Njuncs) {
return (201);
}
break;
default:
return (201);
}
} else {
j = findlink(net, Tok[2]);
if (j == 0) {
return (204);
}
switch (v) {
case r_FLOW:
case r_STATUS:
case r_SETTING:
break;
default:
return (201);
}
}
}
/* Parse relational operator (r) and check for synonyms */
if (i == r_SYSTEM) {
m = 3;
} else {
m = 4;
}
k = findmatch(Tok[m], Operator);
if (k < 0)
return (201);
switch (k) {
case IS:
r = EQ;
break;
case NOT:
r = NE;
break;
case BELOW:
r = LT;
break;
case ABOVE:
r = GT;
break;
default:
r = k;
}
/* Parse for status (s) or numerical value (x) */
s = 0;
x = MISSING;
if (v == r_TIME || v == r_CLOCKTIME) {
if (par->Ntokens == 6)
x = hour(Tok[4], Tok[5]) * 3600.;
else
x = hour(Tok[4], "") * 3600.;
if (x < 0.0)
return (202);
} else if ((k = findmatch(Tok[par->Ntokens - 1], Value)) > IS_NUMBER)
s = k;
else {
if (!getfloat(Tok[par->Ntokens - 1], &x))
return (202);
if (v == r_FILLTIME || v == r_DRAINTIME)
x = x * 3600.0;
}
/* Create new premise structure */
p = (Premise *)malloc(sizeof(Premise));
if (p == NULL)
return (101);
p->object = i;
p->index = j;
p->variable = v;
p->relop = r;
p->logop = logop;
p->status = s;
p->value = x;
/* Add premise to current rule's premise list */
p->next = NULL;
if (rules->Plast == NULL)
rules->Rule[net->Nrules].Pchain = p;
else
rules->Plast->next = p;
rules->Plast = p;
return (0);
}
int newaction(EN_Project *pr)
/*
**----------------------------------------------------------
** Adds new action to current rule.
** Format is:
** THEN/ELSE/AND LINK <id> <variable> IS <value>
**
** Calls findlink() from EPANET.C.
** Calls getfloat() and findmatch() from INPUT2.C.
**----------------------------------------------------------
*/
{
EN_Network *net = &pr->network;
parser_data_t *par = &pr->parser;
rules_t *rules = &pr->rules;
char **Tok = par->Tok;
int j, k, s;
double x;
Action *a;
/* Check for correct number of tokens */
if (par->Ntokens != 6)
return (201);
/* Check that link exists */
j = findlink(net, Tok[2]);
if (j == 0)
return (204);
/*** Updated 9/7/00 ***/
/* Cannot control a CV */
if (net->Link[j].Type == EN_CVPIPE)
return (207);
/* Find value for status or setting */
s = -1;
x = MISSING;
if ((k = findmatch(Tok[5], Value)) > IS_NUMBER) {
s = k;
} else {
if (!getfloat(Tok[5], &x)) {
return (202);
}
if (x < 0.0) {
return (202);
}
}
/*** Updated 9/7/00 ***/
/* Cannot change setting for a GPV ***/
if (x != MISSING && net->Link[j].Type == EN_GPV)
return (202);
/*** Updated 3/1/01 ***/
/* Set status for pipe in case setting was specified */
if (x != MISSING && net->Link[j].Type == EN_PIPE) {
if (x == 0.0)
s = IS_CLOSED;
else
s = IS_OPEN;
x = MISSING;
}
/* Create a new action structure */
a = (Action *)malloc(sizeof(Action));
if (a == NULL)
return (101);
a->link = j;
a->status = s;
a->setting = x;
/* Add action to current rule's action list */
if (rules->RuleState == r_THEN) {
a->next = NULL;
if (rules->Tlast == NULL)
rules->Rule[net->Nrules].Tchain = a;
else
rules->Tlast->next = a;
rules->Tlast = a;
} else {
a->next = NULL;
if (rules->Flast == NULL)
rules->Rule[net->Nrules].Fchain = a;
else
rules->Flast->next = a;
rules->Flast = a;
}
return (0);
}
int newpriority(EN_Project *pr)
/*
**---------------------------------------------------
** Adds priority rating to current rule
**---------------------------------------------------
*/
{
EN_Network *net = &pr->network;
parser_data_t *par = &pr->parser;
rules_t *rules = &pr->rules;
char **Tok = par->Tok;
double x;
if (!getfloat(Tok[1], &x))
return (202);
rules->Rule[net->Nrules].priority = x;
return (0);
}
int evalpremises(EN_Project *pr, int i)
/*
**----------------------------------------------------------
** Checks if premises to rule i are true
**----------------------------------------------------------
*/
{
rules_t *rules = &pr->rules;
int result;
Premise *p;
result = TRUE;
p = rules->Rule[i].Pchain;
while (p != NULL) {
if (p->logop == r_OR) {
if (result == FALSE) {
result = checkpremise(pr,p);
}
} else {
if (result == FALSE)
return (FALSE);
result = checkpremise(pr,p);
}
p = p->next;
}
return (result);
}
int checkpremise(EN_Project *pr, Premise *p)
/*
**----------------------------------------------------------
** Checks if a particular premise is true
**----------------------------------------------------------
*/
{
if (p->variable == r_TIME || p->variable == r_CLOCKTIME)
return (checktime(pr,p));
else if (p->status > IS_NUMBER)
return (checkstatus(pr,p));
else
return (checkvalue(pr,p));
}
int checktime(EN_Project *pr, Premise *p)
/*
**------------------------------------------------------------
** Checks if condition on system time holds
**------------------------------------------------------------
*/
{
time_options_t *time = &pr->time_options;
rules_t *rules = &pr->rules;
char flag;
long t1, t2, x;
/* Get start and end of rule evaluation time interval */
if (p->variable == r_TIME) {
t1 = rules->Time1;
t2 = time->Htime;
}
else if (p->variable == r_CLOCKTIME) {
t1 = (rules->Time1 + time->Tstart) % SECperDAY;
t2 = (time->Htime + time->Tstart) % SECperDAY;
} else
return (0);
/* Test premise's time */
x = (long)(p->value);
switch (p->relop) {
/* For inequality, test against current time */
case LT:
if (t2 >= x)
return (0);
break;
case LE:
if (t2 > x)
return (0);
break;
case GT:
if (t2 <= x)
return (0);
break;
case GE:
if (t2 < x)
return (0);
break;
/* For equality, test if within interval */
case EQ:
case NE:
flag = FALSE;
if (t2 < t1) /* E.g., 11:00 am to 1:00 am */
{
if (x >= t1 || x <= t2)
flag = TRUE;
} else {
if (x >= t1 && x <= t2)
flag = TRUE;
}
if (p->relop == EQ && flag == FALSE)
return (0);
if (p->relop == NE && flag == TRUE)
return (0);
break;
}
/* If we get to here then premise was satisfied */
return (1);
}
int checkstatus(EN_Project *pr, Premise *p)
/*
**------------------------------------------------------------
** Checks if condition on link status holds
**------------------------------------------------------------
*/
{
hydraulics_t *hyd = &pr->hydraulics;
char i;
int j;
switch (p->status) {
case IS_OPEN:
case IS_CLOSED:
case IS_ACTIVE:
i = hyd->LinkStatus[p->index];
if (i <= CLOSED)
j = IS_CLOSED;
else if (i == ACTIVE)
j = IS_ACTIVE;
else
j = IS_OPEN;
if (j == p->status && p->relop == EQ)
return (1);
if (j != p->status && p->relop == NE)
return (1);
}
return (0);
}
int checkvalue(EN_Project *pr, Premise *p)
/*
**----------------------------------------------------------
** Checks if numerical condition on a variable is true.
** Uses tolerance of 0.001 when testing conditions.
**----------------------------------------------------------
*/
{
EN_Network *net = &pr->network;
hydraulics_t *hyd = &pr->hydraulics;
Snode *Node = net->Node;
Slink *Link = net->Link;
Stank *Tank = net->Tank;
const int Njuncs = net->Njuncs;
double *Ucf = pr->Ucf;
double *NodeDemand = hyd->NodeDemand;
double *LinkFlows = hyd->LinkFlows;
double *LinkSetting = hyd->LinkSetting;
int i, j, v;
double x, tol = 1.e-3;
i = p->index;
v = p->variable;
switch (v) {
case r_DEMAND:
if (p->object == r_SYSTEM)
x = hyd->Dsystem * Ucf[DEMAND];
else
x = NodeDemand[i] * Ucf[DEMAND];
break;
case r_HEAD:
case r_GRADE:
x = hyd->NodeHead[i] * Ucf[HEAD];
break;
case r_PRESSURE:
x = (hyd->NodeHead[i] - Node[i].El) * Ucf[PRESSURE];
break;
case r_LEVEL:
x = (hyd->NodeHead[i] - Node[i].El) * Ucf[HEAD];
break;
case r_FLOW:
x = ABS(LinkFlows[i]) * Ucf[FLOW];
break;
case r_SETTING:
if (LinkSetting[i] == MISSING)
return (0);
x = LinkSetting[i];
switch (Link[i].Type) {
case EN_PRV:
case EN_PSV:
case EN_PBV:
x = x * Ucf[PRESSURE];
break;
case EN_FCV:
x = x * Ucf[FLOW];
break;
default:
break;
}
break;
case r_FILLTIME:
if (i <= Njuncs)
return (0);
j = i - Njuncs;
if (Tank[j].A == 0.0)
return (0);
if (NodeDemand[i] <= TINY)
return (0);
x = (Tank[j].Vmax - Tank[j].V) / NodeDemand[i];
break;
case r_DRAINTIME:
if (i <= Njuncs)
return (0);
j = i - Njuncs;
if (Tank[j].A == 0.0)
return (0);
if (NodeDemand[i] >= -TINY)
return (0);
x = (Tank[j].Vmin - Tank[j].V) / NodeDemand[i];
break;
default:
return (0);
}
switch (p->relop) {
case EQ:
if (ABS(x - p->value) > tol)
return (0);
break;
case NE:
if (ABS(x - p->value) < tol)
return (0);
break;
case LT:
if (x > p->value + tol)
return (0);
break;
case LE:
if (x > p->value - tol)
return (0);
break;
case GT:
if (x < p->value - tol)
return (0);
break;
case GE:
if (x < p->value + tol)
return (0);
break;
}
return (1);
}
void updateactlist(rules_t *rules, int i, Action *actions)
/*
**---------------------------------------------------
** Adds rule's actions to action list
**---------------------------------------------------
*/
{
ActItem *item;
Action *a;
/* Iterate through each action of Rule i */
a = actions;
while (a != NULL) {
/* Add action to list if not already on it */
if (!checkaction(rules, i, a)) {
item = (ActItem *)malloc(sizeof(ActItem));
if (item != NULL) {
item->action = a;
item->ruleindex = i;
item->next = rules->ActList;
rules->ActList = item;
}
}
a = a->next;
}
}
int checkaction(rules_t *rules, int i, Action *a)
/*
**-----------------------------------------------------------
** Checks if an action is already on the Action List
**-----------------------------------------------------------
*/
{
int i1, k, k1;
ActItem *item;
Action *a1;
/* Search action list for link named in action */
k = a->link; /* Action applies to link k */
item = rules->ActList;
while (item != NULL) {
a1 = item->action;
i1 = item->ruleindex;
k1 = a1->link;
/* If link on list then replace action if rule has higher priority. */
if (k1 == k) {
if (rules->Rule[i].priority > rules->Rule[i1].priority) {
item->action = a;
item->ruleindex = i;
}
return (1);
}
item = item->next;
}
return (0);
}
int takeactions(EN_Project *pr)
/*
**-----------------------------------------------------------
** Implements actions on action list
**-----------------------------------------------------------
*/
{
EN_Network *net = &pr->network;
hydraulics_t *hyd = &pr->hydraulics;
report_options_t *rep = &pr->report;
rules_t *rules = &pr->rules;
Action *a;
ActItem *item;
char flag;
int k, s, n;
double tol = 1.e-3, v, x;
n = 0;
item = rules->ActList;
while (item != NULL) {
flag = FALSE;
a = item->action;
k = a->link;
s = hyd->LinkStatus[k];
v = hyd->LinkSetting[k];
x = a->setting;
/* Switch link from closed to open */
if (a->status == IS_OPEN && s <= CLOSED) {
setlinkstatus(pr, k, 1, &hyd->LinkStatus[k], &hyd->LinkSetting[k]);
flag = TRUE;
}
/* Switch link from not closed to closed */
else if (a->status == IS_CLOSED && s > CLOSED) {
setlinkstatus(pr, k, 0, &hyd->LinkStatus[k], &hyd->LinkSetting[k]);
flag = TRUE;
}
/* Change link's setting */
else if (x != MISSING) {
switch (net->Link[k].Type) {
case EN_PRV:
case EN_PSV:
case EN_PBV:
x = x / pr->Ucf[PRESSURE];
break;
case EN_FCV:
x = x / pr->Ucf[FLOW];
break;
default:
break;
}
if (ABS(x - v) > tol) {
setlinksetting(pr, k, x, &hyd->LinkStatus[k], &hyd->LinkSetting[k]);
flag = TRUE;
}
}
/* Report rule action */
if (flag == TRUE) {
n++;
if (rep->Statflag)
writeruleaction(pr, k, rules->Rule[item->ruleindex].label);
}
/* Move to next action on list */
item = item->next;
}
return (n);
}
void ruleerrmsg(EN_Project *pr, int err)
/*
**-----------------------------------------------------------
** Reports error message
**-----------------------------------------------------------
*/
{
EN_Network *net = &pr->network;
parser_data_t *par = &pr->parser;
rules_t *rules = &pr->rules;
char **Tok = par->Tok;
int i;
char label[81];
char fmt[256];
switch (err) {
case 201:
strcpy(fmt, R_ERR201);
break;
case 202:
strcpy(fmt, R_ERR202);
break;
case 203:
strcpy(fmt, R_ERR203);
break;
case 204:
strcpy(fmt, R_ERR204);
break;
/*** Updated on 9/7/00 ***/
case 207:
strcpy(fmt, R_ERR207);
break;
case 221:
strcpy(fmt, R_ERR221);
break;
default:
return;
}
if (net->Nrules > 0) {
strcpy(label, t_RULE);
strcat(label, " ");
strcat(label, rules->Rule[net->Nrules].label);
} else
strcpy(label, t_RULES_SECT);
sprintf(pr->Msg, "%s", fmt);
strcat(pr->Msg, label);
strcat(pr->Msg, ":");
writeline(pr, pr->Msg);
strcpy(fmt, Tok[0]);
for (i = 1; i < par->Ntokens; i++) {
strcat(fmt, " ");
strcat(fmt, Tok[i]);
}
writeline(pr, fmt);
}
int writeRuleinInp(EN_Project *pr, FILE *f, int RuleIdx) {
/*
**-----------------------------------------------------------
** This function writes a specific rule (rule ID,
** premises, true and false actions and prioriry in the
** text input file.
** INPUT:
** - FILE *f : pointer to the .inp file to be written
** - int RuleIdx : index of the rule that needs to be written
** OUTPUT: error code
**-----------------------------------------------------------
*/
EN_Network *net = &pr->network;
rules_t *rules = &pr->rules;
Slink *Link = net->Link;
Stank *Tank = net->Tank;
Snode *Node = net->Node;
// int i,j;
Premise *p;
Action *a;
int hours = 0, minutes = 0, seconds = 0;
// the first condition/premises is different from the others because it starts
// with IF (but it is kept in memory as AND)
p = rules->Rule[RuleIdx].Pchain;
if (p->value == MISSING) {
fprintf(f, "\nIF ");
if ((strncmp(Object[p->object], "NODE", 4) == 0) ||
(strncmp(Object[p->object], "Junc", 4) == 0) ||
(strncmp(Object[p->object], "Reser", 5) == 0) ||
(strncmp(Object[p->object], "Tank", 4) == 0)) {
if (p->index <= net->Njuncs)
fprintf(f, "JUNCTION %s %s %s %s", Node[p->index].ID,
Varword[p->variable], Operator[p->relop], Value[p->status]);
else if (Tank[p->index - net->Njuncs].A == 0.0)
fprintf(f, "RESERVOIR %s %s %s %s", Node[p->index].ID,
Varword[p->variable], Operator[p->relop], Value[p->status]);
else
fprintf(f, "TANK %s %s %s %s", Node[p->index].ID, Varword[p->variable],
Operator[p->relop], Value[p->status]);
} else { // it is a link
if (Link[p->index].Type == EN_PIPE || Link[p->index].Type == EN_CVPIPE)
fprintf(f, "PIPE %s %s %s %s", Link[p->index].ID, Varword[p->variable],
Operator[p->relop], Value[p->status]);
else if (Link[p->index].Type == EN_PUMP)
fprintf(f, "PUMP %s %s %s %s", Link[p->index].ID, Varword[p->variable],
Operator[p->relop], Value[p->status]);
else
fprintf(f, "VALVE %s %s %s %s", Link[p->index].ID, Varword[p->variable],
Operator[p->relop], Value[p->status]);
}
} else {
if (p->variable == r_TIME) {
hours = (int)p->value / 3600;
minutes = (int)((p->value - 3600 * hours) / 60);
seconds = (int)(p->value - 3600 * hours - minutes * 60);
fprintf(f, "\nIF %s %s %s %d:%02d:%02d", Object[p->object],
Varword[p->variable], Operator[p->relop], hours, minutes,
seconds);
} else {
if (p->variable == r_CLOCKTIME) {
hours = (int)p->value / 3600;
minutes = (int)((p->value - 3600 * hours) / 60);
seconds = (int)(p->value - 3600 * hours - minutes * 60);
if (hours < 12)
fprintf(f, "\nIF %s %s %s %d:%02d:%02d AM", Object[p->object],
Varword[p->variable], Operator[p->relop], hours, minutes,
seconds);
else
fprintf(f, "\nIF %s %s %s %d:%02d:%02d PM", Object[p->object],
Varword[p->variable], Operator[p->relop], hours - 12, minutes,
seconds);
} else {
if (p->variable == r_FILLTIME || p->variable == r_DRAINTIME)
fprintf(f, "\nIF TANK %s %s %s %.4lf", Node[p->index].ID,
Varword[p->variable], Operator[p->relop], p->value / 3600.0);
else {
fprintf(f, "\nIF ");
if ((strncmp(Object[p->object], "NODE", 4) == 0) ||
(strncmp(Object[p->object], "Junc", 4) == 0) ||
(strncmp(Object[p->object], "Reser", 5) == 0) ||
(strncmp(Object[p->object], "Tank", 4) == 0)) {
if (p->index <= net->Njuncs)
fprintf(f, "JUNCTION %s %s %s %.4lf", Node[p->index].ID,
Varword[p->variable], Operator[p->relop], p->value);
else if (Tank[p->index - net->Njuncs].A == 0.0)
fprintf(f, "RESERVOIR %s %s %s %.4lf", Node[p->index].ID,
Varword[p->variable], Operator[p->relop], p->value);
else
fprintf(f, "TANK %s %s %s %.4lf", Node[p->index].ID,
Varword[p->variable], Operator[p->relop], p->value);
} else { // it is a link
if (Link[p->index].Type == EN_PIPE ||
Link[p->index].Type == EN_CVPIPE)
fprintf(f, "PIPE %s %s %s %.4lf", Link[p->index].ID,
Varword[p->variable], Operator[p->relop], p->value);
else if (Link[p->index].Type == EN_PUMP)
fprintf(f, "PUMP %s %s %s %.4lf", Link[p->index].ID,
Varword[p->variable], Operator[p->relop], p->value);
else
fprintf(f, "VALVE %s %s %s %.4lf", Link[p->index].ID,
Varword[p->variable], Operator[p->relop], p->value);
}
}
}
}
}
p = p->next;
while (p != NULL) // for all other premises/conditions write the corresponding
// logicOperator
{
if (p->value == MISSING) {
fprintf(f, "\n%s ", Ruleword[p->logop]);
if ((strncmp(Object[p->object], "NODE", 4) == 0) ||
(strncmp(Object[p->object], "Junc", 4) == 0) ||
(strncmp(Object[p->object], "Reser", 5) == 0) ||
(strncmp(Object[p->object], "Tank", 4) == 0)) {
if (p->index <= net->Njuncs)
fprintf(f, "JUNCTION %s %s %s %s", Node[p->index].ID,
Varword[p->variable], Operator[p->relop], Value[p->status]);
else if (Tank[p->index - net->Njuncs].A == 0.0)
fprintf(f, "RESERVOIR %s %s %s %s", Node[p->index].ID,
Varword[p->variable], Operator[p->relop], Value[p->status]);
else
fprintf(f, "TANK %s %s %s %s", Node[p->index].ID,
Varword[p->variable], Operator[p->relop], Value[p->status]);
} else { // it is a link
if (Link[p->index].Type == EN_PIPE || Link[p->index].Type == EN_CVPIPE)
fprintf(f, "PIPE %s %s %s %s", Link[p->index].ID,
Varword[p->variable], Operator[p->relop], Value[p->status]);
else if (Link[p->index].Type == EN_PUMP)
fprintf(f, "PUMP %s %s %s %s", Link[p->index].ID,
Varword[p->variable], Operator[p->relop], Value[p->status]);
else
fprintf(f, "VALVE %s %s %s %s", Link[p->index].ID,
Varword[p->variable], Operator[p->relop], Value[p->status]);
}
} else {
if (p->variable == r_TIME) {
hours = (int)p->value / 3600;
minutes = (int)((p->value - 3600 * hours) / 60);
seconds = (int)(p->value - 3600 * hours - minutes * 60);
fprintf(f, "\n%s %s %s %s %d:%02d:%02d", Ruleword[p->logop],
Object[p->object], Varword[p->variable], Operator[p->relop],
hours, minutes, seconds);
} else {
if (p->variable == r_CLOCKTIME) {
hours = (int)p->value / 3600;
minutes = (int)((p->value - 3600 * hours) / 60);
seconds = (int)(p->value - 3600 * hours - minutes * 60);
if (hours < 12)
fprintf(f, "\n%s %s %s %s %d:%02d:%02d AM", Ruleword[p->logop],
Object[p->object], Varword[p->variable], Operator[p->relop],
hours, minutes, seconds);
else
fprintf(f, "\n%s %s %s %s %d:%02d:%02d PM", Ruleword[p->logop],
Object[p->object], Varword[p->variable], Operator[p->relop],
hours - 12, minutes, seconds);
} else {
if (p->variable == r_FILLTIME || p->variable == r_DRAINTIME)
fprintf(f, "\n%s TANK %s %s %s %s %.4lf", Ruleword[p->logop],
Object[p->object], Node[p->index].ID, Varword[p->variable],
Operator[p->relop], p->value / 3600.0);
else {
fprintf(f, "\n%s ", Ruleword[p->logop]);
if ((strncmp(Object[p->object], "NODE", 4) == 0) ||
(strncmp(Object[p->object], "Junc", 4) == 0) ||
(strncmp(Object[p->object], "Reser", 5) == 0) ||
(strncmp(Object[p->object], "Tank", 4) == 0)) {
if (p->index <= net->Njuncs)
fprintf(f, "JUNCTION %s %s %s %.4lf", Node[p->index].ID,
Varword[p->variable], Operator[p->relop], p->value);
else if (Tank[p->index - net->Njuncs].A == 0.0)
fprintf(f, "RESERVOIR %s %s %s %.4lf", Node[p->index].ID,
Varword[p->variable], Operator[p->relop], p->value);
else
fprintf(f, "TANK %s %s %s %.4lf", Node[p->index].ID,
Varword[p->variable], Operator[p->relop], p->value);
} else { // it is a link
if (Link[p->index].Type == EN_PIPE ||
Link[p->index].Type == EN_CVPIPE)
fprintf(f, "PIPE %s %s %s %.4lf", Link[p->index].ID,
Varword[p->variable], Operator[p->relop], p->value);
else if (Link[p->index].Type == EN_PUMP)
fprintf(f, "PUMP %s %s %s %.4lf", Link[p->index].ID,
Varword[p->variable], Operator[p->relop], p->value);
else
fprintf(f, "VALVE %s %s %s %.4lf", Link[p->index].ID,
Varword[p->variable], Operator[p->relop], p->value);
}
}
}
}
}
p = p->next;
}
a = rules->Rule[RuleIdx].Tchain; // The first action in hte list of true actions
// starts with THEN
if (a->setting == MISSING) {
if (Link[a->link].Type == EN_PIPE || Link[a->link].Type == EN_CVPIPE)
fprintf(f, "\nTHEN PIPE %s STATUS IS %s", Link[a->link].ID,
Value[a->status]);
else if (Link[a->link].Type == EN_PUMP)
fprintf(f, "\nTHEN PUMP %s STATUS IS %s", Link[a->link].ID,
Value[a->status]);
else
fprintf(f, "\nTHEN VALVE %s STATUS IS %s", Link[a->link].ID,
Value[a->status]);
} else {
if (Link[a->link].Type == EN_PIPE || Link[a->link].Type == EN_CVPIPE)
fprintf(f, "\nTHEN PIPE %s SETTING IS %.4f", Link[a->link].ID,
a->setting);
else if (Link[a->link].Type == EN_PUMP)
fprintf(f, "\nTHEN PUMP %s SETTING IS %.4f", Link[a->link].ID,
a->setting);
else
fprintf(f, "\nTHEN VALVE %s SETTING IS %.4f", Link[a->link].ID,
a->setting);
}
a = a->next; // The other actions in the list of true actions start with AND
while (a != NULL) {
if (a->setting == MISSING) {
if (Link[a->link].Type == EN_PIPE || Link[a->link].Type == EN_CVPIPE)
fprintf(f, "\nAND PIPE %s STATUS IS %s", Link[a->link].ID,
Value[a->status]);
else if (Link[a->link].Type == EN_PUMP)
fprintf(f, "\nAND PUMP %s STATUS IS %s", Link[a->link].ID,
Value[a->status]);
else
fprintf(f, "\nAND VALVE %s STATUS IS %s", Link[a->link].ID,
Value[a->status]);
} else {
if (Link[a->link].Type == EN_PIPE || Link[a->link].Type == EN_CVPIPE)
fprintf(f, "\nAND PIPE %s SETTING IS %.4f", Link[a->link].ID,
a->setting);
else if (Link[a->link].Type == EN_PUMP)
fprintf(f, "\nAND PUMP %s SETTING IS %.4f", Link[a->link].ID,
a->setting);
else
fprintf(f, "\nAND VALVE %s SETTING IS %.4f", Link[a->link].ID,
a->setting);
}
a = a->next;
}
a = rules->Rule[RuleIdx].Fchain; // The first action in the list of false actions
// starts with ELSE
if (a != NULL) {
if (a->setting == MISSING) {
if (Link[a->link].Type == EN_PIPE || Link[a->link].Type == EN_CVPIPE)
fprintf(f, "\nELSE PIPE %s STATUS IS %s", Link[a->link].ID,
Value[a->status]);
else if (Link[a->link].Type == EN_PUMP)
fprintf(f, "\nELSE PUMP %s STATUS IS %s", Link[a->link].ID,
Value[a->status]);
else
fprintf(f, "\nELSE VALVE %s STATUS IS %s", Link[a->link].ID,
Value[a->status]);
} else {
if (Link[a->link].Type == EN_PIPE || Link[a->link].Type == EN_CVPIPE)
fprintf(f, "\nELSE PIPE %s SETTING IS %.4f", Link[a->link].ID,
a->setting);
else if (Link[a->link].Type == EN_PUMP)
fprintf(f, "\nELSE PUMP %s SETTING IS %.4f", Link[a->link].ID,
a->setting);
else
fprintf(f, "\nELSE VALVE %s SETTING IS %.4f", Link[a->link].ID,
a->setting);
}
a = a->next; // The other actions in the list of false actions start with
// AND
while (a != NULL) {
if (a->setting == MISSING) {
if (Link[a->link].Type == EN_PIPE || Link[a->link].Type == EN_CVPIPE)
fprintf(f, "\nAND PIPE %s STATUS IS %s", Link[a->link].ID,
Value[a->status]);
else if (Link[a->link].Type == EN_PUMP)
fprintf(f, "\nAND PUMP %s STATUS IS %s", Link[a->link].ID,
Value[a->status]);
else
fprintf(f, "\nAND VALVE %s STATUS IS %s", Link[a->link].ID,
Value[a->status]);
} else {
if (Link[a->link].Type == EN_PIPE || Link[a->link].Type == EN_CVPIPE)
fprintf(f, "\nAND PIPE %s SETTING IS %.4f", Link[a->link].ID,
a->setting);
else if (Link[a->link].Type == EN_PUMP)
fprintf(f, "\nAND PUMP %s SETTING IS %.4f", Link[a->link].ID,
a->setting);
else
fprintf(f, "\nAND VALVE %s SETTING IS %.4f", Link[a->link].ID,
a->setting);
}
a = a->next;
}
}
if (rules->Rule[RuleIdx].priority != 0)
fprintf(f, "\nPRIORITY %.4f", rules->Rule[RuleIdx].priority);
return (0);
}
/***************** END OF RULES.C ******************/