Below is a walkthrough of controls.c
from EPA SWMM5. This file handles rule-based controls, allowing a user to write logic rules (in the model input file) that dynamically change pump, gate, or orifice settings during a simulation. The code parses control rules, stores them, and executes them at each simulation time step.
1. Purpose of the File
controls.c
implements SWMM’s “Advanced Control Rules.”- These rules are text-based, with an
IF <condition> THEN <action> ELSE <action>
format plus an optionalPRIORITY
. - The code:
- Parses the rules from the input file into data structures.
- Evaluates those rules every time step to see which actions apply.
- Executes actions (e.g., change a pump from ON to OFF or set an orifice opening).
2. Overall Flow
- During input parsing, each line that starts with
RULE
,IF
,THEN
,ELSE
,PRIORITY
,VARIABLE
, orEXPRESSION
is handled by functions in this file. - Variables (or expressions) are defined that can be referenced in rule premises.
- Premises are stored in a linked list of conditions (e.g., “Node 123 Depth > 4”).
- Actions (e.g., “Pump XYZ status = OFF”) are stored similarly, but separate from premises.
- Each simulation time step:
controls_evaluate()
goes through every rule, checks the premises, determines ifTHEN
orELSE
actions should fire, and records which actions to apply.- The highest‐priority action for any single link overrides any lower‐priority actions for that link in that time step.
- These actions then modify the link’s
targetSetting
(the new setting for a pump, orifice, etc.).
3. Key Data Structures
3.1 TRule
(one per rule)
struct TRule
{
char* ID; // rule's name/ID
double priority; // rule's priority
TPremise* firstPremise; // pointer to the first premise (condition)
TPremise* lastPremise; // pointer to the last premise
TAction* thenActions; // linked list of actions if premises are true
TAction* elseActions; // linked list of actions if premises are false
};
- A single rule can have multiple “premise” lines (joined by AND/OR) and multiple “actions” in the
THEN
orELSE
block.
3.2 TPremise
(linked list of conditions within each rule)
struct TPremise
{
int type; // r_IF, r_AND, or r_OR
int exprIndex; // if premise references a math expression
TVariable lhsVar; // left-hand side variable
TVariable rhsVar; // right-hand side variable
int relation; // relational operator (>, <, =, etc.)
double value; // numerical value on the right-hand side (if not a variable)
TPremise *next; // pointer to next premise in this rule
};
- Example premise:
Node 10 Depth > 5.0
lhsVar
= (object = NODE, index=10, attribute=DEPTH)relation
= GT (>)value
= 5.0
3.3 TAction
(linked list of actions in the THEN/ELSE blocks)
struct TAction
{
int rule; // index of the rule
int link; // link index being controlled
int attribute; // e.g. STATUS, SETTING, etc.
int curve; // reference to a curve if modulated by a curve
int tseries; // reference to a time series if modulated
double value; // direct numeric setting
double kp, ki, kd; // PID coefficients if using a PID controller
double e1, e2; // stored PID errors
TAction* next; // next action in THEN or ELSE
};
- Example action:
Pump P1 status = OFF
link
= index of link P1attribute
= STATUSvalue
= 0 (OFF)
3.4 TNamedVariable
and TExpression
struct TNamedVariable
{
TVariable variable;
char name[MAXVARNAME]; // the variable's name used in rules
};
struct TExpression
{
MathExpr* expression;
char name[MAXVARNAME];
};
- A named variable might be declared in the input file as
VARIABLE MyVar = Node 123 Depth
. - An expression might be declared as
EXPRESSION MyExpr = MyVar + 1.5
.
3.5 Global Arrays
Rules
: an array of sizeRuleCount
.NamedVariable
: array of named variables.Expression
: array of parsed math expressions.
4. Main Functions
4.1 Initialization & Cleanup
-
controls_init()
- Sets up defaults (Rule array pointers =
NULL
, counters = 0, etc.).
- Sets up defaults (Rule array pointers =
-
controls_addToCount(char* s)
- If a line in the input is
VARIABLE ...
, increment the count of variables to be stored. - If a line is
EXPRESSION ...
, increment the count of expressions.
- If a line in the input is
-
controls_create(int n)
- Allocates memory for the global
Rules
array of sizen
. - Also allocates arrays for any named variables or expressions.
- Allocates memory for the global
-
controls_delete()
- Frees memory used by rules, premises, actions, expressions, etc.
4.2 Adding Variables & Expressions
-
controls_addVariable(...)
- Input format:
VARIABLE varName = Node ID Depth
(orSIMULATION TIME
, etc.) - Stores a new
TNamedVariable
inNamedVariable[]
so it can be referenced in rule premises or expressions.
- Input format:
-
controls_addExpression(...)
- Input format:
EXPRESSION exprName = <math expression>
- Uses
mathexpr_create(...)
to parse the expression into tokens. - This expression can be used later in premises, for example:
IF MyExpr > 6.0 THEN ...
- Input format:
4.3 Creating & Parsing Rules
controls_addRuleClause(r, keyword, tok[], nToks)
- Called once a rule is encountered.
- Dispatches to
addPremise(...)
if the clause isIF
,AND
,OR
, or toaddAction(...)
ifTHEN
,ELSE
. - Also handles
PRIORITY
.
4.3.1 Premises
addPremise(r, type, tok, nToks)
- Creates a new
TPremise
(e.g.,Node 10 Depth > 5.0
). - If the premise’s right‐hand side is another variable, it sets that in
rhsVar
; otherwise, it stores a numericvalue
.
- Creates a new
4.3.2 Actions
addAction(r, tok, nToks)
- Creates a
TAction
, e.g., “Pump P1 status = OFF.” - Identifies if the action is a direct numeric setting, or references a “Curve,” “TimeSeries,” or “PID.”
- Creates a
5. Rule Evaluation
5.1 controls_evaluate(currentTime, elapsedTime, tStep)
-
Sets some global time info (
CurrentDate
,CurrentTime
, etc.). -
Resets an ActionList (a temporary list of actions triggered this time step).
-
Loops over every rule
r
:- Evaluate premises (
TRUE
orFALSE
).- Each premise can be
AND
orOR
type:- For
AND
, all premises must beTRUE
to satisfy. - For
OR
, if any premise is true, the entire premise chain is satisfied.
- For
- Each premise can be
- If premises are TRUE, pick the THEN actions; otherwise the ELSE actions.
- For each action:
- Possibly compute a modulated value from a curve/time series or apply a PID loop.
- Add the action to the
ActionList
with the rule’s priority.
- Evaluate premises (
-
After all rules have been processed,
executeActionList(...)
is called:- Goes through the
ActionList
. - If multiple rules propose conflicting actions for the same link, only the highest‐priority action is used.
- The winning action’s
value
is written to the link’stargetSetting
.
- Goes through the
5.2 Premise Evaluation
evaluatePremise(TPremise* p, double tStep)
- Retrieves the lhsValue (left-hand side): either from a variable or an expression.
- Retrieves the rhsValue: from a numeric constant or from a variable.
- If it’s a time-based attribute (
TIME
,CLOCKTIME
, etc.), usescompareTimes(...)
with a half time-step tolerance so that an event ofTIME = 10:00
is true if the time is within ± (tStep/2). - Otherwise uses
compareValues(lhsValue, relation, rhsValue)
(e.g.,lhsValue > rhsValue
). - The function returns TRUE or FALSE.
6. Special Features
-
Named Variables
- Declared as
VARIABLE MyVar = ...
. Allows referencing a node depth or flow by a simpler name.
- Declared as
-
Expressions
- Declared as
EXPRESSION MyExpr = MyVar + 3.0
(or any math formula). - The code uses
mathexpr_create(...)
andmathexpr_eval(...)
to parse and evaluate these on the fly during premise evaluation.
- Declared as
-
PID Control
- A rule action can be
Pump P1 setting = PID Kp Ki Kd
, providing continuous feedback control. - The code in
getPIDSetting(...)
implements the standard discrete‐time PID formula, adjusting the link’s setting each time step based on the error between a “SetPoint” and the measured “ControlValue.”
- A rule action can be
-
Time/Date Attributes
- A premise can check
SIMULATION TIME
,DATE
,CLOCKTIME
,DAYOFYEAR
, etc. - The code compares times with a half-step offset to avoid missing an exact match.
- A premise can check
7. Action Execution & Priority
7.1 ActionList
- A temporary singly linked list of type
TActionList
that collects all actions triggered in the current time step. - Each node points to a
TAction
.
7.2 updateActionList(a)
- If the same link is already on the list, compare rule priorities.
- The highest priority action wins for that link.
7.3 executeActionList(currentTime)
- Actually sets
Link[a->link].targetSetting = a->value
for each action. - Counts how many new settings changed from the previous time step.
- Optionally writes a log of the action to the report file if
RptFlags.controls
is enabled.
8. Cleanup Routines
deleteActionList()
: frees theActionList
linked list.deleteRules()
: for each rule, frees all premise nodes (TPremise
) and action nodes (TAction
). Finally frees theRules
array.
9. Key Takeaways
-
Rule Grammar
RULE name
IF/AND/OR <premise>
linesTHEN/ELSE <action>
linesPRIORITY <p>
-
Premise
- Compares attributes (depth, inflow, date, time, etc.) of nodes/links/gages to either numeric values or other attributes.
- You can also use named variables or math expressions.
-
Action
- Sets the status or setting of a link object (pump, orifice, weir, or conduit).
- Could be a constant, or modulated by a curve/time series, or governed by PID.
-
Execution
- Each time step, all rules are evaluated.
- The highest‐priority action for each link takes effect.
- The link’s
targetSetting
is updated accordingly, which the hydraulic solver then uses.
-
Integration with SWMM
- Rules do not directly override the actual computed flow in a link; they only set the link’s target status or setting.
- The hydraulic solver then uses that status/setting as a boundary condition.
In short, controls.c
is the logic engine for SWMM’s Rule-Based Controls, handling everything from parsing the rule statements to actually applying them to the hydraulic model at each time step.
No comments:
Post a Comment