Simplify preprocessing (#5647)
authorAndrew Reynolds <andrew.j.reynolds@gmail.com>
Wed, 16 Dec 2020 22:35:59 +0000 (16:35 -0600)
committerGitHub <noreply@github.com>
Wed, 16 Dec 2020 22:35:59 +0000 (16:35 -0600)
This simplifies preprocessing so that the only call to theory-preprocess and ite-removal is at the very end. (One exception is early-theory-pp which is used by default in combination with ite-simp to maintain the performance on QF_LIA/nec).

This is in preparation for making theory preprocessing happen lazily, post-CNF conversion.

@HanielB has done SMT-LIB performance runs, see below.

src/options/smt_options.toml
src/preprocessing/passes/unconstrained_simplifier.cpp
src/smt/process_assertions.cpp
src/smt/process_assertions.h
src/smt/set_defaults.cpp
test/regress/regress0/unconstrained/4481.smt2

index 08d53855c6baccc1f4352bc53624ba96b8e0b2b1..b01b7780fff2171408d674190ad94e0e53ec1963 100644 (file)
@@ -361,6 +361,14 @@ header = "options/smt_options.h"
   default    = "false"
   help       = "enables compressing ites after ite simplification"
 
+[[option]]
+  name       = "earlyIteRemoval"
+  category   = "experimental"
+  long       = "early-ite-removal"
+  type       = "bool"
+  default    = "false"
+  help       = "remove ITEs early in preprocessing"
+
 [[option]]
   name       = "unconstrainedSimp"
   category   = "regular"
index 51434e3695c41be644fd94323f2f53a006b93d17..6ebf1b8c88205be2b904a8d47fb884e338130964 100644 (file)
@@ -358,6 +358,8 @@ void UnconstrainedSimplifier::processUnconstrained()
         case kind::BITVECTOR_SHL:
         case kind::BITVECTOR_LSHR:
         case kind::BITVECTOR_ASHR:
+        case kind::BITVECTOR_UDIV:
+        case kind::BITVECTOR_UREM:
         case kind::BITVECTOR_UDIV_TOTAL:
         case kind::BITVECTOR_UREM_TOTAL:
         case kind::BITVECTOR_SDIV:
index c2374c6ffb4963f240535688eff80def69697891..77c7d450e7a589a468ea0de8074f03ce3014c99c 100644 (file)
@@ -203,10 +203,6 @@ bool ProcessAssertions::apply(Assertions& as)
   if (options::solveBVAsInt() != options::SolveBVAsIntMode::OFF)
   {
     d_passes["bv-to-int"]->apply(&assertions);
-    // after running bv-to-int, we need to immediately run
-    // theory-preprocess and ite-removal so that newlly created
-    // terms and assertions are normalized (e.g., div is expanded).
-    d_passes["theory-preprocess"]->apply(&assertions);
   }
 
   // Since this pass is not robust for the information tracking necessary for
@@ -290,6 +286,7 @@ bool ProcessAssertions::apply(Assertions& as)
   }
   Debug("smt") << " assertions     : " << assertions.size() << endl;
 
+  if (options::earlyIteRemoval())
   {
     d_smtStats.d_numAssertionsPre += assertions.size();
     d_passes["ite-removal"]->apply(&assertions);
@@ -309,84 +306,6 @@ bool ProcessAssertions::apply(Assertions& as)
     Chat() << "re-simplifying assertions..." << endl;
     ScopeCounter depth(d_simplifyAssertionsDepth);
     noConflict &= simplifyAssertions(assertions);
-    if (noConflict)
-    {
-      // Need to fix up assertion list to maintain invariants:
-      // Let Sk be the set of Skolem variables introduced by ITE's.  Let <_sk be
-      // the order in which these variables were introduced during ite removal.
-      // For each skolem variable sk, let iteExpr = iteMap(sk) be the ite expr
-      // mapped to by sk.
-
-      // cache for expression traversal
-      unordered_map<Node, bool, NodeHashFunction> cache;
-
-      IteSkolemMap& iskMap = assertions.getIteSkolemMap();
-      // First, find all skolems that appear in the substitution map - their
-      // associated iteExpr will need to be moved to the main assertion set
-      set<TNode> skolemSet;
-      SubstitutionMap& top_level_substs =
-          d_preprocessingPassContext->getTopLevelSubstitutions().get();
-      SubstitutionMap::iterator pos = top_level_substs.begin();
-      for (; pos != top_level_substs.end(); ++pos)
-      {
-        collectSkolems(iskMap, (*pos).first, skolemSet, cache);
-        collectSkolems(iskMap, (*pos).second, skolemSet, cache);
-      }
-      // We need to ensure:
-      // 1. iteExpr has the form (ite cond (sk = t) (sk = e))
-      // 2. if some sk' in Sk appears in cond, t, or e, then sk' <_sk sk
-      // If either of these is violated, we must add iteExpr as a proper
-      // assertion
-      IteSkolemMap::iterator it = iskMap.begin();
-      IteSkolemMap::iterator iend = iskMap.end();
-      std::vector<Node> newConj;
-      vector<TNode> toErase;
-      for (; it != iend; ++it)
-      {
-        if (skolemSet.find((*it).first) == skolemSet.end())
-        {
-          TNode iteExpr = assertions[(*it).second];
-          if (iteExpr.getKind() == ITE && iteExpr[1].getKind() == EQUAL
-              && iteExpr[1][0] == (*it).first && iteExpr[2].getKind() == EQUAL
-              && iteExpr[2][0] == (*it).first)
-          {
-            cache.clear();
-            bool bad =
-                checkForBadSkolems(iskMap, iteExpr[0], (*it).first, cache);
-            bad = bad
-                  || checkForBadSkolems(
-                         iskMap, iteExpr[1][1], (*it).first, cache);
-            bad = bad
-                  || checkForBadSkolems(
-                         iskMap, iteExpr[2][1], (*it).first, cache);
-            if (!bad)
-            {
-              continue;
-            }
-          }
-        }
-        // Move this iteExpr into the main assertions
-        newConj.push_back(assertions[(*it).second]);
-        assertions.replace((*it).second, d_true);
-        toErase.push_back((*it).first);
-      }
-      if (!newConj.empty())
-      {
-        while (!toErase.empty())
-        {
-          iskMap.erase(toErase.back());
-          toErase.pop_back();
-        }
-        size_t index = assertions.getRealAssertionsEnd() - 1;
-        Node newAssertion = NodeManager::currentNM()->mkAnd(newConj);
-        assertions.conjoin(index, newAssertion);
-      }
-      // TODO(b/1256): For some reason this is needed for some benchmarks, such
-      // as
-      // QF_AUFBV/dwp_formulas/try5_small_difret_functions_dwp_tac.re_node_set_remove_at.il.dwp.smt2
-      d_passes["ite-removal"]->apply(&assertions);
-      d_passes["apply-substs"]->apply(&assertions);
-    }
     Trace("smt-proc")
         << "ProcessAssertions::processAssertions() : post-repeat-simplify"
         << endl;
@@ -407,12 +326,19 @@ bool ProcessAssertions::apply(Assertions& as)
                << endl;
   Debug("smt") << " assertions     : " << assertions.size() << endl;
 
+  // ensure rewritten
+  d_passes["rewrite"]->apply(&assertions);
+  // apply theory preprocess
   d_passes["theory-preprocess"]->apply(&assertions);
   // Must remove ITEs again since theory preprocessing may introduce them.
   // Notice that we alternatively could ensure that the theory-preprocess
   // pass above calls TheoryPreprocess::preprocess instead of
   // TheoryPreprocess::theoryPreprocess, as the former also does ITE removal.
   d_passes["ite-removal"]->apply(&assertions);
+  // This is needed because when solving incrementally, removeITEs may
+  // introduce skolems that were solved for earlier and thus appear in the
+  // substitution map.
+  d_passes["apply-substs"]->apply(&assertions);
 
   if (options::bitblastMode() == options::BitblastMode::EAGER)
   {
@@ -470,13 +396,6 @@ bool ProcessAssertions::simplifyAssertions(AssertionPipeline& assertions)
 
     Debug("smt") << " assertions     : " << assertions.size() << endl;
 
-    // Theory preprocessing
-    bool doEarlyTheoryPp = !options::arithRewriteEq();
-    if (doEarlyTheoryPp)
-    {
-      d_passes["theory-preprocess"]->apply(&assertions);
-    }
-
     // ITE simplification
     if (options::doITESimp()
         && (d_simplifyAssertionsDepth <= 1 || options::doITESimpOnRepeat()))
@@ -542,80 +461,5 @@ void ProcessAssertions::dumpAssertions(const char* key,
   }
 }
 
-void ProcessAssertions::collectSkolems(
-    IteSkolemMap& iskMap,
-    TNode n,
-    set<TNode>& skolemSet,
-    unordered_map<Node, bool, NodeHashFunction>& cache)
-{
-  unordered_map<Node, bool, NodeHashFunction>::iterator it;
-  it = cache.find(n);
-  if (it != cache.end())
-  {
-    return;
-  }
-
-  size_t sz = n.getNumChildren();
-  if (sz == 0)
-  {
-    if (iskMap.find(n) != iskMap.end())
-    {
-      skolemSet.insert(n);
-    }
-    cache[n] = true;
-    return;
-  }
-
-  size_t k = 0;
-  for (; k < sz; ++k)
-  {
-    collectSkolems(iskMap, n[k], skolemSet, cache);
-  }
-  cache[n] = true;
-}
-
-bool ProcessAssertions::checkForBadSkolems(
-    IteSkolemMap& iskMap,
-    TNode n,
-    TNode skolem,
-    unordered_map<Node, bool, NodeHashFunction>& cache)
-{
-  unordered_map<Node, bool, NodeHashFunction>::iterator it;
-  it = cache.find(n);
-  if (it != cache.end())
-  {
-    return (*it).second;
-  }
-
-  size_t sz = n.getNumChildren();
-  if (sz == 0)
-  {
-    IteSkolemMap::iterator iit = iskMap.find(n);
-    bool bad = false;
-    if (iit != iskMap.end())
-    {
-      if (!((*iit).first < n))
-      {
-        bad = true;
-      }
-    }
-    cache[n] = bad;
-    return bad;
-  }
-
-  size_t k = 0;
-  for (; k < sz; ++k)
-  {
-    if (checkForBadSkolems(iskMap, n[k], skolem, cache))
-    {
-      cache[n] = true;
-      return true;
-    }
-  }
-
-  cache[n] = false;
-  return false;
-}
-
 }  // namespace smt
 }  // namespace CVC4
index 072603e7db51591a4d6fa6f0fba580668859a9fb..ca834459d48f33cda55a19c9291eea36f2c22cb1 100644 (file)
@@ -118,22 +118,6 @@ class ProcessAssertions
    */
   void dumpAssertions(const char* key,
                       const preprocessing::AssertionPipeline& assertionList);
-  /**
-   * Helper function to fix up assertion list to restore invariants needed after
-   * ite removal.
-   */
-  void collectSkolems(IteSkolemMap& iskMap,
-                      TNode n,
-                      set<TNode>& skolemSet,
-                      NodeToBoolHashMap& cache);
-  /**
-   * Helper function to fix up assertion list to restore invariants needed after
-   * ite removal.
-   */
-  bool checkForBadSkolems(IteSkolemMap& iskMap,
-                          TNode n,
-                          TNode skolem,
-                          NodeToBoolHashMap& cache);
 };
 
 }  // namespace smt
index c8aecf288765cdafc69fe5747c4937554252c392..bbbe73cb15be7da10b90221f2ab6b03a81187b63 100644 (file)
@@ -210,6 +210,18 @@ void setDefaults(LogicInfo& logic, bool isInternalSubsolver)
     }
   }
 
+  // --ite-simp is an experimental option designed for QF_LIA/nec. This
+  // technique is experimental. This benchmark set also requires removing ITEs
+  // during preprocessing, before repeating simplification. Hence, we enable
+  // this by default.
+  if (options::doITESimp())
+  {
+    if (!options::earlyIteRemoval.wasSetByUser())
+    {
+      options::earlyIteRemoval.set(true);
+    }
+  }
+
   // Set default options associated with strings-exp. We also set these options
   // if we are using eager string preprocessing, which may introduce quantified
   // formulas at preprocess time.
index 19179f4d70592ba19408f9c6cebaf5d6a4741978..0286070938886d64b503c2d68180ecb59e4318af 100644 (file)
@@ -1,6 +1,5 @@
 ; COMMAND-LINE: --unconstrained-simp
-; EXPECT: (error "Cannot use unconstrained simplification in this logic, due to (possibly internally introduced) quantified formula.")
-; EXIT: 1
+; EXPECT: unsat
 (set-logic ALL)
 (set-info :status unsat)
 (declare-fun a () Int)