Add is_singleton operator to the theory of sets (#5033)
authormudathirmahgoub <mudathirmahgoub@gmail.com>
Wed, 9 Sep 2020 18:19:40 +0000 (13:19 -0500)
committerGitHub <noreply@github.com>
Wed, 9 Sep 2020 18:19:40 +0000 (13:19 -0500)
This pull request adds a new operator is_singleton for sets to determine whether a set is a singleton.
Before this operator, the user either uses an existential quantifier or the cardinality operator in smtlib. The former affects the performance especially when polarity is negative. The latter requires the cardinality extension in sets theory.
The implementation of is_singleton only uses an internal existential quantifier with the hope of optimizing it later.
New rewriting rules:

(is_singleton (singleton x)) is rewritten as true.
(is_singleton A) is rewritten as (= A (singleton x)) where x is a fresh skolem of the same type of elements of A.
(choose (singleton x)) is rewritten as x.

src/api/cvc4cpp.cpp
src/api/cvc4cppkind.h
src/parser/smt2/smt2.cpp
src/printer/smt2/smt2_printer.cpp
src/theory/sets/kinds
src/theory/sets/theory_sets_private.cpp
src/theory/sets/theory_sets_private.h
src/theory/sets/theory_sets_rewriter.cpp
src/theory/sets/theory_sets_type_rules.h
test/regress/CMakeLists.txt
test/regress/regress1/sets/is_singleton1.smt2 [new file with mode: 0644]

index 5b33844392190ef0d70f417b392a294468b1bc58..f0dabc11b5749affae33db9807d3dc6c90339a12 100644 (file)
@@ -253,6 +253,7 @@ const static std::unordered_map<Kind, CVC4::Kind, KindHashFunction> s_kinds{
     {IDEN, CVC4::Kind::IDEN},
     {COMPREHENSION, CVC4::Kind::COMPREHENSION},
     {CHOOSE, CVC4::Kind::CHOOSE},
+    {IS_SINGLETON, CVC4::Kind::IS_SINGLETON},
     /* Strings ------------------------------------------------------------- */
     {STRING_CONCAT, CVC4::Kind::STRING_CONCAT},
     {STRING_IN_REGEXP, CVC4::Kind::STRING_IN_REGEXP},
@@ -542,6 +543,7 @@ const static std::unordered_map<CVC4::Kind, Kind, CVC4::kind::KindHashFunction>
         {CVC4::Kind::IDEN, IDEN},
         {CVC4::Kind::COMPREHENSION, COMPREHENSION},
         {CVC4::Kind::CHOOSE, CHOOSE},
+        {CVC4::Kind::IS_SINGLETON, IS_SINGLETON},
         /* Strings --------------------------------------------------------- */
         {CVC4::Kind::STRING_CONCAT, STRING_CONCAT},
         {CVC4::Kind::STRING_IN_REGEXP, STRING_IN_REGEXP},
index 48addc67ad6e1b98b198409ba3753065f4b467f1..f1f035460f52eb2252f6eaa31b350000a7f2c54e 100644 (file)
@@ -1956,6 +1956,14 @@ enum CVC4_PUBLIC Kind : int32_t
    *   mkTerm(Kind kind, Term child)
    */
   CHOOSE,
+  /**
+   * Set is_singleton predicate.
+   * Parameters: 1
+   *   -[1]: Term of set sort, is [1] a singleton set?
+   * Create with:
+   *   mkTerm(Kind kind, Term child)
+   */
+  IS_SINGLETON,
 
   /* Strings --------------------------------------------------------------- */
 
index c4899c8a87e735a96904a348d8818b1fdf83606a..6d69a3388eedce1803bd54f128523fd68811eb58 100644 (file)
@@ -684,6 +684,7 @@ Command* Smt2::setLogic(std::string name, bool fromCommand)
     addOperator(api::CARD, "card");
     addOperator(api::COMPLEMENT, "complement");
     addOperator(api::CHOOSE, "choose");
+    addOperator(api::IS_SINGLETON, "is_singleton");
     addOperator(api::JOIN, "join");
     addOperator(api::PRODUCT, "product");
     addOperator(api::TRANSPOSE, "transpose");
index 6c43c9eb17d23033360e4cb20b5d35a5e7dcdc17..960d17cdc07f7ac7bdeedc4ecdc096a56dba77ff 100644 (file)
@@ -740,7 +740,8 @@ void Smt2Printer::toStream(std::ostream& out,
   case kind::SET_TYPE:
   case kind::SINGLETON:
   case kind::COMPLEMENT:
-  case kind::CHOOSE: out << smtKindString(k, d_variant) << " "; break;
+  case kind::CHOOSE:
+  case kind::IS_SINGLETON: out << smtKindString(k, d_variant) << " "; break;
   case kind::UNIVERSE_SET:out << "(as univset " << n.getType() << ")";break;
 
     // fp theory
@@ -1145,6 +1146,7 @@ static string smtKindString(Kind k, Variant v)
   case kind::CARD: return "card";
   case kind::COMPREHENSION: return "comprehension";
   case kind::CHOOSE: return "choose";
+  case kind::IS_SINGLETON: return "is_singleton";
   case kind::JOIN: return "join";
   case kind::PRODUCT: return "product";
   case kind::TRANSPOSE: return "transpose";
index d3c42ef276525d1b127d59c108fa71b4586e5c90..fd941ab2981e2963d9597c45ecb6b498855f1123 100644 (file)
@@ -67,6 +67,9 @@ operator COMPREHENSION 3 "set comprehension specified by a bound variable list,
 # If the set has cardinality > 1, then (choose A) will deterministically return an element in A.
 operator CHOOSE        1  "return an element in the set given as a parameter"
 
+# The operator is_singleton returns whether the given set is a singleton
+operator IS_SINGLETON  1  "return whether the given set is a singleton"
+
 operator JOIN             2  "set join"
 operator PRODUCT          2  "set cartesian product"
 operator TRANSPOSE        1  "set transpose"
@@ -87,6 +90,7 @@ typerule COMPLEMENT     ::CVC4::theory::sets::ComplementTypeRule
 typerule UNIVERSE_SET   ::CVC4::theory::sets::UniverseSetTypeRule
 typerule COMPREHENSION  ::CVC4::theory::sets::ComprehensionTypeRule
 typerule CHOOSE         ::CVC4::theory::sets::ChooseTypeRule
+typerule IS_SINGLETON   ::CVC4::theory::sets::IsSingletonTypeRule
 
 typerule JOIN                  ::CVC4::theory::sets::RelBinaryOperatorTypeRule
 typerule PRODUCT               ::CVC4::theory::sets::RelBinaryOperatorTypeRule
@@ -96,19 +100,6 @@ typerule JOIN_IMAGE     ::CVC4::theory::sets::JoinImageTypeRule
 typerule IDEN                  ::CVC4::theory::sets::RelIdenTypeRule
 
 construle UNION         ::CVC4::theory::sets::SetsBinaryOperatorTypeRule
-construle INTERSECTION  ::CVC4::theory::sets::SetsBinaryOperatorTypeRule
-construle SETMINUS      ::CVC4::theory::sets::SetsBinaryOperatorTypeRule
 construle SINGLETON     ::CVC4::theory::sets::SingletonTypeRule
-construle INSERT        ::CVC4::theory::sets::InsertTypeRule
-construle CARD          ::CVC4::theory::sets::CardTypeRule
-construle COMPLEMENT    ::CVC4::theory::sets::ComplementTypeRule
-construle CHOOSE        ::CVC4::theory::sets::ChooseTypeRule
-
-construle JOIN                         ::CVC4::theory::sets::RelBinaryOperatorTypeRule
-construle PRODUCT              ::CVC4::theory::sets::RelBinaryOperatorTypeRule
-construle TRANSPOSE    ::CVC4::theory::sets::RelTransposeTypeRule
-construle TCLOSURE         ::CVC4::theory::sets::RelTransClosureTypeRule
-construle JOIN_IMAGE   ::CVC4::theory::sets::JoinImageTypeRule
-construle IDEN                         ::CVC4::theory::sets::RelIdenTypeRule
 
 endtheory
index a8837498033e06531336ebe6330305b1c5326307..741f45dd84d3c9c815f4fdd87826cad3aaa7812c 100644 (file)
@@ -1397,35 +1397,88 @@ TrustNode TheorySetsPrivate::expandDefinition(Node node)
 {
   Debug("sets-proc") << "expandDefinition : " << node << std::endl;
 
-  if (node.getKind() == kind::CHOOSE)
+  switch (node.getKind())
+  {
+    case kind::CHOOSE: return expandChooseOperator(node);
+    case kind::IS_SINGLETON: return expandIsSingletonOperator(node);
+    default: return TrustNode::null();
+  }
+}
+
+TrustNode TheorySetsPrivate::expandChooseOperator(const Node& node)
+{
+  Assert(node.getKind() == CHOOSE);
+
+  // we call the rewriter here to handle the pattern (choose (singleton x))
+  // because the rewriter is called after expansion
+  Node rewritten = Rewriter::rewrite(node);
+  if (rewritten.getKind() != CHOOSE)
+  {
+    return TrustNode::mkTrustRewrite(node, rewritten, nullptr);
+  }
+
+  // (choose A) is expanded as
+  // (witness ((x elementType))
+  //    (ite
+  //      (= A (as emptyset setType))
+  //      (= x chooseUf(A))
+  //      (and (member x A) (= x chooseUf(A)))
+
+  NodeManager* nm = NodeManager::currentNM();
+  Node set = rewritten[0];
+  TypeNode setType = set.getType();
+  Node chooseSkolem = getChooseFunction(setType);
+  Node apply = NodeManager::currentNM()->mkNode(APPLY_UF, chooseSkolem, set);
+
+  Node witnessVariable = nm->mkBoundVar(setType.getSetElementType());
+
+  Node equal = witnessVariable.eqNode(apply);
+  Node emptySet = nm->mkConst(EmptySet(setType));
+  Node isEmpty = set.eqNode(emptySet);
+  Node member = nm->mkNode(MEMBER, witnessVariable, set);
+  Node memberAndEqual = member.andNode(equal);
+  Node ite = nm->mkNode(ITE, isEmpty, equal, memberAndEqual);
+  Node witnessVariables = nm->mkNode(BOUND_VAR_LIST, witnessVariable);
+  Node witness = nm->mkNode(WITNESS, witnessVariables, ite);
+  return TrustNode::mkTrustRewrite(node, witness, nullptr);
+}
+
+TrustNode TheorySetsPrivate::expandIsSingletonOperator(const Node& node)
+{
+  Assert(node.getKind() == IS_SINGLETON);
+
+  // we call the rewriter here to handle the pattern
+  // (is_singleton (singleton x)) because the rewriter is called after expansion
+  Node rewritten = Rewriter::rewrite(node);
+  if (rewritten.getKind() != IS_SINGLETON)
+  {
+    return TrustNode::mkTrustRewrite(node, rewritten, nullptr);
+  }
+
+  // (is_singleton A) is expanded as
+  // (exists ((x: T)) (= A (singleton x)))
+  // where T is the sort of elements of A
+
+  NodeManager* nm = NodeManager::currentNM();
+  Node set = rewritten[0];
+
+  std::map<Node, Node>::iterator it = d_isSingletonNodes.find(rewritten);
+
+  if (it != d_isSingletonNodes.end())
   {
-    // (choose A) is expanded as
-    // (witness ((x elementType))
-    //    (ite
-    //      (= A (as emptyset setType))
-    //      (= x chooseUf(A))
-    //      (and (member x A) (= x chooseUf(A)))
-
-    NodeManager* nm = NodeManager::currentNM();
-    Node set = node[0];
-    TypeNode setType = set.getType();
-    Node chooseSkolem = getChooseFunction(setType);
-    Node apply = NodeManager::currentNM()->mkNode(APPLY_UF, chooseSkolem, set);
-
-    Node witnessVariable = nm->mkBoundVar(setType.getSetElementType());
-
-    Node equal = witnessVariable.eqNode(apply);
-    Node emptySet = nm->mkConst(EmptySet(setType));
-    Node isEmpty = set.eqNode(emptySet);
-    Node member = nm->mkNode(MEMBER, witnessVariable, set);
-    Node memberAndEqual = member.andNode(equal);
-    Node ite = nm->mkNode(kind::ITE, isEmpty, equal, memberAndEqual);
-    Node witnessVariables = nm->mkNode(BOUND_VAR_LIST, witnessVariable);
-    Node witness = nm->mkNode(WITNESS, witnessVariables, ite);
-    return TrustNode::mkTrustRewrite(node, witness, nullptr);
+    return TrustNode::mkTrustRewrite(rewritten, it->second, nullptr);
   }
 
-  return TrustNode::null();
+  TypeNode setType = set.getType();
+  Node boundVar = nm->mkBoundVar(setType.getSetElementType());
+  Node singleton = nm->mkNode(kind::SINGLETON, boundVar);
+  Node equal = set.eqNode(singleton);
+  std::vector<Node> variables = {boundVar};
+  Node boundVars = nm->mkNode(BOUND_VAR_LIST, variables);
+  Node exists = nm->mkNode(kind::EXISTS, boundVars, equal);
+  d_isSingletonNodes[rewritten] = exists;
+
+  return TrustNode::mkTrustRewrite(node, exists, nullptr);
 }
 
 Node TheorySetsPrivate::getChooseFunction(const TypeNode& setType)
index 1c038e02f7474ad1be01f082a39d262f0f7b1e00..71ad3781d6c08d4c7a37812d4131be4cef81926b 100644 (file)
@@ -256,6 +256,10 @@ class TheorySetsPrivate {
    * given set type, or creates a new one if it does not exist.
    */
   Node getChooseFunction(const TypeNode& setType);
+  /** expand the definition of the choose operator */
+  TrustNode expandChooseOperator(const Node& node);
+  /** expand the definition of is_singleton operator */
+  TrustNode expandIsSingletonOperator(const Node& node);
   /** subtheory solver for the theory of relations */
   std::unique_ptr<TheorySetsRels> d_rels;
   /** subtheory solver for the theory of sets with cardinality */
@@ -276,10 +280,12 @@ class TheorySetsPrivate {
   /** The theory rewriter for this theory. */
   TheorySetsRewriter d_rewriter;
 
-  /*
-   * a map that stores the choose functions for set types
-   */
+  /** a map that stores the choose functions for set types */
   std::map<TypeNode, Node> d_chooseFunctions;
+
+  /** a map that maps each set to an existential quantifier generated for
+   * operator is_singleton */
+  std::map<Node, Node> d_isSingletonNodes;
 };/* class TheorySetsPrivate */
 
 
index a7fbc8a38a6fca5aac2abd0054fa6c72caa9853e..eb168c6ed17c725a9fb26fa054b3b667686e2ddd 100644 (file)
@@ -240,6 +240,29 @@ RewriteResponse TheorySetsRewriter::postRewrite(TNode node) {
     }
     break;
   }
+    
+  case kind::CHOOSE:
+  {
+    if (node[0].getKind() == SINGLETON)
+    {
+      //(= (choose (singleton x)) x) is a tautology
+      // we return x for (choose (singleton x))
+      return RewriteResponse(REWRITE_AGAIN, node[0][0]);
+    }
+    break;
+  }  // kind::CHOOSE
+  case kind::IS_SINGLETON:
+  {
+    if (node[0].getKind() == SINGLETON)
+    {
+      //(= (is_singleton (singleton x)) is a tautology
+      // we return true for (is_singleton (singleton x))
+      return RewriteResponse(REWRITE_AGAIN,
+                             NodeManager::currentNM()->mkConst(true));
+    }
+    break;
+  } // kind::IS_SINGLETON
+
   case kind::TRANSPOSE: {
     if(node[0].getKind() == kind::TRANSPOSE) {
       return RewriteResponse(REWRITE_AGAIN, node[0][0]);
index 5d01a966aae9f59655d35ec4c8a2d6ad3f69213c..42fc583b857bf890ded55ece4c29ca895df32f07 100644 (file)
@@ -25,42 +25,53 @@ namespace CVC4 {
 namespace theory {
 namespace sets {
 
-struct SetsBinaryOperatorTypeRule {
-  inline static TypeNode computeType(NodeManager* nodeManager, TNode n, bool check)
+struct SetsBinaryOperatorTypeRule
+{
+  inline static TypeNode computeType(NodeManager* nodeManager,
+                                     TNode n,
+                                     bool check)
   {
-    Assert(n.getKind() == kind::UNION || n.getKind() == kind::INTERSECTION
-           || n.getKind() == kind::SETMINUS);
+    Assert(n.getKind() == kind::UNION ||
+           n.getKind() == kind::INTERSECTION ||
+           n.getKind() == kind::SETMINUS);
     TypeNode setType = n[0].getType(check);
-    if( check ) {
-      if(!setType.isSet()) {
-        throw TypeCheckingExceptionPrivate(n, "operator expects a set, first argument is not");
+    if (check)
+    {
+      if (!setType.isSet())
+      {
+        throw TypeCheckingExceptionPrivate(
+            n, "operator expects a set, first argument is not");
       }
       TypeNode secondSetType = n[1].getType(check);
-      if(secondSetType != setType) {
-        if( n.getKind() == kind::INTERSECTION ){
-          setType = TypeNode::mostCommonTypeNode( secondSetType, setType );
-        }else{
-          setType = TypeNode::leastCommonTypeNode( secondSetType, setType );
+      if (secondSetType != setType)
+      {
+        if (n.getKind() == kind::INTERSECTION)
+        {
+          setType = TypeNode::mostCommonTypeNode(secondSetType, setType);
+        }
+        else
+        {
+          setType = TypeNode::leastCommonTypeNode(secondSetType, setType);
         }
-        if( setType.isNull() ){
-          throw TypeCheckingExceptionPrivate(n, "operator expects two sets of comparable types");
+        if (setType.isNull())
+        {
+          throw TypeCheckingExceptionPrivate(
+              n, "operator expects two sets of comparable types");
         }
-        
       }
     }
     return setType;
   }
 
-  inline static bool computeIsConst(NodeManager* nodeManager, TNode n) {
-    Assert(n.getKind() == kind::UNION || n.getKind() == kind::INTERSECTION
-           || n.getKind() == kind::SETMINUS);
-    if(n.getKind() == kind::UNION) {
-      return NormalForm::checkNormalConstant(n);
-    } else {
-      return false;
-    }
+  inline static bool computeIsConst(NodeManager* nodeManager, TNode n)
+  {
+    // only UNION has a const rule in kinds.
+    // INTERSECTION and SETMINUS are not used in the canonical representation of
+    // sets and therefore they do not have const rules in kinds
+    Assert(n.getKind() == kind::UNION);
+    return NormalForm::checkNormalConstant(n);
   }
-};/* struct SetUnionTypeRule */
+}; /* struct SetsBinaryOperatorTypeRule */
 
 struct SubsetTypeRule {
   inline static TypeNode computeType(NodeManager* nodeManager, TNode n, bool check)
@@ -155,11 +166,6 @@ struct CardTypeRule {
     }
     return nodeManager->integerType();
   }
-
-  inline static bool computeIsConst(NodeManager* nodeManager, TNode n) {
-    Assert(n.getKind() == kind::CARD);
-    return false;
-  }
 };/* struct CardTypeRule */
 
 struct ComplementTypeRule {
@@ -174,11 +180,6 @@ struct ComplementTypeRule {
     }
     return setType;
   }
-
-  inline static bool computeIsConst(NodeManager* nodeManager, TNode n) {
-    Assert(n.getKind() == kind::COMPLEMENT);
-    return false;
-  }
 };/* struct ComplementTypeRule */
 
 struct UniverseSetTypeRule {
@@ -235,13 +236,27 @@ struct ChooseTypeRule
     }
     return setType.getSetElementType();
   }
-  inline static bool computeIsConst(NodeManager* nodeManager, TNode n)
+}; /* struct ChooseTypeRule */
+
+struct IsSingletonTypeRule
+{
+  inline static TypeNode computeType(NodeManager* nodeManager,
+                                     TNode n,
+                                     bool check)
   {
-    Assert(n.getKind() == kind::CHOOSE);
-    // choose nodes should be expanded
-    return false;
+    Assert(n.getKind() == kind::IS_SINGLETON);
+    TypeNode setType = n[0].getType(check);
+    if (check)
+    {
+      if (!setType.isSet())
+      {
+        throw TypeCheckingExceptionPrivate(
+            n, "IS_SINGLETON operator expects a set, a non-set is found");
+      }
+    }
+    return nodeManager->booleanType();
   }
-}; /* struct ChooseTypeRule */
+}; /* struct IsSingletonTypeRule */
 
 struct InsertTypeRule {
   inline static TypeNode computeType(NodeManager* nodeManager, TNode n, bool check)
@@ -264,11 +279,6 @@ struct InsertTypeRule {
     }
     return setType;
   }
-
-  inline static bool computeIsConst(NodeManager* nodeManager, TNode n) {
-    Assert(n.getKind() == kind::INSERT);
-    return false;
-  }
 };/* struct InsertTypeRule */
 
 struct RelBinaryOperatorTypeRule {
@@ -308,11 +318,6 @@ struct RelBinaryOperatorTypeRule {
 
     return resultType;
   }
-
-  inline static bool computeIsConst(NodeManager* nodeManager, TNode n) {
-    Assert(n.getKind() == kind::JOIN || n.getKind() == kind::PRODUCT);
-    return false;
-  }
 };/* struct RelBinaryOperatorTypeRule */
 
 struct RelTransposeTypeRule {
@@ -327,10 +332,6 @@ struct RelTransposeTypeRule {
     std::reverse(tupleTypes.begin(), tupleTypes.end());
     return nodeManager->mkSetType(nodeManager->mkTupleType(tupleTypes));
   }
-
-  inline static bool computeIsConst(NodeManager* nodeManager, TNode n) {
-      return false;
-    }
 };/* struct RelTransposeTypeRule */
 
 struct RelTransClosureTypeRule {
@@ -352,11 +353,6 @@ struct RelTransClosureTypeRule {
     }
     return setType;
   }
-
-  inline static bool computeIsConst(NodeManager* nodeManager, TNode n) {
-    Assert(n.getKind() == kind::TCLOSURE);
-    return false;
-    }
 };/* struct RelTransClosureTypeRule */
 
 struct JoinImageTypeRule {
@@ -399,11 +395,6 @@ struct JoinImageTypeRule {
     newTupleTypes.push_back(tupleTypes[0]);
     return nodeManager->mkSetType(nodeManager->mkTupleType(newTupleTypes));
   }
-
-  inline static bool computeIsConst(NodeManager* nodeManager, TNode n) {
-    Assert(n.getKind() == kind::JOIN_IMAGE);
-    return false;
-  }
 };/* struct JoinImageTypeRule */
 
 struct RelIdenTypeRule {
@@ -423,10 +414,6 @@ struct RelIdenTypeRule {
     tupleTypes.push_back(tupleTypes[0]);
     return nodeManager->mkSetType(nodeManager->mkTupleType(tupleTypes));
   }
-
-  inline static bool computeIsConst(NodeManager* nodeManager, TNode n) {
-      return false;
-    }
 };/* struct RelIdenTypeRule */
 
 struct SetsProperties {
index b92f767b233c22d98c913f0443443691076babfd..e06abc0a57e46afa013311d0ce1524e97ba4cb06 100644 (file)
@@ -1762,6 +1762,7 @@ set(regress_1_tests
   regress1/sets/infinite-type/sets-card-int-1.smt2
   regress1/sets/infinite-type/sets-card-int-2.smt2
   regress1/sets/insert_invariant_37_2.smt2
+  regress1/sets/is_singleton1.smt2
   regress1/sets/issue2568.smt2
   regress1/sets/issue2904.smt2
   regress1/sets/issue4391-card-lasso.smt2
diff --git a/test/regress/regress1/sets/is_singleton1.smt2 b/test/regress/regress1/sets/is_singleton1.smt2
new file mode 100644 (file)
index 0000000..01b633d
--- /dev/null
@@ -0,0 +1,9 @@
+(set-logic ALL_SUPPORTED)
+(set-info :status sat)
+(declare-fun S () (Set Int))
+(declare-fun x () Int)
+(assert (= (choose (singleton x)) 5))
+(assert (= (singleton x) S))
+(assert (is_singleton S))
+(assert (is_singleton (singleton 3)))
+(check-sat)
\ No newline at end of file