5. Quantum Intrinsic Operations

In an effort to support low-level quantum programming and build foundational quantum kernels for large-scale applications, CUDA Quantum defines the quantum intrinsic operation as an abstraction for device-specific single-qudit unitary operations. A quantum intrinsic operation is modeled via a standard C++ function with a unique instruction name and general function operands. These operands can model classical rotation parameters or units of quantum information (e.g. the cudaq::qudit). The syntax for these operations is

void INST_NAME( PARAM...?, qudit<N>&...);

where INST_NAME is the name of the instruction, qudit<N>&... indicates one many cudaq::qudit instances, and PARAM...? indicates optional parameters of floating point type (e.g. double, float). All intrinsic operations should start with a base declaration targeting a single cudaq::qudit, and overloads should be provided that take more than one cudaq::qudit instances to model the application of that instruction on all provided cudaq::qudits, e.g. void x(cudaq::qubit&) and x(cudaq::qubit&, cudaq::qubit&, cudaq::qubit&), modeling the NOT operation on a single cudaq::qubit or on multiple cudaq::qubit.

The specific set of quantum intrinsic operations available to the programmer will be platform specific, e.g. the standard Clifford+T gate set on cudaq::qubit versus a continuous variable (photonic) gate set on cudaq::qudit<N>.

Implementations should provide overloads to support broadcasting of an operation across a register of cudaq::qudit, e.g. x(cudaq::qreg<>&) to apply a NOT operation on all cudaq::qubit in the provided cudaq::qreg.

Programmers can further modify quantum intrinsic operations via an extra specified template parameter, and CUDA Quantum leverages this syntax for synthesizing control and adjoint variants of the operation. Here is an example of how one might modify an intrinsic operation for multi-control and adjoint operations.

cudaq::qubit q, r, s;
// Apply T operation
t(q);
// Apply Tdg operation
t<cudaq::adj>(q);
// Apply control hadamard operation
h<cudaq::ctrl>(q,r,s);
// Error, ctrl requires > 1 qubit operands
// h<cudaq::ctrl>(r);

5.1. Operations on cudaq::qubit

The default set of quantum intrinsic operations for the cudaq::qubit type is as follows:

  namespace cudaq {
    struct base;
    struct ctrl;
    struct adj;

    // Single qubit operations, ctrl / adj variants, and broadcasting
    template<typename mod = base, typename... QubitArgs>
    void NAME(QubitArgs&... args) noexcept { ... }

    template<typename mod = base>
    void NAME(const qreg& qr) noexcept { ... }

    template<typename mod = ctrl>
    void NAME(qreg& ctrls, qubit& target) noexcept { ... }

    // Single qubit rotation operations and ctrl / adj variants
    template <typename mod = base, typename ScalarAngle, typename... QubitArgs>
    void ROTATION_NAME(ScalarAngle angle, QubitArgs &...args) noexcept { ... }

    // General swap with control variants
    // must take at least 2 qubits
    template<typename... QubitArgs>
    void swap(QubitArgs&... args) { ... }

    bool MEASURE_OP(qubit &q) noexcept;
    std::vector<bool> MEASURE_OP(qreg &q) noexcept;
    double measure(cudaq::spin_op & term) noexcept { ... }
}

For the default implementation of the cudaq::qubit intrinsic operations, we let NAME be any operation name in the set {x, y, z, h, t, s} and ROTATION_NAME be any operation in {rx, ry, rz, r1 (phase)}. Measurements (MEASURE_OP) can be general qubit measurements in the x, y, or z direction (mx, my, mz). Implementations may provide appropriate function implementations using the above foundational functions to enable other common operations (e.g. cnot -> x<ctrl>).

Control qubits can be specified with positive or negative polarity. By this we mean that a control qubit can specify that a target operation is applied if the control qubit state is a |0> (positive polarity) or |1> (negative polarity). By default all control qubits are assumed to convey positive polarity. The syntax for negating the polarity is the not operator preceeding the control qubit (e.g., x<cudaq::ctrl>(!q, r), for cudaq::qubits q and r). Negating the polarity of control qubits is supported in swap and the gates in sets NAME or ROTATION_NAME. The negate notation is only supported on control qubits and not target qubits. So negating either of the target qubits in the swap operation is not allowed.