Proper implementation of expand definitions for sequences (#5616)
authorAndrew Reynolds <andrew.j.reynolds@gmail.com>
Tue, 8 Dec 2020 06:38:14 +0000 (00:38 -0600)
committerGitHub <noreply@github.com>
Tue, 8 Dec 2020 06:38:14 +0000 (00:38 -0600)
Expand definitions for sequences was wrong in two ways: (1) we replaced str.from_code with a witness term. This led to it being unevaluatable in models. (2) we did not handle seq.nth, meaning its model value was unevaluatable if it was out of bounds. Now it evaluates the value of the uninterpreted function we replace with.

This corrects both issues and adds a regression to demonstrate both kinds of terms evaluate correctly.

To do this, I added a helper function to skolem cache as well as a new (internal-only) kind SEQ_NTH_TOTAL. Notice applications of this kind should only be used for model evaluation.

Notice this fixes several check-model warnings in the regressions. It still does not fix others since other things must be corrected for model evaluation (e.g. expandDefinitions must be applied on theory assertions for --debug-check-models). This will be done in later PRs.

12 files changed:
src/theory/strings/kinds
src/theory/strings/rewrites.cpp
src/theory/strings/rewrites.h
src/theory/strings/sequences_rewriter.cpp
src/theory/strings/skolem_cache.cpp
src/theory/strings/skolem_cache.h
src/theory/strings/theory_strings.cpp
src/theory/strings/theory_strings_preprocess.cpp
test/regress/CMakeLists.txt
test/regress/regress0/seq/seq-expand-defs.smt2 [new file with mode: 0644]
test/regress/regress0/seq/seq-nth-undef-unsat.smt2
test/regress/regress0/seq/seq-types.smt2

index 020cedb30d7153cf33b7e8c70dd74e62eccaf9f7..427e2e4e65dd8bfc9cd50acbf27c482fe5eeceaf 100644 (file)
@@ -84,6 +84,7 @@ constant CONST_SEQUENCE \
 
 operator SEQ_UNIT 1 "a sequence of length one"
 operator SEQ_NTH 2 "The nth element of a sequence"
+operator SEQ_NTH_TOTAL 2 "The nth element of a sequence (internal, for responses to expand definitions only)"
 
 # equal equal / less than / output
 operator STRING_TO_REGEXP 1 "convert string to regexp"
@@ -171,5 +172,6 @@ typerule STRING_TOLOWER "SimpleTypeRule<RString, AString>"
 typerule CONST_SEQUENCE ::CVC4::theory::strings::ConstSequenceTypeRule
 typerule SEQ_UNIT ::CVC4::theory::strings::SeqUnitTypeRule
 typerule SEQ_NTH ::CVC4::theory::strings::SeqNthTypeRule
+typerule SEQ_NTH_TOTAL ::CVC4::theory::strings::SeqNthTypeRule
 
 endtheory
index a32e5bc9ed94fcd7d5f8d68ae7905d913835eee5..dca7930493e0f45de243d46a3a755b5642a4c6f8 100644 (file)
@@ -210,7 +210,8 @@ const char* toString(Rewrite r)
     case Rewrite::LEN_SEQ_UNIT: return "LEN_SEQ_UNIT";
     case Rewrite::CHARAT_ELIM: return "CHARAT_ELIM";
     case Rewrite::SEQ_UNIT_EVAL: return "SEQ_UNIT_EVAL";
-    case Rewrite::SEQ_NTH_EVAL: return "SEQ_NTH_EVAL";      
+    case Rewrite::SEQ_NTH_EVAL: return "SEQ_NTH_EVAL";
+    case Rewrite::SEQ_NTH_TOTAL_OOB: return "SEQ_NTH_TOTAL_OOB";
     default: return "?";
   }
 }
index f9824405b59e985bea3153c85861eca143b3c83a..a450ae7f66f5653c21fd64e3329693a20999adfe 100644 (file)
@@ -213,7 +213,8 @@ enum class Rewrite : uint32_t
   LEN_SEQ_UNIT,
   CHARAT_ELIM,
   SEQ_UNIT_EVAL,
-  SEQ_NTH_EVAL
+  SEQ_NTH_EVAL,
+  SEQ_NTH_TOTAL_OOB
 };
 
 /**
index 2cefe6b09f7112a464e2411ddd41f786841f3a9d..1382ab0f92e529604f84eccfcf4cf2d12333e0cc 100644 (file)
@@ -1491,7 +1491,7 @@ RewriteResponse SequencesRewriter::postRewrite(TNode node)
   {
     retNode = rewriteSeqUnit(node);
   }
-  else if (nk == SEQ_NTH)
+  else if (nk == SEQ_NTH || nk == SEQ_NTH_TOTAL)
   {
     retNode = rewriteSeqNth(node);
   }
@@ -1516,7 +1516,7 @@ RewriteResponse SequencesRewriter::preRewrite(TNode node)
 
 Node SequencesRewriter::rewriteSeqNth(Node node)
 {
-  Assert(node.getKind() == SEQ_NTH);
+  Assert(node.getKind() == SEQ_NTH || node.getKind() == SEQ_NTH_TOTAL);
   Node ret;
   Node s = node[0];
   Node i = node[1];
@@ -1530,6 +1530,12 @@ Node SequencesRewriter::rewriteSeqNth(Node node)
       ret = elements[pos];
       return returnRewrite(node, ret, Rewrite::SEQ_NTH_EVAL);
     }
+    else if (node.getKind() == SEQ_NTH_TOTAL)
+    {
+      // return arbitrary term
+      Node ret = s.getType().getSequenceElementType().mkGroundValue();
+      return returnRewrite(node, ret, Rewrite::SEQ_NTH_TOTAL_OOB);
+    }
     else
     {
       return node;
index a1e04071be8adafd91befd3b7c9ab5aceaf56358..f5f2dfd35061411da065930b3d47434997149a1a 100644 (file)
@@ -130,6 +130,19 @@ Node SkolemCache::mkTypedSkolemCached(TypeNode tn,
   return mkTypedSkolemCached(tn, a, Node::null(), id, c);
 }
 
+Node SkolemCache::mkSkolemSeqNth(TypeNode seqType, const char* c)
+{
+  Assert(seqType.isSequence());
+  NodeManager* nm = NodeManager::currentNM();
+  std::vector<TypeNode> argTypes;
+  argTypes.push_back(seqType);
+  argTypes.push_back(nm->integerType());
+  TypeNode elemType = seqType.getSequenceElementType();
+  TypeNode ufType = nm->mkFunctionType(argTypes, elemType);
+  return mkTypedSkolemCached(
+      ufType, Node::null(), Node::null(), SkolemCache::SK_NTH, c);
+}
+
 Node SkolemCache::mkSkolem(const char* c)
 {
   // TODO: eliminate this
index 0a6dd367f93a820025354862f171a8686d117118..fa76afebdaff89edf12e75bdea59a564357a8071 100644 (file)
@@ -159,6 +159,11 @@ class SkolemCache
       TypeNode tn, Node a, Node b, SkolemId id, const char* c);
   /** Same as mkTypedSkolemCached above for (a,[null],id) */
   Node mkTypedSkolemCached(TypeNode tn, Node a, SkolemId id, const char* c);
+  /**
+   * Specific version for seq.nth, used for cases where the index is out of
+   * range for sequence type seqType.
+   */
+  Node mkSkolemSeqNth(TypeNode seqType, const char* c);
   /** Returns a (uncached) skolem of type string with name c */
   Node mkSkolem(const char* c);
   /** Returns true if n is a skolem allocated by this class */
index 8ce8e0ecb97402be9871fcb604a5fd4d277d8fa6..8915fcd02cc11c508362877d7ef532b8cf3c6c26 100644 (file)
@@ -570,23 +570,21 @@ TrustNode TheoryStrings::expandDefinition(Node node)
 {
   Trace("strings-exp-def") << "TheoryStrings::expandDefinition : " << node << std::endl;
 
-  if (node.getKind() == STRING_FROM_CODE)
+  if (node.getKind() == kind::SEQ_NTH)
   {
-    // str.from_code(t) --->
-    //   witness k. ite(0 <= t < |A|, t = str.to_code(k), k = "")
     NodeManager* nm = NodeManager::currentNM();
-    Node t = node[0];
-    Node card = nm->mkConst(Rational(utils::getAlphabetCardinality()));
-    Node cond =
-        nm->mkNode(AND, nm->mkNode(LEQ, d_zero, t), nm->mkNode(LT, t, card));
-    Node k = nm->mkBoundVar(nm->stringType());
-    Node bvl = nm->mkNode(BOUND_VAR_LIST, k);
-    Node emp = Word::mkEmptyWord(node.getType());
-    Node ret = nm->mkNode(
-        WITNESS,
-        bvl,
-        nm->mkNode(
-            ITE, cond, t.eqNode(nm->mkNode(STRING_TO_CODE, k)), k.eqNode(emp)));
+    SkolemCache* sc = d_termReg.getSkolemCache();
+    Node s = node[0];
+    Node n = node[1];
+    // seq.nth(s, n) --> ite(0 <= n < len(s), seq.nth_total(s,n), Uf(s, n))
+    Node cond = nm->mkNode(AND,
+                           nm->mkNode(LEQ, d_zero, n),
+                           nm->mkNode(LT, n, nm->mkNode(STRING_LENGTH, s)));
+    Node ss = nm->mkNode(SEQ_NTH_TOTAL, s, n);
+    Node uf = sc->mkSkolemSeqNth(s.getType(), "Uf");
+    Node u = nm->mkNode(APPLY_UF, uf, s, n);
+    Node ret = nm->mkNode(ITE, cond, ss, u);
+    Trace("strings-exp-def") << "...return " << ret << std::endl;
     return TrustNode::mkTrustRewrite(node, ret, nullptr);
   }
   return TrustNode::null();
@@ -1006,11 +1004,24 @@ void TheoryStrings::checkRegisterTermsNormalForms()
 TrustNode TheoryStrings::ppRewrite(TNode atom)
 {
   Trace("strings-ppr") << "TheoryStrings::ppRewrite " << atom << std::endl;
-  // first, see if we need to expand definitions
-  TrustNode texp = expandDefinition(atom);
-  if (!texp.isNull())
+  if (atom.getKind() == STRING_FROM_CODE)
   {
-    return texp;
+    // str.from_code(t) --->
+    //   witness k. ite(0 <= t < |A|, t = str.to_code(k), k = "")
+    NodeManager* nm = NodeManager::currentNM();
+    Node t = atom[0];
+    Node card = nm->mkConst(Rational(utils::getAlphabetCardinality()));
+    Node cond =
+        nm->mkNode(AND, nm->mkNode(LEQ, d_zero, t), nm->mkNode(LT, t, card));
+    Node k = nm->mkBoundVar(nm->stringType());
+    Node bvl = nm->mkNode(BOUND_VAR_LIST, k);
+    Node emp = Word::mkEmptyWord(atom.getType());
+    Node ret = nm->mkNode(
+        WITNESS,
+        bvl,
+        nm->mkNode(
+            ITE, cond, t.eqNode(nm->mkNode(STRING_TO_CODE, k)), k.eqNode(emp)));
+    return TrustNode::mkTrustRewrite(atom, ret, nullptr);
   }
   TrustNode ret;
   Node atomRet = atom;
index 87ab533f41b77e4e118f002ae995761c60103196..81cca34afa281d475edfdda7667d33b087a0a624 100644 (file)
@@ -444,13 +444,7 @@ Node StringsPreprocess::reduce(Node t,
     Node b1 = nm->mkNode(AND, b11, b12, b13);
 
     // nodes for the case where `seq.nth` is undefined.
-    std::vector<TypeNode> argTypes;
-    argTypes.push_back(s.getType());
-    argTypes.push_back(nm->integerType());
-    TypeNode elemType = s.getType().getSequenceElementType();
-    TypeNode ufType = nm->mkFunctionType(argTypes, elemType);
-    Node uf = sc->mkTypedSkolemCached(
-        ufType, Node::null(), Node::null(), SkolemCache::SK_NTH, "Uf");
+    Node uf = sc->mkSkolemSeqNth(s.getType(), "Uf");
     Node b2 = nm->mkNode(EQUAL, skt, nm->mkNode(APPLY_UF, uf, s, n));
 
     // the full ite, split on definedness of `seq.nth`
index 689b575144fe76cef4f97a06b289d784168271a3..06247807567ec651285bb1290ffd74276ffcdcc4 100644 (file)
@@ -908,6 +908,7 @@ set(regress_0_tests
   regress0/seq/seq-ex4.smt2
   regress0/seq/seq-ex5-dd.smt2
   regress0/seq/seq-ex5.smt2
+  regress0/seq/seq-expand-defs.smt2
   regress0/seq/seq-nemp.smt2
   regress0/seq/seq-nth.smt2
   regress0/seq/seq-nth-uf.smt2
diff --git a/test/regress/regress0/seq/seq-expand-defs.smt2 b/test/regress/regress0/seq/seq-expand-defs.smt2
new file mode 100644 (file)
index 0000000..3e51627
--- /dev/null
@@ -0,0 +1,19 @@
+; COMMAND-LINE: --strings-exp
+; EXPECT: sat
+; EXPECT: (((seq.nth y 7) 404))
+; EXPECT: (((str.from_code x) "?"))
+(set-logic ALL)
+(set-option :produce-models true)
+(declare-fun x () Int)
+(declare-fun y () (Seq Int))
+
+(assert (< (seq.len y) 5))
+
+(assert (= x 63))
+
+(assert (= (seq.nth y 7) 404))
+
+(check-sat)
+
+(get-value ((seq.nth y 7)))
+(get-value ((str.from_code x)))
index 4fe2a85a9b48e84eefe1a15b96a57b22e26c5071..85382f90e64b79d0abc00624e73e77a2581ff344 100644 (file)
@@ -1,4 +1,4 @@
-; COMMAND-LINE: --strings-exp -q
+; COMMAND-LINE: --strings-exp
 ;EXPECT: unsat
 (set-logic ALL)
 (declare-fun s () (Seq Int))
index d51c9627055478568df6ae64633730ef7432ba7f..3facf688ab91f9c2827ac0d02adb3f565ea6a0e6 100644 (file)
@@ -1,4 +1,4 @@
-; COMMAND-LINE: --strings-exp -q
+; COMMAND-LINE: --strings-exp
 ;EXPECT: unsat
 (set-logic ALL)
 (declare-fun s () (Seq Int))