Do not make SyGuS subsolver in unsat state after solving (#7759)
authorAndrew Reynolds <andrew.j.reynolds@gmail.com>
Thu, 9 Dec 2021 14:38:28 +0000 (08:38 -0600)
committerGitHub <noreply@github.com>
Thu, 9 Dec 2021 14:38:28 +0000 (14:38 +0000)
This makes subsolvers answer "unknown" instead of "unsat" when solving SyGuS inputs, by setting unknown when a SyGuS input is solved instead of asserting the negation of the input. Knowing whether the call was successful is simply obtained by calling getSynthSolutions.

This will allow for multiple solutions for the same conjecture.

12 files changed:
src/preprocessing/passes/sygus_inference.cpp
src/smt/abduction_solver.cpp
src/smt/smt_solver.cpp
src/smt/sygus_solver.cpp
src/smt/sygus_solver.h
src/theory/incomplete_id.cpp
src/theory/incomplete_id.h
src/theory/inference_id.cpp
src/theory/inference_id.h
src/theory/quantifiers/sygus/synth_conjecture.cpp
src/theory/theory_engine.cpp
src/theory/theory_engine.h

index 7ebb3ab801ecc91dab4763853ae23c97880a61cb..cdbd6099efa5974a3ec6a3574685ea6f7e91099e 100644 (file)
@@ -301,15 +301,13 @@ bool SygusInference::solveSygus(const std::vector<Node>& assertions,
   Trace("sygus-infer") << "*** Check sat..." << std::endl;
   Result r = rrSygus->checkSat();
   Trace("sygus-infer") << "...result : " << r << std::endl;
-  if (r.asSatisfiabilityResult().isSat() != Result::UNSAT)
+  // get the synthesis solutions
+  std::map<Node, Node> synth_sols;
+  if (!rrSygus->getSubsolverSynthSolutions(synth_sols))
   {
     // failed, conjecture was infeasible
     return false;
   }
-  // get the synthesis solutions
-  std::map<Node, Node> synth_sols;
-  bool sinferSol = rrSygus->getSubsolverSynthSolutions(synth_sols);
-  AlwaysAssert(sinferSol) << "Failed to get solutions for sygus-inference";
 
   std::vector<Node> final_ff;
   std::vector<Node> final_ff_sol;
index a5a180ffb4e296b7a092b1b74b902ccd7c73b406..f17e768c9ac5df05fede01254c2e3ee5f42cfb52 100644 (file)
@@ -94,13 +94,12 @@ bool AbductionSolver::getAbductInternal(const std::vector<Node>& axioms,
   Result r = d_subsolver->checkSat();
   Trace("sygus-abduct") << "  SolverEngine::getAbduct result: " << r
                         << std::endl;
-  if (r.asSatisfiabilityResult().isSat() == Result::UNSAT)
+  // get the synthesis solution
+  std::map<Node, Node> sols;
+  // use the "getSubsolverSynthSolutions" interface, since we asserted the
+  // internal form of the SyGuS conjecture and used check-sat.
+  if (d_subsolver->getSubsolverSynthSolutions(sols))
   {
-    // get the synthesis solution
-    std::map<Node, Node> sols;
-    // use the "getSubsolverSynthSolutions" interface, since we asserted the
-    // internal form of the SyGuS conjecture and used check-sat.
-    d_subsolver->getSubsolverSynthSolutions(sols);
     Assert(sols.size() == 1);
     std::map<Node, Node>::iterator its = sols.find(d_sssf);
     if (its != sols.end())
index d9108a28cdc89ce1d85555ac4ed3bcad5f2321be..25c6f5f9fb68385b5a93c45782cc021976560c69 100644 (file)
@@ -158,6 +158,7 @@ Result SmtSolver::checkSatisfiability(Assertions& as,
   d_env.verbose(2) << "solving..." << std::endl;
   Trace("smt") << "SmtSolver::check(): running check" << endl;
   Result result = d_propEngine->checkSat();
+  Trace("smt") << "SmtSolver::check(): result " << result << std::endl;
 
   rm->endCall();
   Trace("limit") << "SmtSolver::check(): cumulative millis "
index 12a178814bfde8719c9d94a742d13e2549f7e47e..70ad656442f67c8da7eff5a3a3cc4c73d2292d27 100644 (file)
@@ -261,11 +261,46 @@ Result SygusSolver::checkSynth(Assertions& as)
     query.push_back(d_conj);
     r = d_smtSolver.checkSatisfiability(as, query, false);
   }
-  // Check that synthesis solutions satisfy the conjecture
-  if (options().smt.checkSynthSol
-      && r.asSatisfiabilityResult().isSat() == Result::UNSAT)
+  // The result returned by the above call is typically "unknown", which may
+  // or may not correspond to a state in which we solved the conjecture
+  // successfully. Instead we call getSynthSolutions below. If this returns
+  // true, then we were successful. In this case, we set the result to "unsat",
+  // since the synthesis conjecture was negated when asserted to the subsolver.
+  //
+  // This behavior is done for 2 reasons:
+  // (1) if we do not negate the synthesis conjecture, the subsolver in some
+  // cases cannot answer "sat", e.g. in the presence of recursive function
+  // definitions. Instead the SyGuS language standard itself indicates that
+  // a correct solution for a conjecture is one where the synthesis conjecture
+  // is *T-valid* (in the presence of defined recursive functions). In other
+  // words, a SyGuS query asks to prove that the conjecture is valid when
+  // witnessed by the given solution.
+  // (2) we do not want the solver to explicitly answer "unsat" by giving an
+  // unsatisfiable set of formulas to the underlying PropEngine, or otherwise
+  // we will not be able to ask for further solutions. This is critical for
+  // incremental solving where multiple solutions are returned for the same
+  // set of constraints. Thus, the internal SyGuS solver will mark unknown
+  // with IncompleteId::QUANTIFIERS_SYGUS_SOLVED. Furthermore, this id may be
+  // overwritten by other means of incompleteness, so we cannot rely on this
+  // identifier being the final reason for unknown.
+  //
+  // Thus, we use getSynthSolutions as means of knowing the conjecture was
+  // solved.
+  std::map<Node, Node> sol_map;
+  if (getSynthSolutions(sol_map))
+  {
+    // if we have solutions, we return "unsat" by convention
+    r = Result(Result::UNSAT);
+    // Check that synthesis solutions satisfy the conjecture
+    if (options().smt.checkSynthSol)
+    {
+      checkSynthSolution(as, sol_map);
+    }
+  }
+  else
   {
-    checkSynthSolution(as);
+    // otherwise, we return "unknown"
+    r = Result(Result::SAT_UNKNOWN, Result::UNKNOWN_REASON);
   }
   return r;
 }
@@ -301,21 +336,14 @@ bool SygusSolver::getSubsolverSynthSolutions(std::map<Node, Node>& solMap)
   return true;
 }
 
-void SygusSolver::checkSynthSolution(Assertions& as)
+void SygusSolver::checkSynthSolution(Assertions& as,
+                                     const std::map<Node, Node>& sol_map)
 {
   if (isVerboseOn(1))
   {
     verbose(1) << "SyGuS::checkSynthSolution: checking synthesis solution"
                << std::endl;
   }
-  std::map<Node, Node> sol_map;
-  // Get solutions and build auxiliary vectors for substituting
-  if (!getSynthSolutions(sol_map))
-  {
-    InternalError()
-        << "SygusSolver::checkSynthSolution(): No solution to check!";
-    return;
-  }
   if (sol_map.empty())
   {
     InternalError() << "SygusSolver::checkSynthSolution(): Got empty solution!";
index 9fe9f7a36b23e3f87b72c8ab9d193a365ebccc30..4e7a04364557c0bb853f9bcd1ad8bfd125f81eef 100644 (file)
@@ -169,8 +169,13 @@ class SygusSolver : protected EnvObj
    * conjecture in which the functions-to-synthesize have been replaced by the
    * synthesized solutions, which is a quantifier-free formula, is
    * unsatisfiable. If not, then the found solutions are wrong.
+   *
+   * @param as The background assertions, which may include define-fun and
+   * define-fun-rec,
+   * @param sol_map Map from functions-to-synthesize to their solution (of the
+   * same type) for the asserted synthesis conjecture.
    */
-  void checkSynthSolution(Assertions& as);
+  void checkSynthSolution(Assertions& as, const std::map<Node, Node>& solMap);
   /**
    * Expand definitions in sygus datatype tn, which ensures that all
    * sygus constructors that are used to build values of sygus datatype
index c763fb9a05f77ceaa38c9e2cdd3f8f0906dc6bc5..b1071b5aec2504a175c5fe3f700261dba0d06d5b 100644 (file)
@@ -35,6 +35,8 @@ const char* toString(IncompleteId i)
       return "QUANTIFIERS_RECORDED_INST";
     case IncompleteId::QUANTIFIERS_MAX_INST_ROUNDS:
       return "QUANTIFIERS_MAX_INST_ROUNDS";
+    case IncompleteId::QUANTIFIERS_SYGUS_SOLVED:
+      return "QUANTIFIERS_SYGUS_SOLVED";
     case IncompleteId::SEP: return "SEP";
     case IncompleteId::SETS_RELS_CARD: return "SETS_RELS_CARD";
     case IncompleteId::STRINGS_LOOP_SKIP: return "STRINGS_LOOP_SKIP";
index aaa458d8e24624d40a4ac49d553011fe191af550..1e8bf155a184ef3d652f9dae522bf6769e58bad4 100644 (file)
@@ -44,6 +44,9 @@ enum class IncompleteId
   QUANTIFIERS_RECORDED_INST,
   // incomplete due to limited number of allowed instantiation rounds
   QUANTIFIERS_MAX_INST_ROUNDS,
+  // we solved a negated synthesis conjecture and will terminate as a subsolver
+  // with unknown
+  QUANTIFIERS_SYGUS_SOLVED,
   // incomplete due to separation logic
   SEP,
   // relations were used in combination with set cardinality constraints
index 56d2f0500ee2840b4bf54061e4a8ff584736e895..b525d1c53ded53d1dfa69c4aa369925c1c26c22f 100644 (file)
@@ -229,12 +229,6 @@ const char* toString(InferenceId i)
       return "QUANTIFIERS_SYGUS_EXCLUDE_CURRENT";
     case InferenceId::QUANTIFIERS_SYGUS_STREAM_EXCLUDE_CURRENT:
       return "QUANTIFIERS_SYGUS_STREAM_EXCLUDE_CURRENT";
-    case InferenceId::QUANTIFIERS_SYGUS_SI_SOLVED:
-      return "QUANTIFIERS_SYGUS_SI_SOLVED";
-    case InferenceId::QUANTIFIERS_SYGUS_SAMPLE_TRUST_SOLVED:
-      return "QUANTIFIERS_SYGUS_SAMPLE_TRUST_SOLVED";
-    case InferenceId::QUANTIFIERS_SYGUS_VERIFY_SOLVED:
-      return "QUANTIFIERS_SYGUS_VERIFY_SOLVED";
     case InferenceId::QUANTIFIERS_SYGUS_EXAMPLE_INFER_CONTRA:
       return "QUANTIFIERS_SYGUS_EXAMPLE_INFER_CONTRA";
     case InferenceId::QUANTIFIERS_SYGUS_UNIF_PI_INTER_ENUM_SB:
index d98d3ff256aac910740304f4c01a878de47f3c77..f6e826ac3b0e1ea2fe007ab597c9ddde72bfc0ac 100644 (file)
@@ -342,12 +342,6 @@ enum class InferenceId
   QUANTIFIERS_SYGUS_EXCLUDE_CURRENT,
   // manual exclusion of a current solution for sygus-stream
   QUANTIFIERS_SYGUS_STREAM_EXCLUDE_CURRENT,
-  // Q where Q was solved by a subcall to the single invocation module
-  QUANTIFIERS_SYGUS_SI_SOLVED,
-  // Q where Q was (trusted) solved by sampling
-  QUANTIFIERS_SYGUS_SAMPLE_TRUST_SOLVED,
-  // Q where Q was solved by a verification subcall
-  QUANTIFIERS_SYGUS_VERIFY_SOLVED,
   // ~Q where Q is a PBE conjecture with conflicting examples
   QUANTIFIERS_SYGUS_EXAMPLE_INFER_CONTRA,
   // unif+pi symmetry breaking between multiple enumerators
index a90551a0fb9ac4920866d3d72fc330940fd546cd..e5dfe408b0a17562d2821ce075ef7659b9c5704a 100644 (file)
@@ -306,6 +306,10 @@ bool SynthConjecture::needsCheck()
 
 bool SynthConjecture::doCheck()
 {
+  if (d_hasSolution)
+  {
+    return true;
+  }
   if (isSingleInvocation())
   {
     // We now try to solve with the single invocation solver, which may or may
@@ -314,14 +318,12 @@ bool SynthConjecture::doCheck()
     if (d_ceg_si->solve())
     {
       d_hasSolution = true;
-      // the conjecture has a solution, so its negation holds
-      Node qn = d_quant.negate();
-      d_qim.addPendingLemma(qn, InferenceId::QUANTIFIERS_SYGUS_SI_SOLVED);
+      // the conjecture has a solution, we set incomplete
+      d_qim.setIncomplete(IncompleteId::QUANTIFIERS_SYGUS_SOLVED);
     }
     return true;
   }
   Assert(d_master != nullptr);
-  Assert(!d_hasSolution);
 
   // get the list of terms that the master strategy is interested in
   std::vector<Node> terms;
@@ -506,11 +508,10 @@ bool SynthConjecture::doCheck()
   if (options().quantifiers.cegisSample == options::CegisSampleMode::TRUST)
   {
     // we have that the current candidate passed a sample test
-    // since we trust sampling in this mode, we assert there is no
-    // counterexample to the conjecture here.
-    Node qn = d_quant.negate();
-    d_qim.addPendingLemma(qn,
-                          InferenceId::QUANTIFIERS_SYGUS_SAMPLE_TRUST_SOLVED);
+    // since we trust sampling in this mode, we assume there is a solution here
+    // and set incomplete.
+    d_hasSolution = true;
+    d_qim.setIncomplete(IncompleteId::QUANTIFIERS_SYGUS_SOLVED);
     recordSolution(candidate_values);
     return true;
   }
@@ -573,10 +574,8 @@ bool SynthConjecture::doCheck()
     d_hasSolution = false;
     return false;
   }
-  // Use lemma to terminate with "unsat", this is justified by the verification
-  // check above, which confirms the synthesis conjecture is solved.
-  Node qn = d_quant.negate();
-  d_qim.addPendingLemma(qn, InferenceId::QUANTIFIERS_SYGUS_VERIFY_SOLVED);
+  // We set incomplete and terminate with unknown.
+  d_qim.setIncomplete(IncompleteId::QUANTIFIERS_SYGUS_SOLVED);
   return true;
 }
 
index 8421107a4020b37c00f65d82c80ca21b47ef411f..dd1d9676743084b7e4415275c08540806afdbebc 100644 (file)
@@ -1097,6 +1097,11 @@ void TheoryEngine::getDifficultyMap(std::map<Node, Node>& dmap)
   d_relManager->getDifficultyMap(dmap);
 }
 
+theory::IncompleteId TheoryEngine::getIncompleteId() const
+{
+  return d_incompleteId.get();
+}
+
 Node TheoryEngine::getModelValue(TNode var) {
   if (var.isConst())
   {
index 03f7555b77f58432917015e9383d76575a65c1b5..f1e178055dd55e0ac256a743029f5f014769f613 100644 (file)
@@ -384,6 +384,9 @@ class TheoryEngine : protected EnvObj
    */
   void getDifficultyMap(std::map<Node, Node>& dmap);
 
+  /** Get incomplete id, valid immediately after an `unknown` response. */
+  theory::IncompleteId getIncompleteId() const;
+
   /**
    * Forwards an entailment check according to the given theoryOfMode.
    * See theory.h for documentation on entailmentCheck().