Add tuple projection operator (#5904)
authormudathirmahgoub <mudathirmahgoub@gmail.com>
Wed, 3 Mar 2021 08:16:32 +0000 (02:16 -0600)
committerGitHub <noreply@github.com>
Wed, 3 Mar 2021 08:16:32 +0000 (08:16 +0000)
This PR adds tuple projection operator to the theory of data types.
It Also adds helper functions for selecting elements from a tuple.

16 files changed:
src/CMakeLists.txt
src/api/cvc4cpp.cpp
src/api/cvc4cpp.h
src/api/cvc4cppkind.h
src/parser/smt2/Smt2.g
src/parser/smt2/smt2.cpp
src/printer/smt2/smt2_printer.cpp
src/theory/bags/make_bag_op.h
src/theory/datatypes/datatypes_rewriter.cpp
src/theory/datatypes/kinds
src/theory/datatypes/theory_datatypes_type_rules.h
src/theory/datatypes/tuple_project_op.cpp [new file with mode: 0644]
src/theory/datatypes/tuple_project_op.h [new file with mode: 0644]
test/regress/CMakeLists.txt
test/regress/regress1/datatypes/tuple_projection.smt2 [new file with mode: 0644]
test/unit/api/solver_black.cpp

index 02cb26760663fb4d38787c8a991aabdd40e5a5ca..2b04bb40d91cbddf53a0522e9b88fa2294f9747d 100644 (file)
@@ -584,6 +584,8 @@ libcvc4_add_sources(
   theory/datatypes/theory_datatypes_type_rules.h
   theory/datatypes/theory_datatypes_utils.cpp
   theory/datatypes/theory_datatypes_utils.h
+  theory/datatypes/tuple_project_op.cpp
+  theory/datatypes/tuple_project_op.h
   theory/datatypes/type_enumerator.cpp
   theory/datatypes/type_enumerator.h
   theory/decision_manager.cpp
index e1c64b75078f1d642f719c441cc6a053fe15c4cb..c3490938b52f88152bcf2afdbf58675ed063caf7 100644 (file)
@@ -232,6 +232,7 @@ const static std::unordered_map<Kind, CVC4::Kind, KindHashFunction> s_kinds{
     {TUPLE_UPDATE, CVC4::Kind::TUPLE_UPDATE},
     {RECORD_UPDATE, CVC4::Kind::RECORD_UPDATE},
     {DT_SIZE, CVC4::Kind::DT_SIZE},
+    {TUPLE_PROJECT, CVC4::Kind::TUPLE_PROJECT},
     /* Separation Logic ---------------------------------------------------- */
     {SEP_NIL, CVC4::Kind::SEP_NIL},
     {SEP_EMP, CVC4::Kind::SEP_EMP},
@@ -538,6 +539,7 @@ const static std::unordered_map<CVC4::Kind, Kind, CVC4::kind::KindHashFunction>
         {CVC4::Kind::RECORD_UPDATE_OP, RECORD_UPDATE},
         {CVC4::Kind::RECORD_UPDATE, RECORD_UPDATE},
         {CVC4::Kind::DT_SIZE, DT_SIZE},
+        {CVC4::Kind::TUPLE_PROJECT, TUPLE_PROJECT},
         /* Separation Logic ------------------------------------------------ */
         {CVC4::Kind::SEP_NIL, SEP_NIL},
         {CVC4::Kind::SEP_EMP, SEP_EMP},
@@ -4749,6 +4751,35 @@ Op Solver::mkOp(Kind kind, uint32_t arg1, uint32_t arg2) const
   CVC4_API_SOLVER_TRY_CATCH_END;
 }
 
+Op Solver::mkOp(Kind kind, const std::vector<uint32_t>& args) const
+{
+  CVC4_API_SOLVER_TRY_CATCH_BEGIN;
+  CVC4_API_KIND_CHECK(kind);
+
+  Op res;
+  switch (kind)
+  {
+    case TUPLE_PROJECT:
+    {
+      res = Op(this,
+               kind,
+               *mkValHelper<CVC4::TupleProjectOp>(CVC4::TupleProjectOp(args))
+                    .d_node);
+    }
+    break;
+    default:
+    {
+      std::string message = "operator kind with " + std::to_string(args.size())
+                            + " uint32_t arguments";
+      CVC4_API_KIND_CHECK_EXPECTED(false, kind) << message;
+    }
+  }
+  Assert(!res.isNull());
+  return res;
+
+  CVC4_API_SOLVER_TRY_CATCH_END;
+}
+
 /* Non-SMT-LIB commands                                                       */
 /* -------------------------------------------------------------------------- */
 
index 60127d18bc56e28398129ad702959fbc80588852..d503a317e28dd5d78f0ce3c16f0ce90d8dd17d42 100644 (file)
@@ -2620,6 +2620,14 @@ class CVC4_PUBLIC Solver
    */
   Op mkOp(Kind kind, uint32_t arg1, uint32_t arg2) const;
 
+  /**
+   * Create operator of Kind:
+   *   - TUPLE_PROJECT
+   * See enum Kind for a description of the parameters.
+   * @param kind the kind of the operator
+   */
+  Op mkOp(Kind kind, const std::vector<uint32_t>& args) const;
+
   /* .................................................................... */
   /* Create Constants                                                     */
   /* .................................................................... */
index 3fb16abcbf06136fc3095d13b36dedfbf7d34c88..b2413486e318b982622b6431cc9bce0a4db120c2 100644 (file)
@@ -1715,6 +1715,22 @@ enum CVC4_PUBLIC Kind : int32_t
    *   mkTerm(Kind kind, const std::vector<Term>& children)
    */
   DT_SIZE,
+  /**
+   * Operator for tuple projection indices
+   * Parameters: 1
+   *   -[1]: The tuple projection indices
+   * Create with:
+   *   mkOp(Kind TUPLE_PROJECT, std::vector<uint32_t> param)
+   *
+   * constructs a new tuple from an existing one using the elements at the
+   * given indices
+   * Parameters: 1
+   *   -[1]: a term of tuple sort
+   * Create with:
+   *   mkTerm(Op op, Term child)
+   *   mkTerm(Op op, const std::vector<Term>& children)
+   */
+  TUPLE_PROJECT,
 #if 0
   /* datatypes height bound */
   DT_HEIGHT_BOUND,
index 11ccb4935da4678b462bb41d89ec26531290d0c4..7ef7edbbe6ec86c156dec01e8018918fb29cca05 100644 (file)
@@ -1549,6 +1549,12 @@ termNonVariable[CVC4::api::Term& expr, CVC4::api::Term& expr2]
     }
     expr = SOLVER->mkTuple(sorts, terms);
   }
+  | LPAREN_TOK TUPLE_PROJECT_TOK term[expr,expr2] RPAREN_TOK
+  {
+    std::vector<uint32_t> indices;
+    api::Op op = SOLVER->mkOp(api::TUPLE_PROJECT, indices);
+    expr = SOLVER->mkTerm(op, expr);
+  }
   | /* an atomic term (a term with no subterms) */
     termAtomic[atomTerm] { expr = atomTerm; }
   ;
@@ -1672,6 +1678,19 @@ identifier[CVC4::ParseOp& p]
         // put m in expr so that the caller can deal with this case
         p.d_expr = SOLVER->mkInteger(AntlrInput::tokenToUnsigned($m));
       }
+    | TUPLE_PROJECT_TOK nonemptyNumeralList[numerals]
+      {
+        // we adopt a special syntax (_ tuple_project i_1 ... i_n) where
+        // i_1, ..., i_n are numerals
+        p.d_kind = api::TUPLE_PROJECT;
+        std::vector<uint32_t> indices(numerals.size());
+        for(size_t i = 0; i < numerals.size(); ++i)
+        {
+          // convert uint64_t to uint32_t
+          indices[i] = numerals[i];
+        }
+        p.d_op = SOLVER->mkOp(api::TUPLE_PROJECT, indices);
+      }
     | sym=SIMPLE_SYMBOL nonemptyNumeralList[numerals]
       {
         p.d_op = PARSER_STATE->mkIndexedOp(AntlrInput::tokenText($sym), numerals);
@@ -2279,6 +2298,7 @@ EMP_TOK : { PARSER_STATE->isTheoryEnabled(theory::THEORY_SEP) }? 'emp';
 CHAR_TOK : { PARSER_STATE->isTheoryEnabled(theory::THEORY_STRINGS) }? 'char';
 TUPLE_CONST_TOK: { PARSER_STATE->isTheoryEnabled(theory::THEORY_DATATYPES) }? 'mkTuple';
 TUPLE_SEL_TOK: { PARSER_STATE->isTheoryEnabled(theory::THEORY_DATATYPES) }? 'tupSel';
+TUPLE_PROJECT_TOK: { PARSER_STATE->isTheoryEnabled(theory::THEORY_DATATYPES) }? 'tuple_project';
 
 HO_ARROW_TOK : { PARSER_STATE->isHoEnabled() }? '->';
 HO_LAMBDA_TOK : { PARSER_STATE->isHoEnabled() }? 'lambda';
index 4d75a982b6ecad61dc4d615c59846b256531db93..049bf8b4d090b00fd8a216cb896800703708225f 100644 (file)
@@ -1097,6 +1097,12 @@ api::Term Smt2::applyParseOp(ParseOp& p, std::vector<api::Term>& args)
     Debug("parser") << "applyParseOp: return selector " << ret << std::endl;
     return ret;
   }
+  else if (p.d_kind == api::TUPLE_PROJECT)
+  {
+    api::Term ret = d_solver->mkTerm(p.d_op, args[0]);
+    Debug("parser") << "applyParseOp: return projection " << ret << std::endl;
+    return ret;
+  }
   else if (p.d_kind != api::NULL_EXPR)
   {
     // it should not have an expression or type specified at this point
index fd9d20e4bcbe71a0e7d29e833da4bcf9f91c799a..8c122f26d098d94efeb5d1da5367ee6fac80a355 100644 (file)
@@ -903,6 +903,21 @@ void Smt2Printer::toStream(std::ostream& out,
     }
     break;
   }
+  case kind::TUPLE_PROJECT:
+  {
+    TupleProjectOp op = n.getOperator().getConst<TupleProjectOp>();
+    if (op.getIndices().empty())
+    {
+      // e.g. (tuple_project tuple)
+      out << "project " << n[0] << ")";
+    }
+    else
+    {
+      // e.g. ((_ tuple_project 2 4 4) tuple)
+      out << "(_ tuple_project" << op << ") " << n[0] << ")";
+    }
+    return;
+  }
   case kind::CONSTRUCTOR_TYPE:
   {
     out << n[n.getNumChildren()-1];
index a53000651e117e63c51daec8e7b9c4cca0ba1999..36bd3f5dfec1068c532326bb5b9cebb0eea1f641 100644 (file)
@@ -34,7 +34,7 @@ class TypeNode;
 class MakeBagOp
 {
  public:
-  MakeBagOp(const TypeNode& elementType);
+  explicit MakeBagOp(const TypeNode& elementType);
   MakeBagOp(const MakeBagOp& op);
 
   /** return the type of the current object */
@@ -43,7 +43,6 @@ class MakeBagOp
   bool operator==(const MakeBagOp& op) const;
 
  private:
-  MakeBagOp();
   /** a pointer to the type of the bag element */
   std::unique_ptr<TypeNode> d_type;
 }; /* class MakeBagOp */
index 5334e76dfeead155c9bbaedfc397b80e5e51cdbd..4bf745f160d33cd8e7ac370b5fb07eb3942f8305 100644 (file)
@@ -224,6 +224,41 @@ RewriteResponse DatatypesRewriter::postRewrite(TNode in)
         << "Rewrite match: " << in << " ... " << ret << std::endl;
     return RewriteResponse(REWRITE_AGAIN_FULL, ret);
   }
+  else if (kind == TUPLE_PROJECT)
+  {
+    // returns a tuple that represents
+    // (mkTuple ((_ tupSel i_1) t) ... ((_ tupSel i_n) t))
+    // where each i_j is less than the length of t
+
+    Trace("dt-rewrite-project") << "Rewrite project: " << in << std::endl;
+    TupleProjectOp op = in.getOperator().getConst<TupleProjectOp>();
+    std::vector<uint32_t> indices = op.getIndices();
+    Node tuple = in[0];
+    std::vector<TypeNode> tupleTypes = tuple.getType().getTupleTypes();
+    std::vector<TypeNode> types;
+    std::vector<Node> elements;
+    for (uint32_t index : indices)
+    {
+      TypeNode type = tupleTypes[index];
+      types.push_back(type);
+    }
+    TypeNode projectType = nm->mkTupleType(types);
+    const DType& dt = projectType.getDType();
+    elements.push_back(dt[0].getConstructor());
+    const DType& tupleDType = tuple.getType().getDType();
+    const DTypeConstructor& constructor = tupleDType[0];
+    for (uint32_t index : indices)
+    {
+      Node selector = constructor[index].getSelector();
+      Node element = nm->mkNode(kind::APPLY_SELECTOR, selector, tuple);
+      elements.push_back(element);
+    }
+    Node ret = nm->mkNode(kind::APPLY_CONSTRUCTOR, elements);
+
+    Trace("dt-rewrite-project")
+        << "Rewrite project: " << in << " ... " << ret << std::endl;
+    return RewriteResponse(REWRITE_AGAIN_FULL, ret);
+  }
 
   if (kind == kind::EQUAL)
   {
index ef8dd47f1e4661f41fbc455ca1bd6f986d20d774..f3e5be5c7ca4a6100ff0ef1b0fa8d60bd64b6d41 100644 (file)
@@ -138,4 +138,17 @@ typerule MATCH ::CVC4::theory::datatypes::MatchTypeRule
 typerule MATCH_CASE ::CVC4::theory::datatypes::MatchCaseTypeRule
 typerule MATCH_BIND_CASE ::CVC4::theory::datatypes::MatchBindCaseTypeRule
 
+
+constant TUPLE_PROJECT_OP \
+       ::CVC4::TupleProjectOp \
+       ::CVC4::TupleProjectOpHashFunction \
+       "theory/datatypes/tuple_project_op.h" \
+       "operator for TUPLE_PROJECT; payload is an instance of the CVC4::TupleProjectOp class"
+
+parameterized TUPLE_PROJECT TUPLE_PROJECT_OP 1 \
+    "projects a tuple from an existing tuple using indices passed in TupleProjectOp"
+
+typerule TUPLE_PROJECT_OP  "SimpleTypeRule<RBuiltinOperator>"
+typerule TUPLE_PROJECT     ::CVC4::theory::datatypes::TupleProjectTypeRule
+
 endtheory
index 73aef4dd79819b6b9105a8b60103f2e17acedca0..d7fb984e0b7b4753a80071e1b844596a07fa2243 100644 (file)
@@ -572,6 +572,60 @@ class MatchBindCaseTypeRule
   }
 }; /* class MatchBindCaseTypeRule */
 
+class TupleProjectTypeRule
+{
+ public:
+  static TypeNode computeType(NodeManager* nm, TNode n, bool check)
+  {
+    Assert(n.getKind() == kind::TUPLE_PROJECT && n.hasOperator()
+           && n.getOperator().getKind() == kind::TUPLE_PROJECT_OP);
+    TupleProjectOp op = n.getOperator().getConst<TupleProjectOp>();
+    const std::vector<uint32_t>& indices = op.getIndices();
+    if (check)
+    {
+      if (n.getNumChildren() != 1)
+      {
+        std::stringstream ss;
+        ss << "operands in term " << n << " are " << n.getNumChildren()
+           << ", but TUPLE_PROJECT expects 1 operand.";
+        throw TypeCheckingExceptionPrivate(n, ss.str());
+      }
+      TypeNode tupleType = n[0].getType(check);
+      if (!tupleType.isTuple())
+      {
+        std::stringstream ss;
+        ss << "TUPLE_PROJECT expects a tuple for " << n[0] << ". Found" << tupleType;
+        throw TypeCheckingExceptionPrivate(n, ss.str());
+      }
+
+      // make sure all indices are less than the length of the tuple type
+      DType dType = tupleType.getDType();
+      DTypeConstructor constructor = dType[0];
+      size_t numArgs = constructor.getNumArgs();
+      for (uint32_t index : indices)
+      {
+        std::stringstream ss;
+        if (index >= numArgs)
+        {
+          ss << "Project index " << index << " in term " << n
+             << " is >= " << numArgs << " which is the length of tuple " << n[0]
+             << std::endl;
+          throw TypeCheckingExceptionPrivate(n, ss.str());
+        }
+      }
+    }
+    TypeNode tupleType = n[0].getType(check);
+    std::vector<TypeNode> types;
+    DType dType = tupleType.getDType();
+    DTypeConstructor constructor = dType[0];
+    for (uint32_t index : indices)
+    {
+      types.push_back(constructor.getArgType(index));
+    }
+    return nm->mkTupleType(types);
+  }
+}; /* class TupleProjectTypeRule */
+
 } /* CVC4::theory::datatypes namespace */
 } /* CVC4::theory namespace */
 } /* CVC4 namespace */
diff --git a/src/theory/datatypes/tuple_project_op.cpp b/src/theory/datatypes/tuple_project_op.cpp
new file mode 100644 (file)
index 0000000..d09d433
--- /dev/null
@@ -0,0 +1,54 @@
+/*********************                                                        */
+/*! \file tuple_project_op.cpp
+ ** \verbatim
+ ** Top contributors (to current version):
+ **   Mudathir Mohamed
+ ** This file is part of the CVC4 project.
+ ** Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
+ ** in the top-level source directory and their institutional affiliations.
+ ** All rights reserved.  See the file COPYING in the top-level source
+ ** directory for licensing information.\endverbatim
+ **/
+
+#include "tuple_project_op.h"
+
+#include <iostream>
+
+#include "expr/type_node.h"
+
+namespace CVC4 {
+
+std::ostream& operator<<(std::ostream& out, const TupleProjectOp& op)
+{
+  for (const uint32_t& index : op.getIndices())
+  {
+    out << " " << index;
+  }
+  return out;
+}
+
+size_t TupleProjectOpHashFunction::operator()(const TupleProjectOp& op) const
+{
+  // we expect most tuples to have length < 10.
+  // Therefore we can implement a simple hash function
+  size_t hash = 0;
+  for (uint32_t index : op.getIndices())
+  {
+    hash = hash * 10 + index;
+  }
+  return hash;
+}
+
+TupleProjectOp::TupleProjectOp(std::vector<uint32_t> indices)
+    : d_indices(std::move(indices))
+{
+}
+
+const std::vector<uint32_t>& TupleProjectOp::getIndices() const { return d_indices; }
+
+bool TupleProjectOp::operator==(const TupleProjectOp& op) const
+{
+  return d_indices == op.d_indices;
+}
+
+}  // namespace CVC4
diff --git a/src/theory/datatypes/tuple_project_op.h b/src/theory/datatypes/tuple_project_op.h
new file mode 100644 (file)
index 0000000..045f05c
--- /dev/null
@@ -0,0 +1,58 @@
+/*********************                                                        */
+/*! \file tuple_project_op.h
+ ** \verbatim
+ ** Top contributors (to current version):
+ **   Mudathir Mohamed
+ ** This file is part of the CVC4 project.
+ ** Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
+ ** in the top-level source directory and their institutional affiliations.
+ ** All rights reserved.  See the file COPYING in the top-level source
+ ** directory for licensing information.\endverbatim
+ **
+ ** \brief a class for TupleProjectOp operator
+ **/
+
+#include "cvc4_public.h"
+
+#ifndef CVC4__PROJECT_OP_H
+#define CVC4__PROJECT_OP_H
+
+#include <ostream>
+#include <vector>
+
+namespace CVC4 {
+
+class TypeNode;
+
+/**
+ * The class is an operator for kind project used to project elements in a tuple
+ * It stores the indices of projected elements
+ */
+class TupleProjectOp
+{
+ public:
+  explicit TupleProjectOp(std::vector<uint32_t> indices);
+  TupleProjectOp(const TupleProjectOp& op) = default;
+
+  /** return the indices of the projection */
+  const std::vector<uint32_t>& getIndices() const;
+
+  bool operator==(const TupleProjectOp& op) const;
+
+ private:
+  std::vector<uint32_t> d_indices;
+}; /* class TupleProjectOp */
+
+std::ostream& operator<<(std::ostream& out, const TupleProjectOp& op);
+
+/**
+ * Hash function for the TupleProjectOpHashFunction objects.
+ */
+struct CVC4_PUBLIC TupleProjectOpHashFunction
+{
+  size_t operator()(const TupleProjectOp& op) const;
+}; /* struct TupleProjectOpHashFunction */
+
+}  // namespace CVC4
+
+#endif /* CVC4__PROJECT_OP_H */
index 1a43b0335f19d8f1af1f3888560be33dc14be0c3..8da7cfb05ffb992874b16fe6d1eb12e3b5d3d758 100644 (file)
@@ -1476,6 +1476,7 @@ set(regress_1_tests
   regress1/datatypes/non-simple-rec.smt2
   regress1/datatypes/non-simple-rec-mut-unsat.smt2
   regress1/datatypes/non-simple-rec-param.smt2
+  regress1/datatypes/tuple_projection.smt2
   regress1/decision/bug374a.smtv1.smt2
   regress1/decision/error3.smtv1.smt2
   regress1/decision/quant-Arrays_Q1-noinfer.smt2
diff --git a/test/regress/regress1/datatypes/tuple_projection.smt2 b/test/regress/regress1/datatypes/tuple_projection.smt2
new file mode 100644 (file)
index 0000000..bdd9d54
--- /dev/null
@@ -0,0 +1,11 @@
+(set-logic ALL)
+(set-info :status sat)
+(declare-fun t () (Tuple String String String String))
+(declare-fun u () (Tuple String String))
+(declare-fun v () Tuple)
+(declare-fun x () String)
+(assert (= t (mkTuple "a" "b" "c" "d")))
+(assert (= x ((_ tupSel 0) t)))
+(assert (= u ((_ tuple_project 2 3) t)))
+(assert (= v (tuple_project t)))
+(check-sat)
\ No newline at end of file
index 2e285fcc7fca6e3e88835b7a5137a27dbb24da05..528d697ae792cbcfec2edd31ed0f68ebc1fc7a41 100644 (file)
@@ -554,6 +554,10 @@ TEST_F(TestApiBlackSolver, mkOp)
   // mkOp(Kind kind, uint32_t arg1, uint32_t arg2)
   ASSERT_NO_THROW(d_solver.mkOp(BITVECTOR_EXTRACT, 1, 1));
   ASSERT_THROW(d_solver.mkOp(DIVISIBLE, 1, 2), CVC4ApiException);
+
+  // mkOp(Kind kind, std::vector<uint32_t> args)
+  std::vector<uint32_t> args = {1, 2, 2};
+  ASSERT_NO_THROW(d_solver.mkOp(TUPLE_PROJECT, args));
 }
 
 TEST_F(TestApiBlackSolver, mkPi) { ASSERT_NO_THROW(d_solver.mkPi()); }
@@ -975,7 +979,8 @@ TEST_F(TestApiBlackSolver, defineSort)
   Sort arraySort1 = d_solver.mkArraySort(sortVar0, sortVar1);
   // Now create instantiations of the defined sorts
   ASSERT_NO_THROW(arraySort0.substitute(sortVar0, intSort));
-  ASSERT_NO_THROW(arraySort1.substitute({sortVar0, sortVar1}, {intSort, realSort}));
+  ASSERT_NO_THROW(
+      arraySort1.substitute({sortVar0, sortVar1}, {intSort, realSort}));
 }
 
 TEST_F(TestApiBlackSolver, defineFun)
@@ -2292,5 +2297,62 @@ TEST_F(TestApiBlackSolver, getSynthSolutions)
   ASSERT_THROW(slv.getSynthSolutions({x}), CVC4ApiException);
 }
 
+TEST_F(TestApiBlackSolver, tupleProject)
+{
+  std::vector<Sort> sorts = {d_solver.getBooleanSort(),
+                             d_solver.getIntegerSort(),
+                             d_solver.getStringSort(),
+                             d_solver.mkSetSort(d_solver.getStringSort())};
+  std::vector<Term> elements = {
+      d_solver.mkBoolean(true),
+      d_solver.mkInteger(3),
+      d_solver.mkString("C"),
+      d_solver.mkTerm(SINGLETON, d_solver.mkString("Z"))};
+
+  Term tuple = d_solver.mkTuple(sorts, elements);
+
+  std::vector<uint32_t> indices1 = {};
+  std::vector<uint32_t> indices2 = {0};
+  std::vector<uint32_t> indices3 = {0, 1};
+  std::vector<uint32_t> indices4 = {0, 0, 2, 2, 3, 3, 0};
+  std::vector<uint32_t> indices5 = {4};
+  std::vector<uint32_t> indices6 = {0, 4};
+
+  ASSERT_NO_THROW(
+      d_solver.mkTerm(d_solver.mkOp(TUPLE_PROJECT, indices1), tuple));
+  ASSERT_NO_THROW(
+      d_solver.mkTerm(d_solver.mkOp(TUPLE_PROJECT, indices2), tuple));
+  ASSERT_NO_THROW(
+      d_solver.mkTerm(d_solver.mkOp(TUPLE_PROJECT, indices3), tuple));
+  ASSERT_NO_THROW(
+      d_solver.mkTerm(d_solver.mkOp(TUPLE_PROJECT, indices4), tuple));
+
+  ASSERT_THROW(d_solver.mkTerm(d_solver.mkOp(TUPLE_PROJECT, indices5), tuple),
+               CVC4ApiException);
+  ASSERT_THROW(d_solver.mkTerm(d_solver.mkOp(TUPLE_PROJECT, indices6), tuple),
+               CVC4ApiException);
+
+  std::vector<uint32_t> indices = {0, 3, 2, 0, 1, 2};
+
+  Op op = d_solver.mkOp(TUPLE_PROJECT, indices);
+  Term projection = d_solver.mkTerm(op, tuple);
+
+  Datatype datatype = tuple.getSort().getDatatype();
+  DatatypeConstructor constructor = datatype[0];
+
+  for (size_t i = 0; i < indices.size(); i++)
+  {
+    Term selectorTerm = constructor[indices[i]].getSelectorTerm();
+    Term selectedTerm = d_solver.mkTerm(APPLY_SELECTOR, selectorTerm, tuple);
+    Term simplifiedTerm = d_solver.simplify(selectedTerm);
+    ASSERT_EQ(elements[indices[i]], simplifiedTerm);
+  }
+
+  ASSERT_EQ(
+      "((_ tuple_project 0 3 2 0 1 2) (mkTuple true 3 \"C\" (singleton "
+      "\"Z\")))",
+      projection.toString());
+}
+
 }  // namespace test
 }  // namespace CVC4