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;