Add support for eqrange predicate (#4562)
authorMathias Preiner <mathias.preiner@gmail.com>
Tue, 23 Jun 2020 16:55:01 +0000 (09:55 -0700)
committerGitHub <noreply@github.com>
Tue, 23 Jun 2020 16:55:01 +0000 (09:55 -0700)
This commit adds support for an eqrange predicate. (eqrange a b i j) is true if arrays a and b are equal on all indices within indices i and j, i.e., \forall k . i <= k <= j --> a[k] = b[k]. Requires option --arrays-exp to be enabled.

14 files changed:
src/api/cvc4cpp.cpp
src/api/cvc4cppkind.h
src/options/arrays_options.toml
src/parser/smt2/smt2.cpp
src/printer/smt2/smt2_printer.cpp
src/smt/set_defaults.cpp
src/theory/arrays/kinds
src/theory/arrays/theory_arrays.cpp
src/theory/arrays/theory_arrays.h
src/theory/arrays/theory_arrays_type_rules.h
test/regress/CMakeLists.txt
test/regress/regress0/eqrange1.smt2 [new file with mode: 0644]
test/regress/regress0/eqrange2.smt2 [new file with mode: 0644]
test/regress/regress0/eqrange3.smt2 [new file with mode: 0644]

index 2fd5cb4448c22161b4a550b405e98b232307b218..cebba9b5eb6ed3022017912b066a87736035cfb0 100644 (file)
@@ -214,6 +214,7 @@ const static std::unordered_map<Kind, CVC4::Kind, KindHashFunction> s_kinds{
     {SELECT, CVC4::Kind::SELECT},
     {STORE, CVC4::Kind::STORE},
     {CONST_ARRAY, CVC4::Kind::STORE_ALL},
+    {EQ_RANGE, CVC4::Kind::EQ_RANGE},
     /* Datatypes ----------------------------------------------------------- */
     {APPLY_SELECTOR, CVC4::Kind::APPLY_SELECTOR},
     {APPLY_CONSTRUCTOR, CVC4::Kind::APPLY_CONSTRUCTOR},
index 198f5102406be13ac939408cf2641f9dcf557122..cf907412979d2a36efae85165ccc8378a6e26433 100644 (file)
@@ -1505,6 +1505,23 @@ enum CVC4_PUBLIC Kind : int32_t
    * arrays, the solver doesn't know what to do and aborts (Issue #1667).
    */
   CONST_ARRAY,
+  /**
+   * Equality over arrays a and b over a given range [i,j], i.e.,
+   * \forall k . i <= k <= j --> a[k] = b[k]
+   *
+   * Parameters: 4
+   *   -[1]: First array
+   *   -[2]: Second array
+   *   -[3]: Lower bound of range (inclusive)
+   *   -[4]: Uppper bound of range (inclusive)
+   * Create with:
+   *   mkTerm(Op op, const std::vector<Term>& children)
+   *
+   * Note: We currently support the creation of array equalities over index
+   * types bit-vector, floating-point, integer and real. Option --arrays-exp is
+   * required to support this operator.
+   */
+  EQ_RANGE,
 #if 0
   /* array table function (internal-only symbol) */
   ARR_TABLE_FUN,
index 8c82a7fb578f6edfaf9fbe2b9d210ea541af53e7..389bb6e4c37cf47e435b5e588de8d16ee5369809 100644 (file)
@@ -73,3 +73,12 @@ header = "options/arrays_options.h"
   type       = "int"
   default    = "2"
   help       = "propagation effort for arrays: 0 is none, 1 is some, 2 is full"
+
+[[option]]
+  name       = "arraysExp"
+  category   = "experimental"
+  long       = "arrays-exp"
+  type       = "bool"
+  default    = "false"
+  help       = "enable experimental features in the theory of arrays"
+
index bf4e489bbd5c686e5712a3c3a82d1c04401d82b9..a7eb218af2b89d6284a1e5e8a8450fa728f9c7b9 100644 (file)
@@ -642,6 +642,7 @@ Command* Smt2::setLogic(std::string name, bool fromCommand)
   if(d_logic.isTheoryEnabled(theory::THEORY_ARRAYS)) {
     addOperator(api::SELECT, "select");
     addOperator(api::STORE, "store");
+    addOperator(api::EQ_RANGE, "eqrange");
   }
 
   if(d_logic.isTheoryEnabled(theory::THEORY_BV)) {
@@ -1824,6 +1825,11 @@ api::Term Smt2::applyParseOp(ParseOp& p, std::vector<api::Term>& args)
       Debug("parser") << "applyParseOp: return uminus " << ret << std::endl;
       return ret;
     }
+    if (kind == api::EQ_RANGE && d_solver->getOption("arrays-exp") != "true")
+    {
+      parseError(
+          "eqrange predicate requires option --arrays-exp to be enabled.");
+    }
     api::Term ret = d_solver->mkTerm(kind, args);
     Debug("parser") << "applyParseOp: return default builtin " << ret
                     << std::endl;
index b88e53788453032e9da0cec1240d7bde707a07b6..31aa67ff9953ca936a7a102021954bd49876717c 100644 (file)
@@ -589,8 +589,7 @@ void Smt2Printer::toStream(std::ostream& out,
   case kind::PARTIAL_SELECT_0:
   case kind::PARTIAL_SELECT_1:
   case kind::ARRAY_TYPE:
-    out << smtKindString(k, d_variant) << " ";
-    break;
+  case kind::EQ_RANGE: out << smtKindString(k, d_variant) << " "; break;
 
   // string theory
   case kind::STRING_CONCAT:
@@ -1070,6 +1069,8 @@ static string smtKindString(Kind k, Variant v)
   case kind::ARRAY_TYPE: return "Array";
   case kind::PARTIAL_SELECT_0: return "partial_select_0";
   case kind::PARTIAL_SELECT_1: return "partial_select_1";
+  case kind::EQ_RANGE:
+    return "eqrange";
 
     // bv theory
   case kind::BITVECTOR_CONCAT: return "concat";
index 23ae65044b365fc803b3b3bfe12d16e47d95066b..afc8eb7bbd7ca597628b8342f7ff91fc7f0de81b 100644 (file)
@@ -272,6 +272,22 @@ void setDefaults(SmtEngine& smte, LogicInfo& logic)
     options::proofNew.set(false);
   }
 
+  if (options::arraysExp())
+  {
+    if (!logic.isQuantified())
+    {
+      logic = logic.getUnlockedCopy();
+      logic.enableQuantifiers();
+      logic.lock();
+    }
+    // Allows to answer sat more often by default.
+    if (!options::fmfBound.wasSetByUser())
+    {
+      options::fmfBound.set(true);
+      Trace("smt") << "turning on fmf-bound, for arrays-exp" << std::endl;
+    }
+  }
+
   // sygus inference may require datatypes
   if (!smte.isInternalSubsolver())
   {
index 1adbdb0b22782e0b702094d2a784dca7b121e85f..659f05ae8d33e0c85e9ef2c632ea70f1eb64fe7e 100644 (file)
@@ -31,6 +31,9 @@ operator SELECT 2 "array select; first parameter is an array term, second is the
 # store a i e  is  a[i] <= e
 operator STORE 3 "array store; first parameter is an array term, second is the store index, third is the term to store at the index"
 
+# eqrange a b i j  \forall k. i <= k <= j -> a[k] = b[k]
+operator EQ_RANGE 4 "equality of two arrays over an index range lower/upper"
+
 # storeall t e  is  \all i in indexType(t) <= e
 constant STORE_ALL \
     ::CVC4::ArrayStoreAll \
@@ -53,6 +56,7 @@ typerule STORE ::CVC4::theory::arrays::ArrayStoreTypeRule
 typerule STORE_ALL ::CVC4::theory::arrays::ArrayStoreTypeRule
 typerule ARR_TABLE_FUN ::CVC4::theory::arrays::ArrayTableFunTypeRule
 typerule ARRAY_LAMBDA ::CVC4::theory::arrays::ArrayLambdaTypeRule
+typerule EQ_RANGE ::CVC4::theory::arrays::ArrayEqRangeTypeRule
 
 operator PARTIAL_SELECT_0 0:2 "partial array select, for internal use only"
 operator PARTIAL_SELECT_1 0:2 "partial array select, for internal use only"
index 0f5d9ec8ba8170a756a4ab884cdd644f3a82af93..71e040ccce810530941e5ce90862a28f2983c6a5 100644 (file)
@@ -2312,6 +2312,61 @@ std::string TheoryArrays::TheoryArraysDecisionStrategy::identify() const
   return std::string("th_arrays_dec");
 }
 
+Node TheoryArrays::expandDefinition(Node node)
+{
+  NodeManager* nm = NodeManager::currentNM();
+  Kind kind = node.getKind();
+
+  /* Expand
+   *
+   *   (eqrange a b i j)
+   *
+   * to
+   *
+   *  forall k . i <= k <= j => a[k] = b[k]
+   *
+   */
+  if (kind == kind::EQ_RANGE)
+  {
+    TNode a = node[0];
+    TNode b = node[1];
+    TNode i = node[2];
+    TNode j = node[3];
+    Node k = nm->mkBoundVar(i.getType());
+    Node bvl = nm->mkNode(kind::BOUND_VAR_LIST, k);
+    TypeNode type = k.getType();
+
+    Kind kle;
+    Node range;
+    if (type.isBitVector())
+    {
+      kle = kind::BITVECTOR_ULE;
+    }
+    else if (type.isFloatingPoint())
+    {
+      kle = kind::FLOATINGPOINT_LEQ;
+    }
+    else if (type.isInteger() || type.isReal())
+    {
+      kle = kind::LEQ;
+    }
+    else
+    {
+      Unimplemented() << "Type " << type << " is not supported for predicate "
+                      << kind;
+    }
+
+    range = nm->mkNode(kind::AND, nm->mkNode(kle, i, k), nm->mkNode(kle, k, j));
+
+    Node eq = nm->mkNode(kind::EQUAL,
+                         nm->mkNode(kind::SELECT, a, k),
+                         nm->mkNode(kind::SELECT, b, k));
+    Node implies = nm->mkNode(kind::IMPLIES, range, eq);
+    return nm->mkNode(kind::FORALL, bvl, implies);
+  }
+  return node;
+}
+
 }/* CVC4::theory::arrays namespace */
 }/* CVC4::theory namespace */
 }/* CVC4 namespace */
index 8c94f08cd691bb88023d60da3fe4fd232d8945b1..c5cd24fd38aa815ab4ad2f4f6bb08f8686ac38f7 100644 (file)
@@ -151,6 +151,8 @@ class TheoryArrays : public Theory {
 
   std::string identify() const override { return std::string("TheoryArrays"); }
 
+  Node expandDefinition(Node node) override;
+
   /////////////////////////////////////////////////////////////////////////////
   // PREPROCESSING
   /////////////////////////////////////////////////////////////////////////////
index facc81f437eafa1076ed3028a425aeb5fcc91e6f..e5681d50f774e4110ae6c665591d74c43f0f2460 100644 (file)
@@ -224,6 +224,57 @@ struct ArrayPartialSelectTypeRule {
   }
 };/* struct ArrayPartialSelectTypeRule */
 
+struct ArrayEqRangeTypeRule
+{
+  inline static TypeNode computeType(NodeManager* nodeManager,
+                                     TNode n,
+                                     bool check)
+  {
+    Assert(n.getKind() == kind::EQ_RANGE);
+    if (check)
+    {
+      TypeNode n0_type = n[0].getType(check);
+      TypeNode n1_type = n[1].getType(check);
+      if (!n0_type.isArray())
+      {
+        throw TypeCheckingExceptionPrivate(
+            n, "first operand of eqrange is not an array");
+      }
+      if (!n1_type.isArray())
+      {
+        throw TypeCheckingExceptionPrivate(
+            n, "second operand of eqrange is not an array");
+      }
+      if (n0_type != n1_type)
+      {
+        throw TypeCheckingExceptionPrivate(n, "array types do not match");
+      }
+      TypeNode indexType = n0_type.getArrayIndexType();
+      TypeNode indexRangeType1 = n[2].getType(check);
+      TypeNode indexRangeType2 = n[3].getType(check);
+      if (!indexRangeType1.isSubtypeOf(indexType))
+      {
+        throw TypeCheckingExceptionPrivate(
+            n, "eqrange lower index type does not match array index type");
+      }
+      if (!indexRangeType2.isSubtypeOf(indexType))
+      {
+        throw TypeCheckingExceptionPrivate(
+            n, "eqrange upper index type does not match array index type");
+      }
+      if (!indexType.isBitVector() && !indexType.isFloatingPoint()
+          && !indexType.isInteger() && !indexType.isReal())
+      {
+        throw TypeCheckingExceptionPrivate(
+            n,
+            "eqrange only supports bit-vectors, floating-points, integers, and "
+            "reals as index type");
+      }
+    }
+    return nodeManager->booleanType();
+  }
+}; /* struct ArrayEqRangeTypeRule */
+
 }/* CVC4::theory::arrays namespace */
 }/* CVC4::theory namespace */
 }/* CVC4 namespace */
index 690135b54bd4b827152990c94a7c893b553a5a56..bb0f311d138edc53255ae3d4cab06d1a5fa47655 100644 (file)
@@ -473,6 +473,9 @@ set(regress_0_tests
   regress0/distinct.smtv1.smt2
   regress0/dump-unsat-core-full.smt2
   regress0/simple-dump-model.smt2
+  regress0/eqrange1.smt2
+  regress0/eqrange2.smt2
+  regress0/eqrange3.smt2
   regress0/expect/scrub.01.smtv1.smt2
   regress0/expect/scrub.03.smt2
   regress0/expect/scrub.06.cvc
diff --git a/test/regress/regress0/eqrange1.smt2 b/test/regress/regress0/eqrange1.smt2
new file mode 100644 (file)
index 0000000..90eb7a2
--- /dev/null
@@ -0,0 +1,15 @@
+(set-logic QF_AUFBV)
+(set-option :arrays-exp true)
+(set-option :quiet true) ; Suppress check-model warnings involving quantifiers
+(set-info :status sat)
+(declare-fun a1 () (Array (_ BitVec 2) (_ BitVec 2)))
+(declare-fun a2 () (Array (_ BitVec 2) (_ BitVec 2)))
+(declare-fun e1 () (_ BitVec 2))
+(declare-fun e2 () (_ BitVec 2))
+(assert (distinct e1 e2))
+(assert (= a1 (store (store (store (store a1 #b00 e1) #b01 e1) #b10 e1) #b11 e1)))
+(assert (= a2 (store (store (store (store a2 #b00 e1) #b01 e1) #b10 e2) #b11 e1)))
+(assert (eqrange a1 a2 #b00 #b01))
+(assert (eqrange a1 a2 #b11 #b11))
+(check-sat)
+(exit)
diff --git a/test/regress/regress0/eqrange2.smt2 b/test/regress/regress0/eqrange2.smt2
new file mode 100644 (file)
index 0000000..efddbc8
--- /dev/null
@@ -0,0 +1,12 @@
+(set-logic AUFBV)
+(set-option :arrays-exp true)
+(set-info :status unsat)
+(declare-sort Element 0)
+(declare-const a (Array (_ BitVec 3) Element))
+(declare-const b (Array (_ BitVec 3) Element))
+(declare-const j (_ BitVec 3))
+(assert (eqrange a b (_ bv0 3) j))
+(assert (eqrange a b (bvadd j (_ bv1 3))  (_ bv7 3)))
+(assert (exists ((i (_ BitVec 3))) (not (= (select a i) (select b i)))))
+(check-sat)
+(exit)
diff --git a/test/regress/regress0/eqrange3.smt2 b/test/regress/regress0/eqrange3.smt2
new file mode 100644 (file)
index 0000000..54e9d03
--- /dev/null
@@ -0,0 +1,16 @@
+(set-logic QF_AUFLIA)
+(set-option :arrays-exp true)
+(set-option :quiet true) ; Suppress Warning
+(set-info :status sat)
+(declare-fun a1 () (Array Int Int))
+(declare-fun a2 () (Array Int Int))
+(declare-fun e1 () Int)
+(declare-fun e2 () Int)
+(assert (distinct e1 e2))
+(assert (= a1 (store (store (store (store a1 0 e1) 1 e1) 2 e1) 3 e1)))
+(assert (= a2 (store (store (store (store a2 1 e1) 0 e1) 2 e2) 3 e1)))
+(assert (eqrange a1 a2 (- 1) 1))
+(assert (eqrange a1 a2 3 3))
+(check-sat)
+(exit)
+