.. _sec_gates: ============= 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 :class:`~ket.base.Quant` as input, apply the desired unitary operation, and return the same :class:`~ket.base.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. .. code-block:: python 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 :func:`~ket.operations.ctrl` function or the ``with`` :func:`~ket.operations.control` block construct. The :func:`~ket.operations.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: .. code-block:: python ctrl(a, X)(b) # Controlled Pauli-X gate (CNOT) Note that while :func:`~ket.gates.CNOT`, :func:`~ket.gates.SWAP`, :func:`~ket.gates.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`` :func:`~ket.operations.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``: .. code-block:: python 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: :func:`~ket.operations.cat` for sequential concatenation and :func:`~ket.operations.kron` for parallel tensor products. The resulting composite gate accepts a specific number of :class:`~ket.base.Quant` arguments, which is determined by its constituent operations. Additionally, Ket natively supports operation broadcasting. Whenever a multi-qubit :class:`~ket.base.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 :func:`~ket.operations.cat` function takes a variable number of gates as arguments and returns a new gate representing their sequential application. Conversely, the :func:`~ket.operations.kron` function takes a list of :math:`n` input gates and returns a new combined gate requiring :math:`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: .. code-block:: python 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 :func:`~ket.operations.adj` function. This function takes a gate :math:`U` as input and returns its mathematical inverse :math:`U^\dagger`. For example, using the available Quantum Fourier Transform (:func:`~ket.gates.QFT`) gate, we can dynamically generate the inverse QFT: .. code-block:: python 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 :class:`~ket.base.Quant` so the underlying process can be identified. Another construct that relies on inverse gates is the ``with`` :func:`~ket.operations.around` block, which encapsulates the common circuit pattern :math:`A B A^\dagger`, where :math:`A` and :math:`B` are quantum operations. For example, to implement an :math:`R_{XX}(\theta)` gate acting on qubits ``a`` and ``b``: .. code-block:: python 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 :math:`A` and is applied to both qubits. This is followed by the :func:`~ket.gates.RZ` gate, which acts as operation :math:`B`. At the end of the ``with`` :func:`~ket.operations.around` block's scope, the inverse operation (:math:`A^\dagger`) is automatically applied to complete the sequence. The quantum circuit representing this ``rxx`` gate is: .. figure:: rxx.svg :align: center :width: 45% Visualization produced using the :func:`~ket.qulib.draw` function: ``qulib.draw(rxx, (1, 1), pi)``. .. _subsec_global_phase: ------------ 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 :math:`\sqrt{X}` (also known as the SX gate). For a single, isolated qubit, this is equivalent to the rotation gate :math:`R_X(\pi/2)`. Both operations alter the quantum state identically, differing only by a global phase factor: .. math:: :label: eq_rx_phase \underbrace{\frac{1}{\sqrt{2}} \begin{bmatrix} 1 & -i \\ -i & 1 \end{bmatrix}}_{R_X(\pi/2)} \times \underbrace{ e^{i\tfrac{\pi}{4}}}_{\text{Global Phase}} = \frac{1}{2} \begin{bmatrix} 1+i & 1-i \\ 1-i & 1+i \end{bmatrix} = \sqrt{X}. 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-:math:`R_X(\pi/2)` gate and simply append the global phase, it fails to produce the correct controlled-:math:`\sqrt{X}` (:math:`C\sqrt{X}`) operation: .. math:: \underbrace{\frac{1}{\sqrt{2}} \begin{bmatrix} \sqrt{2} & 0 & 0 & 0 \\ 0 & \sqrt{2} & 0 & 0 \\ 0 & 0 & 1 & -i \\ 0 & 0 & -i & 1 \end{bmatrix}}_{CR_X(\pi/2)} \times \underbrace{ e^{i\tfrac{\pi}{4}}}_{\text{Global Phase}} \not= \frac{1}{2} \begin{bmatrix} 2 & 0 & 0 & 0 \\ 0 & 2 & 0 & 0 \\ 0 & 0 & 1+i & 1-i \\ 0 & 0 & 1-i & 1+i \end{bmatrix} = C\sqrt{X}. Therefore, to implement the gate :math:`C\sqrt{X}` correctly, the global phase from Eq. :eq:`eq_rx_phase` must be embedded directly into the gate definition. Ket supports the injection of global phases into custom quantum gates using the :func:`~ket.gates.global_phase` decorator. This allows programmers to construct gates that maintain their behavior under control conditions. .. code-block:: python @global_phase(pi / 4) def my_sx(qubit): RX(pi / 2, qubit)