Further relax what is considered a value in the model (#8095)
authorAndrew Reynolds <andrew.j.reynolds@gmail.com>
Wed, 23 Feb 2022 21:34:07 +0000 (15:34 -0600)
committerGitHub <noreply@github.com>
Wed, 23 Feb 2022 21:34:07 +0000 (21:34 +0000)
Fixes #8094.

This makes it so that "value-like" terms can appear as subterms in other terms that are then subsequently also considered values. For example (str.++ (witness ((x String)) (= (str.len x) 1000)) "A") is now considered a value.

This also changes the default option for when to use witness terms for strings in models, based on some user feedback.

12 files changed:
src/options/strings_options.toml
src/smt/solver_engine.cpp
src/theory/theory_engine.cpp
src/theory/theory_model.cpp
src/theory/theory_model.h
src/theory/theory_model_builder.cpp
test/regress/CMakeLists.txt
test/regress/regress0/arith/exp-in-model.smt2
test/regress/regress0/arith/issue7984-quant-trans.smt2
test/regress/regress1/nl/transcedental_model_simple.smt2 [new file with mode: 0644]
test/regress/regress1/strings/issue8094-witness-model.smt2 [new file with mode: 0644]
test/regress/regress1/strings/witness-model.smt2 [new file with mode: 0644]

index 610a1089fa5bac3bb5716d112faa773494344073..66753fc0bacb479e6191a29af803b34026d69781 100644 (file)
@@ -247,6 +247,5 @@ name   = "Strings Theory"
   category   = "regular"
   long       = "strings-model-max-len=N"
   type       = "uint64_t"
-  default    = "1000"
-  maximum    = "65536"
+  default    = "65536"
   help       = "The maximum size of string values in models"
index 83d4a903fbc7786986b0ba291dfffcab296f1eca..39ee661a063d0352f5ef5d3623a2e0b7a318c7c1 100644 (file)
@@ -1005,7 +1005,7 @@ Node SolverEngine::getValue(const Node& ex) const
   // Ensure it's a value (constant or const-ish like real algebraic
   // numbers), or a lambda (for uninterpreted functions). This assertion only
   // holds for models that do not have approximate values.
-  if (!TheoryModel::isValue(resultNode))
+  if (!m->isValue(resultNode))
   {
     d_env->warning() << "Could not evaluate " << resultNode
                      << " in getValue." << std::endl;
index 3092664cd359754602784265d38cba9462d20c0d..173a9356670d771e9894a102381f453d2306baff 100644 (file)
@@ -1796,8 +1796,10 @@ void TheoryEngine::checkTheoryAssertionsWithModel(bool hardFailure) {
   bool hasRelevantAssertions = false;
   if (d_relManager != nullptr)
   {
+    d_relManager->beginRound();
     relevantAssertions =
         d_relManager->getRelevantAssertions(hasRelevantAssertions);
+    d_relManager->endRound();
   }
   for(TheoryId theoryId = THEORY_FIRST; theoryId < THEORY_LAST; ++theoryId) {
     Theory* theory = d_theoryTable[theoryId];
@@ -1817,10 +1819,17 @@ void TheoryEngine::checkTheoryAssertionsWithModel(bool hardFailure) {
         if (val != d_true)
         {
           std::stringstream ss;
-          ss << " " << theoryId
-             << " has an asserted fact that the model doesn't satisfy." << endl
-             << "The fact: " << assertion << endl
-             << "Model value: " << val << endl;
+          ss << " " << theoryId << " has an asserted fact that";
+          if (val == d_false)
+          {
+            ss << " the model doesn't satisfy." << std::endl;
+          }
+          else
+          {
+            ss << " the model may not satisfy." << std::endl;
+          }
+          ss << "The fact: " << assertion << std::endl
+             << "Model value: " << val << std::endl;
           if (hardFailure)
           {
             if (val == d_false)
index eb0945ce5ce343e3e29e4f58e64acaf0c1769cc8..36502101b7ab896bdfc38303abd34a4d2eeb15de 100644 (file)
@@ -14,6 +14,7 @@
  */
 #include "theory/theory_model.h"
 
+#include "expr/attribute.h"
 #include "expr/cardinality_constraint.h"
 #include "expr/node_algorithm.h"
 #include "options/quantifiers_options.h"
@@ -803,15 +804,115 @@ std::string TheoryModel::debugPrintModelEqc() const
   return ss.str();
 }
 
-bool TheoryModel::isValue(TNode node)
+struct IsModelValueTag
 {
-  if (node.isConst())
+};
+struct IsModelValueComputedTag
+{
+};
+/** Attribute true for expressions that are quasi-values */
+using IsModelValueAttr = expr::Attribute<IsModelValueTag, bool>;
+using IsModelValueComputedAttr = expr::Attribute<IsModelValueComputedTag, bool>;
+
+bool TheoryModel::isBaseModelValue(TNode n) const
+{
+  if (n.isConst())
   {
     return true;
   }
-  Kind k = node.getKind();
-  return k == kind::REAL_ALGEBRAIC_NUMBER || k == kind::LAMBDA
-         || k == kind::WITNESS;
+  Kind k = n.getKind();
+  if (k == kind::REAL_ALGEBRAIC_NUMBER || k == kind::LAMBDA
+      || k == kind::WITNESS)
+  {
+    // we are a value if we are one of the above kinds
+    return true;
+  }
+  return false;
+}
+
+bool TheoryModel::isValue(TNode n) const
+{
+  IsModelValueAttr imva;
+  IsModelValueComputedAttr imvca;
+  // The list of nodes we are processing, and the current child index of that
+  // node we are inspecting. This vector always specifies a single path in the
+  // original term n. Notice this index accounts for operators, where the
+  // operator of a term is treated as the first child, and subsequently all
+  // other children are shifted up by one.
+  std::vector<std::pair<TNode, size_t>> visit;
+  // the last computed value of whether a node was a value
+  bool currentReturn = false;
+  visit.emplace_back(n, 0);
+  std::pair<TNode, size_t> v;
+  while (!visit.empty())
+  {
+    v = visit.back();
+    TNode cur = v.first;
+    bool finishedComputing = false;
+    // if we just pushed to the stack, do initial checks
+    if (v.second == 0)
+    {
+      if (cur.getAttribute(imvca))
+      {
+        // already cached
+        visit.pop_back();
+        currentReturn = cur.getAttribute(imva);
+        continue;
+      }
+      if (isBaseModelValue(cur))
+      {
+        finishedComputing = true;
+        currentReturn = true;
+      }
+      else if (cur.getNumChildren() == 0 || rewrite(cur) != cur)
+      {
+        finishedComputing = true;
+        currentReturn = false;
+      }
+    }
+    else if (!currentReturn)
+    {
+      // if the last child was not a value, we are not a value
+      finishedComputing = true;
+    }
+    if (!finishedComputing)
+    {
+      bool hasOperator = cur.hasOperator();
+      size_t nextChildIndex = v.second;
+      if (hasOperator && nextChildIndex > 0)
+      {
+        // if have an operator, we shift the child index we are looking at
+        nextChildIndex--;
+      }
+      if (nextChildIndex == cur.getNumChildren())
+      {
+        // finished, we are a value
+        currentReturn = true;
+      }
+      else
+      {
+        visit.back().second++;
+        if (hasOperator && v.second == 0)
+        {
+          // if we have an operator, process it as the first child
+          visit.emplace_back(cur.getOperator(), 0);
+        }
+        else
+        {
+          Assert(nextChildIndex < cur.getNumChildren());
+          // process the next child, which may be shifted from v.second to
+          // account for the operator
+          visit.emplace_back(cur[nextChildIndex], 0);
+        }
+        continue;
+      }
+    }
+    visit.pop_back();
+    cur.setAttribute(imva, currentReturn);
+    cur.setAttribute(imvca, true);
+  }
+  Assert(n.getAttribute(imvca));
+  return currentReturn;
 }
 
 }  // namespace theory
index ada7619e2ea13c3e072c07fbf8bf2915da865e54..b476ddfefc92b58140de4716279c3ef4aa90daf4 100644 (file)
@@ -358,10 +358,16 @@ class TheoryModel : protected EnvObj
   std::string debugPrintModelEqc() const;
 
   /**
-   * Is the node n a "value"? This is true if n is constant, a constant-like
-   * value (e.g. a real algebraic number) or if n is a lambda.
+   * Is the node n a "value"? This is true if n is a "base value", where
+   * a base value is one where isConst() returns true, a constant-like
+   * value (e.g. a real algebraic number) or if n is a lambda or witness
+   * term.
+   *
+   * We also return true for rewritten nodes whose leafs are base values.
+   * For example, (str.++ (witness ((x String)) (= (str.len x) 1000)) "A") is
+   * a value.
    */
-  static bool isValue(TNode node);
+  bool isValue(TNode node) const;
 
  protected:
   /** Unique name of this model */
@@ -416,6 +422,12 @@ class TheoryModel : protected EnvObj
    * a model builder constructs this model.
    */
   virtual void addTermInternal(TNode n);
+  /**
+   * Is base model value?  This is a helper method for isValue, returns true
+   * if n is a base model value.
+   */
+  bool isBaseModelValue(TNode n) const;
+
  private:
   /** cache for getModelValue */
   mutable std::unordered_map<Node, Node> d_modelCache;
index 75c18076ab79dcda4643257ec7ab0768a0880b49..03cdaa7868285c5e2edad48cfd83233bb614e9a9 100644 (file)
@@ -79,7 +79,7 @@ Node TheoryEngineModelBuilder::evaluateEqc(TheoryModel* m, TNode r)
     {
       Trace("model-builder-debug") << "...try to normalize" << std::endl;
       Node normalized = normalize(m, n, true);
-      if (TheoryModel::isValue(normalized))
+      if (m->isValue(normalized))
       {
         return normalized;
       }
@@ -101,7 +101,7 @@ bool TheoryEngineModelBuilder::isAssignerActive(TheoryModel* tm, Assigner& a)
     // Members of exclusion set must have values, otherwise we are not yet
     // assignable.
     Node er = eset[i];
-    if (TheoryModel::isValue(er))
+    if (tm->isValue(er))
     {
       // already processed
       continue;
@@ -503,7 +503,7 @@ bool TheoryEngineModelBuilder::buildModel(TheoryModel* tm)
       // applicable. We check if n is a value here, e.g. a term for which
       // isConst returns true, or a lambda. The latter is required only for
       // higher-order.
-      if (TheoryModel::isValue(n))
+      if (tm->isValue(n))
       {
         Assert(constRep.isNull());
         constRep = n;
@@ -732,7 +732,7 @@ bool TheoryEngineModelBuilder::buildModel(TheoryModel* tm)
             }
             if (!normalized.isNull())
             {
-              Assert(TheoryModel::isValue(normalized));
+              Assert(tm->isValue(normalized));
               typeConstSet.add(tb, normalized);
               assignConstantRep(tm, *i2, normalized);
               Trace("model-builder") << "    Eval: Setting constant rep of "
@@ -771,7 +771,7 @@ bool TheoryEngineModelBuilder::buildModel(TheoryModel* tm)
             Trace("model-builder") << "    Normalizing rep (" << rep
                                    << "), normalized to (" << normalized << ")"
                                    << endl;
-            if (TheoryModel::isValue(normalized))
+            if (tm->isValue(normalized))
             {
               changed = true;
               typeConstSet.add(tb, normalized);
@@ -1203,7 +1203,7 @@ Node TheoryEngineModelBuilder::normalize(TheoryModel* m, TNode r, bool evalOnly)
         {
           ri = normalize(m, ri, evalOnly);
         }
-        if (!TheoryModel::isValue(ri))
+        if (!m->isValue(ri))
         {
           childrenConst = false;
         }
index b8a060cfa73cbd444840e02afcb4d1f51832a7ed..2e53419e49f33b40d05e2c9ffdc2c2d02ef5b994 100644 (file)
@@ -1931,6 +1931,7 @@ set(regress_1_tests
   regress1/nl/sugar-ident-3.smt2
   regress1/nl/sugar-ident.smt2
   regress1/nl/tan-rewrite2.smt2
+  regress1/nl/transcedental_model_simple.smt2
   regress1/nl/zero-subset.smt2
   regress1/non-fatal-errors.smt2
   regress1/proj-issue175.smt2
@@ -2425,6 +2426,7 @@ set(regress_1_tests
   regress1/strings/issue6973-dup-lemma-conc.smt2
   regress1/strings/issue7677-test-const-rv.smt2 
   regress1/strings/issue7918-learned-rewrite.smt2
+  regress1/strings/issue8094-witness-model.smt2
   regress1/strings/kaluza-fl.smt2
   regress1/strings/loop002.smt2
   regress1/strings/loop003.smt2
@@ -2509,6 +2511,7 @@ set(regress_1_tests
   regress1/strings/update-ex1.smt2
   regress1/strings/update-ex2.smt2
   regress1/strings/username_checker_min.smt2
+  regress1/strings/witness-model.smt2
   regress1/sygus/VC22_a.sy
   regress1/sygus/abv.sy
   regress1/sygus/array-grammar-store.sy
index e63b24e2606bae57b7909963af3469a0f2ad16cb..3274ca2f56f166dbfbbf7f3355b1487c4a5bce3e 100644 (file)
@@ -1,7 +1,7 @@
 ; COMMAND-LINE: -q
 ; EXPECT: sat
 (set-logic QF_UFNRAT)
-(set-option :produce-models true)
+(set-info :status sat)
 (declare-fun b (Real) Real)
 (declare-fun v () Real)
 (assert (distinct 0 (* v v)))
index 7252e11cf7064965edfb5d5e258f59fa0c249d26..9a36d87a6818e08c45c183e5cbf3fee5cc611f34 100644 (file)
@@ -1,6 +1,7 @@
 ; COMMAND-LINE: -q
 ; EXPECT: sat
 (set-logic QF_NRAT)
+(set-info :status sat)
 (set-option :re-elim-agg true)
 (declare-fun r6 () Real)
 (assert (= 0.0 (cos r6)))
diff --git a/test/regress/regress1/nl/transcedental_model_simple.smt2 b/test/regress/regress1/nl/transcedental_model_simple.smt2
new file mode 100644 (file)
index 0000000..473ac25
--- /dev/null
@@ -0,0 +1,7 @@
+; COMMAND-LINE: --nl-rlv=always -q
+; EXPECT: sat
+(set-logic QF_NRAT)
+(set-info :status sat)
+(declare-fun x () Real)
+(assert (or (= x (sin 0.1)) (= x (sin 1.1))))
+(check-sat)
diff --git a/test/regress/regress1/strings/issue8094-witness-model.smt2 b/test/regress/regress1/strings/issue8094-witness-model.smt2
new file mode 100644 (file)
index 0000000..44da3d5
--- /dev/null
@@ -0,0 +1,10 @@
+; COMMAND-LINE: --strings-exp -q
+; EXPECT: sat
+(set-logic ALL)
+(set-info :status sat)
+(declare-fun v () String)
+(declare-fun r () Bool)
+(declare-fun w () String)
+(declare-fun q () String)
+(assert (forall ((V String)) (or r (exists ((V String)) (str.in_re (str.++ "z" v) (re.* (str.to_re (str.from_code (str.len v)))))))))
+(check-sat)
diff --git a/test/regress/regress1/strings/witness-model.smt2 b/test/regress/regress1/strings/witness-model.smt2
new file mode 100644 (file)
index 0000000..22e7aa7
--- /dev/null
@@ -0,0 +1,9 @@
+; COMMAND-LINE: --strings-exp -q
+; EXPECT: sat
+(set-logic ALL)
+(set-info :status sat)
+(declare-fun w () String)
+(declare-fun q () String)
+(assert (= (str.len q) 99999999999999999999999999999999999999))
+(assert (= w (str.++ q "A")))
+(check-sat)