Support for SyGuS PBE + recursive functions (#3433)
authorAndrew Reynolds <andrew.j.reynolds@gmail.com>
Wed, 6 Nov 2019 16:00:08 +0000 (10:00 -0600)
committerGitHub <noreply@github.com>
Wed, 6 Nov 2019 16:00:08 +0000 (10:00 -0600)
src/CMakeLists.txt
src/options/quantifiers_options.toml
src/theory/quantifiers/fun_def_evaluator.cpp [new file with mode: 0644]
src/theory/quantifiers/fun_def_evaluator.h [new file with mode: 0644]
src/theory/quantifiers/sygus/synth_conjecture.cpp
src/theory/quantifiers/sygus/synth_engine.cpp
src/theory/quantifiers/sygus/synth_engine.h
src/theory/quantifiers/sygus/term_database_sygus.cpp
src/theory/quantifiers/sygus/term_database_sygus.h
test/regress/CMakeLists.txt
test/regress/regress1/sygus/rec-fun-sygus.sy [new file with mode: 0644]

index 77db3b4db272e232de269f055b4aa6907c685fc4..4f64760e0b5feaa559d8dcbca6b7c567881ebc3c 100644 (file)
@@ -506,6 +506,8 @@ libcvc4_add_sources(
   theory/quantifiers/fmf/model_builder.h
   theory/quantifiers/fmf/model_engine.cpp
   theory/quantifiers/fmf/model_engine.h
+  theory/quantifiers/fun_def_evaluator.cpp
+  theory/quantifiers/fun_def_evaluator.h
   theory/quantifiers/fun_def_process.cpp
   theory/quantifiers/fun_def_process.h
   theory/quantifiers/inst_match.cpp
index 89ccd90b289165daa59710847bbbbe280821548d..338f5544f114175926a02dd58758cd4e75c4ea94 100644 (file)
@@ -1213,6 +1213,14 @@ header = "options/quantifiers_options.h"
   default    = "false"
   help       = "static inference techniques for computing whether arguments of functions-to-synthesize are relevant"
 
+[[option]]
+  name       = "sygusRecFun"
+  category   = "regular"
+  long       = "sygus-rec-fun"
+  type       = "bool"
+  default    = "false"
+  help       = "enable efficient support for recursive functions in sygus grammars"
+
 # Internal uses of sygus
 
 [[option]]
diff --git a/src/theory/quantifiers/fun_def_evaluator.cpp b/src/theory/quantifiers/fun_def_evaluator.cpp
new file mode 100644 (file)
index 0000000..83debe0
--- /dev/null
@@ -0,0 +1,187 @@
+/*********************                                                        */
+/*! \file fun_def_evaluator.cpp
+ ** \verbatim
+ ** Top contributors (to current version):
+ **   Andrew Reynolds
+ ** This file is part of the CVC4 project.
+ ** Copyright (c) 2009-2019 by the authors listed in the file AUTHORS
+ ** in the top-level source directory) and their institutional affiliations.
+ ** All rights reserved.  See the file COPYING in the top-level source
+ ** directory for licensing information.\endverbatim
+ **
+ ** \brief Implementation of techniques for evaluating terms with recursively
+ ** defined functions.
+ **/
+
+#include "theory/quantifiers/fun_def_evaluator.h"
+
+#include "theory/quantifiers/quantifiers_attributes.h"
+#include "theory/rewriter.h"
+
+using namespace CVC4::kind;
+
+namespace CVC4 {
+namespace theory {
+namespace quantifiers {
+
+FunDefEvaluator::FunDefEvaluator() {}
+
+void FunDefEvaluator::assertDefinition(Node q)
+{
+  Trace("fd-eval") << "FunDefEvaluator: assertDefinition " << q << std::endl;
+  Node h = QuantAttributes::getFunDefHead(q);
+  if (h.isNull())
+  {
+    // not a function definition
+    return;
+  }
+  // h possibly with zero arguments?
+  Node f = h.hasOperator() ? h.getOperator() : h;
+  Assert(d_funDefMap.find(f) == d_funDefMap.end())
+      << "FunDefEvaluator::assertDefinition: function already defined";
+  FunDefInfo& fdi = d_funDefMap[f];
+  fdi.d_body = QuantAttributes::getFunDefBody(q);
+  Assert(!fdi.d_body.isNull());
+  fdi.d_args.insert(fdi.d_args.end(), q[0].begin(), q[0].end());
+  Trace("fd-eval") << "FunDefEvaluator: function " << f << " is defined with "
+                   << fdi.d_args << " / " << fdi.d_body << std::endl;
+}
+
+Node FunDefEvaluator::evaluate(Node n) const
+{
+  // should do standard rewrite before this call
+  Assert(Rewriter::rewrite(n) == n);
+  Trace("fd-eval") << "FunDefEvaluator: evaluate " << n << std::endl;
+  NodeManager* nm = NodeManager::currentNM();
+  std::unordered_map<TNode, Node, TNodeHashFunction> visited;
+  std::unordered_map<TNode, Node, TNodeHashFunction>::iterator it;
+  std::map<Node, FunDefInfo>::const_iterator itf;
+  std::vector<TNode> visit;
+  TNode cur;
+  TNode curEval;
+  Node f;
+  visit.push_back(n);
+  do
+  {
+    cur = visit.back();
+    visit.pop_back();
+    it = visited.find(cur);
+    Trace("fd-eval-debug") << "evaluate subterm " << cur << std::endl;
+
+    if (it == visited.end())
+    {
+      if (cur.isConst())
+      {
+        Trace("fd-eval-debug") << "constant " << cur << std::endl;
+        visited[cur] = cur;
+      }
+      else
+      {
+        Trace("fd-eval-debug") << "recurse " << cur << std::endl;
+        visited[cur] = Node::null();
+        visit.push_back(cur);
+        for (const Node& cn : cur)
+        {
+          visit.push_back(cn);
+        }
+      }
+    }
+    else
+    {
+      curEval = it->second;
+      if (curEval.isNull())
+      {
+        Trace("fd-eval-debug") << "from arguments " << cur << std::endl;
+        Node ret = cur;
+        bool childChanged = false;
+        std::vector<Node> children;
+        Kind ck = cur.getKind();
+        // If a parameterized node that is not APPLY_UF (which is handled below,
+        // we add it to the children vector.
+        if (ck != APPLY_UF && cur.getMetaKind() == metakind::PARAMETERIZED)
+        {
+          children.push_back(cur.getOperator());
+        }
+        for (const Node& cn : cur)
+        {
+          it = visited.find(cn);
+          Assert(it != visited.end());
+          Assert(!it->second.isNull());
+          childChanged = childChanged || cn != it->second;
+          children.push_back(it->second);
+        }
+        if (cur.getKind() == APPLY_UF)
+        {
+          // need to evaluate it
+          f = cur.getOperator();
+          itf = d_funDefMap.find(f);
+          if (itf == d_funDefMap.end())
+          {
+            Trace("fd-eval") << "FunDefEvaluator: no definition for " << f
+                             << ", FAIL" << std::endl;
+            return Node::null();
+          }
+          // get the function definition
+          Node sbody = itf->second.d_body;
+          const std::vector<Node>& args = itf->second.d_args;
+          if (!args.empty())
+          {
+            // invoke it on arguments
+            sbody = sbody.substitute(
+                args.begin(), args.end(), children.begin(), children.end());
+            // rewrite it
+            sbody = Rewriter::rewrite(sbody);
+          }
+          // our result is the result of the body
+          visited[cur] = sbody;
+          // If its not constant, we push back self and the substituted body.
+          // Thus, we evaluate the body first; our result will be the result of
+          // evaluating the body.
+          if (!sbody.isConst())
+          {
+            visit.push_back(cur);
+            visit.push_back(sbody);
+          }
+        }
+        else
+        {
+          if (childChanged)
+          {
+            ret = nm->mkNode(cur.getKind(), children);
+            ret = Rewriter::rewrite(ret);
+          }
+          if (!ret.isConst())
+          {
+            Trace("fd-eval") << "FunDefEvaluator: non-constant subterm " << ret
+                             << ", FAIL" << std::endl;
+            // failed, possibly due to free variable
+            return Node::null();
+          }
+          visited[cur] = ret;
+        }
+      }
+      else if (!curEval.isConst())
+      {
+        Trace("fd-eval-debug") << "from body " << cur << std::endl;
+        // we had to evaluate our body, which should have a definition now
+        it = visited.find(curEval);
+        Assert(it != visited.end());
+        // our definition is the result of the body
+        visited[cur] = it->second;
+        Assert(it->second.isConst());
+      }
+    }
+  } while (!visit.empty());
+  Assert(visited.find(n) != visited.end());
+  Assert(!visited.find(n)->second.isNull());
+  Assert(visited.find(n)->second.isConst());
+  Trace("fd-eval") << "FunDefEvaluator: return SUCCESS " << visited[n]
+                   << std::endl;
+  return visited[n];
+}
+
+bool FunDefEvaluator::hasDefinitions() const { return !d_funDefMap.empty(); }
+
+}  // namespace quantifiers
+}  // namespace theory
+}  // namespace CVC4
diff --git a/src/theory/quantifiers/fun_def_evaluator.h b/src/theory/quantifiers/fun_def_evaluator.h
new file mode 100644 (file)
index 0000000..ad08dfa
--- /dev/null
@@ -0,0 +1,72 @@
+/*********************                                                        */
+/*! \file fun_def_evaluator.h
+ ** \verbatim
+ ** Top contributors (to current version):
+ **   Andrew Reynolds
+ ** This file is part of the CVC4 project.
+ ** Copyright (c) 2009-2019 by the authors listed in the file AUTHORS
+ ** in the top-level source directory) and their institutional affiliations.
+ ** All rights reserved.  See the file COPYING in the top-level source
+ ** directory for licensing information.\endverbatim
+ **
+ ** \brief Techniques for evaluating terms with recursively defined functions.
+ **/
+
+#include "cvc4_private.h"
+
+#ifndef CVC4__QUANTIFIERS_FUN_DEF_EVALUATOR_H
+#define CVC4__QUANTIFIERS_FUN_DEF_EVALUATOR_H
+
+#include <map>
+#include <vector>
+#include "expr/node.h"
+
+namespace CVC4 {
+namespace theory {
+namespace quantifiers {
+
+/**
+ * Techniques for evaluating recursively defined functions.
+ */
+class FunDefEvaluator
+{
+ public:
+  FunDefEvaluator();
+  ~FunDefEvaluator() {}
+  /**
+   * Assert definition of a (recursive) function definition given by quantified
+   * formula q.
+   */
+  void assertDefinition(Node q);
+  /**
+   * Simplify node based on the (recursive) function definitions known by this
+   * class. If n cannot be simplified to a constant, then this method returns
+   * null.
+   */
+  Node evaluate(Node n) const;
+  /**
+   * Has a call to assertDefinition been made? If this returns false, then
+   * the evaluate method is the same as calling the rewriter, and returning
+   * false if the result is non-constant.
+   */
+  bool hasDefinitions() const;
+
+ private:
+  /** information cached per function definition */
+  class FunDefInfo
+  {
+   public:
+    /** the body */
+    Node d_body;
+    /** the formal argument list */
+    std::vector<Node> d_args;
+  };
+  /** maps functions to the above information */
+  std::map<Node, FunDefInfo> d_funDefMap;
+};
+
+}  // namespace quantifiers
+}  // namespace theory
+}  // namespace CVC4
+
+#endif
index 5edd18464d935d5771d34707939fc78e20ab9bd6..6e69715efda6e7c9a784ead2224bce0d208fdc44 100644 (file)
@@ -539,7 +539,8 @@ bool SynthConjecture::doCheck(std::vector<Node>& lems)
     return false;
   }
 
-  lem = Rewriter::rewrite(lem);
+  // simplify the lemma based on the term database sygus utility
+  lem = d_tds->rewriteNode(lem);
   // eagerly unfold applications of evaluation function
   Trace("cegqi-debug") << "pre-unfold counterexample : " << lem << std::endl;
   // record the instantiation
index 88d00ce3a175b3daea31fd80f073c90e45ecbfbe..9f6954416f34c2bea49ded752be4a75378c68bb5 100644 (file)
@@ -33,7 +33,7 @@ namespace theory {
 namespace quantifiers {
 
 SynthEngine::SynthEngine(QuantifiersEngine* qe, context::Context* c)
-    : QuantifiersModule(qe)
+    : QuantifiersModule(qe), d_tds(qe->getTermDatabaseSygus())
 {
   d_conjs.push_back(std::unique_ptr<SynthConjecture>(
       new SynthConjecture(d_quantEngine, this)));
@@ -274,6 +274,8 @@ void SynthEngine::assignConjecture(Node q)
 
 void SynthEngine::registerQuantifier(Node q)
 {
+  Trace("cegqi-debug") << "SynthEngine: Register quantifier : " << q
+                       << std::endl;
   if (d_quantEngine->getOwner(q) == this)
   {
     Trace("cegqi") << "Register conjecture : " << q << std::endl;
@@ -287,9 +289,15 @@ void SynthEngine::registerQuantifier(Node q)
       assignConjecture(q);
     }
   }
-  else
+  if (options::sygusRecFun())
   {
-    Trace("cegqi-debug") << "Register quantifier : " << q << std::endl;
+    if (d_quantEngine->getQuantAttributes()->isFunDef(q))
+    {
+      // If it is a recursive function definition, add it to the function
+      // definition evaluator class.
+      FunDefEvaluator* fde = d_tds->getFunDefEvaluator();
+      fde->assertDefinition(q);
+    }
   }
 }
 
index 20e4deec6bd085f185dba31f9895ca16b5578c4e..1eca56959a1d0383b77eadd70216fb5046f3219a 100644 (file)
@@ -30,33 +30,6 @@ class SynthEngine : public QuantifiersModule
 {
   typedef context::CDHashMap<Node, bool, NodeHashFunction> NodeBoolMap;
 
- private:
-  /** the conjecture formula(s) we are waiting to assign */
-  std::vector<Node> d_waiting_conj;
-  /** The synthesis conjectures that this class is managing. */
-  std::vector<std::unique_ptr<SynthConjecture> > d_conjs;
-  /**
-   * The first conjecture in the above vector. We track this conjecture
-   * so that a synthesis conjecture can be preregistered during a call to
-   * preregisterAssertion.
-   */
-  SynthConjecture* d_conj;
-  /** assign quantified formula q as a conjecture
-   *
-   * This method either assigns q to a synthesis conjecture object in d_conjs,
-   * or otherwise reduces q to an equivalent form. This method does the latter
-   * if this class determines that it would rather rewrite q to an equivalent
-   * form r (in which case this method returns the lemma q <=> r). An example of
-   * this is the quantifier elimination step option::sygusQePreproc().
-   */
-  void assignConjecture(Node q);
-  /** check conjecture
-   *
-   * This method returns true if the conjecture is finished processing solutions
-   * for this call to SynthEngine::check().
-   */
-  bool checkConjecture(SynthConjecture* conj);
-
  public:
   SynthEngine(QuantifiersEngine* qe, context::Context* c);
   ~SynthEngine();
@@ -113,6 +86,35 @@ class SynthEngine : public QuantifiersModule
     ~Statistics();
   }; /* class SynthEngine::Statistics */
   Statistics d_statistics;
+
+ private:
+  /** term database sygus of d_qe */
+  TermDbSygus* d_tds;
+  /** the conjecture formula(s) we are waiting to assign */
+  std::vector<Node> d_waiting_conj;
+  /** The synthesis conjectures that this class is managing. */
+  std::vector<std::unique_ptr<SynthConjecture> > d_conjs;
+  /**
+   * The first conjecture in the above vector. We track this conjecture
+   * so that a synthesis conjecture can be preregistered during a call to
+   * preregisterAssertion.
+   */
+  SynthConjecture* d_conj;
+  /** assign quantified formula q as a conjecture
+   *
+   * This method either assigns q to a synthesis conjecture object in d_conjs,
+   * or otherwise reduces q to an equivalent form. This method does the latter
+   * if this class determines that it would rather rewrite q to an equivalent
+   * form r (in which case this method returns the lemma q <=> r). An example of
+   * this is the quantifier elimination step option::sygusQePreproc().
+   */
+  void assignConjecture(Node q);
+  /** check conjecture
+   *
+   * This method returns true if the conjecture is finished processing solutions
+   * for this call to SynthEngine::check().
+   */
+  bool checkConjecture(SynthConjecture* conj);
 }; /* class SynthEngine */
 
 }  // namespace quantifiers
index d17c343f4b7126d927e18092a9d1336d9bc13b3d..84a9c2405f777b1fdcae045627dd114aa67c5ffd 100644 (file)
@@ -50,6 +50,7 @@ TermDbSygus::TermDbSygus(context::Context* c, QuantifiersEngine* qe)
       d_syexp(new SygusExplain(this)),
       d_ext_rw(new ExtendedRewriter(true)),
       d_eval(new Evaluator),
+      d_funDefEval(new FunDefEvaluator),
       d_eval_unfold(new SygusEvalUnfold(this))
 {
   d_true = NodeManager::currentNM()->mkConst( true );
@@ -725,6 +726,28 @@ SygusTypeInfo& TermDbSygus::getTypeInfo(TypeNode tn)
   return d_tinfo[tn];
 }
 
+Node TermDbSygus::rewriteNode(Node n) const
+{
+  Node res = Rewriter::rewrite(n);
+  if (options::sygusRecFun())
+  {
+    if (d_funDefEval->hasDefinitions())
+    {
+      // If recursive functions are enabled, then we use the recursive function
+      // evaluation utility.
+      Node fres = d_funDefEval->evaluate(res);
+      if (!fres.isNull())
+      {
+        return fres;
+      }
+      // It may have failed, in which case there are undefined symbols in res.
+      // In this case, we revert to the result of rewriting in the return
+      // statement below.
+    }
+  }
+  return res;
+}
+
 unsigned TermDbSygus::getSelectorWeight(TypeNode tn, Node sel)
 {
   std::map<TypeNode, std::map<Node, unsigned> >::iterator itsw =
@@ -1061,7 +1084,9 @@ Node TermDbSygus::evaluateBuiltin(TypeNode tn,
     return res;
   }
   res = bn.substitute(varlist.begin(), varlist.end(), args.begin(), args.end());
-  return Rewriter::rewrite(res);
+  // Call the rewrite node function, which may involve recursive function
+  // evaluation.
+  return rewriteNode(res);
 }
 
 Node TermDbSygus::evaluateWithUnfolding(
index c591400e15f263eb9c62d0bacd5c09f7a65c869b..0ec883537d43a43c7bc6be3b23048d15d43867f8 100644 (file)
@@ -21,6 +21,7 @@
 
 #include "theory/evaluator.h"
 #include "theory/quantifiers/extended_rewrite.h"
+#include "theory/quantifiers/fun_def_evaluator.h"
 #include "theory/quantifiers/sygus/sygus_eval_unfold.h"
 #include "theory/quantifiers/sygus/sygus_explain.h"
 #include "theory/quantifiers/sygus/type_info.h"
@@ -77,6 +78,8 @@ class TermDbSygus {
   ExtendedRewriter* getExtRewriter() { return d_ext_rw.get(); }
   /** get the evaluator */
   Evaluator* getEvaluator() { return d_eval.get(); }
+  /** (recursive) function evaluator utility */
+  FunDefEvaluator* getFunDefEvaluator() { return d_funDefEval.get(); }
   /** evaluation unfolding utility */
   SygusEvalUnfold* getEvalUnfold() { return d_eval_unfold.get(); }
   //------------------------------end utilities
@@ -302,6 +305,11 @@ class TermDbSygus {
    * (a subfield type of) a type that has been registered to this class.
    */
   SygusTypeInfo& getTypeInfo(TypeNode tn);
+  /**
+   * Rewrite the given node using the utilities in this class. This may
+   * involve (recursive function) evaluation.
+   */
+  Node rewriteNode(Node n) const;
 
   /** print to sygus stream n on trace c */
   static void toStreamSygus(const char* c, Node n);
@@ -317,6 +325,8 @@ class TermDbSygus {
   std::unique_ptr<ExtendedRewriter> d_ext_rw;
   /** evaluator */
   std::unique_ptr<Evaluator> d_eval;
+  /** (recursive) function evaluator utility */
+  std::unique_ptr<FunDefEvaluator> d_funDefEval;
   /** evaluation function unfolding utility */
   std::unique_ptr<SygusEvalUnfold> d_eval_unfold;
   //------------------------------end utilities
index 7765591f87d594c47e05193f1d699e7caa5f34cc..c98dd1be2c6baddddb755c92cecc67ae0cc10f6b 100644 (file)
@@ -1736,6 +1736,7 @@ set(regress_1_tests
   regress1/sygus/qe.sy
   regress1/sygus/qf_abv.smt2
   regress1/sygus/real-grammar.sy
+  regress1/sygus/rec-fun-sygus.sy
   regress1/sygus/re-concat.sy
   regress1/sygus/repair-const-rl.sy
   regress1/sygus/simple-regexp.sy
diff --git a/test/regress/regress1/sygus/rec-fun-sygus.sy b/test/regress/regress1/sygus/rec-fun-sygus.sy
new file mode 100644 (file)
index 0000000..13d4782
--- /dev/null
@@ -0,0 +1,21 @@
+; EXPECT: unsat
+; COMMAND-LINE: --sygus-out=status --sygus-rec-fun --lang=sygus2
+
+(set-logic ALL)
+
+(declare-datatype List ((cons (head Int) (tail List)) (nil)))
+
+(define-fun-rec sum ((x List)) Int (ite ((_ is nil) x) 0 (+ (head x) (sum (tail x)))))
+(define-fun-rec size ((x List)) Int (ite ((_ is nil) x) 0 (+ 1 (size (tail x)))))
+
+(synth-fun f ((x List)) Int
+  ((I Int) (L List))
+  ((I Int  ((+ I I) (sum L) (size L) 0 1))
+   (L List (x))))
+   
+(constraint (= (f (cons 3 (cons 4 (cons 5 nil)))) 15))
+(constraint (= (f (cons 3 (cons (- 4) (cons 5 nil)))) 7))
+(constraint (= (f (cons 5 (cons 5 nil))) 12))
+(constraint (= (f (cons 0 (cons 0 (cons 0 (cons 0 (cons 0 (cons 0 nil))))))) 6))
+(constraint (= (f (cons (- 2) (cons 0 nil))) 0))
+(check-synth)