From fab3b682c386b9ab7ff26fbb432338e5d1bcf615 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Villemot?= <sebastien@dynare.org>
Date: Tue, 21 May 2024 18:21:42 +0200
Subject: [PATCH] More flexible syntax for complementarity conditions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

— Bounds can now be arbitrary expressions of parameters (and not simply
  constants as previously);
— It is now possible to specify both a lower and an upper bound in the same
  condition.

So for example one can now write something like:

 lhs = rhs ⟂ 1+alpha < y < 2+beta;
---
 src/ExprNode.cc | 58 +++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 47 insertions(+), 11 deletions(-)

diff --git a/src/ExprNode.cc b/src/ExprNode.cc
index 44923c17..4665f9aa 100644
--- a/src/ExprNode.cc
+++ b/src/ExprNode.cc
@@ -9380,7 +9380,7 @@ ExprNode::matchComplementarityCondition() const
 tuple<int, expr_t, expr_t>
 BinaryOpNode::matchComplementarityCondition() const
 {
-  bool is_lower_bound {[&] {
+  bool is_greater {[&] {
     switch (op_code)
       {
       case BinaryOpcode::less:
@@ -9394,16 +9394,52 @@ BinaryOpNode::matchComplementarityCondition() const
       }
   }()};
 
-  auto* varg = dynamic_cast<VariableNode*>(arg1);
-  if (!varg)
-    throw MatchFailureException {"Left-hand side is not a variable"};
-  if (varg->lag != 0)
-    throw MatchFailureException {"Left-hand side variable must not have a lead or a lag"};
-  if (datatree.symbol_table.getType(varg->symb_id) != SymbolType::endogenous)
-    throw MatchFailureException {"Left-hand side is not an endogenous variable"};
+  auto match_contemporaneous_endogenous = [&](expr_t e) -> optional<int> {
+    auto* ve = dynamic_cast<VariableNode*>(e);
+    if (ve && ve->lag == 0 && datatree.symbol_table.getType(ve->symb_id) == SymbolType::endogenous)
+      return ve->symb_id;
+    else
+      return nullopt;
+  };
+
+  auto check_bound_constant = [](expr_t e) {
+    if (!e->isConstant())
+      throw MatchFailureException {"Bounds must not contain any endogenous or exogenous variable"};
+  };
+
+  // Match “endogenous ≶ bound”
+  if (auto id = match_contemporaneous_endogenous(arg1); id)
+    {
+      check_bound_constant(arg2);
+      return {*id, is_greater ? arg2 : nullptr, is_greater ? nullptr : arg2};
+    }
+
+  // Match “bound ≶ endogenous”
+  if (auto id = match_contemporaneous_endogenous(arg2); id)
+    {
+      check_bound_constant(arg1);
+      return {*id, is_greater ? nullptr : arg1, is_greater ? arg1 : nullptr};
+    }
+
+  // Match: “bound < endogenous < bound” or “bound > endogenous > bound”
+  /* NB: we exploit the fact that inequality signs are left-associative, hence the constraint is
+     necessarily “(bound < endogenous) < bound” or “(bound > endogenous) > bound” (assuming the user
+     did not add a spurious parenthesis). */
+  auto barg1 = dynamic_cast<BinaryOpNode*>(arg1);
+  if (!(barg1
+        && ((is_greater
+             && (barg1->op_code == BinaryOpcode::greater
+                 || barg1->op_code == BinaryOpcode::greaterEqual))
+            || (!is_greater
+                && (barg1->op_code == BinaryOpcode::less
+                    || barg1->op_code == BinaryOpcode::lessEqual)))))
+    throw MatchFailureException {"Complementarity condition does not have the right form"};
 
-  if (!arg2->isConstant())
-    throw MatchFailureException {"Right-hand side is not a constant"};
+  auto id = match_contemporaneous_endogenous(barg1->arg2);
+  if (!id)
+    throw MatchFailureException {"Complementarity condition does not have the right form"};
+  check_bound_constant(barg1->arg1);
+  check_bound_constant(arg2);
 
-  return {varg->symb_id, is_lower_bound ? arg2 : nullptr, is_lower_bound ? nullptr : arg2};
+  return {*id, is_greater ? arg2 : barg1->arg1, is_greater ? barg1->arg1 : arg2};
 }
-- 
GitLab