Decompositions
The qdecomp.decompositions subpackage contains various decomposition and approximation algorithms for single and two-qubits quantum gates.
The following sections provide an overview of the available decompositions.
SQG Module
This module contains the main function to decompose single qubit gates (SQG) into a sequence of Clifford+T gates up to a given tolerance \(\varepsilon\).
This module combines the functions from the qdecomp.decompositions.rz and qdecomp.decompositions.zyz modules to achieve this goal.
Example
>>> from scipy.stats import unitary_group >>> from qdecomp.decompositions import sqg_decomp # Decompose a random single qubit gate with tolerance 0.001 >>> sqg = unitary_group.rvs(2, random_state=42) >>> sequence, alpha = sqg_decomp(sqg, epsilon=0.001, add_global_phase=True) >>> print(sequence, alpha) sequence : T H S T H S T [...] S H S W W W W alpha : 0.27 # Decompose a random single qubit gate with tolerance 0.001 up to a global phase >>> sqg = unitary_group.rvs(2, random_state=42) >>> sequence, _ = sqg_decomp(sqg, epsilon=0.001, add_global_phase=False) >>> print(sequence) T H S T H S T [...] Z T H Z S H S
- qdecomp.decompositions.sqg.zyz_decomp(U: ndarray[tuple[Any, ...], dtype[_ScalarT]]) tuple[float, ...][source]
Any single qubit gate can be decomposed into a series of three rotations around the Z, Y, and Z axis and a global phase factor:
\[U = e^{i \alpha} R_z(\theta_2) R_y(\theta_1) R_z(\theta_0),\]where \(R_z\) and \(R_y\) are the rotation gates around the Z and Y axes, respectively. This is known as the ZYZ decomposition.
This function performs this decomposition on a given unitary 2 x 2 matrix. It returns the three rotation angles \(\theta_0, \theta_1, \theta_2\) and the phase \(\alpha\). For more details, see [Crooks, 2024].
- Parameters:
U (NDArray) – A 2 x 2 unitary matrix.
- Returns:
(t0, t1, t2, alpha), the three rotation angles (rad) and the global phase (rad)
- Return type:
tuple[float, …]
- Raises:
ValueError – If the input matrix is not 2 x 2.
ValueError – If the input matrix is not unitary.
Examples:
>>> import numpy as np >>> from qdecomp.decompositions import zyz_decomp # Define rotation and phase matrices >>> ry = lambda teta: np.array([[np.cos(teta / 2), -np.sin(teta / 2)], [np.sin(teta / 2), np.cos(teta / 2)]]) >>> rz = lambda teta: np.array([[np.exp(-1.0j * teta / 2), 0], [0, np.exp(1.0j * teta / 2)]]) >>> phase = lambda alpha: np.exp(1.0j * alpha) # Create a unitary matrix U >>> a = complex(1, 1) / np.sqrt(3) >>> b = np.sqrt(complex(1, 0) - np.abs(a) ** 2) # Ensure that U is unitary >>> alpha = np.pi/3 >>> U = np.exp(1.0j * alpha) * np.array([[a, -b.conjugate()], [b, a.conjugate()]]) # Compute the decomposition of U >>> t0, t1, t2, alpha_ = zyz_decomp(U) # Recreate U from the decomposition >>> U_calculated = phase(alpha_) * rz(t2) @ ry(t1) @ rz(t0) # Print the results >>> print("U =\n", U) U = [[-0.21132487+0.78867513j -0.28867513-0.5j ] [ 0.28867513+0.5j 0.78867513+0.21132487j]] >>> print("U_calculated =\n", U_calculated) U_calculated = [[-0.21132487+0.78867513j -0.28867513-0.5j ] [ 0.28867513+0.5j 0.78867513+0.21132487j]] >>> print(f"Error = {np.linalg.norm(U - U_calculated)}") Error = 1.0007415106216802e-16
- qdecomp.decompositions.sqg.rz_decomp(angle: float, epsilon: float, add_global_phase=False) str[source]
This function decomposes a \(R_z\) gate of the form
\[\begin{split}R_z = \begin{pmatrix} e^{-i\theta / 2} & 0 \\ 0 & e^{i\theta / 2} \end{pmatrix},\end{split}\]into a sequence of Clifford+T gates where \(\theta\) is the rotation angle around the Z axis. The decomposition is up to a given tolerance \(\varepsilon\). The algorithm implemented in this function is based on the one presented by Ross and Selinger in [Ross and Selinger, 2014]. Note: when the add_global_phase argument is set to True, the sequence includes global phase gates \(W = e^{i\pi/4}\).
This function uses the
qdecomp.utils.exact_synthesis,qdecomp.utils.grid_problemandqdecomp.utils.diophantinemodules to achieve this goal.- Parameters:
angle (float) – The angle of the RZ gate in radians.
epsilon (float) – The tolerance for the approximation based on the operator norm.
add_global_phase (bool) – If True, adds global phase gates W to the sequence (default: False).
- Returns:
The sequence of Clifford+T gates that approximates the RZ gate.
- Return type:
sequence (str)
Example
>>> from qdecomp.decompositions import rz_decomp >>> from math import pi # Decompose a RZ gate with angle pi/128 and tolerance 0.001 exactly >>> sequence = rz_decomp(epsilon=0.001, angle=pi/128, add_global_phase=True) >>> print(sequence) H S T H S T H [...] Z S W W W W W W # Decompose a RZ gate with angle pi/128 and tolerance 0.001 up to a global phase >>> sequence = rz_decomp(epsilon=0.001, angle=pi/128, add_global_phase=False) >>> print(sequence) H S T H S T H [...] Z S H S T H Z S
- qdecomp.decompositions.sqg.sqg_decomp(sqg: ndarray | QGate, epsilon: float, add_global_phase: bool = False) tuple[str, float][source]
Decomposes any single qubit gate (SQG) into its optimal sequence of Clifford+T gates up to a given error.
- Parameters:
sqg (Union[np.ndarray, QGate]) – The matrix representation of the single qubit gate to decompose.
epsilon (float) – The error tolerance for the decomposition.
add_global_phase (bool) – If True, adds the global phase to the sequence and return statements (default: False).
- Returns:
A tuple containing the sequence of gates that approximates the input SQG and the global phase alpha associated with the zyz decomposition of the gate. (if add_global_phase is False, alpha is set to 0 and the sequence doesn’t contain any global phase gate W).
- Return type:
tuple(str, float)
- Raises:
ValueError – If the input is a QGate object with no epsilon value set.
ValueError – If the input is not a QGate object or a 2x2 matrix.
TQG Module
This module contains functions to decompose general 2-qubits quantum gates into single-qubit and CNOT gates.
The module contains the following functions:
kronecker_decomp(): Decompose a 4 x 4 matrix into two 2 x 2 matrices such that their Kronecker product is the closest to the original matrix.so4_decomp(): Decompose a 4 x 4 matrix in SO(4) into a circuit of 2 CNOT gates and 8 single-qubit gates.o4_det_minus1_decomp(): Decompose a 4 x 4 matrix in O(4) with a determinant of -1 into a circuit of 3 CNOT gates and 8 single-qubit gates.canonical_decomp(): Decompose a 4 x 4 unitary matrix into a global phase, two local 4 x 4 matrices, and the three parameters of the canonical gate.u4_decomp(): Decompose a 4 x 4 matrix in U(4) into a circuit of 3 CNOT and 7 single-qubit gates.known_decomp(): Decompose a 4 x 4 matrix into a circuit of CNOT and single-qubit gates using predefined decompositions for common gates.cnot_decomp(): Decompose any two-qubits gate into a circuit of CNOT and single-qubit gates.sqg_decomp(): Decompose any two-qubits gate into a series of Clifford+T gates.
The function sqg_decomp() is the main function of the module.
It decomposes any 4 x 4 unitary matrix into a circuit of Clifford+T gates by using the cnot_decomp() and sqg_decomp() functions.
The function cnot_decomp is the second most important function of the module.
It decomposes any 4 x 4 unitary matrix into a circuit of CNOT and single-qubit gates.
The function determines which decomposition to use based on the Lie group of the input matrix (SO(4), O(4), U(4)) or uses a predefined decomposition if the gate is common (SWAP, identity, CNOT).
The function returns a list of QGate objects representing the circuit decomposition.
For more details on the theory, see [Crooks, 2024, Loan, 2000, Vatan and Williams, 2004, Zhang et al., 2003].
- qdecomp.decompositions.tqg.kronecker_decomp(matrix: ndarray[tuple[Any, ...], dtype[floating]]) tuple[ndarray[tuple[Any, ...], dtype[floating]], ndarray[tuple[Any, ...], dtype[floating]]][source]
Compute the Kronecker decomposition of a 4 x 4 matrix.
Given a 4 x 4 matrix
M, find the two 2 x 2 matrixAandBsuch that their Kronecker product is the closest to the matrixMin the Frobenius norm [Crooks, 2024, Loan, 2000].- Parameters:
matrix (NDArray[float]) – 4 x 4 matrix.
- Returns:
The two 2 x 2 matrix of the decomposition.
- Return type:
tuple[NDArray[float], NDArray[float]]
- Raises:
TypeError – If matrix is not a numpy array.
ValueError – If matrix is not a 4 x 4 matrix.
Example:
>>> import numpy as np >>> from qdecomp.decompositions import kronecker_decomp # Define two 2 x 2 matrices >>> A = np.array([[1, 2], [3, 4]]) >>> B = np.array([[5, 6], [7, 8]]) # Compute the Kronecker decomposition >>> a, b = kronecker_decomp(np.kron(A, B)) >>> print(np.allclose(np.kron(A, B), np.kron(a, b))) True
- qdecomp.decompositions.tqg.so4_decomp(U: ndarray[tuple[Any, ...], dtype[floating]] | QGate) list[QGate][source]
Circuit decomposition of SO(4) matrices.
Decompose a 4 x 4 matrix in SO(4) (special orthogonal group) into a circuit of 2 CNOT gates and 8 single-qubit gates [Crooks, 2024, Vatan and Williams, 2004]. The output is a list of QGate objects.
- Parameters:
U (NDArray[float]) – 4 x 4 matrix in SO(4).
- Returns:
Circuit decomposition of the input matrix. The output is a list of QGate objects.
- Return type:
list[QGate]
- Raises:
TypeError – If the input matrix is not a numpy array or a QGate object.
ValueError – If the input matrix is not in SO(4).
- qdecomp.decompositions.tqg.o4_det_minus1_decomp(U: ndarray[tuple[Any, ...], dtype[floating]] | QGate) list[QGate][source]
Circuit decomposition of O(4) matrices with a determinant of -1.
Decompose a 4 x 4 matrix in O(4) (orthogonal group) with a determinant of -1 into a circuit of 3 CNOT and 8 single-qubit gates [Crooks, 2024, Vatan and Williams, 2004]. The output is a list of QGate objects.
- Parameters:
U (NDArray[float]) – 4 x 4 matrix in O(4) with a determinant of -1.
- Returns:
Circuit decomposition of the input matrix. The output is a list of QGate objects.
- Return type:
list[QGate]
- Raises:
TypeError – If the input matrix is not a numpy array or a QGate object.
ValueError – If the input matrix is not in O(4) with a determinant of -1.
- qdecomp.decompositions.tqg.canonical_decomp(U: ndarray[tuple[Any, ...], dtype[floating]]) CanonicalDecomposition[source]
Perform the canonical decomposition of a given 4 x 4 unitary matrix.
Given a 4 x 4 unitary matrix
U, find the phasealpha, the two 4 x 4 local unitariesAandB, and the three parameters of the canonical gate to decompose the input matrixUlike\[U = e^{i \alpha} B \times Can(t_x, t_y, t_z) \times A.\]Can(tx, ty, tz)is the canonical gate defined as\[Can(t_x, t_y, t_z) = exp(-i\frac{\pi}{2} (t_x X\otimes X + t_y Y\otimes Y + t_z Z\otimes Z)),\]where X, Y, and Z are the Pauli matrices [Crooks, 2024, Zhang et al., 2003].
- Parameters:
U (NDArray[float]) – 4 x 4 unitary matrix.
- Returns:
- A namedtuple with the following attributes:
A (NDArray[float]): 4 x 4 matrix A of the decomposition. A is the Kronecker product of two 2 x 2 matrices.
B (NDArray[float]): 4 x 4 matrix B of the decomposition. B is the Kronecker product of two 2 x 2 matrices.
t (tuple[float, float, float]): The three coordinates (tx, ty, tz) of the canonical gate.
phase (float): Phase of the unitary matrix.
- Return type:
CanonicalDecomposition
- Raises:
TypeError – If the matrix U is not a numpy object.
ValueError – If U is not a 4 x 4 unitary matrix.
Example:
>>> from scipy.stats import unitary_group >>> import numpy as np >>> from qdecomp.decompositions import canonical_decomp >>> from qdecomp.utils import gates # Define a 4 x 4 unitary matrix >>> U = unitary_group.rvs(4) # Perform the canonical decomposition and reconstruct the matrix >>> decomp = canonical_decomp(U) >>> reconstructed_matrix = np.exp(1.j * decomp.phase) * decomp.B @ gates.canonical_gate(*decomp.t) @ decomp.A # Check if the decomposition is correct >>> print(np.allclose(U, reconstructed_matrix)) True
- qdecomp.decompositions.tqg.u4_decomp(U: ndarray[tuple[Any, ...], dtype[floating]] | QGate) list[QGate][source]
Circuit decomposition of U(4) matrices.
Decompose a 4 x 4 matrix in U(4) (unitary group) into a circuit of 3 CNOT a 7 single-qubit gates [Crooks, 2024, Vatan and Williams, 2004]. The output is a list of QGate objects.
- Parameters:
U (NDArray[float]) – 4 x 4 matrix in U(4).
- Returns:
Circuit decomposition of the input matrix. The output is a list of QGate objects.
- Return type:
list[QGate]
- Raises:
TypeError – If the input matrix is not a numpy array or a QGate object.
ValueError – If the input matrix is not in U(4).
- qdecomp.decompositions.tqg.cnot_decomp(U: ndarray[tuple[Any, ...], dtype[floating]]) list[QGate][source]
Circuit decomposition of 4 x 4 quantum gates.
Decompose any two-qubits gate into a circuit of CNOT and single-qubit gates. The function determines which decomposition to use based on the Lie group of the input matrix (SO(4), O(4), U(4)) or uses a predefined decomposition if the gate is common (SWAP, identity, CNOT, etc.). The output is a list of QGate objects.
- Parameters:
U (NDArray[float]) – 4 x 4 unitary matrix.
- Returns:
Circuit decomposition of the input matrix. The output is a list of QGate objects.
- Return type:
list[QGate]
- Raises:
TypeError – If the input matrix is not a numpy array or a QGate object.
ValueError – If the input matrix is not a 4 x 4 unitary matrix.
Example:
>>> from qdecomp.decompositions import cnot_decomp >>> from scipy.stats import unitary_group # Use an arbitrary 4 x 4 unitary matrix >>> U = unitary_group.rvs(4) # Decompose the matrix into a circuit of CNOT and single-qubit gates >>> circuit = cnot_decomp(U) >>> for gate in circuit: ... print(f"{gate.target} -> {gate.name}") (0,) -> A1 (1,) -> A2 (0, 1) -> CNOT1 (0,) -> PZ (1,) -> PY (0, 1) -> CNOT (1,) -> PY (0, 1) -> CNOT1 (0,) -> B1 (1,) -> B2
- qdecomp.decompositions.tqg.known_decomp(U: ndarray[tuple[Any, ...], dtype[floating]] | QGate) list[QGate] | None[source]
Circuit decompositions of common 4 x 4 matrices.
Decompose a 4 x 4 matrix into a circuit of CNOT and single-qubit gates using predefined decompositions for common gates (SWAP, identity, CNOT, etc.). The output is a list of QGate objects. If the decomposition is not known, the function returns None.
- Parameters:
U (NDArray[float]) – 4 x 4 matrix in U(4).
- Returns:
Circuit decomposition of the input matrix. The output is a list of QGate objects. Return None if the decomposition is not known.
- Return type:
(list[QGate] | None)
- Raises:
TypeError – If the input matrix is not a numpy array or a QGate object.
ValueError – If the input matrix is not in U(4).
- qdecomp.decompositions.tqg.tqg_decomp(tqg: ndarray | QGate, epsilon: float = 0.01) list[QGate][source]
This function decomposes a two-qubit gate (TQG) into a sequence of CNOT and single qubit gates up to a given tolerance \(\varepsilon\). It uses and combines the
qdecomp.decompositions.sqgandqdecomp.decompositions.cnotdecomposition algorithms to achieve this goal.- Parameters:
tqg (Union[np.array, QGate]) – The matrix representation of the two-qubit gate to decompose.
epsilon (float) – The tolerance for the decomposition (default: 0.01).
- Returns:
A list of QGate objects representing the decomposed gates with their sequences defined.
- Return type:
list[QGate]
- Raises:
TypeError – If the input is not a numpy array or QGate object.
ValueError – If the input is not a 4x4 matrix or QGate object with a 4x4 matrix.
Example
>>> from scipy.stats import unitary_group >>> from qdecomp.decompositions import tqg_decomp # Decompose a random two qubit gate with tolerance 0.001 >>> tqg = unitary_group.rvs(4, random_state=42) >>> circuit = tqg_decomp(tqg, epsilon=0.001) >>> for gates in circuit: ... print(f"{gates.target} -> {gates.sequence}") (0,) -> S T H T [...] H Z S T (1,) -> S T H T [...] S H S T (0, 1) -> CNOT1 ... (1,) -> H T H S [...] T H Z S
Circuit Module
This module implements a helper function, simplifying the process of decomposing large circuits which contain SQG and TQG.
It uses the qdecomp.decompositions.tqg module to decompose each gate in the circuit.
- qdecomp.decompositions.circuit.circuit_decomp(circuit: Iterable[QGate]) list[QGate][source]
Decompose a quantum circuit into the Clifford+T gate set using
QGateobjects.- Parameters:
circuit (Iterable[QGate]) – An iterable of QGate objects representing the quantum circuit to decompose.
- Returns:
A list of QGate objects representing the decomposed gates in the Clifford+T gate set.
- Return type:
list[QGate]
- Raises:
TypeError – If the input circuit is not a list or contains non-QGate objects.
ValueError – If a gate in the circuit has an unsupported number of qubits (not 1 or 2).
References
G. E. Crooks. Quantum Gates. 2024. URL: https://threeplusone.com/pubs/on_gates.pdf.
Charles F.Van Loan. The ubiquitous kronecker product. Journal of Computational and Applied Mathematics, 123(1):85–100, 2000. Numerical Analysis 2000. Vol. III: Linear Algebra. URL: https://www.sciencedirect.com/science/article/pii/S0377042700003939, doi:https://doi.org/10.1016/S0377-0427(00)00393-9.
N. J. Ross and P. Selinger. Optimal ancilla-free clifford+t approximation of z-rotations. Quantum Information & Computation, 2014. URL: https://arxiv.org/abs/1403.2975.
Farrokh Vatan and Colin Williams. Optimal quantum circuits for general two-qubit gates. Physical Review A, march 2004. URL: http://dx.doi.org/10.1103/PhysRevA.69.032315, doi:10.1103/physreva.69.032315.
Jun Zhang, Jiri Vala, Shankar Sastry, and K. Birgitta Whaley. Geometric theory of nonlocal two-qubit operations. Physical Review A, april 2003. URL: http://dx.doi.org/10.1103/PhysRevA.67.042313, doi:10.1103/physreva.67.042313.