Allow SyGuS subsolver to be reused in incremental mode (#7785)
authorAndrew Reynolds <andrew.j.reynolds@gmail.com>
Mon, 20 Dec 2021 15:23:16 +0000 (09:23 -0600)
committerGitHub <noreply@github.com>
Mon, 20 Dec 2021 15:23:16 +0000 (15:23 +0000)
This allows SyGuS subsolvers to be called multiple times to produce solutions for a given set of SyGuS constraints using a new command check-synth-next.

By default, the SyGuS subsolver will generate a new solution for each successive check-synth.

This simplifies the internal SyGuS solver in several ways for this purpose. It also ensures that solutions are cached; current master may recompute solutions in the same state more than once if asked, which is no longer the case.

This completes our support for incremental SyGuS.

28 files changed:
src/api/cpp/cvc5.cpp
src/api/cpp/cvc5.h
src/api/java/io/github/cvc5/api/Solver.java
src/api/java/jni/solver.cpp
src/api/python/cvc5.pxd
src/api/python/cvc5.pxi
src/parser/smt2/Smt2.g
src/printer/printer.cpp
src/printer/printer.h
src/printer/smt2/smt2_printer.cpp
src/printer/smt2/smt2_printer.h
src/smt/command.cpp
src/smt/command.h
src/smt/smt_mode.cpp
src/smt/smt_mode.h
src/smt/solver_engine.cpp
src/smt/solver_engine.h
src/smt/solver_engine_state.cpp
src/smt/solver_engine_state.h
src/smt/sygus_solver.cpp
src/smt/sygus_solver.h
src/theory/quantifiers/sygus/synth_conjecture.cpp
src/theory/quantifiers/sygus/synth_conjecture.h
test/regress/CMakeLists.txt
test/regress/regress1/sygus/incremental-stream-ex.sy [new file with mode: 0644]
test/unit/api/cpp/solver_black.cpp
test/unit/api/java/SolverTest.java
test/unit/api/python/test_solver.py

index 24b4500d59f6b4795444468b1519d90d59c56085..bdaef769e0cc1dee97fce548ad01b68b88afa411 100644 (file)
@@ -7944,6 +7944,18 @@ Result Solver::checkSynth() const
   CVC5_API_TRY_CATCH_END;
 }
 
+Result Solver::checkSynthNext() const
+{
+  CVC5_API_TRY_CATCH_BEGIN;
+  CVC5_API_CHECK(d_slv->getOptions().base.incrementalSolving)
+      << "Cannot checkSynthNext when not solving incrementally (use "
+         "--incremental)";
+  //////// all checks before this line
+  return d_slv->checkSynth(true);
+  ////////
+  CVC5_API_TRY_CATCH_END;
+}
+
 Term Solver::getSynthSolution(Term term) const
 {
   CVC5_API_TRY_CATCH_BEGIN;
index f2bfb48e29b82a90cddbe9391704db4503b75c7b..3b22f0c6f205a774f1af5012246b19a097198169 100644 (file)
@@ -4811,10 +4811,30 @@ class CVC5_EXPORT Solver
    *     (check-synth)
    * \endverbatim
    *
-   * @return the result of the synthesis conjecture.
+   * @return the result of the check, which is unsat if the check succeeded,
+   * in which case solutions are available via getSynthSolutions.
    */
   Result checkSynth() const;
 
+  /**
+   * Try to find a next solution for the synthesis conjecture corresponding to
+   * the current list of functions-to-synthesize, universal variables and
+   * constraints. Must be called immediately after a successful call to
+   * check-synth or check-synth-next. Requires incremental mode.
+   *
+   * SyGuS v2:
+   *
+   * \verbatim embed:rst:leading-asterisk
+   * .. code:: smtlib
+   *
+   *     (check-synth-next)
+   * \endverbatim
+   *
+   * @return the result of the check, which is unsat if the check succeeded,
+   * in which case solutions are available via getSynthSolutions.
+   */
+  Result checkSynthNext() const;
+
   /**
    * Get the synthesis solution of the given term. This method should be called
    * immediately after the solver answers unsat for sygus input.
index d04b766e0633cd187c52cb70cfa114c5a7ea1a23..64bef09c90a8a13b604ef0fd273fb51225b9f76d 100644 (file)
@@ -2594,7 +2594,8 @@ public class Solver implements IPointer, AutoCloseable
    * {@code
    *   ( check-synth )
    * }
-   * @return the result of the synthesis conjecture.
+   * @return the result of the check, which is unsat if the check succeeded,
+   * in which case solutions are available via getSynthSolutions.
    */
   public Result checkSynth()
   {
@@ -2604,6 +2605,26 @@ public class Solver implements IPointer, AutoCloseable
 
   private native long checkSynth(long pointer);
 
+  /**
+   * Try to find a next solution for the synthesis conjecture corresponding to
+   * the current list of functions-to-synthesize, universal variables and
+   * constraints. Must be called immediately after a successful call to
+   * check-synth or check-synth-next. Requires incremental mode.
+   * SyGuS v2:
+   * {@code
+   *   ( check-synth-next )
+   * }
+   * @return the result of the check, which is UNSAT if the check succeeded,
+   * in which case solutions are available via getSynthSolutions.
+   */
+  public Result checkSynthNext()
+  {
+    long resultPointer = checkSynthNext(pointer);
+    return new Result(this, resultPointer);
+  }
+
+  private native long checkSynthNext(long pointer);
+
   /**
    * Get the synthesis solution of the given term. This method should be called
    * immediately after the solver answers unsat for sygus input.
index 8008ebada03e87fe0e5c0276938308452b96a1a7..73e481b49756277295d820b49046ac8c6f00e1b6 100644 (file)
@@ -2629,6 +2629,21 @@ JNIEXPORT jlong JNICALL Java_io_github_cvc5_api_Solver_checkSynth(JNIEnv* env,
   CVC5_JAVA_API_TRY_CATCH_END_RETURN(env, 0);
 }
 
+/*
+ * Class:     io_github_cvc5_api_Solver
+ * Method:    checkSynthNext
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_io_github_cvc5_api_Solver_checkSynthNext(
+    JNIEnv* env, jobject, jlong pointer)
+{
+  CVC5_JAVA_API_TRY_CATCH_BEGIN;
+  Solver* solver = reinterpret_cast<Solver*>(pointer);
+  Result* retPointer = new Result(solver->checkSynthNext());
+  return reinterpret_cast<jlong>(retPointer);
+  CVC5_JAVA_API_TRY_CATCH_END_RETURN(env, 0);
+}
+
 /*
  * Class:     io_github_cvc5_api_Solver
  * Method:    getSynthSolution
index 5a33628a6eedbcc193af802706b902a3f2a7e2ce..552fe6399f7085de28bb3df8ce91db9f8086afb9 100644 (file)
@@ -204,6 +204,7 @@ cdef extern from "api/cpp/cvc5.h" namespace "cvc5::api":
         Term synthFun(const string& symbol, const vector[Term]& bound_vars, Sort sort) except +
         Term synthFun(const string& symbol, const vector[Term]& bound_vars, Sort sort, Grammar grammar) except +
         Result checkSynth() except +
+        Result checkSynthNext() except +
         Term getSynthSolution(Term t) except +
         vector[Term] getSynthSolutions(const vector[Term]& terms) except +
         Term synthInv(const string& symbol, const vector[Term]& bound_vars) except +
index d3f58ce620bcab7f2f8adb5be0a7b21ab02d44e5..63e21bf7ec862aedbd26e6f860727da3aa777d50 100644 (file)
@@ -1694,6 +1694,26 @@ cdef class Solver:
         r.cr = self.csolver.checkSynth()
         return r
 
+    def checkSynthNext(self):
+        """
+        Try to find a next solution for the synthesis conjecture corresponding
+        to the current list of functions-to-synthesize, universal variables and
+        constraints. Must be called immediately after a successful call to
+        check-synth or check-synth-next. Requires incremental mode.
+
+        SyGuS v2:
+
+        .. code-block:: smtlib
+
+            ( check-synth )
+
+        :return: the result of the check, which is unsat if the check succeeded,
+        in which case solutions are available via getSynthSolutions.
+        """
+        cdef Result r = Result()
+        r.cr = self.csolver.checkSynthNext()
+        return r
+
     def getSynthSolution(self, Term term):
         """
         Get the synthesis solution of the given term. This method should be
index 41cd4869a9fd8dfa8c5adef7be592814f872102f..7f95368b026f97e8c566819938134a6ea814b6dc 100644 (file)
@@ -576,6 +576,12 @@ sygusCommand returns [std::unique_ptr<cvc5::Command> cmd]
       PARSER_STATE->checkThatLogicIsSet();
       cmd.reset(new CheckSynthCommand());
     }
+  | /* check-synth-next */
+    CHECK_SYNTH_NEXT_TOK
+    {
+      PARSER_STATE->checkThatLogicIsSet();
+      cmd.reset(new CheckSynthCommand(true));
+    }
   | /* set-feature */
     SET_FEATURE_TOK keyword[name] symbolicExpr[expr]
     {
@@ -2240,6 +2246,7 @@ DECLARE_POOL : 'declare-pool';
 SYNTH_FUN_TOK : { PARSER_STATE->sygus() }?'synth-fun';
 SYNTH_INV_TOK : { PARSER_STATE->sygus()}?'synth-inv';
 CHECK_SYNTH_TOK : { PARSER_STATE->sygus()}?'check-synth';
+CHECK_SYNTH_NEXT_TOK : { PARSER_STATE->sygus()}?'check-synth-next';
 DECLARE_VAR_TOK : { PARSER_STATE->sygus()}?'declare-var';
 CONSTRAINT_TOK : { PARSER_STATE->sygus()}?'constraint';
 ASSUME_TOK : { PARSER_STATE->sygus()}?'assume';
index 16b626e3046926e030b174bcffb8f72c5f572e02..d1c2c58a5d2dd95be1360e0025db8c58878c1fb8 100644 (file)
@@ -350,6 +350,10 @@ void Printer::toStreamCmdCheckSynth(std::ostream& out) const
 {
   printUnknownCommand(out, "check-synth");
 }
+void Printer::toStreamCmdCheckSynthNext(std::ostream& out) const
+{
+  printUnknownCommand(out, "check-synth-next");
+}
 
 void Printer::toStreamCmdSimplify(std::ostream& out, Node n) const
 {
index 485cd70e6973cedc18880b5e8d2f4c579c25b5b9..d55723443042ff320c245a4a7e68011766b57dc4 100644 (file)
@@ -165,6 +165,9 @@ class Printer
   /** Print check-synth command */
   virtual void toStreamCmdCheckSynth(std::ostream& out) const;
 
+  /** Print check-synth-next command */
+  virtual void toStreamCmdCheckSynthNext(std::ostream& out) const;
+
   /** Print simplify command */
   virtual void toStreamCmdSimplify(std::ostream& out, Node n) const;
 
index 875ca7dc25acce93c99a00eed534ca8c69d1132f..2a20bba36eb52618c9a8ac9e56bf28ce6b1b47c2 100644 (file)
@@ -1899,6 +1899,11 @@ void Smt2Printer::toStreamCmdCheckSynth(std::ostream& out) const
   out << "(check-synth)" << std::endl;
 }
 
+void Smt2Printer::toStreamCmdCheckSynthNext(std::ostream& out) const
+{
+  out << "(check-synth-next)" << std::endl;
+}
+
 void Smt2Printer::toStreamCmdGetInterpol(std::ostream& out,
                                          const std::string& name,
                                          Node conj,
index 4ee333f67e19bb773d8106c9bb5bd2bc2268cb0a..b97ad2ffb1c928bd8ed5644fc8cd0092af7636c3 100644 (file)
@@ -142,6 +142,9 @@ class Smt2Printer : public cvc5::Printer
   /** Print check-synth command */
   void toStreamCmdCheckSynth(std::ostream& out) const override;
 
+  /** Print check-synth-next command */
+  void toStreamCmdCheckSynthNext(std::ostream& out) const override;
+
   /** Print simplify command */
   void toStreamCmdSimplify(std::ostream& out, Node nodes) const override;
 
index 1321c558e5b1d20653b2ff722e5cefa456e17c6f..ac393d568bcd18b27b9d845f8b597e59a466a698 100644 (file)
@@ -833,7 +833,7 @@ void CheckSynthCommand::invoke(api::Solver* solver, SymbolManager* sm)
 {
   try
   {
-    d_result = solver->checkSynth();
+    d_result = d_isNext ? solver->checkSynthNext() : solver->checkSynth();
     d_commandStatus = CommandSuccess::instance();
     d_solution.clear();
     // check whether we should print the status
@@ -901,14 +901,24 @@ void CheckSynthCommand::printResult(std::ostream& out) const
 
 Command* CheckSynthCommand::clone() const { return new CheckSynthCommand(); }
 
-std::string CheckSynthCommand::getCommandName() const { return "check-synth"; }
+std::string CheckSynthCommand::getCommandName() const
+{
+  return d_isNext ? "check-synth-next" : "check-synth";
+}
 
 void CheckSynthCommand::toStream(std::ostream& out,
                                  int toDepth,
                                  size_t dag,
                                  Language language) const
 {
-  Printer::getPrinter(language)->toStreamCmdCheckSynth(out);
+  if (d_isNext)
+  {
+    Printer::getPrinter(language)->toStreamCmdCheckSynthNext(out);
+  }
+  else
+  {
+    Printer::getPrinter(language)->toStreamCmdCheckSynth(out);
+  }
 }
 
 /* -------------------------------------------------------------------------- */
index 6ca5a7fc8b2c0a3ab2e60461c52ed8b63f74efc3..1a00c2adaceeb7c9fad28a7de8abd6f506b52374 100644 (file)
@@ -821,7 +821,7 @@ class CVC5_EXPORT SygusInvConstraintCommand : public Command
 class CVC5_EXPORT CheckSynthCommand : public Command
 {
  public:
-  CheckSynthCommand(){};
+  CheckSynthCommand(bool isNext = false) : d_isNext(isNext){};
   /** returns the result of the check-synth call */
   api::Result getResult() const;
   /** prints the result of the check-synth-call */
@@ -846,6 +846,8 @@ class CVC5_EXPORT CheckSynthCommand : public Command
                 Language language = Language::LANG_AUTO) const override;
 
  protected:
+  /** Whether this is a check-synth-next call */
+  bool d_isNext;
   /** result of the check-synth call */
   api::Result d_result;
   /** string stream that stores the output of the solution */
index 6a6e35dd952fb25839885b29043393f256e39440..37d1d5950bdc7ec12f4302b82423c9af3e9ae4eb 100644 (file)
@@ -30,6 +30,7 @@ std::ostream& operator<<(std::ostream& out, SmtMode m)
     case SmtMode::UNSAT: out << "UNSAT"; break;
     case SmtMode::ABDUCT: out << "ABDUCT"; break;
     case SmtMode::INTERPOL: out << "INTERPOL"; break;
+    case SmtMode::SYNTH: out << "SYNTH"; break;
     default: out << "SmtMode!Unknown"; break;
   }
   return out;
index aff9b63660002f0d2d7d86638707bf3e95576d40..098bf1cb754eeaada9aa10ef30c12f7227e2e449 100644 (file)
@@ -42,7 +42,9 @@ enum class SmtMode
   // immediately after a successful call to get-abduct
   ABDUCT,
   // immediately after a successful call to get-interpol
-  INTERPOL
+  INTERPOL,
+  // immediately after a successful call to check-synth or check-synth-next
+  SYNTH
 };
 /**
  * Writes a SmtMode to a stream.
index 13d061f1f8da2b09416a49960623245106c82730..09814c3008acdedf107f21e9537de79ad9eecaad 100644 (file)
@@ -934,11 +934,19 @@ void SolverEngine::assertSygusInvConstraint(Node inv,
   d_sygusSolver->assertSygusInvConstraint(inv, pre, trans, post);
 }
 
-Result SolverEngine::checkSynth()
+Result SolverEngine::checkSynth(bool isNext)
 {
   SolverEngineScope smts(this);
   finishInit();
-  return d_sygusSolver->checkSynth(*d_asserts);
+  if (isNext && d_state->getMode() != SmtMode::SYNTH)
+  {
+    throw RecoverableModalException(
+        "Cannot check-synth-next unless immediately preceded by a successful "
+        "call to check-synth.");
+  }
+  Result r = d_sygusSolver->checkSynth(*d_asserts, isNext);
+  d_state->notifyCheckSynthResult(r);
+  return r;
 }
 
 /*
index 1299a493a42b525d16f908c9e504c5a8277728f0..ecfb521a01d176d944f9cb75c1eb6f85e3c1acce 100644 (file)
@@ -451,9 +451,12 @@ class CVC5_EXPORT SolverEngine
    * in which f1...fn are the functions-to-synthesize, v1...vm are the declared
    * universal variables and F is the set of declared constraints.
    *
+   * @param isNext Whether we are asking for the next synthesis solution (if
+   * using incremental).
+   *
    * @throw Exception
    */
-  Result checkSynth();
+  Result checkSynth(bool isNext = false);
 
   /*------------------------- end of sygus commands ------------------------*/
 
index 8558e91cf63181a46d80be38312dbadff188cd63..4ec577b68833a7fd47e347aebac3332d976e7950 100644 (file)
@@ -113,6 +113,20 @@ void SolverEngineState::notifyCheckSatResult(bool hasAssumptions, Result r)
   }
 }
 
+void SolverEngineState::notifyCheckSynthResult(Result r)
+{
+  if (r.asSatisfiabilityResult().isSat() == Result::UNSAT)
+  {
+    // successfully generated a synthesis solution, update to abduct state
+    d_smtMode = SmtMode::SYNTH;
+  }
+  else
+  {
+    // failed, we revert to the assert state
+    d_smtMode = SmtMode::ASSERT;
+  }
+}
+
 void SolverEngineState::notifyGetAbduct(bool success)
 {
   if (success)
index 7a06b0510b9c294eca50c9b19c008a872f1ffc7e..5cc72fd08255d4741fe19f427f05c7dc840212f6 100644 (file)
@@ -87,6 +87,11 @@ class SolverEngineState : protected EnvObj
    * @param r The result of the check-sat call.
    */
   void notifyCheckSatResult(bool hasAssumptions, Result r);
+  /**
+   * Notify that the result of the last check-synth or check-synth-next was r.
+   * @param r The result of the check-synth or check-synth-next call.
+   */
+  void notifyCheckSynthResult(Result r);
   /**
    * Notify that we finished an abduction query, where success is whether the
    * command was successful. This is managed independently of the above
index 70ad656442f67c8da7eff5a3a3cc4c73d2292d27..692a123dbc78eba6545cc7dff929752df6ddc712 100644 (file)
@@ -183,10 +183,15 @@ void SygusSolver::assertSygusInvConstraint(Node inv,
   d_sygusConjectureStale = true;
 }
 
-Result SygusSolver::checkSynth(Assertions& as)
+Result SygusSolver::checkSynth(Assertions& as, bool isNext)
 {
   Trace("smt") << "SygusSolver::checkSynth" << std::endl;
   // if applicable, check if the subsolver is the correct one
+  if (!isNext)
+  {
+    // if we are not using check-synth-next, we always reconstruct the solver.
+    d_sygusConjectureStale = true;
+  }
   if (usingSygusSubsolver() && d_subsolverCd.get() != d_subsolver.get())
   {
     // this can occur if we backtrack to a place where we were using a different
@@ -194,11 +199,6 @@ Result SygusSolver::checkSynth(Assertions& as)
     // the subsolver.
     d_sygusConjectureStale = true;
   }
-  // TODO (project #7): we currently must always rebuild the synthesis
-  // conjecture + subsolver, since it answers unsat. When the subsolver is
-  // updated to treat "sat" as solution for synthesis conjecture, this line
-  // will be deleted.
-  d_sygusConjectureStale = true;
   if (d_sygusConjectureStale)
   {
     NodeManager* nm = NodeManager::currentNM();
index 4e7a04364557c0bb853f9bcd1ad8bfd125f81eef..b37762ce1ceb3168a6271971e9dda3c821a497ba 100644 (file)
@@ -131,7 +131,7 @@ class SygusSolver : protected EnvObj
    * in which f1...fn are the functions-to-synthesize, v1...vm are the declared
    * universal variables and F is the set of declared constraints.
    */
-  Result checkSynth(Assertions& as);
+  Result checkSynth(Assertions& as, bool isNext);
   /**
    * Get synth solution.
    *
index e5dfe408b0a17562d2821ce075ef7659b9c5704a..2a1258082c38b60c6589f262f6bd5d32c611608a 100644 (file)
@@ -59,6 +59,7 @@ SynthConjecture::SynthConjecture(Env& env,
       d_tds(tr.getTermDatabaseSygus()),
       d_verify(env, d_tds),
       d_hasSolution(false),
+      d_computedSolution(false),
       d_ceg_si(new CegSingleInv(env, tr, s)),
       d_templInfer(new SygusTemplateInfer(env)),
       d_ceg_proc(new SynthConjectureProcess(env)),
@@ -93,8 +94,22 @@ SynthConjecture::~SynthConjecture() {}
 
 void SynthConjecture::presolve()
 {
-  // we don't have a solution yet
-  d_hasSolution = false;
+  // If we previously had a solution, block it. Notice that
+  // excludeCurrentSolution in the block below ensures we implement a
+  // policy where a *new* solution is generated for check-synth if the set of
+  // SyGuS constraints has not changed. This call will block solutions for
+  // *smart* enumerators only. This behavior makes smart enumeration have
+  // a consistent policy with *fast* enumerators, which will generate
+  // a new, next solution in their enumeration.
+  if (d_hasSolution)
+  {
+    excludeCurrentSolution(d_solutionValues.back());
+    // we don't have a solution yet
+    d_hasSolution = false;
+    d_computedSolution = false;
+    d_sol.clear();
+    d_solStatus.clear();
+  }
 }
 
 void SynthConjecture::assign(Node q)
@@ -345,15 +360,10 @@ bool SynthConjecture::doCheck()
   {
     // have we tried to repair the previous solution?
     // if not, call the repair constant utility
-    unsigned ninst = d_cinfo[d_candidates[0]].d_inst.size();
+    size_t ninst = d_solutionValues.size();
     if (d_repair_index < ninst)
     {
-      std::vector<Node> fail_cvs;
-      for (const Node& cprog : d_candidates)
-      {
-        Assert(d_repair_index < d_cinfo[cprog].d_inst.size());
-        fail_cvs.push_back(d_cinfo[cprog].d_inst[d_repair_index]);
-      }
+      std::vector<Node> fail_cvs = d_solutionValues[d_repair_index];
       if (Trace.isOn("sygus-engine"))
       {
         Trace("sygus-engine") << "CegConjuncture : repair previous solution ";
@@ -468,7 +478,7 @@ bool SynthConjecture::doCheck()
   {
     if (!checkSideCondition(candidate_values))
     {
-      excludeCurrentSolution(terms, candidate_values);
+      excludeCurrentSolution(candidate_values);
       Trace("sygus-engine") << "...failed side condition" << std::endl;
       return false;
     }
@@ -555,7 +565,7 @@ bool SynthConjecture::doCheck()
     // In the rare case that the subcall is unknown, we simply exclude the
     // solution, without adding a counterexample point. This should only
     // happen if the quantifier free logic is undecidable.
-    excludeCurrentSolution(terms, candidate_values);
+    excludeCurrentSolution(candidate_values);
     // We should set incomplete, since a "sat" answer should not be
     // interpreted as "infeasible", which would make a difference in the rare
     // case where e.g. we had a finite grammar and exhausted the grammar.
@@ -569,9 +579,12 @@ bool SynthConjecture::doCheck()
   if (options().quantifiers.sygusStream)
   {
     // immediately print the current solution
-    printAndContinueStream(terms, candidate_values);
+    printAndContinueStream(candidate_values);
     // streaming means now we immediately are looking for a new solution
     d_hasSolution = false;
+    d_computedSolution = false;
+    d_sol.clear();
+    d_solStatus.clear();
     return false;
   }
   // We set incomplete and terminate with unknown.
@@ -634,13 +647,9 @@ bool SynthConjecture::processCounterexample(const std::vector<Node>& skModel)
     Trace("sygus-engine-debug") << "  ...(warning) failed to refine candidate, "
                                    "manually exclude candidate."
                                 << std::endl;
-    std::vector<Node> cvals;
-    for (const Node& c : d_candidates)
-    {
-      cvals.push_back(d_cinfo[c].d_inst.back());
-    }
+    std::vector<Node> cvals = d_solutionValues.back();
     // something went wrong, exclude the current candidate
-    excludeCurrentSolution(d_candidates, cvals);
+    excludeCurrentSolution(cvals);
     // Note this happens when evaluation is incapable of disproving a candidate
     // for counterexample point c, but satisfiability checking happened to find
     // the the same point c is indeed a true counterexample. It is sound
@@ -728,28 +737,26 @@ void SynthConjecture::debugPrint(const char* c)
   Trace(c) << "  * Counterexample skolems : " << d_innerSks << std::endl;
 }
 
-void SynthConjecture::printAndContinueStream(const std::vector<Node>& enums,
-                                             const std::vector<Node>& values)
+void SynthConjecture::printAndContinueStream(const std::vector<Node>& values)
 {
   Assert(d_master != nullptr);
   // we have generated a solution, print it
   // get the current output stream
   printSynthSolutionInternal(*options().base.out);
-  excludeCurrentSolution(enums, values);
+  excludeCurrentSolution(values);
 }
 
-void SynthConjecture::excludeCurrentSolution(const std::vector<Node>& enums,
-                                             const std::vector<Node>& values)
+void SynthConjecture::excludeCurrentSolution(const std::vector<Node>& values)
 {
-  Trace("cegqi-debug") << "Exclude current solution: " << enums << " / "
-                       << values << std::endl;
+  Assert(values.size() == d_candidates.size());
+  Trace("cegqi-debug") << "Exclude current solution: " << values << std::endl;
   // However, we need to exclude the current solution using an explicit
   // blocking clause, so that we proceed to the next solution. We do this only
   // for passively-generated enumerators (TermDbSygus::isPassiveEnumerator).
   std::vector<Node> exp;
-  for (unsigned i = 0, tsize = enums.size(); i < tsize; i++)
+  for (size_t i = 0, tsize = d_candidates.size(); i < tsize; i++)
   {
-    Node cprog = enums[i];
+    Node cprog = d_candidates[i];
     Assert(d_tds->isEnumerator(cprog));
     if (d_tds->isPassiveEnumerator(cprog))
     {
@@ -935,14 +942,11 @@ bool SynthConjecture::getSynthSolutions(
   return true;
 }
 
-void SynthConjecture::recordSolution(std::vector<Node>& vs)
+void SynthConjecture::recordSolution(const std::vector<Node>& vs)
 {
   Assert(vs.size() == d_candidates.size());
-  d_cinfo.clear();
-  for (unsigned i = 0; i < vs.size(); i++)
-  {
-    d_cinfo[d_candidates[i]].d_inst.push_back(vs[i]);
-  }
+  d_solutionValues.clear();
+  d_solutionValues.push_back(vs);
 }
 
 bool SynthConjecture::getSynthSolutionsInternal(std::vector<Node>& sols,
@@ -952,6 +956,19 @@ bool SynthConjecture::getSynthSolutionsInternal(std::vector<Node>& sols,
   {
     return false;
   }
+  if (d_computedSolution)
+  {
+    sols.insert(sols.end(), d_sol.begin(), d_sol.end());
+    statuses.insert(statuses.end(), d_solStatus.begin(), d_solStatus.end());
+    return true;
+  }
+  d_computedSolution = true;
+  // get the (SyGuS datatype) values of the solutions, if they exist
+  std::vector<Node> svals;
+  if (!d_solutionValues.empty())
+  {
+    svals = d_solutionValues.back();
+  }
   for (unsigned i = 0, size = d_embed_quant[0].getNumChildren(); i < size; i++)
   {
     Node prog = d_embed_quant[0][i];
@@ -974,10 +991,10 @@ bool SynthConjecture::getSynthSolutionsInternal(std::vector<Node>& sols,
     else
     {
       Node cprog = d_candidates[i];
-      if (!d_cinfo[cprog].d_inst.empty())
+      if (!svals.empty())
       {
-        // the solution is just the last instantiated term
-        sol = d_cinfo[cprog].d_inst.back();
+        // the solution is the value of the last term
+        sol = svals[i];
         status = 1;
 
         // check if there was a template
@@ -1025,9 +1042,11 @@ bool SynthConjecture::getSynthSolutionsInternal(std::vector<Node>& sols,
                             << std::endl;
       }
     }
-    sols.push_back(sol);
-    statuses.push_back(status);
+    d_sol.push_back(sol);
+    d_solStatus.push_back(status);
   }
+  sols.insert(sols.end(), d_sol.begin(), d_sol.end());
+  statuses.insert(statuses.end(), d_solStatus.begin(), d_solStatus.end());
   return true;
 }
 
index 005ae43611f847ff3d8e4d0c2cea028f02df7bc9..ce9788c5090cf25f320d47ddd90ce81d5fbab21e 100644 (file)
@@ -62,7 +62,9 @@ class SynthConjecture : protected EnvObj
                   TermRegistry& tr,
                   SygusStatistics& s);
   ~SynthConjecture();
-  /** presolve */
+  /**
+   * Presolve, called once at the beginning of every check-sat.
+   */
   void presolve();
   /** get original version of conjecture */
   Node getConjecture() const { return d_quant; }
@@ -197,6 +199,19 @@ class SynthConjecture : protected EnvObj
    * on every call to presolve.
    */
   bool d_hasSolution;
+  /** Whether we have computed a solution */
+  bool d_computedSolution;
+  /**
+   * The final solution and status, caches getSynthSolutionsInternal, valid
+   * if d_computedSolution is true.
+   */
+  std::vector<Node> d_sol;
+  std::vector<int8_t> d_solStatus;
+  /**
+   * (SyGuS datatype) values for solutions, which is populated if we have a
+   * solution and only if we are not using the single invocation solver.
+   */
+  std::vector<std::vector<Node>> d_solutionValues;
   /** the decision strategy for the feasible guard */
   std::unique_ptr<DecisionStrategy> d_feasible_strategy;
   /** single invocation utility */
@@ -283,22 +298,13 @@ class SynthConjecture : protected EnvObj
   Node d_simp_quant;
   /** (negated) conjecture after simplification, conversion to deep embedding */
   Node d_embed_quant;
-  /** candidate information */
-  class CandidateInfo
-  {
-   public:
-    CandidateInfo() {}
-    /** list of terms we have instantiated candidates with */
-    std::vector<Node> d_inst;
-  };
-  std::map<Node, CandidateInfo> d_cinfo;
   /**
    * The first index of an instantiation in CandidateInfo::d_inst that we have
    * not yet tried to repair.
    */
   unsigned d_repair_index;
   /** record solution (this is used to construct solutions later) */
-  void recordSolution(std::vector<Node>& vs);
+  void recordSolution(const std::vector<Node>& vs);
   /** get synth solutions internal
    *
    * This function constructs the body of solutions for all
@@ -328,11 +334,9 @@ class SynthConjecture : protected EnvObj
    * The argument enums is the set of enumerators that comprise the current
    * solution, and values is their current values.
    */
-  void printAndContinueStream(const std::vector<Node>& enums,
-                              const std::vector<Node>& values);
+  void printAndContinueStream(const std::vector<Node>& values);
   /** exclude the current solution { enums -> values } */
-  void excludeCurrentSolution(const std::vector<Node>& enums,
-                              const std::vector<Node>& values);
+  void excludeCurrentSolution(const std::vector<Node>& values);
   /**
    * Whether we have guarded a stream exclusion lemma when using sygusStream.
    * This is an optimization that allows us to guard only the first stream
index 568fec3ce93997da9a42c62a3810f87903a778e6..4a54e2fcb5961f83996923ef32e3e84c6776eac1 100644 (file)
@@ -2468,6 +2468,7 @@ set(regress_1_tests
   regress1/sygus/icfp_14_12_diff_types.sy
   regress1/sygus/icfp_28_10.sy
   regress1/sygus/icfp_easy-ite.sy
+  regress1/sygus/incremental-stream-ex.sy
   regress1/sygus/int-any-const.sy
   regress1/sygus/inv-example.sy
   regress1/sygus/inv-missed-sol-true.sy
diff --git a/test/regress/regress1/sygus/incremental-stream-ex.sy b/test/regress/regress1/sygus/incremental-stream-ex.sy
new file mode 100644 (file)
index 0000000..127a2e3
--- /dev/null
@@ -0,0 +1,28 @@
+; COMMAND-LINE: -i --sygus-out=status
+;EXPECT: unsat
+;EXPECT: unsat
+;EXPECT: unsat
+;EXPECT: unsat
+
+(set-logic LIA)
+
+(synth-fun f ((x Int) (y Int)) Int
+  ((Start Int) (StartBool Bool))
+  ((Start Int (0 1 x y
+               (+ Start Start)
+               (- Start Start)
+               (ite StartBool Start Start)))
+   (StartBool Bool ((and StartBool StartBool)
+                    (not StartBool)
+                    (<= Start Start)))))
+
+
+(declare-var x Int)
+(declare-var y Int)
+
+(constraint (>= (f x y) 0))
+
+(check-synth)
+(check-synth-next)
+(check-synth-next)
+(check-synth-next)
index f3bc91eaab913cec2cce6f84eb122728438eef3f..d96ff17a07f439658e2642854ffb1a31663c3e64 100644 (file)
@@ -2589,6 +2589,37 @@ TEST_F(TestApiBlackSolver, getSynthSolutions)
   ASSERT_THROW(slv.getSynthSolutions({x}), CVC5ApiException);
 }
 
+TEST_F(TestApiBlackSolver, checkSynthNext)
+{
+  d_solver.setOption("lang", "sygus2");
+  d_solver.setOption("incremental", "true");
+  Term f = d_solver.synthFun("f", {}, d_solver.getBooleanSort());
+
+  d_solver.checkSynth();
+  ASSERT_NO_THROW(d_solver.getSynthSolutions({f}));
+  d_solver.checkSynthNext();
+  ASSERT_NO_THROW(d_solver.getSynthSolutions({f}));
+}
+
+TEST_F(TestApiBlackSolver, checkSynthNext2)
+{
+  d_solver.setOption("lang", "sygus2");
+  d_solver.setOption("incremental", "false");
+  Term f = d_solver.synthFun("f", {}, d_solver.getBooleanSort());
+
+  d_solver.checkSynth();
+  ASSERT_THROW(d_solver.checkSynthNext(), CVC5ApiException);
+}
+
+TEST_F(TestApiBlackSolver, checkSynthNext3)
+{
+  d_solver.setOption("lang", "sygus2");
+  d_solver.setOption("incremental", "true");
+  Term f = d_solver.synthFun("f", {}, d_solver.getBooleanSort());
+
+  ASSERT_THROW(d_solver.checkSynthNext(), CVC5ApiException);
+}
+
 TEST_F(TestApiBlackSolver, tupleProject)
 {
   std::vector<Sort> sorts = {d_solver.getBooleanSort(),
index f6fa3d6f1eca86dffa42b1c52c4a677f57cd19b0..128141bfa895737f01a8731969f69fc4c35693f6 100644 (file)
@@ -2584,6 +2584,36 @@ class SolverTest
     assertThrows(CVC5ApiException.class, () -> slv.getSynthSolutions(new Term[] {x}));
     slv.close();
   }
+  @Test void checkSynthNext() throws CVC5ApiException
+  {
+    d_solver.setOption("lang", "sygus2");
+    d_solver.setOption("incremental", "true");
+    Term f = d_solver.synthFun("f", new Term[] {}, d_solver.getBooleanSort());
+
+    d_solver.checkSynth();
+    assertDoesNotThrow(() -> d_solver.getSynthSolutions(new Term[] {f}));
+    d_solver.checkSynthNext();
+    assertDoesNotThrow(() -> d_solver.getSynthSolutions(new Term[] {f}));
+  }
+
+  @Test void checkSynthNext2() throws CVC5ApiException
+  {
+    d_solver.setOption("lang", "sygus2");
+    d_solver.setOption("incremental", "false");
+    Term f = d_solver.synthFun("f", new Term[] {}, d_solver.getBooleanSort());
+
+    d_solver.checkSynth();
+    assertThrows(CVC5ApiException.class, () -> d_solver.checkSynthNext());
+  }
+
+  @Test void checkSynthNext3() throws CVC5ApiException
+  {
+    d_solver.setOption("lang", "sygus2");
+    d_solver.setOption("incremental", "true");
+    Term f = d_solver.synthFun("f", new Term[] {}, d_solver.getBooleanSort());
+
+    assertThrows(CVC5ApiException.class, () -> d_solver.checkSynthNext());
+  }
 
   @Test void tupleProject() throws CVC5ApiException
   {
index 1e9dbcf1f50ec66a1bd1ba0402e8ad8cba0c22a8..0fe0d465ece0b7574c36ffa92a5dcc1dbaef1cce 100644 (file)
@@ -1960,6 +1960,32 @@ def test_get_synth_solution(solver):
     with pytest.raises(RuntimeError):
         slv.getSynthSolution(f)
 
+def test_check_synth_next(solver):
+    solver.setOption("lang", "sygus2")
+    solver.setOption("incremental", "true")
+    f = solver.synthFun("f", [], solver.getBooleanSort())
+
+    solver.checkSynth()
+    solver.getSynthSolutions([f])
+
+    solver.checkSynthNext()
+    solver.getSynthSolutions([f])
+
+def test_check_synth_next2(solver):
+    solver.setOption("lang", "sygus2")
+    solver.setOption("incremental", "false")
+    f = solver.synthFun("f", [], solver.getBooleanSort())
+
+    solver.checkSynth()
+    with pytest.raises(RuntimeError):
+        solver.checkSynthNext()
+
+def test_check_synth_next3(solver):
+    solver.setOption("lang", "sygus2")
+    solver.setOption("incremental", "true")
+    f = solver.synthFun("f", [], solver.getBooleanSort())
+    with pytest.raises(RuntimeError):
+        solver.checkSynthNext()
 
 def test_declare_pool(solver):
     intSort = solver.getIntegerSort()