Quantum Gates¶
Ket provides a core set of built-in quantum gates, namely the Pauli gates, Hadamard, Phase, and the Rotation gates. These are implemented as functions that take a Quant as input, apply the desired unitary operation, and return the same Quant. Furthermore, any function that directly or indirectly calls these gates, provided it does not allocate or measure a qubit, is also considered a quantum gate. This loose definition allows for a highly expressive approach to creating custom quantum gates.
H(qubits) # Applying a Hadamard gate to every qubit in the Quant
Controlled Gates¶
It is important to note that the built-in standard gates are single-qubit operations. To achieve universal quantum computation, Ket allows any quantum gate to be conditioned on control qubits. This is performed using either the ctrl function or the with control block construct.
The ctrl function takes two positional arguments, the control qubits and the target gate, and returns a controlled version of that gate. For example, to apply a CNOT gate with qubit a as the control and b as the target:
ctrl(a, X)(b) # Controlled Pauli-X gate (CNOT)
Note that while CNOT, SWAP, QFT, and other commonly used multi-qubit gates are readily available within the platform, they can also be constructed manually from foundational built-in gates.
Alternatively, the with control instruction allows controlling entire scopes of quantum operations. For instance, to apply a Fredkin (Controlled-SWAP) gate, swapping qubits a and b based on the state of control qubit c:
with control(c):
SWAP(a, b)
Gate Concatenation and Tensor Products¶
Programmers can construct custom quantum gates by combining existing ones using higher-order functions: cat for sequential concatenation and kron for parallel tensor products. The resulting composite gate accepts a specific number of Quant arguments, which is determined by its constituent operations.
Additionally, Ket natively supports operation broadcasting. Whenever a multi-qubit Quant array is passed to a gate, the operation is automatically applied qubit-wise. For example, while CNOT(a, b) is fundamentally a two-argument gate, passing arrays of equal length for a and b will apply the CNOT operation pairwise across their qubits.
The cat function takes a variable number of gates as arguments and returns a new gate representing their sequential application. Conversely, the kron function takes a list of \(n\) input gates and returns a new combined gate requiring \(n\) arguments (one for each gate’s respective target). These constructs are useful when working with functions that accept gates as arguments, similar to the use of anonymous lambda functions.
To illustrate how these higher-order functions can be combined, consider the construction of a custom gate that prepares two qubits in the Bell state:
bell = cat(kron(H, I), CNOT) # Create a Bell gate
bell(a, b) # Prepare qubits a and b in the Bell state
Inverse Gates¶
Because unitary quantum operations are inherently reversible, Ket allows programmers to easily call the adjoint (inverse) of any gate using the adj function. This function takes a gate \(U\) as input and returns its mathematical inverse \(U^\dagger\). For example, using the available Quantum Fourier Transform (QFT) gate, we can dynamically generate the inverse QFT:
iqft = adj(QFT) # Create the inverse gate
iqft(qubits) # Call the inverse gate
Since every operation is tied to a specific process context, the inverse operation must also be correctly linked to that process. This linkage is achieved automatically via the arguments passed to the inverse gate call; at least one of the arguments must be a Quant so the underlying process can be identified.
Another construct that relies on inverse gates is the with around block, which encapsulates the common circuit pattern \(A B A^\dagger\), where \(A\) and \(B\) are quantum operations. For example, to implement an \(R_{XX}(\theta)\) gate acting on qubits a and b:
def rxx(theta: float, a: Quant, b: Quant):
with around(cat(kron(H, H), CNOT), a, b):
RZ(theta, b)
Here, the composite operation cat(kron(H, H), CNOT) acts as operation \(A\) and is applied to both qubits. This is followed by the RZ gate, which acts as operation \(B\). At the end of the with around block’s scope, the inverse operation (\(A^\dagger\)) is automatically applied to complete the sequence. The quantum circuit representing this rxx gate is:
Visualization produced using the draw function: qulib.draw(rxx, (1, 1), pi).¶
Global Phase¶
It is important to note that not every single-qubit gate can be naively implemented using just the basic built-in rotation gates, particularly when these operations are subjected to control qubits. This limitation arises due to the concept of global phase.
Consider the gate \(\sqrt{X}\) (also known as the SX gate). For a single, isolated qubit, this is equivalent to the rotation gate \(R_X(\pi/2)\). Both operations alter the quantum state identically, differing only by a global phase factor:
However, a crucial difference emerges when these operations are executed in a controlled manner. A global phase, which is physically unobservable on an isolated qubit, becomes an observable relative phase when conditioned on control qubits, altering the quantum circuit’s behavior. If we naively construct a controlled-\(R_X(\pi/2)\) gate and simply append the global phase, it fails to produce the correct controlled-\(\sqrt{X}\) (\(C\sqrt{X}\)) operation:
Therefore, to implement the gate \(C\sqrt{X}\) correctly, the global phase from Eq. (1) must be embedded directly into the gate definition. Ket supports the injection of global phases into custom quantum gates using the global_phase decorator. This allows programmers to construct gates that maintain their behavior under control conditions.
@global_phase(pi / 4)
def my_sx(qubit):
RX(pi / 2, qubit)