This document inventories the exact files, structs, functions, and control flow in SWMM 5.2 that implement Named Variables and Arithmetic Expressions for control rules, including how they are parsed from INP, stored, and evaluated during simulation.
swmm/src/solver/controls.c: rule parsing, named variables, expressions, evaluationswmm/src/solver/input.c: INP parsing, rule section handling, countsswmm/src/solver/mathexpr.candmathexpr.h: expression tokenizer, parser, evaluatorswmm/src/solver/keywords.candtext.h: control-specific keywordsswmm/src/solver/routing.candproject.c: simulation integration points
- Control keywords:
w_RULE,w_IF,w_AND,w_OR,w_THEN,w_ELSE,w_PRIORITY - New tokens in 5.2:
w_VARIABLE,w_EXPRESSION(seetext.handkeywords.c)
#define w_RULE "RULE"
#define w_IF "IF"
#define w_AND "AND"
#define w_OR "OR"
#define w_ELSE "ELSE"
#define w_PRIORITY "PRIORITY"
#define w_VARIABLE "VARIABLE"
#define w_EXPRESSION "EXPRESSION"char* RuleKeyWords[] = { w_RULE, w_IF, w_AND, w_OR, w_THEN, w_ELSE,
w_PRIORITY, NULL};TVariable: identifies an attribute of an object (object type, index, attribute)TNamedVariable: maps anameto aTVariableTExpression: stores an expressionnameand compiledMathExpr*TPremise: one clause; holds either an expression index on LHS or an LHSTVariable, a relation, and an RHS variable or numeric valueTAction,TActionList,TRule: actions and rule definitions
struct TVariable { int object; int index; int attribute; };
struct TNamedVariable { struct TVariable variable; char name[MAXVARNAME+1]; };
struct TExpression { MathExpr* expression; char name[MAXVARNAME+1]; };
struct TPremise { int type; int exprIndex; struct TVariable lhsVar; struct TVariable rhsVar; int relation; double value; struct TPremise *next; };Shared globals for rule system:
Rules,ActionList,RuleCountNamedVariable(array),Expression(array), their counts and current indices
int VariableCount; int ExpressionCount; int CurrentVariable; int CurrentExpression;
struct TNamedVariable* NamedVariable; struct TExpression* Expression;- First pass counts variables/expressions and rules
for (i = 0; i < MAX_OBJ_TYPES; i++) Nobjects[i] = 0; ...
controls_init();
...
case s_CONTROL:
if ( match(id, w_RULE) ) Nobjects[CONTROL]++;
else controls_addToCount(id);controls_addToCountincrementsVariableCountorExpressionCountif the line starts with the respective keyword.
void controls_addToCount(char* s) { if (match(s, w_VARIABLE)) VariableCount++; else if (match(s, w_EXPRESSION)) ExpressionCount++; }- Allocation during project create
// --- create control rules
ErrorCode = controls_create(Nobjects[CONTROL]);if (VariableCount > 0) NamedVariable = calloc(VariableCount, sizeof(TNamedVariable));
if (ExpressionCount > 0) Expression = calloc(ExpressionCount, sizeof(TExpression));- Second pass parses lines in
[CONTROLS]
if (match(tok[0], w_VARIABLE)) return controls_addVariable(tok, ntoks);
if (match(tok[0], w_EXPRESSION)) return controls_addExpression(tok, ntoks);
// otherwise, a RULE clause
keyword = findmatch(tok[0], RuleKeyWords);
return controls_addRuleClause(index, keyword, Tok, Ntokens);controls_addVariableenforces uniqueness vs attribute names and parsesVARIABLE name = <object ...>orSIMULATIONvariants.
CurrentVariable++;
if (findExactMatch(tok[1], AttribWords) >= 0) return error_setInpError(ERR_KEYWORD, tok[1]);
... getPremiseVariable(...,&v1);
NamedVariable[k].variable = v1; sstrncpy(NamedVariable[k].name, tok[1], MAXVARNAME);controls_addExpressioncomposes the formula string and compiles it withmathexpr_create, providing a callbackgetVariableIndexto resolve named-variable identifiers.
Expression[k].expression = NULL; sstrncpy(Expression[k].name, tok[1], MAXVARNAME);
// join tokens[3..]
expr = mathexpr_create(s, getVariableIndex);
Expression[k].expression = expr;- For a premise, LHS can be either an expression name or a named variable; otherwise it’s an object/id/attribute triple parsed by
getPremiseVariable.
exprIndex = getExpressionIndex(tok[1]);
if (exprIndex < 0) { varIndex = getVariableIndex(tok[n]); if (varIndex >= 0) v1 = NamedVariable[varIndex].variable; else err = getPremiseVariable(...,&v1); }- RHS can be a named variable, another object/id/attribute, or a literal value parsed by
getPremiseValue.
varIndex = getVariableIndex(tok[n]); if (varIndex >= 0) v2 = NamedVariable[varIndex].variable; else { obj = findmatch(tok[n], ObjectWords); if (obj >= 0) getPremiseVariable(...,&v2); else getPremiseValue(tok[n], v1.attribute, &value); }getVariableIndex(name)scansNamedVariable[]for a matchgetNamedVariableValue(idx)fetches live value viagetVariableValue(TVariable)getExpressionIndex(name)scansExpression[]
for (i=0;i<VariableCount;i++) if (match(varName, NamedVariable[i].name)) return i;
...
return getVariableValue(NamedVariable[varIndex].variable);- Rules evaluated each routing step when rule-time is reached
controls_evaluate(currentDate, currentDate - StartDateTime, routingStep / SECperDAY);- Evaluation iterates rules, evaluates premises, and enqueues actions
while (p) { result = (p->type==r_OR) ? (result || evaluatePremise(p,tStep)) : (result && evaluatePremise(p,tStep)); p=p->next; }
... updateActionValue(...); updateActionList(...);- Premise evaluation: if LHS is an expression, evaluate with
mathexpr_eval, passing callback to resolve named variable values; otherwise evaluate LHS variable value viagetVariableValue. RHS is either a variable value or literal. Comparison usescompareTimesorcompareValues.
if (p->exprIndex >= 0) lhsValue = mathexpr_eval(Expression[p->exprIndex].expression, getNamedVariableValue);
else lhsValue = getVariableValue(p->lhsVar);
if (p->value == MISSING) rhsValue = getVariableValue(p->rhsVar); else rhsValue = p->value;
... switch(p->lhsVar.attribute) { case r_TIME... default: return compareValues(...); }getVariableValuesupports SIMULATION time/date/day/month, and object attributes for nodes and links, plus rain gage attributes.
case r_TIME: return ElapsedTime; case r_DATE: return CurrentDate; case r_CLOCKTIME: return CurrentTime; ... diverse node/link attributes- Tokenizer supports numbers, variable names (resolved by
getVariableIndex), functions, and operators; builds an expression tree then a postfix linked list (MathExpr) - Supported functions and operators match the manual; evaluator uses a local stack and callback
getVariableValue(int)to fetch the live value of named variables
char *MathFunc[] = {"COS","SIN","TAN","COT","ABS","SGN","SQRT","LOG","EXP","ASIN","ACOS","ATAN","ACOT","SINH","COSH","TANH","COTH","LOG10","STEP", NULL};double mathexpr_eval(MathExpr *expr, double (*getVariableValue) (int)) { ... switch(node->opcode) { case 3:+, 4:-, 5:*, 6:/, 31:^, 7:number, 8:variable (via callback), 9:negation, 10..28: math functions } }- SWMM core does not round-trip write INP files from the solver; this is typically handled in UI projects. The core does not include a writer for
[CONTROLS]back to INP. The features are fully supported for reading and runtime evaluation.
controls_addVariablerejects variable names that collide with attribute keywordscontrols_addExpressionreturnsERR_MATH_EXPRif the expression fails to compileaddPremisewarns if LHS and RHS attributes differ when both are variables (non-expression)
if (findExactMatch(tok[1], AttribWords) >= 0) return error_setInpError(ERR_KEYWORD, tok[1]);expr = mathexpr_create(s, getVariableIndex);
if (expr == NULL) return error_setInpError(ERR_MATH_EXPR, "");if (exprIndex < 0 && v1.attribute != v2.attribute) report_writeWarningMsg(WARN11, Rules[r].ID);- Count pass: detect
VARIABLEandEXPRESSIONlines, count rules - Create: allocate arrays for rules, named vars, expressions
- Parse pass: add variables and expressions; build rules with possible expression LHS and variable RHS
- Simulation: at each rule evaluation step, evaluate clause values; expression LHS uses
mathexpr_evalwith named variable callback - Execute: apply actions with priority handling
- The expression engine is self-contained (
mathexpr.c/h) with callback hooks for variable name resolution and value retrieval - Named variables are resolved by name at parse/compile time and evaluated by index at runtime
- Premise clauses allow either expressions or plain variables on LHS; RHS is variable or literal
- Enums:
RuleState,RuleObject,RuleAttrib,RuleRelation,RuleSetting
enum RuleState {r_RULE, r_IF, r_AND, r_OR, r_THEN, r_ELSE, r_PRIORITY,
r_VARIABLE, r_EXPRESSION, r_ERROR};
enum RuleObject {r_GAGE, r_NODE, r_LINK, r_CONDUIT, r_PUMP, r_ORIFICE,
r_WEIR, r_OUTLET, r_SIMULATION};
enum RuleAttrib {r_DEPTH, r_MAXDEPTH, r_HEAD, r_VOLUME, r_INFLOW,
r_FLOW, r_FULLFLOW, r_FULLDEPTH, r_STATUS, r_SETTING,
r_LENGTH, r_SLOPE, r_VELOCITY, r_TIMEOPEN, r_TIMECLOSED,
r_TIME, r_DATE, r_CLOCKTIME, r_DAYOFYEAR, r_DAY, r_MONTH};
enum RuleRelation {EQ, NE, LT, LE, GT, GE};
enum RuleSetting {r_CURVE, r_TIMESERIES, r_PID, r_NUMERIC};- Keyword tables:
ObjectWords[],AttribWords[],RelOpWords[],StatusWords[],ConduitWords[],SettingTypeWords[]
static char* ObjectWords[] = {"GAGE","NODE","LINK","CONDUIT","PUMP","ORIFICE","WEIR","OUTLET","SIMULATION", NULL};
static char* AttribWords[] = {"DEPTH","MAXDEPTH","HEAD","VOLUME","INFLOW","FLOW","FULLFLOW","FULLDEPTH","STATUS","SETTING","LENGTH","SLOPE","VELOCITY","TIMEOPEN","TIMECLOSED","TIME","DATE","CLOCKTIME","DAYOFYEAR","DAY","MONTH", NULL};- Lifecycle
controls_init()initializes globalscontrols_addToCount(char* s)counts VARIABLE/EXPRESSION linescontrols_create(int n)allocates arrays for rules, named variables, expressionscontrols_delete()frees expressions, variables, rules, and action list
void controls_init() { Rules = NULL; NamedVariable = NULL; Expression = NULL; RuleCount = 0; VariableCount = 0; ExpressionCount = 0; }- INP Parsing and Rule Building
controls_addVariable(char* tok[], int nToks)adds named variablecontrols_addExpression(char* tok[], int nToks)compiles and stores expressioncontrols_addRuleClause(int r, int keyword, char* tok[], int nToks)dispatches toaddPremiseoraddActionaddPremise(...)parses a premise: possible expression LHS or named variable, relation, and RHS variable or valuegetPremiseVariable(...)parses<OBJECT ID ATTRIBUTE>orSIMULATION ATTRIBUTEgetPremiseValue(...)parses RHS literal given attribute context (supports time/date/status)addAction(...)parses control actions and optional modulated settings (Curve, Time Series, PID)setActionSetting(...)handles action setting types
int addPremise(int r, int type, char* tok[], int nToks) { ... p->exprIndex = exprIndex; p->lhsVar = v1; p->rhsVar = v2; p->relation = relation; p->value = value; }int getPremiseVariable(char* tok[], int nToks, int* k, struct TVariable* v);int getPremiseValue(char* token, int attrib, double* value);int addAction(int r, char* tok[], int nToks); // validates object type, attribute, and settingint setActionSetting(char* tok[], int nToks, int* curve, int* tseries, int* attrib, double values[]);- Expression/Variable Lookup
getVariableIndex(char* varName);getNamedVariableValue(int varIndex)getExpressionIndex(char* exprName)
int getVariableIndex(char* varName);
double getNamedVariableValue(int varIndex);
int getExpressionIndex(char* exprName);- Evaluation
controls_evaluate(DateTime currentTime, DateTime elapsedTime, double tStep)iterates rules, evaluates premises, updates action list, executes actionsevaluatePremise(struct TPremise* p, double tStep)computes lhs/rhs and comparesgetVariableValue(struct TVariable v)retrieves live valuescompareTimes(...),compareValues(...)- Action list helpers:
updateActionValue,getPIDSetting,updateActionList,executeActionList,clearActionList,deleteActionList,deleteRules
int controls_evaluate(DateTime currentTime, DateTime elapsedTime, double tStep);int evaluatePremise(struct TPremise* p, double tStep);double getVariableValue(struct TVariable v);- Rule evaluation is invoked from the routing step logic when the rule time is reached (uses internal scheduling like
RuleStep)
if (RuleStep == 0 || fabs(NewRoutingTime - NewRuleTime) < 1.0)
{ controls_evaluate(currentDate, currentDate - StartDateTime, routingStep / SECperDAY); }w_RULE_STEPoption keyword exists to configure rule evaluation frequency
#define w_RULE_STEP "RULE_STEP"- Creation and evaluation API:
MathExpr* mathexpr_create(char* s, int (*getVar) (char *));
double mathexpr_eval(MathExpr* expr, double (*getVal) (int));
void mathexpr_delete(MathExpr* expr);- Operators and functions supported (opcode mapping):
// 7 number, 8 variable, 31 ^, and functions: cos,sin,tan,cot,abs,sgn,sqrt,log,exp,asin,acos,atan,acot,sinh,cosh,tanh,coth,log10,step- Read:
[CONTROLS]section processed in two passes; variable/expression lines handled before rule clauses. - Write: The SWMM solver library does not implement INP writing of control definitions; UI/front-ends handle INP serialization. Only input summaries are written (
inputrpt.c).
- Mixed-attribute variable-to-variable comparisons (non-expression) generate warning
WARN11during premise parsing.
if (exprIndex < 0 && v1.attribute != v2.attribute)
report_writeWarningMsg(WARN11, Rules[r].ID);