Add support for minimal unsat cores (#4605)
authorAndres Noetzli <andres.noetzli@gmail.com>
Thu, 22 Jul 2021 07:50:02 +0000 (00:50 -0700)
committerGitHub <noreply@github.com>
Thu, 22 Jul 2021 07:50:02 +0000 (07:50 +0000)
This commit adds support for computing minimal unsat cores. The
algorithm implemented in this commit is just a trivial deletion-based
algorithm that tries to remove each assertion in the unsat core
individually.

NEWS
src/options/smt_options.toml
src/smt/set_defaults.cpp
src/smt/smt_engine.cpp
src/smt/smt_engine.h
test/regress/CMakeLists.txt
test/regress/regress1/minimal_unsat_core.smt2 [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index 74ee8616548a6850a5009bbcfd9e0473014b588d..f0164364392a1e4a80673181a931e6e5b7337bcf 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -23,6 +23,7 @@ New Features:
   * Support for `str.indexof_re(s, r, n)`, which returns the index of the first
     occurrence of a regular expression `r` in a string `s` after index `n` or
     -1 if `r` does not match a substring after `n`.
+* A new option to compute minimal unsat cores (`--minimal-unsat-cores`).
 
 Improvements:
 * New API: Added functions to retrieve the heap/nil term when using separation
index 9b5a93486341ccfbed63d99a9f6b99aecfd142fd..a97fe572fd6e9ce46a0ba0b56aa00f80cf7969a1 100644 (file)
@@ -194,6 +194,14 @@ name   = "SMT Layer"
   name = "assumptions"
   help = "Produce unsat cores using solving under assumptions and preprocessing proofs."
 
+[[option]]
+  name       = "minimalUnsatCores"
+  category   = "regular"
+  long       = "minimal-unsat-cores"
+  type       = "bool"
+  default    = "false"
+  help       = "if an unsat core is produced, it is reduced to a minimal unsat core"
+
 [[option]]
   name       = "checkUnsatCores"
   category   = "regular"
index bb104e98d41c8d2d8e1fe1d4c9ab58a4502bcadd..d5b3b7929b75c7035b49b4b975a40b886da109f7 100644 (file)
@@ -69,7 +69,7 @@ void setDefaults(LogicInfo& logic, bool isInternalSubsolver)
     opts.driver.dumpUnsatCores = true;
   }
   if (options::checkUnsatCores() || options::dumpUnsatCores()
-      || options::unsatAssumptions()
+      || options::unsatAssumptions() || options::minimalUnsatCores()
       || options::unsatCoresMode() != options::UnsatCoresMode::OFF)
   {
     opts.smt.unsatCores = true;
index 9056e7c94158e193539adaad5bf8a4ce3f7fbbfa..b09fdb4d7ca509d36dbf06d51e182785ec99ef86 100644 (file)
@@ -1400,9 +1400,80 @@ UnsatCore SmtEngine::getUnsatCoreInternal()
   std::shared_ptr<ProofNode> pfn = d_pfManager->getFinalProof(pepf, *d_asserts);
   std::vector<Node> core;
   d_ucManager->getUnsatCore(pfn, *d_asserts, core);
+  if (options::minimalUnsatCores())
+  {
+    core = reduceUnsatCore(core);
+  }
   return UnsatCore(core);
 }
 
+std::vector<Node> SmtEngine::reduceUnsatCore(const std::vector<Node>& core)
+{
+  Assert(options::unsatCores())
+      << "cannot reduce unsat core if unsat cores are turned off";
+
+  Notice() << "SmtEngine::reduceUnsatCore(): reducing unsat core" << endl;
+  std::unordered_set<Node> removed;
+  for (const Node& skip : core)
+  {
+    std::unique_ptr<SmtEngine> coreChecker;
+    initializeSubsolver(coreChecker);
+    coreChecker->setLogic(getLogicInfo());
+    coreChecker->getOptions().smt.checkUnsatCores = false;
+    // disable all proof options
+    coreChecker->getOptions().smt.produceProofs = false;
+    coreChecker->getOptions().smt.checkProofs = false;
+    coreChecker->getOptions().proof.proofEagerChecking = false;
+
+    for (const Node& ucAssertion : core)
+    {
+      if (ucAssertion != skip && removed.find(ucAssertion) == removed.end())
+      {
+        Node assertionAfterExpansion = expandDefinitions(ucAssertion);
+        coreChecker->assertFormula(assertionAfterExpansion);
+      }
+    }
+    Result r;
+    try
+    {
+      r = coreChecker->checkSat();
+    }
+    catch (...)
+    {
+      throw;
+    }
+
+    if (r.asSatisfiabilityResult().isSat() == Result::UNSAT)
+    {
+      removed.insert(skip);
+    }
+    else if (r.asSatisfiabilityResult().isUnknown())
+    {
+      Warning() << "SmtEngine::reduceUnsatCore(): could not reduce unsat core "
+                   "due to "
+                   "unknown result.";
+    }
+  }
+
+  if (removed.empty())
+  {
+    return core;
+  }
+  else
+  {
+    std::vector<Node> newUcAssertions;
+    for (const Node& n : core)
+    {
+      if (removed.find(n) == removed.end())
+      {
+        newUcAssertions.push_back(n);
+      }
+    }
+
+    return newUcAssertions;
+  }
+}
+
 void SmtEngine::checkUnsatCore() {
   Assert(d_env->getOptions().smt.unsatCores)
       << "cannot check unsat core if unsat cores are turned off";
index 3128257e6507381d139ce93063b2fb25155e616e..02e5c6b064cc09466e1f9e51d2bbf8f55e7c446d 100644 (file)
@@ -1,6 +1,6 @@
 /******************************************************************************
  * Top contributors (to current version):
- *   Andrew Reynolds, Morgan Deters, Aina Niemetz
+
  *
  * This file is part of the cvc5 project.
  *
@@ -367,6 +367,11 @@ class CVC5_EXPORT SmtEngine
    */
   Result assertFormula(const Node& formula, bool inUnsatCore = true);
 
+  /**
+   * Reduce an unsatisfiable core to make it minimal.
+   */
+  std::vector<Node> reduceUnsatCore(const std::vector<Node>& core);
+
   /**
    * Check if a given (set of) expression(s) is entailed with respect to the
    * current set of assertions. We check this by asserting the negation of
index db6a7ae489bc9c181e0698ef7d7fc97de3bccad1..6ebb91f9e1fe00f48026fcf1e1183a95bb06e8f4 100644 (file)
@@ -1649,6 +1649,7 @@ set(regress_1_tests
   regress1/issue5739-rtf-processed.smt2
   regress1/lemmas/clocksynchro_5clocks.main_invar.base.smtv1.smt2
   regress1/lemmas/pursuit-safety-8.smtv1.smt2
+  regress1/minimal_unsat_core.smt2
   regress1/model-blocker-simple.smt2
   regress1/model-blocker-values.smt2
   regress1/nl/approx-sqrt.smt2
diff --git a/test/regress/regress1/minimal_unsat_core.smt2 b/test/regress/regress1/minimal_unsat_core.smt2
new file mode 100644 (file)
index 0000000..ef1d3ce
--- /dev/null
@@ -0,0 +1,12 @@
+; COMMAND-LINE: --minimal-unsat-cores
+(set-logic QF_NIA)
+(set-info :status unsat)
+(declare-fun n () Int)
+
+(assert (= n 0))
+(assert (= (div (div n n) n)
+           (div (div (div n n) n) n)))
+(assert (distinct (div (div n n) n)
+                  (div (div (div (div (div n n) n) n) n) n)))
+
+(check-sat)