EPANET icon indicating copy to clipboard operation
EPANET copied to clipboard

Add Option to Configure Logical Operator Precedence in Rules

Open 0tkl opened this issue 8 months ago • 3 comments

Most mainstream programming languages define logical operator precedence such that AND binds more tightly than OR (i.e., AND has higher precedence). An expression A AND B OR C is typically evaluated as (A AND B) OR C. However, control rules in the input file of EPANET currently uses the opposite convention: OR has higher precedence than AND. In EPANET rules, an expression like IF condition1 AND condition2 OR condition3 THEN ... is evaluated as condition1 AND (condition2 OR condition3).

This difference from standard programming practice can be a source of confusion and potential errors, particularly for new users who come from a programming background and expect the standard AND > OR precedence. While the EPANET manual documents the current behaviour, it's easy to overlook or forget, leading to rules that don't function as intended.

We propose adding a new option to the [OPTIONS] section of the EPANET input file:

[OPTIONS]
; To use standard AND > OR precedence:
RULE PRECEDENCE         STANDARD

; To explicitly use the default EPANET OR > AND precedence (or if line is omitted):
; RULE PRECEDENCE       LEGACY

The default behaviour should be LEGACY (until we have EPANET 3.0? ). Feedback on the option names is appreciated.


On the implementation side, refactoring the evalpremises() function is straightforward:

int evalpremises(Project *pr, int i)
{
    Network *net = &pr->network;
    int result;
    Spremise *p = net->Rule[i].Premises;

+    if (legacy precedence) // TODO
+    {
        result = TRUE;
        while (p != NULL)
        {
            if (p->logop == r_OR)
            {
                if (result == FALSE) result = checkpremise(pr, p);
            }
            else
            {
                if (result == FALSE) return (FALSE);
                result = checkpremise(pr, p);
            }
            p = p->next;
        }
+    }
+    else
+    {
+        result = FALSE;
+        while (p != NULL)
+        {
+            if (p->logop == r_AND)
+            {
+                if (result == TRUE) result = checkpremise(pr, p);
+            }
+            else
+            {
+                if (result == TRUE) return (TRUE);
+                result = checkpremise(pr, p);
+            }
+        }
+    }

    return result;
}

To me, the main challenge lies in deciding which struct should hold the precedence policy.

0tkl avatar Apr 02 '25 12:04 0tkl

If a user is required to stipulate a RULE PRECEDENCE option to switch to a different rule processing method, then it follows that they would already know about EPANET's current (or legacy) method and could simply adhere to it. Adding the precedence option seems to be an unnecessary feature that requires making changes to 14 files.

The real issue is that the current Rule syntax doesn't allow for the use of parentheses to group conditionals together. As noted in the User Manual, there is no way to correctly express IF A or (B and C) in a single rule. We could, however, introduce parentheses into the current Rule format so that the previous expression could be written as:

Rule 1
IF A
OR ( B
AND C )
THEN
...

This would allow for more complex and unambiguous rule statements without breaking backward compatibility. I believe that only the rules.c file would be affected along with the Rule API functions in epanet.c. A new stack-based method would be needed to store the individual premises and evaluate them (similar to how a calculator stores and evaluates an arithmetic expression).

LRossman avatar May 19 '25 14:05 LRossman

I’m trying to understand your idea… I interpret that the premise struct can be modified to add lparen/rparen fields or something to it. But speaking of APIs, I don't see a way to support parentheses in the rule format without either changing the EN_getpremise()/EN_setpremise() function signatures, or restricting their input arguments to premises without parentheses.

0tkl avatar May 20 '25 08:05 0tkl

One could always create "extended" versions of the functions, EN_getpremiseX()/EN_setpremiseX(), that include additional arguments. The Windows API, which is also C-based, commonly does this when adding new features to functions since unlike C++, C doesn't support function overloading.

In addition, rule-based controls could be also be extended to allow named variables and math expressions (see issue #725). This was already done in SWMM - see the example shown below - and would also require adding new API functions.

Image

LRossman avatar May 20 '25 14:05 LRossman