Saturday, December 28, 2024

SWMM5 Controls.c Code

 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 optional PRIORITY.
  • The code:
    1. Parses the rules from the input file into data structures.
    2. Evaluates those rules every time step to see which actions apply.
    3. Executes actions (e.g., change a pump from ON to OFF or set an orifice opening).

2. Overall Flow

  1. During input parsing, each line that starts with RULE, IF, THEN, ELSE, PRIORITY, VARIABLE, or EXPRESSION is handled by functions in this file.
  2. Variables (or expressions) are defined that can be referenced in rule premises.
  3. Premises are stored in a linked list of conditions (e.g., “Node 123 Depth > 4”).
  4. Actions (e.g., “Pump XYZ status = OFF”) are stored similarly, but separate from premises.
  5. Each simulation time step:
    • controls_evaluate() goes through every rule, checks the premises, determines if THEN or ELSE 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 or ELSE 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 P1
    • attribute = STATUS
    • value = 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 size RuleCount.
  • NamedVariable: array of named variables.
  • Expression: array of parsed math expressions.

4. Main Functions

4.1 Initialization & Cleanup

  1. controls_init()

    • Sets up defaults (Rule array pointers = NULL, counters = 0, etc.).
  2. 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.
  3. controls_create(int n)

    • Allocates memory for the global Rules array of size n.
    • Also allocates arrays for any named variables or expressions.
  4. 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 (or SIMULATION TIME, etc.)
    • Stores a new TNamedVariable in NamedVariable[] so it can be referenced in rule premises or expressions.
  • 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 ...

4.3 Creating & Parsing Rules

  • controls_addRuleClause(r, keyword, tok[], nToks)
    • Called once a rule is encountered.
    • Dispatches to addPremise(...) if the clause is IF, AND, OR, or to addAction(...) if THEN, 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 numeric value.

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.”

5. Rule Evaluation

5.1 controls_evaluate(currentTime, elapsedTime, tStep)

  1. Sets some global time info (CurrentDate, CurrentTime, etc.).

  2. Resets an ActionList (a temporary list of actions triggered this time step).

  3. Loops over every rule r:

    1. Evaluate premises (TRUE or FALSE).
      • Each premise can be AND or OR type:
        • For AND, all premises must be TRUE to satisfy.
        • For OR, if any premise is true, the entire premise chain is satisfied.
    2. If premises are TRUE, pick the THEN actions; otherwise the ELSE actions.
    3. 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.
  4. 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’s targetSetting.

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.), uses compareTimes(...) with a half time-step tolerance so that an event of TIME = 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

  1. Named Variables

    • Declared as VARIABLE MyVar = .... Allows referencing a node depth or flow by a simpler name.
  2. Expressions

    • Declared as EXPRESSION MyExpr = MyVar + 3.0 (or any math formula).
    • The code uses mathexpr_create(...) and mathexpr_eval(...) to parse and evaluate these on the fly during premise evaluation.
  3. 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.”
  4. 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.

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 the ActionList linked list.
  • deleteRules(): for each rule, frees all premise nodes (TPremise) and action nodes (TAction). Finally frees the Rules array.

9. Key Takeaways

  1. Rule Grammar

    • RULE name
    • IF/AND/OR <premise> lines
    • THEN/ELSE <action> lines
    • PRIORITY <p>
  2. 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.
  3. 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.
  4. 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.
  5. 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:

SWMM5 Delphi GUI Dabout.pas Summary

 The Dabout.pas unit is part of the EPA SWMM (Storm Water Management Model) project and contains the logic for the "About" dialo...