Evaluation unfolding for symbolic SyGuS constructors (#3483)
authorAndrew Reynolds <andrew.j.reynolds@gmail.com>
Thu, 21 Nov 2019 15:54:12 +0000 (09:54 -0600)
committerGitHub <noreply@github.com>
Thu, 21 Nov 2019 15:54:12 +0000 (09:54 -0600)
src/theory/quantifiers/sygus/cegis.cpp
src/theory/quantifiers/sygus/cegis.h
src/theory/quantifiers/sygus/sygus_eval_unfold.cpp
src/theory/quantifiers/sygus/sygus_eval_unfold.h
src/theory/quantifiers/sygus/sygus_unif_rl.cpp
src/theory/quantifiers/sygus/sygus_unif_strat.cpp
src/theory/quantifiers/sygus/synth_engine.cpp
src/theory/quantifiers/sygus/term_database_sygus.cpp
src/theory/quantifiers/sygus/term_database_sygus.h

index aa0af6f1d10d2f350bc211fdf8c8d16bde882bf4..c806bb1e7466344429d1db6ff7f041487e1f8225 100644 (file)
@@ -31,12 +31,9 @@ namespace theory {
 namespace quantifiers {
 
 Cegis::Cegis(QuantifiersEngine* qe, SynthConjecture* p)
-    : SygusModule(qe, p), d_eval_unfold(nullptr), d_using_gr_repair(false)
+    : SygusModule(qe, p), d_eval_unfold(nullptr), d_usingSymCons(false)
 {
-  if (options::sygusEvalUnfold())
-  {
-    d_eval_unfold = qe->getTermDatabaseSygus()->getEvalUnfold();
-  }
+  d_eval_unfold = qe->getTermDatabaseSygus()->getEvalUnfold();
 }
 
 bool Cegis::initialize(Node conj,
@@ -80,7 +77,7 @@ bool Cegis::processInitialize(Node conj,
   for (unsigned i = 0; i < csize; i++)
   {
     Trace("cegis") << "...register enumerator " << candidates[i];
-    bool do_repair_const = false;
+    bool useSymCons = false;
     if (options::sygusRepairConst())
     {
       TypeNode ctn = candidates[i].getType();
@@ -88,15 +85,15 @@ bool Cegis::processInitialize(Node conj,
       SygusTypeInfo& cti = d_tds->getTypeInfo(ctn);
       if (cti.hasSubtermSymbolicCons())
       {
-        do_repair_const = true;
-        // remember that we are doing grammar-based repair
-        d_using_gr_repair = true;
-        Trace("cegis") << " (using repair)";
+        useSymCons = true;
+        // remember that we are using symbolic constructors
+        d_usingSymCons = true;
+        Trace("cegis") << " (using symbolic constructors)";
       }
     }
     Trace("cegis") << std::endl;
     d_tds->registerEnumerator(
-        candidates[i], candidates[i], d_parent, erole, do_repair_const);
+        candidates[i], candidates[i], d_parent, erole, useSymCons);
   }
   return true;
 }
@@ -135,7 +132,10 @@ bool Cegis::addEvalLemmas(const std::vector<Node>& candidates,
   }
   NodeManager* nm = NodeManager::currentNM();
   bool addedEvalLemmas = false;
-  if (options::sygusRefEval())
+  // Refinement evaluation should not be done for grammars with symbolic
+  // constructors.
+  bool doRefEval = options::sygusRefEval() && !d_usingSymCons;
+  if (doRefEval)
   {
     Trace("cegqi-engine") << "  *** Do refinement lemma evaluation"
                           << (doGen ? " with conjecture-specific refinement"
@@ -169,7 +169,8 @@ bool Cegis::addEvalLemmas(const std::vector<Node>& candidates,
     }
   }
   // we only do evaluation unfolding for passive enumerators
-  if (doGen && d_eval_unfold != nullptr)
+  bool doEvalUnfold = (doGen && options::sygusEvalUnfold()) || d_usingSymCons;
+  if (doEvalUnfold)
   {
     Trace("cegqi-engine") << "  *** Do evaluation unfolding..." << std::endl;
     std::vector<Node> eager_terms, eager_vals, eager_exps;
@@ -239,7 +240,7 @@ bool Cegis::constructCandidates(const std::vector<Node>& enums,
     }
   }
   // if we are using grammar-based repair
-  if (d_using_gr_repair)
+  if (d_usingSymCons && options::sygusRepairConst())
   {
     SygusRepairConst* src = d_parent->getRepairConst();
     Assert(src != nullptr);
index 08cf98c993e9bdb1b0a155394cd8434ba1190c71..adaecc7f667965066bb3565220d9efc090f7713e 100644 (file)
@@ -204,15 +204,15 @@ class Cegis : public SygusModule
    */
   std::unordered_set<unsigned> d_cegis_sample_refine;
 
-  //---------------------------------for sygus repair
-  /** are we using grammar-based repair?
+  //---------------------------------for symbolic constructors
+  /** are we using symbolic constants?
    *
    * This flag is set ot true if at least one of the enumerators allocated
    * by this class has been configured to allow model values with symbolic
    * constructors, such as the "any constant" constructor.
    */
-  bool d_using_gr_repair;
-  //---------------------------------end for sygus repair
+  bool d_usingSymCons;
+  //---------------------------------end for symbolic constructors
 };
 
 } /* CVC4::theory::quantifiers namespace */
index 5286ab6f7cb8cfb7001ebd4202d1855b7cd2761e..42ddbbb7db159e9e4412669974b4c36a0f3e9350 100644 (file)
@@ -14,7 +14,9 @@
 
 #include "theory/quantifiers/sygus/sygus_eval_unfold.h"
 
+#include "expr/sygus_datatype.h"
 #include "options/quantifiers_options.h"
+#include "theory/datatypes/theory_datatypes_utils.h"
 #include "theory/quantifiers/sygus/term_database_sygus.h"
 
 using namespace std;
@@ -101,6 +103,10 @@ void SygusEvalUnfold::registerModelValue(Node a,
           antec_exp.size() == 1 ? antec_exp[0] : nm->mkNode(AND, antec_exp);
       // Node antec = n.eqNode( vn );
       TypeNode tn = n.getType();
+      // Check if the sygus type has any symbolic constructors. This will
+      // impact how the unfolding is computed below.
+      SygusTypeInfo& sti = d_tds->getTypeInfo(tn);
+      bool hasSymCons = sti.hasSubtermSymbolicCons();
       // n occurs as an evaluation head, thus it has sygus datatype type
       Assert(tn.isDatatype());
       const Datatype& dt = static_cast<DatatypeType>(tn.toType()).getDatatype();
@@ -148,7 +154,7 @@ void SygusEvalUnfold::registerModelValue(Node a,
             do_unfold = true;
           }
         }
-        if (do_unfold)
+        if (do_unfold || hasSymCons)
         {
           // note that this is replicated for different values
           std::map<Node, Node> vtm;
@@ -158,7 +164,10 @@ void SygusEvalUnfold::registerModelValue(Node a,
               eval_children.end(), it->second[i].begin(), it->second[i].end());
           Node eval_fun = nm->mkNode(DT_SYGUS_EVAL, eval_children);
           eval_children.resize(1);
-          res = d_tds->unfold(eval_fun, vtm, exp);
+          // If we explicitly asked to unfold, we use single step, otherwise
+          // we use multi step.
+          res = unfold(eval_fun, vtm, exp, true, !do_unfold);
+          Trace("sygus-eval-unfold") << "Unfold returns " << res << std::endl;
           expn = exp.size() == 1 ? exp[0] : nm->mkNode(AND, exp);
         }
         else
@@ -170,6 +179,8 @@ void SygusEvalUnfold::registerModelValue(Node a,
           eval_children[0] = vn;
           Node eval_fun = nm->mkNode(DT_SYGUS_EVAL, eval_children);
           res = d_tds->evaluateWithUnfolding(eval_fun);
+          Trace("sygus-eval-unfold")
+              << "Evaluate with unfolding returns " << res << std::endl;
           esit.init(conj, n, res);
           eval_children.resize(1);
           eval_children[0] = n;
@@ -193,6 +204,130 @@ void SygusEvalUnfold::registerModelValue(Node a,
   }
 }
 
+Node SygusEvalUnfold::unfold(Node en,
+                             std::map<Node, Node>& vtm,
+                             std::vector<Node>& exp,
+                             bool track_exp,
+                             bool doRec)
+{
+  if (en.getKind() != DT_SYGUS_EVAL)
+  {
+    Assert(en.isConst());
+    return en;
+  }
+  Trace("sygus-eval-unfold-debug")
+      << "Unfold : " << en << ", track exp is " << track_exp << ", doRec is "
+      << doRec << std::endl;
+  Node ev = en[0];
+  if (track_exp)
+  {
+    std::map<Node, Node>::iterator itv = vtm.find(en[0]);
+    Assert(itv != vtm.end());
+    if (itv != vtm.end())
+    {
+      ev = itv->second;
+    }
+    Assert(en[0].getType() == ev.getType());
+    Assert(ev.isConst());
+  }
+  Trace("sygus-eval-unfold-debug")
+      << "Unfold model value is : " << ev << std::endl;
+  AlwaysAssert(ev.getKind() == APPLY_CONSTRUCTOR);
+  std::vector<Node> args;
+  for (unsigned i = 1, nchild = en.getNumChildren(); i < nchild; i++)
+  {
+    args.push_back(en[i]);
+  }
+
+  TypeNode headType = en[0].getType();
+  Type headTypeT = headType.toType();
+  NodeManager* nm = NodeManager::currentNM();
+  const Datatype& dt = headType.getDatatype();
+  unsigned i = datatypes::utils::indexOf(ev.getOperator());
+  if (track_exp)
+  {
+    // explanation
+    Node ee =
+        nm->mkNode(APPLY_TESTER, Node::fromExpr(dt[i].getTester()), en[0]);
+    if (std::find(exp.begin(), exp.end(), ee) == exp.end())
+    {
+      exp.push_back(ee);
+    }
+  }
+  // if we are a symbolic constructor, unfolding returns the subterm itself
+  Node sop = Node::fromExpr(dt[i].getSygusOp());
+  if (sop.getAttribute(SygusAnyConstAttribute()))
+  {
+    Trace("sygus-eval-unfold-debug")
+        << "...it is an any-constant constructor" << std::endl;
+    Assert(dt[i].getNumArgs() == 1);
+    // If the argument to evaluate is itself concrete, then we use its
+    // argument; otherwise we return its selector.
+    if (en[0].getKind() == APPLY_CONSTRUCTOR)
+    {
+      Trace("sygus-eval-unfold-debug")
+          << "...return (from constructor) " << en[0][0] << std::endl;
+      return en[0][0];
+    }
+    else
+    {
+      Node ret = nm->mkNode(
+          APPLY_SELECTOR_TOTAL, dt[i].getSelectorInternal(headTypeT, 0), en[0]);
+      Trace("sygus-eval-unfold-debug")
+          << "...return (from constructor) " << ret << std::endl;
+      return ret;
+    }
+  }
+
+  Assert(!dt.isParametric());
+  std::map<int, Node> pre;
+  for (unsigned j = 0, nargs = dt[i].getNumArgs(); j < nargs; j++)
+  {
+    std::vector<Node> cc;
+    Node s;
+    // get the j^th subfield of en
+    if (en[0].getKind() == APPLY_CONSTRUCTOR)
+    {
+      // if it is a concrete constructor application, as an optimization,
+      // just return the argument
+      s = en[0][j];
+    }
+    else
+    {
+      s = nm->mkNode(
+          APPLY_SELECTOR_TOTAL, dt[i].getSelectorInternal(headTypeT, j), en[0]);
+    }
+    cc.push_back(s);
+    if (track_exp)
+    {
+      // update vtm map
+      vtm[s] = ev[j];
+    }
+    cc.insert(cc.end(), args.begin(), args.end());
+    Node argj = nm->mkNode(DT_SYGUS_EVAL, cc);
+    if (doRec)
+    {
+      Trace("sygus-eval-unfold-debug") << "Recurse on " << s << std::endl;
+      // evaluate recursively
+      argj = unfold(argj, vtm, exp, track_exp, doRec);
+    }
+    pre[j] = argj;
+  }
+  Node ret = d_tds->mkGeneric(dt, i, pre);
+  // apply the appropriate substitution to ret
+  ret = datatypes::utils::applySygusArgs(dt, sop, ret, args);
+  // rewrite
+  ret = Rewriter::rewrite(ret);
+  return ret;
+}
+
+Node SygusEvalUnfold::unfold(Node en)
+{
+  std::map<Node, Node> vtm;
+  std::vector<Node> exp;
+  return unfold(en, vtm, exp, false, false);
+}
+
 }  // namespace quantifiers
 }  // namespace theory
 }  // namespace CVC4
index adc54c6a7ddf2b4859f2c3f7a7be258dca0a065a..50b361fc446b4690522b2109b39ab74dc92b22a2 100644 (file)
@@ -83,6 +83,49 @@ class SygusEvalUnfold
                           std::vector<Node>& exps,
                           std::vector<Node>& terms,
                           std::vector<Node>& vals);
+  /** unfold
+   *
+   * This method is called when a sygus term d (typically a variable for a SyGuS
+   * enumerator) has a model value specified by the map vtm. The argument en
+   * is an application of kind DT_SYGUS_EVAL, i.e. eval( d, c1, ..., cm ).
+   * Typically, d is a shared selector chain, although it may also be a
+   * non-constant application of a constructor.
+   *
+   * If doRec is false, this method returns the one-step unfolding of this
+   * evaluation function application. An example of a one step unfolding is:
+   *    eval( C_+( d1, d2 ), t ) ---> +( eval( d1, t ), eval( d2, t ) )
+   *
+   * This function does this unfolding for a (possibly symbolic) evaluation
+   * head, where the argument "variable to model" vtm stores the model value of
+   * variables from this head. This allows us to track an explanation of the
+   * unfolding in the vector exp when track_exp is true.
+   *
+   * For example, if vtm[d] = C_+( C_x(), C_0() ) and track_exp is true, then
+   * this method applied to eval( d, t ) will return
+   * +( eval( d.0, t ), eval( d.1, t ) ), and is-C_+( d ) is added to exp.
+   *
+   * If the argument doRec is true, we do a multi-step unfolding instead of
+   * a single-step unfolding. For example, if vtm[d] = C_+( C_x(), C_0() ),
+   * then this method applied to eval(d,5) will return 5+0 = 0.
+   *
+   * Furthermore, notice that any-constant constructors are *never* expanded to
+   * their concrete model values. This means that the multi-step unfolding when
+   * vtm[d] = C_+( C_x(), any_constant(n) ), then this method applied to
+   * eval(d,5) will return 5 + d.2.1, where the latter term is a shared selector
+   * chain. In other words, this unfolding elaborates the connection between
+   * the builtin integer field d.2.1 of d and the builtin interpretation of
+   * this sygus term, for the given argument.
+   */
+  Node unfold(Node en,
+              std::map<Node, Node>& vtm,
+              std::vector<Node>& exp,
+              bool track_exp = true,
+              bool doRec = false);
+  /**
+   * Same as above, but without explanation tracking. This is used for concrete
+   * evaluation heads
+   */
+  Node unfold(Node en);
 
  private:
   /** sygus term database associated with this utility */
index 3f09a43462c98c2e0c16ad8af852540ab1c2956b..5d4aaf7aedca65c3c82755ef0d6e6b224987be23 100644 (file)
@@ -105,7 +105,8 @@ Node SygusUnifRl::purifyLemma(Node n,
       {
         TNode cand = n[0];
         Node tmp = n.substitute(cand, it->second);
-        nv = d_tds->evaluateWithUnfolding(tmp);
+        // should be concrete, can just use the rewriter
+        nv = Rewriter::rewrite(tmp);
         Trace("sygus-unif-rl-purify")
             << "PurifyLemma : model value for " << tmp << " is " << nv << "\n";
       }
index e74068ace32fe42417712b77641ec807328a9a8a..07997221f6576e513b3ffb81e81119b149c2c180 100644 (file)
@@ -15,6 +15,7 @@
 #include "theory/quantifiers/sygus/sygus_unif_strat.h"
 
 #include "theory/datatypes/theory_datatypes_utils.h"
+#include "theory/quantifiers/sygus/sygus_eval_unfold.h"
 #include "theory/quantifiers/sygus/sygus_unif.h"
 #include "theory/quantifiers/sygus/term_database_sygus.h"
 #include "theory/quantifiers/term_util.h"
@@ -258,7 +259,7 @@ void SygusUnifStrategy::buildStrategyGraph(TypeNode tn, NodeRole nrole)
     Node eut = nm->mkNode(DT_SYGUS_EVAL, echildren);
     Trace("sygus-unif-debug2") << "  Test evaluation of " << eut << "..."
                                << std::endl;
-    eut = d_qe->getTermDatabaseSygus()->unfold(eut);
+    eut = d_qe->getTermDatabaseSygus()->getEvalUnfold()->unfold(eut);
     Trace("sygus-unif-debug2") << "  ...got " << eut;
     Trace("sygus-unif-debug2") << ", type : " << eut.getType() << std::endl;
 
index 9f6954416f34c2bea49ded752be4a75378c68bb5..cbe907d416478552e91db6a1b598869dad2d9bf2 100644 (file)
@@ -321,8 +321,6 @@ bool SynthEngine::checkConjecture(SynthConjecture* conj)
     bool addedLemma = false;
     for (const Node& lem : cclems)
     {
-      Trace("cegqi-lemma") << "Cegqi::Lemma : counterexample : " << lem
-                           << std::endl;
       if (d_quantEngine->addLemma(lem))
       {
         ++(d_statistics.d_cegqi_lemmas_ce);
index 79b4c9caaad799f6a3e1e485358fa89cd6ae2143..679d47779ecba889263b240ef8695f4f2beed121 100644 (file)
@@ -953,107 +953,6 @@ unsigned TermDbSygus::getAnchorDepth( Node n ) {
   }
 }
 
-Node TermDbSygus::unfold( Node en, std::map< Node, Node >& vtm, std::vector< Node >& exp, bool track_exp ) {
-  if (en.getKind() != DT_SYGUS_EVAL)
-  {
-    Assert(en.isConst());
-    return en;
-  }
-  Trace("sygus-db-debug") << "Unfold : " << en << std::endl;
-  Node ev = en[0];
-  if (track_exp)
-  {
-    std::map<Node, Node>::iterator itv = vtm.find(en[0]);
-    Assert(itv != vtm.end());
-    if (itv != vtm.end())
-    {
-      ev = itv->second;
-    }
-    Assert(en[0].getType() == ev.getType());
-    Assert(ev.isConst());
-  }
-  Assert(ev.getKind() == kind::APPLY_CONSTRUCTOR);
-  std::vector<Node> args;
-  for (unsigned i = 1, nchild = en.getNumChildren(); i < nchild; i++)
-  {
-    args.push_back(en[i]);
-  }
-
-  Type headType = en[0].getType().toType();
-  NodeManager* nm = NodeManager::currentNM();
-  const Datatype& dt = static_cast<DatatypeType>(headType).getDatatype();
-  unsigned i = datatypes::utils::indexOf(ev.getOperator());
-  if (track_exp)
-  {
-    // explanation
-    Node ee = nm->mkNode(
-        kind::APPLY_TESTER, Node::fromExpr(dt[i].getTester()), en[0]);
-    if (std::find(exp.begin(), exp.end(), ee) == exp.end())
-    {
-      exp.push_back(ee);
-    }
-  }
-  // if we are a symbolic constructor, unfolding returns the subterm itself
-  Node sop = Node::fromExpr(dt[i].getSygusOp());
-  if (sop.getAttribute(SygusAnyConstAttribute()))
-  {
-    Trace("sygus-db-debug") << "...it is an any-constant constructor"
-                            << std::endl;
-    Assert(dt[i].getNumArgs() == 1);
-    if (en[0].getKind() == APPLY_CONSTRUCTOR)
-    {
-      return en[0][0];
-    }
-    else
-    {
-      return nm->mkNode(
-          APPLY_SELECTOR_TOTAL, dt[i].getSelectorInternal(headType, 0), en[0]);
-    }
-  }
-
-  Assert(!dt.isParametric());
-  std::map<int, Node> pre;
-  for (unsigned j = 0, nargs = dt[i].getNumArgs(); j < nargs; j++)
-  {
-    std::vector<Node> cc;
-    Node s;
-    // get the j^th subfield of en
-    if (en[0].getKind() == kind::APPLY_CONSTRUCTOR)
-    {
-      // if it is a concrete constructor application, as an optimization,
-      // just return the argument
-      s = en[0][j];
-    }
-    else
-    {
-      s = nm->mkNode(kind::APPLY_SELECTOR_TOTAL,
-                     dt[i].getSelectorInternal(headType, j),
-                     en[0]);
-    }
-    cc.push_back(s);
-    if (track_exp)
-    {
-      // update vtm map
-      vtm[s] = ev[j];
-    }
-    cc.insert(cc.end(), args.begin(), args.end());
-    pre[j] = nm->mkNode(DT_SYGUS_EVAL, cc);
-  }
-  Node ret = mkGeneric(dt, i, pre);
-  // apply the appropriate substitution to ret
-  ret = datatypes::utils::applySygusArgs(dt, sop, ret, args);
-  // rewrite
-  ret = Rewriter::rewrite(ret);
-  return ret;
-}
-
-Node TermDbSygus::unfold(Node en)
-{
-  std::map<Node, Node> vtm;
-  std::vector<Node> exp;
-  return unfold(en, vtm, exp, false);
-}
-
 Node TermDbSygus::evaluateBuiltin(TypeNode tn,
                                   Node bn,
                                   std::vector<Node>& args,
@@ -1105,6 +1004,8 @@ Node TermDbSygus::evaluateWithUnfolding(
         Trace("dt-eval-unfold-debug")
             << "Optimize: evaluate constant head " << ret << std::endl;
         // can just do direct evaluation here
+        // notice we prefer this code to the rewriter since it may use
+        // the evaluator
         std::vector<Node> args;
         bool success = true;
         for (unsigned i = 1, nchild = ret.getNumChildren(); i < nchild; i++)
@@ -1127,7 +1028,7 @@ Node TermDbSygus::evaluateWithUnfolding(
           return rete;
         }
       }
-      ret = unfold( ret );
+      ret = d_eval_unfold->unfold(ret);
     }    
     if( ret.getNumChildren()>0 ){
       std::vector< Node > children;
@@ -1136,7 +1037,7 @@ Node TermDbSygus::evaluateWithUnfolding(
       }
       bool childChanged = false;
       for( unsigned i=0; i<ret.getNumChildren(); i++ ){
-        Node nc = evaluateWithUnfolding( ret[i], visited ); 
+        Node nc = evaluateWithUnfolding(ret[i], visited);
         childChanged = childChanged || nc!=ret[i];
         children.push_back( nc );
       }
@@ -1152,9 +1053,10 @@ Node TermDbSygus::evaluateWithUnfolding(
   }
 }
 
-Node TermDbSygus::evaluateWithUnfolding( Node n ) {
+Node TermDbSygus::evaluateWithUnfolding(Node n)
+{
   std::unordered_map<Node, Node, NodeHashFunction> visited;
-  return evaluateWithUnfolding( n, visited );
+  return evaluateWithUnfolding(n, visited);
 }
 
 bool TermDbSygus::isEvaluationPoint(Node n) const
index 0ec883537d43a43c7bc6be3b23048d15d43867f8..76b5039f6786acab23543c5a524bb27ed3e2ac97 100644 (file)
@@ -453,31 +453,6 @@ class TermDbSygus {
   static Node getAnchor( Node n );
   static unsigned getAnchorDepth( Node n );
 
- public:
-  /** unfold
-   *
-   * This method returns the one-step unfolding of an evaluation function
-   * application. An example of a one step unfolding is:
-   *    eval( C_+( d1, d2 ), t ) ---> +( eval( d1, t ), eval( d2, t ) )
-   *
-   * This function does this unfolding for a (possibly symbolic) evaluation
-   * head, where the argument "variable to model" vtm stores the model value of
-   * variables from this head. This allows us to track an explanation of the
-   * unfolding in the vector exp when track_exp is true.
-   *
-   * For example, if vtm[d] = C_+( C_x(), C_0() ) and track_exp is true, then
-   * this method applied to eval( d, t ) will return
-   * +( eval( d.0, t ), eval( d.1, t ) ), and is-C_+( d ) is added to exp.
-   */
-  Node unfold(Node en,
-              std::map<Node, Node>& vtm,
-              std::vector<Node>& exp,
-              bool track_exp = true);
-  /**
-   * Same as above, but without explanation tracking. This is used for concrete
-   * evaluation heads
-   */
-  Node unfold(Node en);
 };
 
 }/* CVC4::theory::quantifiers namespace */