Water network controls

One of the key features of water network models is the ability to control pipes, pumps, and valves using simple and complex conditions. EPANET uses “controls” and “rules” to define conditions [Ross00]. WNTR replicates EPANET functionality, and includes additional options, as described below.

Controls are defined using an “IF condition; THEN action” format. Controls use a single action (i.e., closing/opening a link or changing the setting) based on a single condition (i.e., time based or tank level based). If a time based or tank level condition is not exactly matched at a simulation timestep, controls make use of partial timesteps to match the condition before the control is deployed. Controls in WNTR emulate EPANET controls.

Rules are more complex; rules are defined using an “IF condition; THEN action1; ELSE action2” format, where the ELSE block is optional. Rules can use multiple conditions and multiple actions in each of the logical blocks. Rules can also be prioritized to set the order of operation. If rules with conflicting actions should occur at the same time, the rule with the highest priority will override all others. Rules operate on a rule timestep specified by the user, which can be different from the simulation timestep. Rules in WNTR emulate EPANET rules.

When generating a water network model from an EPANET INP file, WNTR generates controls and rules based on input from the [CONTROLS] and [RULES] sections. These controls and rules are then used when simulating hydraulics with either the EpanetSimulator or the WNTRSimulator. Controls and rules can also be defined directly in WNTR using the API described below. WNTR includes additional options to define controls and rules that can be used by the WNTRSimulator.

The basic steps to define a control or rule are:

  1. Define the action(s)
  2. Define condition(s) (i.e., define what should cause the action to occur)
  3. Define the control or rule using the action(s) and condition(s)
  4. Add the control or rule to the water network model

These steps are defined below. Examples use the “Net3.inp” EPANET INP file to generate the water network model object, called wn.

Actions

Control and rule actions tell the simulator what to do when a condition becomes “true.” Actions are created using the ControlAction class. An action is defined by a target link, the attribute to change, and the value to change it to. The following example creates an action that opens pipe 330:

>>> import wntr.network.controls as controls
>>> pipe = wn.get_link('330')
>>> act1 = controls.ControlAction(pipe, 'status', 1)
>>> print(act1)
set Pipe('330').status to Open

Conditions

Conditions define when an action should occur. The condition classes are listed in Table 6.

Table 6 Condition Classes
Condition class Description
TimeOfDayCondition Time-of-day or “clocktime” based condition statement
SimTimeCondition Condition based on time since start of the simulation
ValueCondition Compare a network element attribute to a set value
RelativeCondition Compare attributes of two different objects (e.g., levels from tanks 1 and 2)
OrCondition Combine two WNTR Conditions with an OR
AndCondition Combine two WNTR Conditions with an AND

All of the above conditions are valid EpanetSimulator conditions except RelativeCondition. The EpanetSimulator is also limited to always repeat conditions that are defined with TimeOfDayCondition and not repeat conditions that are defined with in SimTimeCondition. The WNTRSimulator can handle repeat or not repeat options for both of these conditions.

Controls

A control is created in WNTR with the Control class, which takes an instance of any of the above conditions, and an action that should occur when the condition is true.

In the following example, a conditional control is defined that opens pipe 330 if the level of tank 1 goes above 46.0248 m. The target is the tank and the attribute is the tanks level. To specify that the condition should be true when the level is greater than the threshold, the operation is set to > and the threshold is set to 46.0248. The action act1 from above is used in the control.

>>> tank = wn.get_node('1')
>>> cond1 = controls.ValueCondition(tank, 'level', '>', 46.0248)
>>> print(cond1)
Tank('1').level > 46.0248

>>> ctrl1 = controls.Control(cond1, act1, name='control1')
>>> print(ctrl1)
Control control1 := if Tank('1').level > 46.0248 then set Pipe('330').status to Open with priority 3

In the following example, a time-based control is defined that opens Pump 10 at hour 121. A new action is defined that opens the pump.

>>> pump = wn.get_link('10')
>>> act2 = controls.ControlAction(pump, 'status', 1)
>>> cond2 = controls.SimTimeCondition(wn, '=', '121:00:00')
>>> print(cond2)
sim_time = 435600 sec

>>> ctrl2 = controls.Control(cond2, act2, name='control2')
>>> print(ctrl2)
Control control2 := if sim_time = 435600 sec then set HeadPump('10').status to Open with priority 3

Rules

A rule is created in WNTR with the Rule class, which takes any of the above conditions, a list of actions that should occur when the condition is true, and an optional list of actions that should occur when the condition is false. Rules also take an optional priority. If rules with conflicting actions should occur at the same time, the rule with the highest priority will override all others. The priority argument should be an element of the ControlPriority enum. The default priority is medium (3).

The following examples illustrate the creation of a rules, using conditions and actions similar to those defined above.

>>> cond2 = controls.SimTimeCondition(wn, controls.Comparison.ge, '121:00:00')
>>> rule1 = controls.Rule(cond1, [act1], name='rule1')
>>> print(rule1)
Rule rule1 := if Tank('1').level > 46.0248 then set Pipe('330').status to Open with priority 3

>>> rule2 = controls.Rule(cond2, [act2], name='rule2')
>>> print(rule2)
Rule rule2 := if sim_time >= 435600 sec then set HeadPump('10').status to Open with priority 3

Since rules operate on a different timestep than controls, these rules might behave differently than the controls defined above.

More complex rules can be written using one of the Boolean logic condition classes. The following example creates a new rule that will open pipe 330 if both conditions are true, and otherwise it will open pipe 10.

>>> cond3 = controls.AndCondition(cond1, cond2)
>>> print(cond3)
( Tank('1').level > 46.0248 && sim_time >= 435600 sec )

>>> rule3 = controls.Rule(cond3, [act1], [act2], priority=3, name='complex_rule')
>>> print(rule3)
Rule complex_rule := if ( Tank('1').level > 46.0248 && sim_time >= 435600 sec ) then set Pipe('330').status to Open else set HeadPump('10').status to Open with priority 3

Actions can also be combined, as shown in the following example.

>>> cond4 = controls.OrCondition(cond1, cond2)
>>> rule4 = controls.Rule(cond4, [act1, act2])
>>> print(rule4)
Rule  := if ( Tank('1').level > 46.0248 || sim_time >= 435600 sec ) then set Pipe('330').status to Open and set HeadPump('10').status to Open with priority 3

The flexibility of rules provides an extremely powerful tool for defining complex network operations.

Adding controls/rules to a network

Once a control or rule is created, it can be added to the network. This is accomplished using the add_control method of the water network model object. The control or rule should be named so that it can be retrieved and modified if desired.

>>> wn.add_control('NewTimeControl', ctrl2)
>>> wn.get_control('NewTimeControl')
<Control: 'control2', <SimTimeCondition: model, 'Is', '5-01:00:00', False, 0>, [<ControlAction: 10, status, Open>], [], priority=3>