From 46aa6610ab13f449ba4423e02e5df3a2cb6f7e01 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Villemot?= <sebastien@dynare.org>
Date: Tue, 14 Nov 2023 17:21:31 +0100
Subject: [PATCH] model_replace, model_remove: allow selecting an equation with
 several (conjunct) tags

NB: does not (yet) works with Occbin regime-specific equations.

Ref. dynare#1890
---
 src/DynamicModel.cc  | 30 ++++++++++++++++++++++--------
 src/DynamicModel.hh  | 21 ++++++++++++---------
 src/DynareBison.yy   | 18 +++++++++++++-----
 src/EquationTags.cc  | 16 ++++++++++++++++
 src/EquationTags.hh  |  3 +++
 src/ParsingDriver.cc |  4 ++--
 src/ParsingDriver.hh |  4 ++--
 7 files changed, 70 insertions(+), 26 deletions(-)

diff --git a/src/DynamicModel.cc b/src/DynamicModel.cc
index 0fc43d76..c68c12f4 100644
--- a/src/DynamicModel.cc
+++ b/src/DynamicModel.cc
@@ -496,7 +496,7 @@ DynamicModel::writeDynamicMCompatFile(const string &basename) const
   output.close();
 }
 
-vector<pair<string, string>>
+vector<map<string, string>>
 DynamicModel::parseIncludeExcludeEquations(const string &inc_exc_option_value, bool exclude_eqs)
 {
   auto removeLeadingTrailingWhitespace = [](string &str)
@@ -584,7 +584,7 @@ DynamicModel::parseIncludeExcludeEquations(const string &inc_exc_option_value, b
       exit(EXIT_FAILURE);
     }
 
-  vector<pair<string, string>> eq_tag_set;
+  vector<map<string, string>> eq_tag_set;
   regex s(quote_regex + "|" + non_quote_regex);
   for (auto it = sregex_iterator(tags.begin(), tags.end(), s);
        it != sregex_iterator(); ++it)
@@ -595,13 +595,13 @@ DynamicModel::parseIncludeExcludeEquations(const string &inc_exc_option_value, b
           str.remove_prefix(1);
           str.remove_suffix(1);
         }
-      eq_tag_set.emplace_back(tagname, str);
+      eq_tag_set.push_back({ { tagname, string{str} } });
     }
   return eq_tag_set;
 }
 
 vector<int>
-DynamicModel::removeEquationsHelper(set<pair<string, string>> &listed_eqs_by_tag, bool exclude_eqs,
+DynamicModel::removeEquationsHelper(set<map<string, string>> &listed_eqs_by_tag, bool exclude_eqs,
                                     bool excluded_vars_change_type,
                                     vector<BinaryOpNode *> &all_equations,
                                     vector<optional<int>> &all_equations_lineno,
@@ -616,7 +616,7 @@ DynamicModel::removeEquationsHelper(set<pair<string, string>> &listed_eqs_by_tag
      the caller knows which tag pairs have not been handled. */
   set<int> listed_eqs_by_number;
   for (auto it = listed_eqs_by_tag.begin(); it != listed_eqs_by_tag.end();)
-    if (auto tmp = all_equation_tags.getEqnsByTag(it->first, it->second);
+    if (auto tmp = all_equation_tags.getEqnsByTags(*it);
         !tmp.empty())
       {
         listed_eqs_by_number.insert(tmp.begin(), tmp.end());
@@ -691,7 +691,7 @@ DynamicModel::removeEquationsHelper(set<pair<string, string>> &listed_eqs_by_tag
 }
 
 void
-DynamicModel::removeEquations(const vector<pair<string, string>> &listed_eqs_by_tag, bool exclude_eqs,
+DynamicModel::removeEquations(const vector<map<string, string>> &listed_eqs_by_tag, bool exclude_eqs,
                               bool excluded_vars_change_type)
 {
   /* Convert the const vector to a (mutable) set */
@@ -710,8 +710,22 @@ DynamicModel::removeEquations(const vector<pair<string, string>> &listed_eqs_by_
   if (!listed_eqs_by_tag2.empty())
     {
       cerr << "ERROR: model_remove/model_replace/exclude_eqs/include_eqs: The equations specified by" << endl;
-      for (const auto &[tagname, tagvalue] : listed_eqs_by_tag)
-        cerr << " " << tagname << "=" << tagvalue << endl;
+      for (const auto &m : listed_eqs_by_tag)
+        {
+          cerr << " ";
+          if (m.size() > 1)
+            cerr << "[ ";
+          bool first_printed {false};
+          for (const auto &[tagname, tagvalue] : m)
+            {
+              if (exchange(first_printed, true))
+                cerr << ", ";
+              cerr << tagname << "=" << tagvalue;
+            }
+          if (m.size() > 1)
+            cerr << " ]";
+          cerr << endl;
+        }
       cerr << "were not found." << endl;
       exit(EXIT_FAILURE);
     }
diff --git a/src/DynamicModel.hh b/src/DynamicModel.hh
index 1dc73424..636ee605 100644
--- a/src/DynamicModel.hh
+++ b/src/DynamicModel.hh
@@ -221,11 +221,12 @@ private:
     Returns a set of pairs (tag name, tag value) corresponding to the set of
     equations to be included or excluded.
    */
-  static vector<pair<string, string>> parseIncludeExcludeEquations(const string &inc_exc_option_value, bool exclude_eqs);
+  static vector<map<string, string>> parseIncludeExcludeEquations(const string &inc_exc_option_value, bool exclude_eqs);
 
   /* Helper for the removeEquations() method.
-     listed_eqs_by_tag is the list of (tag name, tag value) pairs corresponding
-     to the option value, exclude_eqs is a boolean indicating whether we’re
+     listed_eqs_by_tag describes a list of equations to remove (identified by
+     one or more tags; if multiple tags are present for a single equation, they
+     are understood as a conjunction), exclude_eqs is a boolean indicating whether we’re
      excluding or including, and excluded_vars_change_type is a boolean
      indicating whether to compute variables to be excluded.
 
@@ -233,13 +234,13 @@ private:
      equations. They are either the main structures for storing equations in
      ModelTree, or their counterpart for static-only equations. The
      static_equations boolean indicates when we are in the latter case.
-     The listed_eqs_by_tag structure will be updated by removing those tag
-     pairs that have been matched with equations in the all_equations*
-     argument*.
+
+     The listed_eqs_by_tag structure will be updated by removing the tags
+     matched with equations in the all_equations* argument*.
 
      Returns a list of excluded variables (empty if
      excluded_vars_change_type=false) */
-  vector<int> removeEquationsHelper(set<pair<string, string>> &listed_eqs_by_tag,
+  vector<int> removeEquationsHelper(set<map<string, string>> &listed_eqs_by_tag,
                                     bool exclude_eqs, bool excluded_vars_change_type,
                                     vector<BinaryOpNode *> &all_equations,
                                     vector<optional<int>> &all_equations_lineno,
@@ -396,10 +397,12 @@ public:
   //! Implements the include_eqs/exclude_eqs options
   void includeExcludeEquations(const string &inc_exc_option_value, bool exclude_eqs);
 
-  /* Removes equations from the model (identified by their name tags).
+  /* Removes equations from the model (identified by one or more tags; if
+     multiple tags are present for a single equation, they are understood as a
+     conjunction).
      Used for include_eqs/exclude_eqs options and for model_remove and
      model_replace blocks */
-  void removeEquations(const vector<pair<string, string>> &listed_eqs_by_tag, bool exclude_eqs,
+  void removeEquations(const vector<map<string, string>> &listed_eqs_by_tag, bool exclude_eqs,
                        bool excluded_vars_change_type);
 
   /* Replaces model equations with derivatives of Lagrangian w.r.t. endogenous.
diff --git a/src/DynareBison.yy b/src/DynareBison.yy
index f3ca4265..5ac6f632 100644
--- a/src/DynareBison.yy
+++ b/src/DynareBison.yy
@@ -235,7 +235,8 @@ str_tolower(string s)
 %type <PriorDistributions> prior_pdf prior_distribution
 %type <pair<expr_t,expr_t>> calibration_range
 %type <pair<string,string>> partition_elem subsamples_eq_opt integer_range_w_inf tag_pair
-%type <vector<pair<string,string>>> partition partition_1 tag_pair_list_for_selection symbol_list_with_tex
+%type <vector<pair<string,string>>> partition partition_1 symbol_list_with_tex
+%type <vector<map<string, string>>> tag_pair_list_for_selection
 %type <map<string, string>> tag_pair_list
 %type <tuple<string,string,string,string>> prior_eq_opt options_eq_opt
 %type <vector<pair<int, int>>> period_list
@@ -1155,18 +1156,25 @@ model_replace : MODEL_REPLACE '(' tag_pair_list_for_selection ')' ';'
 model_options : MODEL_OPTIONS '(' model_options_list ')' ';'
 
 tag_pair_list_for_selection : QUOTED_STRING
-                              { $$ = { { "name", $1 } }; }
+                              { $$ = { { { "name", $1 } } }; }
                             | tag_pair
-                              { $$ = { $1 }; }
+                              { $$ = { { $1 } }; }
+                            | '[' tag_pair_list ']'
+                              { $$ = { $2 }; }
                             | tag_pair_list_for_selection COMMA QUOTED_STRING
                               {
                                 $$ = $1;
-                                $$.emplace_back("name", $3);
+                                $$.push_back({ { "name", $3 } });
                               }
                             | tag_pair_list_for_selection COMMA tag_pair
                               {
                                 $$ = $1;
-                                $$.push_back($3);
+                                $$.push_back({ $3 });
+                              }
+                            | tag_pair_list_for_selection COMMA '[' tag_pair_list ']'
+                              {
+                                $$ = $1;
+                                $$.push_back($4);
                               }
                             ;
 
diff --git a/src/EquationTags.cc b/src/EquationTags.cc
index 063db797..f980d438 100644
--- a/src/EquationTags.cc
+++ b/src/EquationTags.cc
@@ -52,6 +52,22 @@ EquationTags::getEqnByTag(const string &key, const string &value) const
   return nullopt;
 }
 
+set<int>
+EquationTags::getEqnsByTags(const map<string, string> &tags_selected) const
+{
+  set<int> retval;
+  for (const auto &[eqn, tags] : eqn_tags)
+    {
+      for (const auto &[key, value] : tags_selected)
+        if (auto tmp = tags.find(key); tmp == tags.end() || tmp->second != value)
+          goto next_eq;
+      retval.insert(eqn);
+    next_eq:
+      ;
+    }
+  return retval;
+}
+
 void
 EquationTags::erase(const set<int> &eqns, const map<int, int> &old_eqn_num_2_new)
 {
diff --git a/src/EquationTags.hh b/src/EquationTags.hh
index 9ac418bd..75321d96 100644
--- a/src/EquationTags.hh
+++ b/src/EquationTags.hh
@@ -79,6 +79,9 @@ public:
   //! Get the first equation that has the given key and value
   optional<int> getEqnByTag(const string &key, const string &value) const;
 
+  // Get equations that have all the given keys and values (seen as a conjunction)
+  set<int> getEqnsByTags(const map<string, string> &tags_selected) const;
+
   //! Get the tag value given the equation number and key
   optional<string>
   getTagValueByEqnAndKey(int eqn, const string &key) const
diff --git a/src/ParsingDriver.cc b/src/ParsingDriver.cc
index 6a61a54d..a91f5030 100644
--- a/src/ParsingDriver.cc
+++ b/src/ParsingDriver.cc
@@ -3756,13 +3756,13 @@ ParsingDriver::isSymbolIdentifier(const string &str)
 }
 
 void
-ParsingDriver::model_remove(const vector<pair<string, string>> &listed_eqs_by_tags)
+ParsingDriver::model_remove(const vector<map<string, string>> &listed_eqs_by_tags)
 {
   mod_file->dynamic_model.removeEquations(listed_eqs_by_tags, true, true);
 }
 
 void
-ParsingDriver::begin_model_replace(const vector<pair<string, string>> &listed_eqs_by_tags)
+ParsingDriver::begin_model_replace(const vector<map<string, string>> &listed_eqs_by_tags)
 {
   mod_file->dynamic_model.removeEquations(listed_eqs_by_tags, true, false);
   set_current_data_tree(&mod_file->dynamic_model);
diff --git a/src/ParsingDriver.hh b/src/ParsingDriver.hh
index f75b39b6..b945b0d4 100644
--- a/src/ParsingDriver.hh
+++ b/src/ParsingDriver.hh
@@ -904,9 +904,9 @@ public:
   //! Add an occbin_constraints block
   void end_occbin_constraints(vector<tuple<string, BinaryOpNode *, BinaryOpNode *, expr_t, expr_t>> constraints);
   // Process a model_remove statement
-  void model_remove(const vector<pair<string, string>> &listed_eqs_by_tags);
+  void model_remove(const vector<map<string, string>> &listed_eqs_by_tags);
   // Begin a model_replace statement
-  void begin_model_replace(const vector<pair<string, string>> &listed_eqs_by_tags);
+  void begin_model_replace(const vector<map<string, string>> &listed_eqs_by_tags);
   // Add a var_remove statement
   void var_remove(const vector<string> &symbol_list);
   void begin_pac_target_info(string name);
-- 
GitLab