3. Quantum Types

The CUDA-Q language extension provides certain fundamental types that are pertinent to quantum-classical computing. These types are provided via defined classical library types in C++ in the cudaq namespace.

3.1. cudaq::qudit<Levels>

[1] The cudaq::qudit models a \(D\)-level unit of quantum information. The state of this system (for \(N\) qudits) can be described by a \(D\)N-dimensional vector in Hilbert space with the absolute square of all elements summing to 1.

[2] The cudaq::qudit encapsulates a unique std::size_t modeling the index of the qudit in the underlying quantum memory space (assuming an infinite register of available qudits).

[4] To adhere to the no-cloning theorem of quantum mechanics, the cudaq::qudit is non-copyable and non-movable. Therefore, all cudaq::qudit instances must be passed by reference, and the no-cloning theorem is satisfied at compile-time.

[5] cudaq::qudit instances can only be allocated within CUDA-Q quantum kernel code and can never be allocated from classical host code.

[6] All instantiated cudaq::qudit instances start in the |0> computational basis state and serve as the primary input argument for quantum intrinsic operations modeling typical logical quantum gate operations.

[7] cudaq::qudit instances can be negated when leveraged as controls in quantum operation invocations. The mechanism for negation should be via overloading of qudit<N>::operator!().

The cudaq::qudit takes on the following structure

template <std::size_t Levels>
class qudit {
  protected:
    const std::size_t idx = 0;
  public:
    qudit();
    qudit(const qudit&) = delete;
    qudit(qudit &&) = delete;
    std::size_t id() const;
    static constexpr std::size_t n_levels();
    qudit<Levels> &operator!();
};

3.2. cudaq::qubit

[1] The specification provides a primitive cudaq::qubit type which models a single quantum bit (\(2\)-level) in the discrete quantum memory space. cudaq::qubit is an alias for cudaq::qudit<2>

namespace cudaq {
  using qubit = qudit<2>;
}
{
  // Allocate a qubit in the |0> state
  cudaq::qubit q;
  // Put the qubit in a superposition of |0> and |1>
  h(q); // cudaq::h == hadamard, ADL leveraged
  printf("ID = %lu\n", q.id()); // prints 0
  cudaq::qubit r;
  printf("ID = %lu\n", r.id()); // prints 1
  // qubit out of scope, implicit deallocation
}
cudaq::qubit q;
printf("ID = %lu\n", q.id()); // prints 0 (previous deallocated)
# Allocate a qubit in the |0> state
q = cudaq.qubit()
# Put the qubit in a superposition of |0> and |1>
h(q)
print("ID = {}".format(q.id())) # prints 0

r = cudaq.qubit()
print("ID = {}", r.id()) # prints 1
# qubits go out of scope, implicit deallocation

3.3. Quantum Containers

[1] CUDA-Q specifies abstractions for dealing with groups of cudaq::qudit instances in the form of familiar C++ containers.

[2] The underlying connectivity of the cudaq::qudit instances stored in these containers is opaque to the programmer and any logical-to-physical program connectivity mapping should be done by compiler implementations.

3.3.1. cudaq::qview<Levels = 2>

[1] cudaq::qview<N> is a non-owning reference to a subset of the discrete quantum memory space, and as such, it is a std::span-like C++ range of cudaq::qudit.

[2] The cudaq::qview does not own its elements and can therefore be passed by value or reference.

[3] The cudaq::qview is templated on the dimensionality of the contained quantum information unit, and defaults to \(2\) for qubit systems.

[4] The cudaq::qview provides an API for individual qubit extraction and sub-register slicing. Programmers can extract the front \(N\) qudits, the back \(N\) qudits, and the inner slice starting at a given index and including user-specified count qudits.

The cudaq::qview should take on the following structure:

namespace cudaq {
  template <std::size_t Levels = 2>
  class qview {
    private:
      std::span<qudit<Levels>> qudits;
    public:
      // Construct a span that refers to the qudits in `other`.
      template <typename R>
      requires(std::ranges::range<R>)
      qview(R&& other);
      qview(const qview& other);

      // Iterator interface.
      auto begin();
      auto end();

      // Returns the qudit at `idx`.
      qudit<Levels>& operator[](const std::size_t idx);

      // Returns the `[0, count)` qudits.
      qview<Levels> front(std::size_t count);
      // Returns the first qudit.
      qudit<Levels>& front();
      // Returns the `[count, size())` qudits.
      qview<Levels> back(std::size_t count);
      // Returns the last qudit.
      qudit<Levels>& back();


      // Returns the `[start, start+count)` qudits.
      qview<Levels>
      slice(std::size_t start, std::size_t count);

      // Returns the number of contained qudits.
      std::size_t size() const;
  };
}

3.3.2. cudaq::qvector<Levels = 2>

[1] cudaq::qvector<Levels> is a container of elements from the discrete quantum memory space - a C++ container of cuda::qudit.

[2] The cudaq::qvector is a dynamically constructed owning container for cuda::qudit (std::vector-like), and since it owns the quantum memory, it cannot be copied or moved.

[3] The cudaq::qvector is templated on the dimensionality of the contained quantum information unit, and defaults to \(2\) for qubit systems.

[4] The cudaq::qvector can only be instantiated within CUDA-Q kernels

[5] All qudits in the cudaq::qvector start in the |0> computational basis state.

[6] The cudaq::qvector provides an API for individual qubit extraction and sub-register slicing. Programmers can extract the front \(N\) qudits, the back \(N\) qudits, and the inner slice starting at a given index and including user-specified count qudits.

The cudaq::qview should take on the following structure:

namespace cudaq {
  template <std::size_t Levels = 2>
  class qvector {
    private:
      std::vector<qudit<Levels>> qudits;

    public:
      // Construct a qreg with `size` qudits in the |0> state.
      qvector(std::size_t size);
      qvector(const qvector&) = delete;

      // Iterator interface.
      auto begin();
      auto end();

      // Returns the qudit at `idx`.
      qudit<Levels>& operator[](const std::size_t idx);

      // Returns the `[0, count)` qudits.
      qview<Levels> front(std::size_t count);
      // Returns the first qudit.
      qudit<Levels>& front();
      // Returns the `[count, size())` qudits.
      qview<Levels> back(std::size_t count);
      // Returns the last qudit.
      qudit<Levels>& back();

      // Returns the `[start, start+count)` qudits.
      qview<Levels>
      slice(std::size_t start, std::size_t count);

      // Returns the `{start, start + stride, ...}` qudits.
      qview<Levels>
      slice(std::size_t start, std::size_t stride, std::size_t end);

      // Returns the number of contained qudits.
      std::size_t size() const;

      // Destroys all contained qudits. Postcondition: `size() == 0`.
      void clear();
  };
}
// Allocate 20 qubits, std::vector-like semantics
cudaq::qvector q(20);
// Get first qubit
auto first = q.front();
// Get first 5 qubits
auto first_5 = q.front(5);
// Get last qubit
auto last = q.back();
// Can loop over qubits with size() method
for (int i = 0; i < q.size(); i++) {
  ... do something with q[i] ...
}
// Range based for loop supported
for (auto & qb : q) {
  ... do something with qb ...
}
# Allocate 20 qubits, vector-like semantics
q = cudaq.qvector(20)
# Get the first qubit
first = q.front()
# Get the first 5 qubits
first_5 = q.front(5)
# Get the last qubit
last = q.back()
# Can loop over qubits with size or len function
for i in range(len(q)):
  .. do something with q[i] ..
# Range based for loop
for qb in q:
  .. do something with qb ..

3.3.3. cudaq::qarray<N, Levels = 2>

[1] cudaq::qarray<N, Levels> (where N is an integral constant) is a statically allocated container (std::array-like). The utility of this type is in the compile-time knowledge of allocated containers of qudits that may directly enable ahead-of-time quantum optimization and synthesis.

[2] The second template parameter defaults to \(2\)-level cudaq::qudit.

[3] The cudaq::qarray owns the quantum memory it contains, and therefore cannot be copied or moved.

[4] The cudaq::qarray can only be instantiated within CUDA-Q kernels

[5] All qudits in the cudaq::qarray start in the |0> computational basis state.

[6] The cudaq::qarray provides an API for individual qubit extraction and sub-register slicing. Programmers can extract the front \(N\) qudits, the back \(N\) qudits, and the inner slice starting at a given index and including user-specified count qudits.

The cudaq::qarray should take on the following structure:

namespace cudaq {
  template <std::size_t N, std::size_t Levels = 2>
  class qarray {
    private:
      std::array<qudit<Levels>, N> qudits;

    public:
      // Construct a qreg with `size` qudits in the |0> state.
      qarray();
      qarray(const qvector&) = delete;
      qarray(qarray &&) = delete;

      qarray& operator=(const qarray &) = delete;

      // Iterator interface.
      auto begin();
      auto end();

      // Returns the qudit at `idx`.
      qudit<Levels>& operator[](const std::size_t idx);

      // Returns the `[0, count)` qudits.
      qview<Levels> front(std::size_t count);
      // Returns the first qudit.
      qudit<Levels>& front();
      // Returns the `[count, size())` qudits.
      qview<Levels> back(std::size_t count);
      // Returns the last qudit.
      qudit<Levels>& back();

      // Returns the `[start, start+count)` qudits.
      qview<Levels>
      slice(std::size_t start, std::size_t count);

      // Returns the `{start, start + stride, ...}` qudits.
      qview<Levels>
      slice(std::size_t start, std::size_t stride, std::size_t end);

      // Returns the number of contained qudits.
      std::size_t size() const;

      // Destroys all contained qudits. Postcondition: `size() == 0`.
      void clear();
  };
}

3.3.4. cudaq::qspan<N, Levels> (Deprecated. Use cudaq::qview<Levels> instead.)

[1] cudaq::qspan is a non-owning reference to a part of the discrete quantum memory space, a std::span-like C++ range of cudaq::qudit (see C++ span). It does not own its elements. It takes a single template parameter indicating the levels for the underlying qudits that it stores. This parameter defaults to 2 for qubits. It takes on the following structure:

namespace cudaq {
  template <std::size_t Levels = 2>
  class qspan {
    private:
      std::span<qudit<Levels>> qubits;
    public:
      // Construct a span that refers to the qudits in `other`.
      qspan(std::ranges::range<qudit<Levels>> auto& other);
      qspan(qspan const& other);

      // Iterator interface.
      auto begin();
      auto end();

      // Returns the qudit at `idx`.
      qudit<Levels>& operator[](const std::size_t idx);

      // Returns the `[0, count)` qudits.
      qspan<Levels> front(std::size_t count);
      // Returns the first qudit.
      qudit<Levels>& front();
      // Returns the `[count, size())` qudits.
      qspan<Levels> back(std::size_t count);
      // Returns the last qudit.
      qudit<Levels>& back();

      // Returns the `[start, start+count)` qudits.
      qspan<Levels>
      slice(std::size_t start, std::size_t count);

      // Returns the number of contained qudits.
      std::size_t size() const;
  };
}

3.3.5. cudaq::qreg<N, Levels> (Deprecated. Use cudaq::qvector<Levels> instead.)

[1] cudaq::qreg<N, Levels> models a register of the discrete quantum memory space - a C++ container of cudaq::qudit. As a container, it owns its elements and their storage. qreg<dyn, Levels> is a dynamically allocated container (std::vector-like, see C++ vector). cudaq::qreg<N, Levels> (where N is an integral constant) is a statically allocated container (std::array-like, see array). Its template parameters default to dynamic allocation and cudaq::qudit<2>.

namespace cudaq {
  template <std::size_t N = dyn, std::size_t Levels = 2>
  class qreg {
    private:
      std::conditional_t<
        N == dyn,
        std::vector<qudit<Levels>>,
        std::array<qudit<Levels>, N>
      > qudits;
    public:
      // Construct a qreg with `size` qudits in the |0> state.
      qreg(std::size_t size) requires (N == dyn);
      qreg(qreg const&) = delete;

      // Iterator interface.
      auto begin();
      auto end();

      // Returns the qudit at `idx`.
      qudit<Levels>& operator[](const std::size_t idx);

      // Returns the `[0, count)` qudits.
      qspan<dyn, Levels> front(std::size_t count);
      // Returns the first qudit.
      qudit<Levels>& front();
      // Returns the `[count, size())` qudits.
      qspan<dyn, Levels> back(std::size_t count);
      // Returns the last qudit.
      qudit<Levels>& back();

      // Returns the `[start, start+count)` qudits.
      qspan<dyn, Levels>
      slice(std::size_t start, std::size_t count);

      // Returns the number of contained qudits.
      std::size_t size() const;

      // Destroys all contained qudits. Postcondition: `size() == 0`.
      void clear();
  };
}

qreg instances can only be instantiated from within quantum kernels, they cannot be instantiated in host code. All qubits in the qreg start in the |0> computational basis state.

// Allocate 20 qubits, std::vector-like semantics
cudaq::qreg q(20);
auto first = q.front();
auto first_5 = q.front(5);
auto last = q.back();
for (int i = 0; i < q.size(); i++) {
  ... do something with q[i] ...
}
for (auto & qb : q) {
  ... do something with qb ...
}

// std::array-like semantics
cudaq::qreg<5> fiveCompileTimeQubits;