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 [22]. WNTR replicates EPANET functionality, and includes additional options, as described below. The EPANET user manual provides more information on simple controls and rule-based controls (controls and rules, respectively in WNTR) [22].

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). Unlike EPANET simple controls which are evaluated based on the order in which they are defined in the INP file, controls in WNTR can be prioritized to set the order of operation. If controls with conflicting actions should occur at the same time, the control with the highest priority will override all others. Controls are evaluated after each simulation timestep. 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.

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, which can be different from the simulation timestep. Rules in WNTR emulate EPANET rule-based controls.

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) (i.e., define the action that should occur, such as closing/opening a link)

  2. Define condition(s) (i.e., define what should cause the action to occur, such as a tank level)

  3. Define the control or rule using the action(s) and condition(s) (i.e., combine the defined action and condition)

  4. Add the control or rule to the water network model

These steps are defined below.

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, in which a status of 1 means open:

>>> import wntr 
>>> import wntr.network.controls as controls

>>> wn = wntr.network.WaterNetworkModel('networks/Net3.inp') 
>>> pipe = wn.get_link('330')
>>> act1 = controls.ControlAction(pipe, 'status', 1)
>>> print(act1)
PIPE 330 STATUS IS OPEN

Conditions#

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

Table 5 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 within SimTimeCondition. The WNTRSimulator can handle repeat or not repeat options for both of these conditions.

Priority#

Priority levels are defined in the ControlPriority class and include the following options.

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.

Controls are also assigned a priority. If controls with conflicting actions should occur at the same time, the control with the highest priority will override all others. The priority argument should be an element of the ControlPriority class. The default priority is medium (3).

In the following example, a conditional control is defined that opens pipe 330 if the level of tank 1 goes above 46.0248 m (151.0 ft). The target is the tank and the attribute is the tank’s 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 ABOVE 46.0248

>>> ctrl1 = controls.Control(cond1, act1, name='control1')
>>> print(ctrl1)
IF TANK 1 LEVEL ABOVE 46.0248 THEN PIPE 330 STATUS IS OPEN 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. The SimTimeCondition parameter can be specified as decimal hours or as a string in [dd-]hh:mm[:ss] format. When printed, the output is converted to seconds.

>>> pump = wn.get_link('10')
>>> act2 = controls.ControlAction(pump, 'status', 1)
>>> cond2 = controls.SimTimeCondition(wn, '=', '121:00:00')
>>> print(cond2)
SYSTEM TIME IS 121:00:00

>>> ctrl2 = controls.Control(cond2, act2, name='control2')
>>> print(ctrl2)
IF SYSTEM TIME IS 121:00:00 THEN PUMP 10 STATUS IS OPEN 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.

Like controls, rules are also assigned a 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 class. The default priority is medium (3). Priority can only be assigned when the rule is created.

The following examples illustrate the creation of 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)
IF TANK 1 LEVEL ABOVE 46.0248 THEN PIPE 330 STATUS IS OPEN PRIORITY 3

>>> pri5 = controls.ControlPriority.high
>>> rule2 = controls.Rule(cond2, [act2], name='rule2', priority=pri5)
>>> print(rule2)
IF SYSTEM TIME >= 121:00:00 THEN PUMP 10 STATUS IS OPEN PRIORITY 5

Since rules operate on a different timestep than controls, these rules might behave differently than the equivalent controls defined above. Controls (or simple controls in EPANET) operate on the hydraulic timestep while Rules (or rule-based controls in EPANET) operate at a smaller timestep. By default, the rule timestep is 1/10th of the hydraulic timestep. It is important to remember that significant differences might occur when timesteps are smaller; this applies not only to rule timesteps, but also to hydraulic or quality timesteps.

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 pump 10.

>>> cond3 = controls.AndCondition(cond1, cond2)
>>> print(cond3)
 TANK 1 LEVEL ABOVE 46.0248 AND SYSTEM TIME >= 121:00:00

>>> rule3 = controls.Rule(cond3, [act1], [act2], priority=3, name='complex_rule')
>>> print(rule3)
IF  TANK 1 LEVEL ABOVE 46.0248 AND SYSTEM TIME >= 121:00:00  THEN PIPE 330 STATUS IS OPEN ELSE PUMP 10 STATUS IS OPEN PRIORITY 3

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

>>> cond4 = controls.OrCondition(cond1, cond2)
>>> rule4 = controls.Rule(cond4, [act1, act2], name='rule4')
>>> print(rule4)
IF  TANK 1 LEVEL ABOVE 46.0248 OR SYSTEM TIME >= 121:00:00  THEN PIPE 330 STATUS IS OPEN AND PUMP 10 STATUS IS OPEN 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>

Accessing and modifying controls/rules#

Controls and rules can be accessed and modified in several ways. For example, the following example returns a list of control names that are included in the model.

>>> control_name_list = wn.control_name_list
>>> print(control_name_list)
['control 1', 'control 2', 'control 3', 'control 4', 'control 5', 'control 6', 'control 7', 'control 8', 'control 9', 'control 10', 'control 11', 'control 12', 'control 13', 'control 14', 'control 15', 'control 16', 'control 17', 'control 18']

The following example loops through all controls in the model and identifies controls that require pipe ‘330’.

>>> pipe = wn.get_link('330')
>>> for name, control in wn.controls():
...     if pipe in control.requires():
...         print(name, control)
control 17 IF TANK 1 LEVEL BELOW 5.21208 THEN PIPE 330 STATUS IS CLOSED PRIORITY 3
control 18 IF TANK 1 LEVEL ABOVE 5.821680000000001 THEN PIPE 330 STATUS IS OPEN PRIORITY 3

The following example changes the priority of ‘control 5’ from medium (3) to low (1).

>>> control = wn.get_control('control 5')
>>> print(control)
IF SYSTEM TIME IS 49:00:00 THEN PUMP 10 STATUS IS OPEN PRIORITY 3
>>> control.update_priority(1) # low
>>> print(control)
IF SYSTEM TIME IS 49:00:00 THEN PUMP 10 STATUS IS OPEN PRIORITY 1