From 19344b2e2810fdc5030490dad7cf38204b76e7b6 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 19 May 2025 23:42:33 +0800 Subject: [PATCH 01/33] - Trying an idea by using rounding on M2 (this will be okay given M2 is always going to be for 2q unitaries) - Added windows in to test the idea. --- .github/workflows/tests.yml | 2 +- .../gate_decompositions/two_qubit_decomposition/weyl.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ba5fdaf..8f62b94 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest] + os: [ubuntu-latest, windows-latest] python-version: ["3.10", "3.11", "3.12"] steps: diff --git a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py index 56724eb..18353d8 100644 --- a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py +++ b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py @@ -487,7 +487,7 @@ def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ global_phase = cmath.phase(U_det) / 4 U_magic_basis = transform_to_magic_basis(U.astype(complex), reverse=True) - M2 = U_magic_basis.T.dot(U_magic_basis) + M2 = np.round(U_magic_basis.T.dot(U_magic_basis), 14) # There is a floating point error in this implementation # for certain U, which depends on OS and Python version From 20fb1bf2eddddaecdbd39e2afed7cbb6654611ff Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Tue, 20 May 2025 00:16:16 +0800 Subject: [PATCH 02/33] - Trying an idea for fixing floating point error with uniformly controlled gate (Multiplexor specifically) --- quick/circuit/circuit_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index 46070ce..ccebaab 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -207,6 +207,7 @@ def extract_uvr_matrices( # Eigendecomposition of r @ x @ r (Eq 8) # This is done via reforming Eq 6 to be similar to an eigenvalue decomposition rxr = r @ X @ r + rxr = np.round(rxr, 14) eigenvalues, u = np.linalg.eig(rxr) # Put the eigenvalues into a diagonal form From 5d26bb48ca9ca11a229dfc3bc38c6095225ed54d Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Tue, 20 May 2025 01:05:40 +0800 Subject: [PATCH 03/33] - Trying an idea by rounding v u r only. --- quick/circuit/circuit_utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index ccebaab..01e5ab5 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -207,7 +207,6 @@ def extract_uvr_matrices( # Eigendecomposition of r @ x @ r (Eq 8) # This is done via reforming Eq 6 to be similar to an eigenvalue decomposition rxr = r @ X @ r - rxr = np.round(rxr, 14) eigenvalues, u = np.linalg.eig(rxr) # Put the eigenvalues into a diagonal form @@ -221,6 +220,11 @@ def extract_uvr_matrices( # Calculate v based on the decomposition (Eq 7) v = diagonal @ np.conj(u).T @ np.conj(r).T @ b + # Round the values to avoid floating point errors + v = np.round(v, 15).astype(np.complex128) + u = np.round(u, 15).astype(np.complex128) + r = np.round(r, 15).astype(np.complex128) + return v, u, r def extract_single_qubits_and_diagonal( From 9b0428bd2dd7e28ddb9b454704f0508399c34d16 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Tue, 20 May 2025 01:41:11 +0800 Subject: [PATCH 04/33] - Trying rounding the gates during application in Multiplexor (rounding u v r fixed some issues but broke other cases, so not a viable approach) --- quick/circuit/circuit.py | 2 ++ quick/circuit/circuit_utils.py | 5 ----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index a94be3f..ecd177e 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -4838,6 +4838,8 @@ def Multiplexor( # of the multiplexor with self.controlled_state(control_state, control_indices): for i, gate in enumerate(single_qubit_gates): + gate = np.round(gate, 15) + if i == 0: self.unitary(gate, target_index) self.H(target_index) diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index 01e5ab5..46070ce 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -220,11 +220,6 @@ def extract_uvr_matrices( # Calculate v based on the decomposition (Eq 7) v = diagonal @ np.conj(u).T @ np.conj(r).T @ b - # Round the values to avoid floating point errors - v = np.round(v, 15).astype(np.complex128) - u = np.round(u, 15).astype(np.complex128) - r = np.round(r, 15).astype(np.complex128) - return v, u, r def extract_single_qubits_and_diagonal( From 56add990a1d7d898d790caf350330dfea679b91a Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Sat, 24 May 2025 02:55:16 +0800 Subject: [PATCH 05/33] - Trying an idea to see if the two qubit inconsistency between different os can be fixed via np.complex128 use. - Cleaned up and removed some breaking code from last commit. --- quick/circuit/circuit.py | 34 ++++++++++++------- .../two_qubit_decomposition/weyl.py | 6 ++-- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index ecd177e..6695e2c 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -20,7 +20,7 @@ __all__ = ["Circuit"] from abc import ABC, abstractmethod -from collections.abc import Sequence +from collections.abc import Callable, Sequence from contextlib import contextmanager import copy import cmath @@ -29,7 +29,7 @@ from numpy.typing import NDArray from types import NotImplementedType from typing import ( - Any, Callable, Literal, overload, SupportsFloat, SupportsIndex, TYPE_CHECKING + Any, Literal, overload, SupportsFloat, SupportsIndex, TYPE_CHECKING ) import qiskit # type: ignore @@ -41,7 +41,10 @@ if TYPE_CHECKING: from quick.backend import Backend from quick.circuit.circuit_utils import ( - multiplexed_rz_angles, decompose_multiplexor_rotations, extract_single_qubits_and_diagonal, simplify + multiplexed_rz_angles, + decompose_multiplexor_rotations, + extract_single_qubits_and_diagonal, + simplify ) from quick.circuit.dag import DAGCircuit from quick.circuit.from_framework import FromCirq, FromPennyLane, FromQiskit, FromTKET @@ -53,7 +56,7 @@ UnitaryPreparation, ShannonDecomposition, QiskitUnitaryTranspiler ) -EPSILON = 1e-10 +EPSILON = 1e-15 """ Set the frozensets for the keys to be used: - Decorator `Circuit.gatemethod()` @@ -4793,17 +4796,22 @@ def Multiplexor( raise ValueError(f"The dimension of a gate is not equal to 2x2. Received {gate.shape}.") # Check if number of gates in gate_list is a positive power of two - num_control = np.log2(len(single_qubit_gates)) - if num_control < 0 or not int(num_control) == num_control: + num_controls = int( + np.log2( + len(single_qubit_gates) + ) + ) + + if num_controls < 0 or not int(num_controls) == num_controls: raise ValueError( "The number of single-qubit gates is not a non-negative power of 2." ) - if not num_control == len(control_indices): + if not num_controls == len(control_indices): raise ValueError( "The number of control qubits passed must be equal to the number of gates. " f"Received {len(control_indices)}. " - f"Expected {int(num_control)}." + f"Expected {int(num_controls)}." ) # Check if the single-qubit gates are unitaries @@ -4816,7 +4824,7 @@ def Multiplexor( # If the multiplexor simplification is enabled, we simplify the multiplexor # based on [2] if multiplexor_simplification: - new_controls, single_qubit_gates = simplify(single_qubit_gates, int(num_control)) + new_controls, single_qubit_gates = simplify(single_qubit_gates, num_controls) control_indices = [qubits[len(control_indices) + 1 - i] for i in new_controls] control_indices.reverse() @@ -4833,18 +4841,18 @@ def Multiplexor( len(control_indices) + 1 ) + num_single_qubit_gates = len(single_qubit_gates) + # Now, it is easy to place the CX gates and some Hadamards and RZ(pi/2) gates # which are absorbed into the single-qubit unitaries to get back the full decomposition # of the multiplexor with self.controlled_state(control_state, control_indices): for i, gate in enumerate(single_qubit_gates): - gate = np.round(gate, 15) - if i == 0: self.unitary(gate, target_index) self.H(target_index) - elif i == len(single_qubit_gates) - 1: + elif i == num_single_qubit_gates - 1: self.H(target_index) self.RZ(-PI2, target_index) self.unitary(gate, target_index) @@ -4862,7 +4870,7 @@ def Multiplexor( control_index = num_trailing_zeros # Apply the CX gate - if not i == len(single_qubit_gates) - 1: + if not i == num_single_qubit_gates - 1: self.CX(control_indices[control_index], target_index) self.GlobalPhase(-PI4) diff --git a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py index 18353d8..4696422 100644 --- a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py +++ b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py @@ -481,13 +481,13 @@ def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ >>> a, b, c, K1l, K1r, K2l, K2r, global_phase = TwoQubitWeylDecomposition.decompose_unitary(np.eye(4)) """ # Make U be in SU(4) - U = np.array(unitary_matrix, dtype=complex, copy=True) + U = np.array(unitary_matrix, dtype=np.complex128, copy=True) U_det = scipy.linalg.det(U) U *= U_det ** (-0.25) global_phase = cmath.phase(U_det) / 4 - U_magic_basis = transform_to_magic_basis(U.astype(complex), reverse=True) - M2 = np.round(U_magic_basis.T.dot(U_magic_basis), 14) + U_magic_basis = transform_to_magic_basis(U.astype(np.complex128), reverse=True) + M2 = U_magic_basis.T.dot(U_magic_basis) # There is a floating point error in this implementation # for certain U, which depends on OS and Python version From 6fdbbc0059ea2c528cb7797959e532235c215f6f Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Sat, 24 May 2025 03:15:51 +0800 Subject: [PATCH 06/33] - Printing full output for two qubit case --- .../two_qubit_decomposition/test_two_qubit_decomposition.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py b/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py index 409b2e3..84a9e0d 100644 --- a/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py +++ b/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py @@ -144,6 +144,11 @@ def test_decomp1(self) -> None: # Apply the decomposition two_qubit_decomposition.apply_unitary(new_circuit, unitary_matrix, [0, 1]) + print("Unitary matrix:") + print(unitary_matrix) + print("New circuit unitary:") + print(new_circuit.get_unitary()) + # Check that the circuit is equivalent to the original unitary matrix assert_almost_equal(new_circuit.get_unitary(), unitary_matrix, decimal=8) From cf1c9378204bfe6441c73c213828beac1778cce7 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Sat, 24 May 2025 03:41:14 +0800 Subject: [PATCH 07/33] - Testing to see if the phase difference seen in previous push is due to U's symmetry or not. --- .../two_qubit_decomposition/test_two_qubit_decomposition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py b/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py index 84a9e0d..0d0ee21 100644 --- a/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py +++ b/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py @@ -120,7 +120,7 @@ def test_decomp1(self) -> None: circuit = QiskitCircuit(2) # Create a GHZ state (one CX gate) - circuit.H(0) + circuit.U3([0.1, 0.2, 0.3], 0) circuit.CX(0, 1) # Extract the unitary matrix From 796c9b2d7ca0e386ad5c7b8730f65a5ca904d3e2 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 26 May 2025 01:59:12 +0800 Subject: [PATCH 08/33] - Trying an idea for fixing the two-qubit issue (Turns out it's not due to eig, as rounding essentially makes Linux have the same eig result as Windows, and it still passes which means issue is likely from somewhere else) - Changed `complex` dtype to `np.complex128` for better consistency. --- .../two_qubit_decomposition.py | 38 +++++++++---------- .../two_qubit_decomposition/weyl.py | 37 ++++++------------ .../test_two_qubit_decomposition.py | 7 +--- 3 files changed, 31 insertions(+), 51 deletions(-) diff --git a/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.py b/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.py index 7a351bd..e7f6d6d 100644 --- a/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.py +++ b/quick/synthesis/gate_decompositions/two_qubit_decomposition/two_qubit_decomposition.py @@ -46,97 +46,97 @@ Q0L = np.array([ [0.5+0.5j, 0.5-0.5j], [-0.5-0.5j, 0.5-0.5j] -], dtype=complex) +], dtype=np.complex128) Q0R = np.array([ [-0.5-0.5j, 0.5-0.5j], [-0.5-0.5j, -0.5+0.5j] -], dtype=complex) +], dtype=np.complex128) Q1LA = np.array([ [0.+0.j, -1-1j], [1-1j, 0.+0.j] -], dtype=complex) * SQRT2 +], dtype=np.complex128) * SQRT2 Q1LB = np.array([ [-0.5+0.5j, -0.5-0.5j], [0.5-0.5j, -0.5-0.5j] -], dtype=complex) +], dtype=np.complex128) Q1RA = np.array([ [1+0.j, 1+0.j], [-1+0.j, 1+0.j] -], dtype=complex) * SQRT2 +], dtype=np.complex128) * SQRT2 Q1RB = np.array([ [0.5-0.5j, 0.5+0.5j], [-0.5+0.5j, 0.5+0.5j] -], dtype=complex) +], dtype=np.complex128) Q2L = np.array([ [-1+1j, 0.+0.j], [0.+0.j, -1-1j] -], dtype=complex) * SQRT2 +], dtype=np.complex128) * SQRT2 Q2R = np.array([ [0.+1j, 0.-1j], [0.-1j, 0.-1j] -], dtype=complex) * SQRT2 +], dtype=np.complex128) * SQRT2 U0L: NDArray[np.complex128] = np.array([ [-1, 1], [-1, -1] -], dtype=complex) * SQRT2 +], dtype=np.complex128) * SQRT2 U0R: NDArray[np.complex128] = np.array([ [-1j, 1j], [1j, 1j] -], dtype=complex) * SQRT2 +], dtype=np.complex128) * SQRT2 U1L: NDArray[np.complex128] = np.array([ [-0.5+0.5j, -0.5+0.5j], [0.5+0.5j, -0.5-0.5j] -], dtype=complex) +], dtype=np.complex128) U1RA: NDArray[np.complex128] = np.array([ [0.5-0.5j, -0.5-0.5j], [0.5-0.5j, 0.5+0.5j] -], dtype=complex) +], dtype=np.complex128) UR1B: NDArray[np.complex128] = np.array([ [-1, -1j], [-1j, -1] -], dtype=complex) * SQRT2 +], dtype=np.complex128) * SQRT2 u2la: NDArray[np.complex128] = np.array([ [0.5+0.5j, 0.5-0.5j], [-0.5-0.5j, 0.5-0.5j] -], dtype=complex) +], dtype=np.complex128) U2LB: NDArray[np.complex128] = np.array([ [-0.5+0.5j, -0.5-0.5j], [0.5-0.5j, -0.5-0.5j] -], dtype=complex) +], dtype=np.complex128) U2RA: NDArray[np.complex128] = np.array([ [-0.5+0.5j, 0.5-0.5j], [-0.5-0.5j, -0.5-0.5j] -], dtype=complex) +], dtype=np.complex128) U2RB: NDArray[np.complex128] = np.array([ [0.5-0.5j, 0.5+0.5j], [-0.5+0.5j, 0.5+0.5j] -], dtype=complex) +], dtype=np.complex128) U3L: NDArray[np.complex128] = np.array([ [-1+1j, 0+0j], [0+0j, -1-1j] -], dtype=complex) * SQRT2 +], dtype=np.complex128) * SQRT2 U3R: NDArray[np.complex128] = np.array([ [1j, -1j], [-1j, -1j] -], dtype=complex) * SQRT2 +], dtype=np.complex128) * SQRT2 class TwoQubitDecomposition(UnitaryPreparation): diff --git a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py index 4696422..40438a0 100644 --- a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py +++ b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py @@ -56,7 +56,7 @@ [0, 0, 1j, 1], [0, 0, 1j, -1], [1, -1j, 0, 0] -], dtype=complex) +], dtype=np.complex128) M_UNNORMALIZED_DAGGER = 0.5 * M_UNNORMALIZED.conj().T @@ -64,17 +64,17 @@ X_MAGIC_BASIS = np.array([ [0, 1j], [1j, 0] -], dtype=complex) +], dtype=np.complex128) Y_MAGIC_BASIS = np.array([ [0, 1], [-1, 0] -], dtype=complex) +], dtype=np.complex128) Z_MAGIC_BASIS = np.array([ [1j, 0], [0, -1j] -], dtype=complex) +], dtype=np.complex128) # Constants PI = np.pi @@ -283,7 +283,7 @@ def diagonalize_unitary_complex_symmetric( # If there are no degenerate subspaces, we return the eigenvalues and identity matrix # as the eigenvectors if len(spaces) == 1: - return eigenvalues, np.eye(4).astype(complex) # type: ignore + return eigenvalues, np.eye(4).astype(np.complex128) # type: ignore out_vectors = np.empty((4, 4), dtype=np.float64) n_done = 0 @@ -298,17 +298,17 @@ def diagonalize_unitary_complex_symmetric( # This is the hardest case, because there might not have even one real vector a, b = eigenvectors[:, spaces[0]].T b_zeros = np.abs(b) <= atol + if np.any(np.abs(a[b_zeros]) > atol): # Make `a` real where `b` has zeros. a = remove_global_phase(a, index=np.argmax(np.where(b_zeros, np.abs(a), 0))) - if np.max(np.abs(a.imag)) <= atol: - # `a` is already all real - pass - else: + + if np.max(np.abs(a.imag)) > atol: # We have to solve `(b.imag, b.real) @ (re, im).T = a.imag` for `re` # and `im`, which is overspecified multiplier, *_ = scipy.linalg.lstsq(np.transpose([b.imag, b.real]), a.imag) # type: ignore a = a - complex(*multiplier) * b + a = a.real / scipy.linalg.norm(a.real) b = remove_global_phase(b - (a @ b) * a) out_vectors[:, :2] = np.transpose([a, b.real]) @@ -362,8 +362,6 @@ def decompose_two_qubit_product_gate( ----- >>> L, R, phase = decompose_two_qubit_product_gate(np.eye(4)) """ - special_unitary_matrix = np.asarray(special_unitary_matrix, dtype=complex) - # Extract the right component R = special_unitary_matrix[:2, :2].copy() R_det = R[0, 0] * R[1, 1] - R[0, 1] * R[1, 0] @@ -486,21 +484,8 @@ def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ U *= U_det ** (-0.25) global_phase = cmath.phase(U_det) / 4 - U_magic_basis = transform_to_magic_basis(U.astype(np.complex128), reverse=True) - M2 = U_magic_basis.T.dot(U_magic_basis) - - # There is a floating point error in this implementation - # for certain U, which depends on OS and Python version - # This causes the numpy.linalg.eig() to produce different results - # for the same input matrix, leading to a decomposition failure - # To contribute to this issue, please refer to: - # https://github.com/Qualition/quick/issues/11 - - # Alternatively, you may propose an entirely new implementation - # so that we can replace this two qubit decomposition implementation - # with a more robust one that doesn't have floating point errors - # To contribute to this feature request, please refer to: - # https://github.com/Qualition/quick/issues/14 + U_magic_basis = transform_to_magic_basis(U, reverse=True) + M2 = np.round(U_magic_basis.T.dot(U_magic_basis), decimals=15) D, P = diagonalize_unitary_complex_symmetric(M2) d = -np.angle(D) / 2 diff --git a/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py b/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py index 0d0ee21..409b2e3 100644 --- a/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py +++ b/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py @@ -120,7 +120,7 @@ def test_decomp1(self) -> None: circuit = QiskitCircuit(2) # Create a GHZ state (one CX gate) - circuit.U3([0.1, 0.2, 0.3], 0) + circuit.H(0) circuit.CX(0, 1) # Extract the unitary matrix @@ -144,11 +144,6 @@ def test_decomp1(self) -> None: # Apply the decomposition two_qubit_decomposition.apply_unitary(new_circuit, unitary_matrix, [0, 1]) - print("Unitary matrix:") - print(unitary_matrix) - print("New circuit unitary:") - print(new_circuit.get_unitary()) - # Check that the circuit is equivalent to the original unitary matrix assert_almost_equal(new_circuit.get_unitary(), unitary_matrix, decimal=8) From b4a3fc8e23344507a5f7027e69d8a5c459eee6fe Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 26 May 2025 03:01:36 +0800 Subject: [PATCH 09/33] - Checking if the windows non-rounded fail is due to wrong diagonalization. --- .../two_qubit_decomposition/weyl.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py index 40438a0..aefee45 100644 --- a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py +++ b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py @@ -474,6 +474,13 @@ def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ `global_phase` : float The global phase. + Raises + ------ + ValueError + - If the diagonalization of the unitary complex-symmetric matrix fails. + - If the determinant of the right or left component is not in the expected range. + - If the decomposition fails due to a deviation from the expected unitary matrix. + Usage ----- >>> a, b, c, K1l, K1r, K2l, K2r, global_phase = TwoQubitWeylDecomposition.decompose_unitary(np.eye(4)) @@ -486,8 +493,21 @@ def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ U_magic_basis = transform_to_magic_basis(U, reverse=True) M2 = np.round(U_magic_basis.T.dot(U_magic_basis), decimals=15) + M2_windows_fail = U_magic_basis.T.dot(U_magic_basis) D, P = diagonalize_unitary_complex_symmetric(M2) + + if not np.allclose(P.dot(np.diag(D)).dot(P.T), M2, rtol=0, atol=1e-13): + raise ValueError( + "The diagonalization of the unitary complex-symmetric matrix failed. " + "The result is not close enough to the original matrix." + ) + + if not np.allclose(P.dot(np.diag(D)).dot(P.T), M2_windows_fail, rtol=0, atol=1e-13): + raise ValueError( + "Non-rounded failed." + ) + d = -np.angle(D) / 2 d[3] = -d[0] - d[1] - d[2] weyl_coordinates = np.mod((d[:3] + d[3]) / 2, PI_DOUBLE) From 16fe3a3e8f0f7b09fed54ee418c6f0ad40f2c609 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 26 May 2025 03:23:26 +0800 Subject: [PATCH 10/33] - Trying a quick sanity check (P is real-symmetric, so conj shouldn't really matter, but just to check) --- .../gate_decompositions/two_qubit_decomposition/weyl.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py index aefee45..d498847 100644 --- a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py +++ b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py @@ -497,13 +497,15 @@ def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ D, P = diagonalize_unitary_complex_symmetric(M2) - if not np.allclose(P.dot(np.diag(D)).dot(P.T), M2, rtol=0, atol=1e-13): + print(f"Dtype {P.dtype}") + + if not np.allclose(P.dot(np.diag(D)).dot(P.conj().T), M2, rtol=0, atol=1e-13): raise ValueError( "The diagonalization of the unitary complex-symmetric matrix failed. " "The result is not close enough to the original matrix." ) - if not np.allclose(P.dot(np.diag(D)).dot(P.T), M2_windows_fail, rtol=0, atol=1e-13): + if not np.allclose(P.dot(np.diag(D)).dot(P.conj().T), M2_windows_fail, rtol=0, atol=1e-13): raise ValueError( "Non-rounded failed." ) From aa8d8dd48d4e6e5eaf363460b43be1fd3291aa46 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 26 May 2025 17:06:17 +0800 Subject: [PATCH 11/33] - Added print lines to see what is causing the uniformly controlled issue in Windows. - Removed `M2_windows_fail` since it seems diagonalization was not the issue. --- .../two_qubit_decomposition/weyl.py | 18 ++++++---------- .../test_uniformly_controlled_gates.py | 21 ++++++++++++------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py index d498847..a31a5ea 100644 --- a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py +++ b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py @@ -477,7 +477,7 @@ def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ Raises ------ ValueError - - If the diagonalization of the unitary complex-symmetric matrix fails. + - If the diagonalization of M2 fails. - If the determinant of the right or left component is not in the expected range. - If the decomposition fails due to a deviation from the expected unitary matrix. @@ -493,21 +493,15 @@ def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ U_magic_basis = transform_to_magic_basis(U, reverse=True) M2 = np.round(U_magic_basis.T.dot(U_magic_basis), decimals=15) - M2_windows_fail = U_magic_basis.T.dot(U_magic_basis) D, P = diagonalize_unitary_complex_symmetric(M2) - print(f"Dtype {P.dtype}") - - if not np.allclose(P.dot(np.diag(D)).dot(P.conj().T), M2, rtol=0, atol=1e-13): - raise ValueError( - "The diagonalization of the unitary complex-symmetric matrix failed. " - "The result is not close enough to the original matrix." - ) - - if not np.allclose(P.dot(np.diag(D)).dot(P.conj().T), M2_windows_fail, rtol=0, atol=1e-13): + # Given P is a real-symmetric unitary matrix we only use transpose + if not np.allclose(P.dot(np.diag(D)).dot(P.T), M2, rtol=0, atol=1e-13): raise ValueError( - "Non-rounded failed." + "Failed to diagonalize M2." + "Kindly report this at https://github.com/Qualition/quick/issues/11: " + f"U: {U}" ) d = -np.angle(D) / 2 diff --git a/tests/circuit/test_uniformly_controlled_gates.py b/tests/circuit/test_uniformly_controlled_gates.py index 1ab7f08..2ad9ce6 100644 --- a/tests/circuit/test_uniformly_controlled_gates.py +++ b/tests/circuit/test_uniformly_controlled_gates.py @@ -20,7 +20,6 @@ from numpy.testing import assert_almost_equal from numpy.typing import NDArray import pytest -from typing import Type from quick.circuit import Circuit from quick.circuit.gate_matrix import PauliX, PauliY, Hadamard, RX, RY @@ -366,7 +365,7 @@ def test_Multiplexor_no_diagonal_no_simplification( # Ensure the unitary matrix is correct assert_almost_equal(circuit.get_unitary(), expected, 8) - @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + # @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ [ [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], @@ -413,7 +412,6 @@ def test_Multiplexor_no_diagonal_no_simplification( ]) def test_Multiplexor_diagonal_no_simplification( self, - circuit_framework: type[Circuit], single_qubit_gates: list[NDArray[np.complex128]], control_indices: list[int], target_index: int, @@ -434,8 +432,10 @@ def test_Multiplexor_diagonal_no_simplification( `expected` : NDArray[np.complex128] The expected unitary matrix. """ + from quick.circuit import QiskitCircuit + # Define the quantum circuit - circuit = circuit_framework(len(control_indices) + 1) + circuit = QiskitCircuit(len(control_indices) + 1) # Apply the Multiplexor gate circuit.Multiplexor( @@ -446,10 +446,13 @@ def test_Multiplexor_diagonal_no_simplification( multiplexor_simplification=False ) + print(f"Multiplexor unitary matrix:\n{circuit.get_unitary()}") + print(f"Expected unitary matrix:\n{expected}") + # Ensure the unitary matrix is correct assert_almost_equal(circuit.get_unitary(), expected, 8) - @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + # @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ [ [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], @@ -496,7 +499,6 @@ def test_Multiplexor_diagonal_no_simplification( ]) def test_Multiplexor_no_diagonal_simplification( self, - circuit_framework: type[Circuit], single_qubit_gates: list[NDArray[np.complex128]], control_indices: list[int], target_index: int, @@ -517,8 +519,10 @@ def test_Multiplexor_no_diagonal_simplification( `expected` : NDArray[np.complex128] The expected unitary matrix. """ + from quick.circuit import QiskitCircuit + # Define the quantum circuit - circuit = circuit_framework(len(control_indices) + 1) + circuit = QiskitCircuit(len(control_indices) + 1) # Apply the Multiplexor gate circuit.Multiplexor( @@ -529,6 +533,9 @@ def test_Multiplexor_no_diagonal_simplification( multiplexor_simplification=True ) + print(f"Multiplexor unitary matrix:\n{circuit.get_unitary()}") + print(f"Expected unitary matrix:\n{expected}") + # Ensure the unitary matrix is correct assert_almost_equal(circuit.get_unitary(), expected, 8) From 2b2e611eee9d39cc57745e286a6000cf0611952c Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 26 May 2025 17:52:26 +0800 Subject: [PATCH 12/33] - Checking the single qubit gates produced to see if they are the cause of the issue. - For sanity, I changed from np.linalg.eig to scipy.linalg.eig (should not matter, but anyways). --- quick/circuit/circuit.py | 3 +++ quick/circuit/circuit_utils.py | 5 +++-- .../circuit/test_uniformly_controlled_gates.py | 17 +++++++---------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index 6695e2c..db66970 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -4843,6 +4843,9 @@ def Multiplexor( num_single_qubit_gates = len(single_qubit_gates) + for gate in single_qubit_gates: + print(f"Single qubit gate: {gate}") + # Now, it is easy to place the CX gates and some Hadamards and RZ(pi/2) gates # which are absorbed into the single-qubit unitaries to get back the full decomposition # of the multiplexor diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index 46070ce..61a78ee 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -32,6 +32,7 @@ import numpy as np from numpy.typing import NDArray +import scipy.linalg """ Constants for decomposing multiplexed RZ gates from Bergholm et al. These are the (0, 0) and (1, 1) elements of the RZ gate matrix with angle -pi/2 @@ -207,7 +208,7 @@ def extract_uvr_matrices( # Eigendecomposition of r @ x @ r (Eq 8) # This is done via reforming Eq 6 to be similar to an eigenvalue decomposition rxr = r @ X @ r - eigenvalues, u = np.linalg.eig(rxr) + eigenvalues, u = scipy.linalg.eig(rxr) # type: ignore # Put the eigenvalues into a diagonal form diagonal = np.diag(np.sqrt(eigenvalues)) @@ -220,7 +221,7 @@ def extract_uvr_matrices( # Calculate v based on the decomposition (Eq 7) v = diagonal @ np.conj(u).T @ np.conj(r).T @ b - return v, u, r + return v, u, r # type: ignore def extract_single_qubits_and_diagonal( single_qubit_gates: list[NDArray[np.complex128]], diff --git a/tests/circuit/test_uniformly_controlled_gates.py b/tests/circuit/test_uniformly_controlled_gates.py index 2ad9ce6..33beb36 100644 --- a/tests/circuit/test_uniformly_controlled_gates.py +++ b/tests/circuit/test_uniformly_controlled_gates.py @@ -446,13 +446,10 @@ def test_Multiplexor_diagonal_no_simplification( multiplexor_simplification=False ) - print(f"Multiplexor unitary matrix:\n{circuit.get_unitary()}") - print(f"Expected unitary matrix:\n{expected}") - # Ensure the unitary matrix is correct assert_almost_equal(circuit.get_unitary(), expected, 8) - # @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ [ [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], @@ -499,6 +496,7 @@ def test_Multiplexor_diagonal_no_simplification( ]) def test_Multiplexor_no_diagonal_simplification( self, + circuit_framework: type[Circuit], single_qubit_gates: list[NDArray[np.complex128]], control_indices: list[int], target_index: int, @@ -519,10 +517,8 @@ def test_Multiplexor_no_diagonal_simplification( `expected` : NDArray[np.complex128] The expected unitary matrix. """ - from quick.circuit import QiskitCircuit - # Define the quantum circuit - circuit = QiskitCircuit(len(control_indices) + 1) + circuit = circuit_framework(len(control_indices) + 1) # Apply the Multiplexor gate circuit.Multiplexor( @@ -539,7 +535,7 @@ def test_Multiplexor_no_diagonal_simplification( # Ensure the unitary matrix is correct assert_almost_equal(circuit.get_unitary(), expected, 8) - @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + # @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ [ [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], @@ -586,7 +582,6 @@ def test_Multiplexor_no_diagonal_simplification( ]) def test_Multiplexor_diagonal_simplification( self, - circuit_framework: type[Circuit], single_qubit_gates: list[NDArray[np.complex128]], control_indices: list[int], target_index: int, @@ -607,8 +602,10 @@ def test_Multiplexor_diagonal_simplification( `expected` : NDArray[np.complex128] The expected unitary matrix. """ + from quick.circuit import QiskitCircuit + # Define the quantum circuit - circuit = circuit_framework(len(control_indices) + 1) + circuit = QiskitCircuit(len(control_indices) + 1) # Apply the Multiplexor gate circuit.Multiplexor( From 3492fdc4caf74610844b6aef826a6e53f4e020d3 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 26 May 2025 18:10:19 +0800 Subject: [PATCH 13/33] Fixed the mypy issue --- quick/circuit/circuit_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index 61a78ee..38253ed 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -32,7 +32,7 @@ import numpy as np from numpy.typing import NDArray -import scipy.linalg +import scipy.linalg # type: ignore """ Constants for decomposing multiplexed RZ gates from Bergholm et al. These are the (0, 0) and (1, 1) elements of the RZ gate matrix with angle -pi/2 From cfa11994f14dcce48948432eb3ab05f14df90fcf Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 26 May 2025 19:03:42 +0800 Subject: [PATCH 14/33] - Checking to see if the issue is from `extract_uvr_matrices` (the single qubit gates produced were indeed wrong, so we have to check how they are defined and what could cause inconsistency depending on OS) - Removed an unnecessary function in circuit utils. --- quick/circuit/circuit.py | 2 +- quick/circuit/circuit_utils.py | 48 +++++----------------------------- 2 files changed, 8 insertions(+), 42 deletions(-) diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index db66970..5183a6d 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -4836,7 +4836,7 @@ def Multiplexor( # If there is at least one control qubit, we decompose the multiplexor # into a sequence of single-qubit gates and CX gates - (single_qubit_gates, diagonal) = extract_single_qubits_and_diagonal( + single_qubit_gates, diagonal = extract_single_qubits_and_diagonal( single_qubit_gates, len(control_indices) + 1 ) diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index 38253ed..8bc5fca 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -22,7 +22,6 @@ "multiplexed_rz_angles", "extract_uvr_matrices", "extract_single_qubits_and_diagonal", - "multiplexor_diagonal_matrix", "simplify", "repetition_search", "repetition_verify", @@ -208,6 +207,9 @@ def extract_uvr_matrices( # Eigendecomposition of r @ x @ r (Eq 8) # This is done via reforming Eq 6 to be similar to an eigenvalue decomposition rxr = r @ X @ r + + print(f"rxr: {rxr}") + eigenvalues, u = scipy.linalg.eig(rxr) # type: ignore # Put the eigenvalues into a diagonal form @@ -221,6 +223,10 @@ def extract_uvr_matrices( # Calculate v based on the decomposition (Eq 7) v = diagonal @ np.conj(u).T @ np.conj(r).T @ b + print(f"v: {v}") + print(f"u: {u}") + print(f"r: {r}") + return v, u, r # type: ignore def extract_single_qubits_and_diagonal( @@ -306,46 +312,6 @@ def extract_single_qubits_and_diagonal( return single_qubit_gates, diagonal -def multiplexor_diagonal_matrix( - single_qubit_gates: list[NDArray[np.complex128]], - num_qubits: int, - simplified_controls: set[int] - ) -> NDArray[np.complex128]: - """ Get the diagonal matrix arising in the decomposition of multiplexor - gates given in the paper by Bergholm et al. - - Notes - ----- - This function to extract the diagonal matrix arising in the decomposition - of multiplexed gates based on the paper by Bergholm et al. - - Parameters - ---------- - `single_qubit_gates` : list[NDArray[np.complex128]] - The list of single qubit gates. - `num_qubits` : int - The number of qubits. - - Returns - ------- - NDArray[np.complex128] - The diagonal matrix. - """ - _, diagonal = extract_single_qubits_and_diagonal(single_qubit_gates, num_qubits) - - # Simplify the diagonal to minimize the number of controlled gates - # needed to implement the diagonal gate - if simplified_controls: - control_qubits = sorted([num_qubits - i for i in simplified_controls], reverse=True) - for i in range(num_qubits): - if i not in [0] + control_qubits: - step = 2**i - diagonal = np.repeat(diagonal, 2, axis=0) - for j in range(step, len(diagonal), 2 * step): - diagonal[j:j + step] = diagonal[j - step:j] - - return diagonal - def simplify( single_qubit_gates: list[NDArray[np.complex128]], num_controls: int From 300eb7c3493b5ecf2aea56f067ce4776dbb3af84 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 26 May 2025 22:58:51 +0800 Subject: [PATCH 15/33] - Trying cmath for uniformly controlled issue. --- quick/circuit/circuit_utils.py | 33 ++++++++----------- quick/predicates/predicates.py | 6 ++-- .../test_uniformly_controlled_gates.py | 3 -- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index 8bc5fca..30a4910 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -29,6 +29,7 @@ "reshape" ] +import cmath import numpy as np from numpy.typing import NDArray import scipy.linalg # type: ignore @@ -185,20 +186,20 @@ def extract_uvr_matrices( The diagonal matrix r. """ # Hermitian conjugate of b (Eq 6) - X = a @ np.conj(b).T + X = a @ b.conj().T # Determinant and phase of x det_X = np.linalg.det(X) - X_11 = X[0, 0] / np.sqrt(det_X) + X_11 = X[0, 0] / cmath.sqrt(det_X) phi = np.angle(det_X) # Compute the diagonal matrix r - arg_X_11 = np.angle(X_11) + arg_X_11 = cmath.phase(X_11) # The implementation of the diagonal matrix r is # given below, but it can be chosen freely - r_1 = np.exp(1j / 2 * ((np.pi - phi)/2 - arg_X_11)) - r_2 = np.exp(1j / 2 * ((np.pi - phi)/2 + arg_X_11 + np.pi)) + r_1 = cmath.exp(1j / 2 * ((np.pi - phi)/2 - arg_X_11)) + r_2 = cmath.exp(1j / 2 * ((np.pi - phi)/2 + arg_X_11 + np.pi)) r = np.array([ [r_1, 0], [0, r_2] @@ -207,26 +208,20 @@ def extract_uvr_matrices( # Eigendecomposition of r @ x @ r (Eq 8) # This is done via reforming Eq 6 to be similar to an eigenvalue decomposition rxr = r @ X @ r - - print(f"rxr: {rxr}") - eigenvalues, u = scipy.linalg.eig(rxr) # type: ignore - # Put the eigenvalues into a diagonal form - diagonal = np.diag(np.sqrt(eigenvalues)) - - # Handle specific case where the eigenvalue is near -i - if np.abs(diagonal[0, 0] + 1j) < 1e-10: - diagonal = np.flipud(diagonal) + # Handle specific case where the first eigenvalue is near -i + # This is done by interchanging the eigenvalues and eigenvectors (Eq 13) + if abs(eigenvalues[0] + 1j) < 1e-10: + print("Flipping") + eigenvalues = np.flipud(eigenvalues) u = np.fliplr(u) + diagonal = np.diag(np.sqrt(eigenvalues)) + # Calculate v based on the decomposition (Eq 7) v = diagonal @ np.conj(u).T @ np.conj(r).T @ b - print(f"v: {v}") - print(f"u: {u}") - print(f"r: {r}") - return v, u, r # type: ignore def extract_single_qubits_and_diagonal( @@ -291,7 +286,7 @@ def extract_single_qubits_and_diagonal( single_qubit_gates[shift + len_multiplexor // 2 + i] = u # Decompose D gates per figure 3 - r_dagger = np.conj(r).T + r_dagger = r.conj().T if multiplexor_index < num_multiplexors - 1: k = shift + len_multiplexor + i diff --git a/quick/predicates/predicates.py b/quick/predicates/predicates.py index 396f9d4..92dbcc8 100644 --- a/quick/predicates/predicates.py +++ b/quick/predicates/predicates.py @@ -246,7 +246,7 @@ def is_unitary_matrix( if not is_square_matrix(matrix): return False - matrix = np.conj(matrix.T).dot(matrix) + matrix = matrix.conj().T @ matrix return is_identity_matrix(matrix, ignore_phase=False, rtol=rtol, atol=atol) def is_hermitian_matrix( @@ -277,7 +277,7 @@ def is_hermitian_matrix( if not is_square_matrix(matrix): return False - return np.allclose(matrix, np.conj(matrix.T), rtol=rtol, atol=atol) + return np.allclose(matrix, matrix.conj().T, rtol=rtol, atol=atol) def is_positive_semidefinite_matrix( matrix: NDArray[np.complex128], @@ -343,5 +343,5 @@ def is_isometry( return False identity = np.eye(matrix.shape[1]) - matrix = np.conj(matrix.T).dot(matrix) + matrix = matrix.conj().T @ matrix return np.allclose(matrix, identity, rtol=rtol, atol=atol) \ No newline at end of file diff --git a/tests/circuit/test_uniformly_controlled_gates.py b/tests/circuit/test_uniformly_controlled_gates.py index 33beb36..a6e9cda 100644 --- a/tests/circuit/test_uniformly_controlled_gates.py +++ b/tests/circuit/test_uniformly_controlled_gates.py @@ -529,9 +529,6 @@ def test_Multiplexor_no_diagonal_simplification( multiplexor_simplification=True ) - print(f"Multiplexor unitary matrix:\n{circuit.get_unitary()}") - print(f"Expected unitary matrix:\n{expected}") - # Ensure the unitary matrix is correct assert_almost_equal(circuit.get_unitary(), expected, 8) From 5efb19684a3ed3c3e725b7929fdeac8e71096850 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 26 May 2025 23:28:40 +0800 Subject: [PATCH 16/33] - Trying rounding again, with 16 decimals --- quick/circuit/circuit_utils.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index 30a4910..1c3c9ad 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -29,7 +29,6 @@ "reshape" ] -import cmath import numpy as np from numpy.typing import NDArray import scipy.linalg # type: ignore @@ -190,16 +189,16 @@ def extract_uvr_matrices( # Determinant and phase of x det_X = np.linalg.det(X) - X_11 = X[0, 0] / cmath.sqrt(det_X) + X_11 = X[0, 0] / np.sqrt(det_X) phi = np.angle(det_X) # Compute the diagonal matrix r - arg_X_11 = cmath.phase(X_11) + arg_X_11 = np.angle(X_11) # The implementation of the diagonal matrix r is # given below, but it can be chosen freely - r_1 = cmath.exp(1j / 2 * ((np.pi - phi)/2 - arg_X_11)) - r_2 = cmath.exp(1j / 2 * ((np.pi - phi)/2 + arg_X_11 + np.pi)) + r_1 = np.exp(1j / 2 * ((np.pi - phi)/2 - arg_X_11)) + r_2 = np.exp(1j / 2 * ((np.pi - phi)/2 + arg_X_11 + np.pi)) r = np.array([ [r_1, 0], [0, r_2] @@ -208,12 +207,11 @@ def extract_uvr_matrices( # Eigendecomposition of r @ x @ r (Eq 8) # This is done via reforming Eq 6 to be similar to an eigenvalue decomposition rxr = r @ X @ r - eigenvalues, u = scipy.linalg.eig(rxr) # type: ignore + eigenvalues, u = scipy.linalg.eig(np.round(rxr, 16)) # type: ignore # Handle specific case where the first eigenvalue is near -i # This is done by interchanging the eigenvalues and eigenvectors (Eq 13) if abs(eigenvalues[0] + 1j) < 1e-10: - print("Flipping") eigenvalues = np.flipud(eigenvalues) u = np.fliplr(u) From ae0d633ddf652ccc07ef08b9590d3c8783f81c73 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Tue, 27 May 2025 00:11:41 +0800 Subject: [PATCH 17/33] - Added full framework to the uniformly controlled testers. --- tests/circuit/test_uniformly_controlled_gates.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/circuit/test_uniformly_controlled_gates.py b/tests/circuit/test_uniformly_controlled_gates.py index a6e9cda..c8146b1 100644 --- a/tests/circuit/test_uniformly_controlled_gates.py +++ b/tests/circuit/test_uniformly_controlled_gates.py @@ -365,7 +365,7 @@ def test_Multiplexor_no_diagonal_no_simplification( # Ensure the unitary matrix is correct assert_almost_equal(circuit.get_unitary(), expected, 8) - # @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ [ [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], @@ -412,6 +412,7 @@ def test_Multiplexor_no_diagonal_no_simplification( ]) def test_Multiplexor_diagonal_no_simplification( self, + circuit_framework: type[Circuit], single_qubit_gates: list[NDArray[np.complex128]], control_indices: list[int], target_index: int, @@ -432,10 +433,8 @@ def test_Multiplexor_diagonal_no_simplification( `expected` : NDArray[np.complex128] The expected unitary matrix. """ - from quick.circuit import QiskitCircuit - # Define the quantum circuit - circuit = QiskitCircuit(len(control_indices) + 1) + circuit = circuit_framework(len(control_indices) + 1) # Apply the Multiplexor gate circuit.Multiplexor( @@ -532,7 +531,7 @@ def test_Multiplexor_no_diagonal_simplification( # Ensure the unitary matrix is correct assert_almost_equal(circuit.get_unitary(), expected, 8) - # @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ [ [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], @@ -579,6 +578,7 @@ def test_Multiplexor_no_diagonal_simplification( ]) def test_Multiplexor_diagonal_simplification( self, + circuit_framework: type[Circuit], single_qubit_gates: list[NDArray[np.complex128]], control_indices: list[int], target_index: int, @@ -599,10 +599,8 @@ def test_Multiplexor_diagonal_simplification( `expected` : NDArray[np.complex128] The expected unitary matrix. """ - from quick.circuit import QiskitCircuit - # Define the quantum circuit - circuit = QiskitCircuit(len(control_indices) + 1) + circuit = circuit_framework(len(control_indices) + 1) # Apply the Multiplexor gate circuit.Multiplexor( From 3db28fa95ff6e941449758e9777b932a4b9d836f Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Tue, 27 May 2025 01:18:27 +0800 Subject: [PATCH 18/33] - Updated workflow ymls --- .github/{dependabot.yaml => dependabot.yml} | 0 ...ython-publish-pypi.yaml => python-publish-pypi.yml} | 10 ++++++++-- .github/workflows/tests.yml | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) rename .github/{dependabot.yaml => dependabot.yml} (100%) rename .github/workflows/{python-publish-pypi.yaml => python-publish-pypi.yml} (84%) diff --git a/.github/dependabot.yaml b/.github/dependabot.yml similarity index 100% rename from .github/dependabot.yaml rename to .github/dependabot.yml diff --git a/.github/workflows/python-publish-pypi.yaml b/.github/workflows/python-publish-pypi.yml similarity index 84% rename from .github/workflows/python-publish-pypi.yaml rename to .github/workflows/python-publish-pypi.yml index 66da8ce..e1f3027 100644 --- a/.github/workflows/python-publish-pypi.yaml +++ b/.github/workflows/python-publish-pypi.yml @@ -14,7 +14,10 @@ on: jobs: build: name: Build distribution 📦 - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] permissions: attestations: write id-token: write @@ -32,7 +35,10 @@ jobs: name: Publish Python 🐍 distribution 📦 to PyPI needs: build if: ${{ github.event.action == 'published' }} - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] environment: name: pypi url: https://pypi.org/project/quick-core/${{ github.ref_name }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8f62b94..266379b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, windows-latest] + os: [ubuntu-latest, windows-latest, macos-latest] python-version: ["3.10", "3.11", "3.12"] steps: From 10ddce15fcb819adfc0c73c193091fdce512dfb1 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Wed, 28 May 2025 01:30:03 +0800 Subject: [PATCH 19/33] - Bumped versions where possible to minimize warnings. --- pyproject.toml | 18 +++++++++--------- .../statepreparation/statepreparation_utils.py | 2 +- .../qiskit_unitary_transpiler.py | 2 +- .../shannon_decomposition.py | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0c01e05..9a75de2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,17 +8,17 @@ version = "0.0.0" dependencies = [ "cirq-core == 1.4.1", "genQC == 0.1.0", - "numpy >= 1.23,< 3.0", + "numpy >= 1.23", "pennylane == 0.39.0", - "pytket == 1.37.0", - "pytket-qiskit == 0.62.0", - "pytket-cirq == 0.39.0", - "qiskit == 1.3.1", - "qiskit_aer == 0.16.0", - "qiskit_ibm_runtime == 0.34.0", - "qiskit-transpiler-service == 0.4.14", + "pytket == 2.4.1", + "pytket-qiskit == 0.68.0", + "pytket-cirq == 0.40.0", + "qiskit == 2.0.1", + "qiskit_aer == 0.17.0", + "qiskit_ibm_runtime == 0.39.0", + "qiskit-ibm-transpiler == 0.11.0", "quimb == 1.10.0", - "tket2 == 0.6.0" + "tket2 == 0.10.0" ] requires-python = ">=3.10, <3.13" authors = [ diff --git a/quick/synthesis/statepreparation/statepreparation_utils.py b/quick/synthesis/statepreparation/statepreparation_utils.py index 7530a1c..401c1a0 100644 --- a/quick/synthesis/statepreparation/statepreparation_utils.py +++ b/quick/synthesis/statepreparation/statepreparation_utils.py @@ -522,7 +522,7 @@ def apply_diagonal_gate_to_diag( NDArray[np.complex128] The diagonal matrix after applying the diagonal gate. """ - if not m_diagonal: + if m_diagonal.size == 0: return m_diagonal for state in product([0, 1], repeat=num_qubits): diagonal_index = sum(state[i] << (len(action_qubit_labels) - 1 - idx) for idx, i in enumerate(action_qubit_labels)) diff --git a/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.py b/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.py index 371c8ee..e6cabcd 100644 --- a/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.py +++ b/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.py @@ -28,7 +28,7 @@ from qiskit import QuantumCircuit, transpile # type: ignore from qiskit.transpiler.passes import unitary_synthesis_plugin_names # type: ignore from qiskit_ibm_runtime import QiskitRuntimeService # type: ignore -from qiskit_transpiler_service.transpiler_service import TranspilerService # type: ignore +from qiskit_ibm_transpiler.transpiler_service import TranspilerService # type: ignore if TYPE_CHECKING: from quick.circuit import Circuit diff --git a/quick/synthesis/unitarypreparation/shannon_decomposition.py b/quick/synthesis/unitarypreparation/shannon_decomposition.py index 6df9dcf..518f027 100644 --- a/quick/synthesis/unitarypreparation/shannon_decomposition.py +++ b/quick/synthesis/unitarypreparation/shannon_decomposition.py @@ -291,8 +291,8 @@ def demultiplexor( # Take the square root of the eigenvalues to obtain the singular values # This is necessary because the singular values provide a more convenient form # for constructing the diagonal matrix D, which is used in the final decomposition - # We need to use `np.emath.sqrt` to handle negative eigenvalues - eigenvalues_sqrt = np.emath.sqrt(eigenvalues) + # We need to use `np.lib.scimath.sqrt` to handle negative eigenvalues + eigenvalues_sqrt = np.lib.scimath.sqrt(eigenvalues) # Create a diagonal matrix D from the singular values # The diagonal matrix D is used to scale the eigenvectors appropriately in the final step From 77767ca8de269b47634f48827b5c75152f9743fd Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Sun, 1 Jun 2025 23:57:06 +0800 Subject: [PATCH 20/33] - Added additional testers for known cases of M2 diagnolization issues. - Changed error raise to warning as Ubuntu still succeeds in properly encoding the unitary. --- .../two_qubit_decomposition/weyl.py | 22 ++++-- .../test_two_qubit_decomposition.py | 76 +++++++++++++++++++ 2 files changed, 93 insertions(+), 5 deletions(-) diff --git a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py index a31a5ea..b9edfef 100644 --- a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py +++ b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py @@ -36,6 +36,7 @@ import numpy as np from numpy.typing import NDArray import scipy.linalg # type: ignore +import warnings """ Define the M matrix from section III to tranform the unitary matrix into the magic basis: @@ -448,7 +449,17 @@ def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ NDArray[np.complex128], float ]: - """ Decompose a two-qubit unitary matrix into the Weyl coordinates and the product of two single-qubit unitaries. + """ Decompose a two-qubit unitary matrix into the Weyl coordinates and the + product of two single-qubit unitaries. + + Notes + ----- + M2 diagnolization may be wrong due to floating point errors, but it will work + correctly in Linux. Should you encounter a failure in the code, and the result + fails to encode the correct unitary matrix, please report it at + https://github.com/Qualition/quick/issues/11 + + with the unitary matrix that caused the failure. Parameters ---------- @@ -477,9 +488,10 @@ def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ Raises ------ ValueError - - If the diagonalization of M2 fails. - - If the determinant of the right or left component is not in the expected range. - - If the decomposition fails due to a deviation from the expected unitary matrix. + - If the determinant of the right or left component is + not in the expected range. + - If the decomposition fails due to a deviation from the + expected unitary matrix. Usage ----- @@ -498,7 +510,7 @@ def decompose_unitary(unitary_matrix: NDArray[np.complex128]) -> tuple[ # Given P is a real-symmetric unitary matrix we only use transpose if not np.allclose(P.dot(np.diag(D)).dot(P.T), M2, rtol=0, atol=1e-13): - raise ValueError( + warnings.warn( "Failed to diagonalize M2." "Kindly report this at https://github.com/Qualition/quick/issues/11: " f"U: {U}" diff --git a/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py b/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py index 409b2e3..cfccf39 100644 --- a/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py +++ b/tests/synthesis/gate_decompositions/two_qubit_decomposition/test_two_qubit_decomposition.py @@ -17,6 +17,7 @@ __all__ = ["TestTwoQubitDecomposition"] import numpy as np +from numpy.typing import NDArray from numpy.testing import assert_almost_equal import pytest from scipy.stats import unitary_group @@ -252,6 +253,81 @@ def test_decomp3_supercontrolled(self) -> None: # Check that the number of CX gates is 3 or less assert num_cx_gates <= 3 + @pytest.mark.parametrize("unitary", [ + np.array([ + [2./3, 1./3 + 1.j/3, 7 * np.sqrt(17)/51, (-1 - 1.j) * np.sqrt(17)/51], + [1./3 - 1.j/3, -1./3, (-1 + 1.j) * np.sqrt(17)/51, 10 * np.sqrt(17)/51], + [7*np.sqrt(17)/51, (-1 - 1.j) * np.sqrt(17)/51, -2./3, -1./3 - 1.j/3], + [(-1 + 1.j) * np.sqrt(17)/51, 10 * np.sqrt(17)/51, -1./3 + 1.j/3, 1./3] + ]), + np.array([ + [ + 0.043602684126304955 + -0.4986216208233326j, -0.22461079447224863 + -0.09679517443671315j, + 0.15592626697527698 + 0.13943283228059802j, -0.803224008021238 + 0.027067469117215155j + ], + [ + -0.09679517443669944 + 0.22461079447226426j, 0.4986216208232763 + 0.043602684126298856j, + 0.02706746911718458 + 0.8032240080213412j, -0.13943283228056091 + 0.15592626697531453j + ], + [ + 0.13943283228059844 + -0.15592626697527806j, 0.027067469117214762 + 0.8032240080212403j, + 0.49862162082333095 + 0.0436026841263046j, 0.09679517443671384 + -0.22461079447224833j + ], + [ + 0.8032240080213434 + -0.027067469117184207j, 0.1559262669753156 + 0.13943283228056125j, + -0.2246107944722639 + -0.09679517443670013j, -0.043602684126298495 + 0.4986216208232746j + ] + ]), + np.array([ + [-0.62695238, -0.1993407 , -0.63291226, -0.40818632], + [-0.62695237, -0.42741604, 0.61214869, 0.2225314 ], + [-0.32700263, 0.43412304, -0.31468734, 0.77818914], + [ 0.32700264, -0.76753892, -0.35449671, 0.42223851] + ]), + np.array([ + [ + -0.3812064266367201 + 0.38120642663672005j, -0.08953682318096808 + 0.08953682318096806j, + -0.5214184531408846 + 0.5214184531408846j, -0.27347323579354943 + 0.2734732357935494j, + ], + [ + 0.11105398348218393 - 0.11105398348218391j, 0.145430219977459 - 0.14543021997745897j, + 0.23096365206101888 - 0.23096365206101882j, -0.6427852354467597 + 0.6427852354467596j, + ], + [ + -0.544932087007239 + 0.5449320870072389j, -0.16786656959437854 + 0.16786656959437854j, + 0.41778724529336947 - 0.4177872452933694j, 0.01799033088883041 - 0.017990330888830407j, + ], + [ + -0.213067345160641 + 0.21306734516064096j, 0.6653224962721628 - 0.6653224962721627j, + -0.0152448403255109 + 0.015244840325510897j, 0.10823991063233353 - 0.10823991063233351j, + ], + ]) + ]) + def test_m2_correctness( + self, + unitary: NDArray[np.complex128] + ) -> None: + """ Test the correctness of the M2 decomposition. This tests + many recorded cases of failure of two qubit decomposition on + non-Ubuntu OS. + + Parameters + ---------- + `unitary` : NDArray[np.complex128] + The two qubit unitary to encode. + """ + # Create a two qubit decomposition object + two_qubit_decomposition = TwoQubitDecomposition(output_framework=QiskitCircuit) + + # Initialize a circuit + circuit = QiskitCircuit(2) + + # Apply the decomposition + two_qubit_decomposition.apply_unitary(circuit, unitary, [0, 1]) + + # Check that the circuit is equivalent to the original unitary matrix + assert_almost_equal(circuit.get_unitary(), unitary, decimal=8) + def test_invalid_indices_fail(self) -> None: """ Test that invalid indices fail. """ From d09b3f4025553262d63350707872609433118a74 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Tue, 8 Jul 2025 03:19:58 +0800 Subject: [PATCH 21/33] - Added more unit tests. - Removed rounding from rxr for testing purposes. --- quick/circuit/circuit.py | 5 - quick/circuit/circuit_utils.py | 25 +- ..._6qubits_31405control_RYRX_alternating.npy | Bin 0 -> 65664 bytes ..._6qubits_31405control_RYRX_alternating.npy | Bin 0 -> 65664 bytes ..._6qubits_31405control_RYRX_alternating.npy | Bin 0 -> 65664 bytes ..._6qubits_31405control_RYRX_alternating.npy | Bin 0 -> 65664 bytes tests/circuit/gate_utils.py | 2648 ++++++++++++----- .../test_uniformly_controlled_gates.py | 162 +- 8 files changed, 2029 insertions(+), 811 deletions(-) create mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_6qubits_31405control_RYRX_alternating.npy create mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_6qubits_31405control_RYRX_alternating.npy create mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_6qubits_31405control_RYRX_alternating.npy create mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_6qubits_31405control_RYRX_alternating.npy diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index 5183a6d..03b0feb 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -4843,9 +4843,6 @@ def Multiplexor( num_single_qubit_gates = len(single_qubit_gates) - for gate in single_qubit_gates: - print(f"Single qubit gate: {gate}") - # Now, it is easy to place the CX gates and some Hadamards and RZ(pi/2) gates # which are absorbed into the single-qubit unitaries to get back the full decomposition # of the multiplexor @@ -4872,12 +4869,10 @@ def Multiplexor( num_trailing_zeros = len(binary_rep) - len(binary_rep.rstrip("0")) control_index = num_trailing_zeros - # Apply the CX gate if not i == num_single_qubit_gates - 1: self.CX(control_indices[control_index], target_index) self.GlobalPhase(-PI4) - # If `up_to_diagonal` is False, we apply the diagonal gate if not up_to_diagonal: self.Diagonal(diagonal, qubit_indices=[target_index] + list(control_indices)) diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index 1c3c9ad..abe7c90 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -46,6 +46,8 @@ SQRT2, -SQRT2 ) +EPSILON = 1e-16 + # Type hint for nested lists of floats Params = list[list[float] | float] | list[float] @@ -187,14 +189,26 @@ def extract_uvr_matrices( # Hermitian conjugate of b (Eq 6) X = a @ b.conj().T + print("X:", X) + # Determinant and phase of x det_X = np.linalg.det(X) + + print("det_X:", det_X) + X_11 = X[0, 0] / np.sqrt(det_X) + + print("X_11:", X_11) + phi = np.angle(det_X) + print("phi:", phi) + # Compute the diagonal matrix r arg_X_11 = np.angle(X_11) + print("arg_X_11:", arg_X_11) + # The implementation of the diagonal matrix r is # given below, but it can be chosen freely r_1 = np.exp(1j / 2 * ((np.pi - phi)/2 - arg_X_11)) @@ -204,14 +218,20 @@ def extract_uvr_matrices( [0, r_2] ]) + print("r:", r) + # Eigendecomposition of r @ x @ r (Eq 8) # This is done via reforming Eq 6 to be similar to an eigenvalue decomposition rxr = r @ X @ r - eigenvalues, u = scipy.linalg.eig(np.round(rxr, 16)) # type: ignore + + print("rxr:", rxr) + + eigenvalues, u = np.linalg.eig(rxr) # type: ignore # Handle specific case where the first eigenvalue is near -i # This is done by interchanging the eigenvalues and eigenvectors (Eq 13) - if abs(eigenvalues[0] + 1j) < 1e-10: + print("eigenvalues:", eigenvalues[0] + 1j) + if abs(eigenvalues[0] + 1j) < EPSILON: eigenvalues = np.flipud(eigenvalues) u = np.fliplr(u) @@ -522,7 +542,6 @@ def flatten(array: Params) -> tuple[list[float], Params]: # pragma: no cover return flattened, shape - def reshape( flattened: list[float], shape: Params diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_6qubits_31405control_RYRX_alternating.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_6qubits_31405control_RYRX_alternating.npy new file mode 100644 index 0000000000000000000000000000000000000000..ef7f2679f29096b18f990ebe303bb47683d74b56 GIT binary patch literal 65664 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 wfDdGUM!*PqGl4X}Q))j<;Nc*i_@% literal 0 HcmV?d00001 diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_6qubits_31405control_RYRX_alternating.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_6qubits_31405control_RYRX_alternating.npy new file mode 100644 index 0000000000000000000000000000000000000000..df4180c7dcefa0b914cb6affb0a5c4b665073424 GIT binary patch literal 65664 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 Date: Tue, 8 Jul 2025 03:37:04 +0800 Subject: [PATCH 22/33] - Minimized test cases for UC to make debugging easier. --- .../test_uniformly_controlled_gates.py | 250 +++++++++--------- 1 file changed, 126 insertions(+), 124 deletions(-) diff --git a/tests/circuit/test_uniformly_controlled_gates.py b/tests/circuit/test_uniformly_controlled_gates.py index ca0d38d..6c326d0 100644 --- a/tests/circuit/test_uniformly_controlled_gates.py +++ b/tests/circuit/test_uniformly_controlled_gates.py @@ -408,127 +408,127 @@ def test_Multiplexor_no_diagonal_no_simplification( # Ensure the unitary matrix is correct assert_almost_equal(circuit.get_unitary(), expected, 8) - @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) - @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ - [ - [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], - [0, 1], - 2, - UC_unitary_matrix_diagonal_no_simplification_3qubits_01control_HXHX - ], - [ - [Hadamard().matrix, PauliY().matrix, Hadamard().matrix, PauliY().matrix], - [1, 0], - 2, - UC_unitary_matrix_diagonal_no_simplification_3qubits_10control_HYHY - ], - [ - [ - RX(np.pi/2).matrix, - RY(np.pi/3).matrix, - RX(np.pi/4).matrix, - RY(np.pi/5).matrix, - RX(np.pi/6).matrix, - RY(np.pi/7).matrix, - RX(np.pi/8).matrix, - RY(np.pi/9).matrix - ], - [0, 2, 3], - 1, - UC_unitary_matrix_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY - ], - [ - [ - RX(np.pi/2).matrix, - RY(np.pi/3).matrix, - RX(np.pi/4).matrix, - RY(np.pi/5).matrix, - RX(np.pi/6).matrix, - RY(np.pi/7).matrix, - RX(np.pi/8).matrix, - RY(np.pi/9).matrix - ], - [2, 1, 3], - 0, - UC_unitary_matrix_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY - ], - [ - [ - RY(np.pi).matrix, - RX(np.pi/2).matrix, - RY(np.pi/3).matrix, - RX(np.pi/4).matrix, - RY(np.pi/5).matrix, - RX(np.pi/6).matrix, - RY(np.pi/7).matrix, - RX(np.pi/8).matrix, - RY(np.pi/9).matrix, - RX(np.pi/10).matrix, - RY(np.pi/11).matrix, - RX(np.pi/12).matrix, - RY(np.pi/13).matrix, - RX(np.pi/14).matrix, - RY(np.pi/15).matrix, - RX(np.pi/16).matrix, - RY(np.pi/17).matrix, - RX(np.pi/18).matrix, - RY(np.pi/19).matrix, - RX(np.pi/20).matrix, - RY(np.pi/21).matrix, - RX(np.pi/22).matrix, - RY(np.pi/23).matrix, - RX(np.pi/24).matrix, - RY(np.pi/25).matrix, - RX(np.pi/26).matrix, - RY(np.pi/27).matrix, - RX(np.pi/28).matrix, - RY(np.pi/29).matrix, - RX(np.pi/30).matrix, - RY(np.pi/31).matrix, - RX(np.pi/32).matrix - ], - [3, 1, 4, 0, 5], - 2, - UC_unitary_matrix_diagonal_no_simplification_6qubits_31405control_RYRX_alternating - ] - ]) - def test_Multiplexor_diagonal_no_simplification( - self, - circuit_framework: type[Circuit], - single_qubit_gates: list[NDArray[np.complex128]], - control_indices: list[int], - target_index: int, - expected: NDArray[np.complex128] - ) -> None: - """ Test the `Multiplexor` gate with diagonal and without simplification. - - Parameters - ---------- - `circuit_framework` : type[quick.circuit.Circuit] - The quantum circuit framework. - `single_qubit_gates` : list[NDArray[np.complex128]] - The single-qubit gates. - `control_indices` : list[int] - The control qubits. - `target_index` : int - The target qubit. - `expected` : NDArray[np.complex128] - The expected unitary matrix. - """ - # Define the quantum circuit - circuit = circuit_framework(len(control_indices) + 1) - - # Apply the Multiplexor gate - circuit.Multiplexor( - single_qubit_gates, - control_indices, - target_index, - up_to_diagonal=True, - multiplexor_simplification=False - ) - - # Ensure the unitary matrix is correct - assert_almost_equal(circuit.get_unitary(), expected, 8) + # @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + # @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ + # [ + # [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], + # [0, 1], + # 2, + # UC_unitary_matrix_diagonal_no_simplification_3qubits_01control_HXHX + # ], + # [ + # [Hadamard().matrix, PauliY().matrix, Hadamard().matrix, PauliY().matrix], + # [1, 0], + # 2, + # UC_unitary_matrix_diagonal_no_simplification_3qubits_10control_HYHY + # ], + # [ + # [ + # RX(np.pi/2).matrix, + # RY(np.pi/3).matrix, + # RX(np.pi/4).matrix, + # RY(np.pi/5).matrix, + # RX(np.pi/6).matrix, + # RY(np.pi/7).matrix, + # RX(np.pi/8).matrix, + # RY(np.pi/9).matrix + # ], + # [0, 2, 3], + # 1, + # UC_unitary_matrix_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY + # ], + # [ + # [ + # RX(np.pi/2).matrix, + # RY(np.pi/3).matrix, + # RX(np.pi/4).matrix, + # RY(np.pi/5).matrix, + # RX(np.pi/6).matrix, + # RY(np.pi/7).matrix, + # RX(np.pi/8).matrix, + # RY(np.pi/9).matrix + # ], + # [2, 1, 3], + # 0, + # UC_unitary_matrix_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY + # ], + # [ + # [ + # RY(np.pi).matrix, + # RX(np.pi/2).matrix, + # RY(np.pi/3).matrix, + # RX(np.pi/4).matrix, + # RY(np.pi/5).matrix, + # RX(np.pi/6).matrix, + # RY(np.pi/7).matrix, + # RX(np.pi/8).matrix, + # RY(np.pi/9).matrix, + # RX(np.pi/10).matrix, + # RY(np.pi/11).matrix, + # RX(np.pi/12).matrix, + # RY(np.pi/13).matrix, + # RX(np.pi/14).matrix, + # RY(np.pi/15).matrix, + # RX(np.pi/16).matrix, + # RY(np.pi/17).matrix, + # RX(np.pi/18).matrix, + # RY(np.pi/19).matrix, + # RX(np.pi/20).matrix, + # RY(np.pi/21).matrix, + # RX(np.pi/22).matrix, + # RY(np.pi/23).matrix, + # RX(np.pi/24).matrix, + # RY(np.pi/25).matrix, + # RX(np.pi/26).matrix, + # RY(np.pi/27).matrix, + # RX(np.pi/28).matrix, + # RY(np.pi/29).matrix, + # RX(np.pi/30).matrix, + # RY(np.pi/31).matrix, + # RX(np.pi/32).matrix + # ], + # [3, 1, 4, 0, 5], + # 2, + # UC_unitary_matrix_diagonal_no_simplification_6qubits_31405control_RYRX_alternating + # ] + # ]) + # def test_Multiplexor_diagonal_no_simplification( + # self, + # circuit_framework: type[Circuit], + # single_qubit_gates: list[NDArray[np.complex128]], + # control_indices: list[int], + # target_index: int, + # expected: NDArray[np.complex128] + # ) -> None: + # """ Test the `Multiplexor` gate with diagonal and without simplification. + + # Parameters + # ---------- + # `circuit_framework` : type[quick.circuit.Circuit] + # The quantum circuit framework. + # `single_qubit_gates` : list[NDArray[np.complex128]] + # The single-qubit gates. + # `control_indices` : list[int] + # The control qubits. + # `target_index` : int + # The target qubit. + # `expected` : NDArray[np.complex128] + # The expected unitary matrix. + # """ + # # Define the quantum circuit + # circuit = circuit_framework(len(control_indices) + 1) + + # # Apply the Multiplexor gate + # circuit.Multiplexor( + # single_qubit_gates, + # control_indices, + # target_index, + # up_to_diagonal=True, + # multiplexor_simplification=False + # ) + + # # Ensure the unitary matrix is correct + # assert_almost_equal(circuit.get_unitary(), expected, 8) @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ @@ -652,7 +652,7 @@ def test_Multiplexor_no_diagonal_simplification( # Ensure the unitary matrix is correct assert_almost_equal(circuit.get_unitary(), expected, 8) - @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + # @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ [ [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], @@ -738,7 +738,7 @@ def test_Multiplexor_no_diagonal_simplification( ]) def test_Multiplexor_diagonal_simplification( self, - circuit_framework: type[Circuit], + # circuit_framework: type[Circuit], single_qubit_gates: list[NDArray[np.complex128]], control_indices: list[int], target_index: int, @@ -759,8 +759,10 @@ def test_Multiplexor_diagonal_simplification( `expected` : NDArray[np.complex128] The expected unitary matrix. """ + from quick.circuit import QiskitCircuit + # Define the quantum circuit - circuit = circuit_framework(len(control_indices) + 1) + circuit = QiskitCircuit(len(control_indices) + 1) # Apply the Multiplexor gate circuit.Multiplexor( From 952a342f9aef41f63e29838a81bdda26fbe26e04 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Tue, 8 Jul 2025 03:52:37 +0800 Subject: [PATCH 23/33] - Updated workflow to run all workflows. --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 266379b..58bfae5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,6 +9,7 @@ jobs: test: runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] python-version: ["3.10", "3.11", "3.12"] From 5e9277e3f615dc8fe1d25d34c5081ea15e2d0d5d Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Tue, 8 Jul 2025 19:56:24 +0800 Subject: [PATCH 24/33] - Checking diagonal. --- quick/circuit/circuit.py | 2 ++ quick/circuit/circuit_utils.py | 26 ++++++-------------------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index 03b0feb..aa97538 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -4873,6 +4873,8 @@ def Multiplexor( self.CX(control_indices[control_index], target_index) self.GlobalPhase(-PI4) + print("Diagonal:", diagonal) + if not up_to_diagonal: self.Diagonal(diagonal, qubit_indices=[target_index] + list(control_indices)) diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index abe7c90..e8d0a9b 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -46,7 +46,7 @@ SQRT2, -SQRT2 ) -EPSILON = 1e-16 +EPSILON = 1e-10 # Type hint for nested lists of floats Params = list[list[float] | float] | list[float] @@ -189,26 +189,14 @@ def extract_uvr_matrices( # Hermitian conjugate of b (Eq 6) X = a @ b.conj().T - print("X:", X) - # Determinant and phase of x det_X = np.linalg.det(X) - - print("det_X:", det_X) - X_11 = X[0, 0] / np.sqrt(det_X) - - print("X_11:", X_11) - phi = np.angle(det_X) - print("phi:", phi) - # Compute the diagonal matrix r arg_X_11 = np.angle(X_11) - print("arg_X_11:", arg_X_11) - # The implementation of the diagonal matrix r is # given below, but it can be chosen freely r_1 = np.exp(1j / 2 * ((np.pi - phi)/2 - arg_X_11)) @@ -218,27 +206,25 @@ def extract_uvr_matrices( [0, r_2] ]) - print("r:", r) - # Eigendecomposition of r @ x @ r (Eq 8) # This is done via reforming Eq 6 to be similar to an eigenvalue decomposition rxr = r @ X @ r - print("rxr:", rxr) - eigenvalues, u = np.linalg.eig(rxr) # type: ignore # Handle specific case where the first eigenvalue is near -i # This is done by interchanging the eigenvalues and eigenvectors (Eq 13) - print("eigenvalues:", eigenvalues[0] + 1j) if abs(eigenvalues[0] + 1j) < EPSILON: eigenvalues = np.flipud(eigenvalues) u = np.fliplr(u) - diagonal = np.diag(np.sqrt(eigenvalues)) + diagonal = np.array([ + [RZ_PI2_00, 0], + [0, RZ_PI2_11] + ]) # Calculate v based on the decomposition (Eq 7) - v = diagonal @ np.conj(u).T @ np.conj(r).T @ b + v = diagonal @ u.conj().T @ r.conj().T @ b return v, u, r # type: ignore From 11748eb8b644f9b8b0c34a95869ed1125c35f3bf Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Tue, 8 Jul 2025 21:22:39 +0800 Subject: [PATCH 25/33] - Checking why diagonal is different on different os. --- quick/circuit/circuit.py | 2 -- quick/circuit/circuit_utils.py | 5 +++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index aa97538..03b0feb 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -4873,8 +4873,6 @@ def Multiplexor( self.CX(control_indices[control_index], target_index) self.GlobalPhase(-PI4) - print("Diagonal:", diagonal) - if not up_to_diagonal: self.Diagonal(diagonal, qubit_indices=[target_index] + list(control_indices)) diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index e8d0a9b..5b57738 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -206,6 +206,8 @@ def extract_uvr_matrices( [0, r_2] ]) + print("r:", r) + # Eigendecomposition of r @ x @ r (Eq 8) # This is done via reforming Eq 6 to be similar to an eigenvalue decomposition rxr = r @ X @ r @@ -226,6 +228,9 @@ def extract_uvr_matrices( # Calculate v based on the decomposition (Eq 7) v = diagonal @ u.conj().T @ r.conj().T @ b + print("v:", v) + print("u:", u) + return v, u, r # type: ignore def extract_single_qubits_and_diagonal( From 25a30826f42b0c9d3e51af166cdd0a818cb9c3f2 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Wed, 9 Jul 2025 01:37:21 +0800 Subject: [PATCH 26/33] - Additional prints to pinpoint divergence. --- quick/circuit/circuit_utils.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index 5b57738..ca28bb6 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -39,11 +39,11 @@ """ SQRT2 = 1/np.sqrt(2) -RZ_PI2_00 = complex( - SQRT2, SQRT2 +RZ_PI2_00 = np.complex128( + SQRT2 + SQRT2 * 1j ) -RZ_PI2_11 = complex( - SQRT2, -SQRT2 +RZ_PI2_11 = np.complex128( + SQRT2 - SQRT2 * 1j ) EPSILON = 1e-10 @@ -186,17 +186,28 @@ def extract_uvr_matrices( `r` : NDArray[np.complex128] The diagonal matrix r. """ + print("a:", a) + print("b:", b) + # Hermitian conjugate of b (Eq 6) X = a @ b.conj().T + print("X:", X) + # Determinant and phase of x det_X = np.linalg.det(X) X_11 = X[0, 0] / np.sqrt(det_X) phi = np.angle(det_X) + print("det_X:", det_X) + print("X_11:", X_11) + print("phi:", phi) + # Compute the diagonal matrix r arg_X_11 = np.angle(X_11) + print("arg_X_11:", arg_X_11) + # The implementation of the diagonal matrix r is # given below, but it can be chosen freely r_1 = np.exp(1j / 2 * ((np.pi - phi)/2 - arg_X_11)) From a91feed3473a7393ff497de7100f7f44d591b84f Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Sun, 13 Jul 2025 00:09:20 +0800 Subject: [PATCH 27/33] - Fixed testing for better assertion of the property based on D U' = U where U is the block diagonal of single qubit unitaries. --- quick/circuit/circuit.py | 16 +- quick/circuit/circuit_utils.py | 17 - ..._simplification_3qubits_01control_HXHX.npy | Bin 1152 -> 0 bytes ..._simplification_3qubits_10control_HYHY.npy | Bin 1152 -> 0 bytes ...cation_4qubits_023control_RXRYRXRYRXRY.npy | Bin 4224 -> 0 bytes ...cation_4qubits_213control_RXRYRXRYRXRY.npy | Bin 4224 -> 0 bytes ..._6qubits_31405control_RYRX_alternating.npy | Bin 65664 -> 0 bytes ..._simplification_3qubits_01control_HXHX.npy | Bin 1152 -> 0 bytes ..._simplification_3qubits_10control_HYHY.npy | Bin 1152 -> 0 bytes ...cation_4qubits_023control_RXRYRXRYRXRY.npy | Bin 4224 -> 0 bytes ...cation_4qubits_213control_RXRYRXRYRXRY.npy | Bin 4224 -> 0 bytes ..._6qubits_31405control_RYRX_alternating.npy | Bin 65664 -> 0 bytes ..._simplification_3qubits_01control_HXHX.npy | Bin 1152 -> 0 bytes ..._simplification_3qubits_10control_HYHY.npy | Bin 1152 -> 0 bytes ...cation_4qubits_023control_RXRYRXRYRXRY.npy | Bin 4224 -> 0 bytes ...cation_4qubits_213control_RXRYRXRYRXRY.npy | Bin 4224 -> 0 bytes ..._6qubits_31405control_RYRX_alternating.npy | Bin 65664 -> 0 bytes ..._simplification_3qubits_01control_HXHX.npy | Bin 1152 -> 0 bytes ..._simplification_3qubits_10control_HYHY.npy | Bin 1152 -> 0 bytes ...cation_4qubits_023control_RXRYRXRYRXRY.npy | Bin 4224 -> 0 bytes ...cation_4qubits_213control_RXRYRXRYRXRY.npy | Bin 4224 -> 0 bytes ..._6qubits_31405control_RYRX_alternating.npy | Bin 65664 -> 0 bytes tests/circuit/gate_utils.py | 554 ----------------- .../test_uniformly_controlled_gates.py | 557 +++--------------- 24 files changed, 94 insertions(+), 1050 deletions(-) delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_3qubits_01control_HXHX.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_3qubits_10control_HYHY.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_6qubits_31405control_RYRX_alternating.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_3qubits_01control_HXHX.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_3qubits_10control_HYHY.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_4qubits_023control_RXRYRXRYRXRY.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_4qubits_213control_RXRYRXRYRXRY.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_6qubits_31405control_RYRX_alternating.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_3qubits_01control_HXHX.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_3qubits_10control_HYHY.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_6qubits_31405control_RYRX_alternating.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_3qubits_01control_HXHX.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_3qubits_10control_HYHY.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_4qubits_023control_RXRYRXRYRXRY.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_4qubits_213control_RXRYRXRYRXRY.npy delete mode 100644 tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_6qubits_31405control_RYRX_alternating.npy diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index 03b0feb..35b0fff 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -4657,7 +4657,7 @@ def _Diagonal( # Repeatedly apply the decomposition of Theorem 7 from [1] while num_diagonal_entries >= 2: - rz_angles = [] + rz_angles: list[float] = [] # Extract the RZ angles and the relative phase # between the two diagonal entries @@ -4788,6 +4788,12 @@ def Multiplexor( ... [[0, 1], ... [1, 0]]], multiplexor_simplification=False, control_state="01") """ + # If there are no control indices, we use the ZYZ decomposition + # to apply the single qubit gate + if not control_indices: + self.unitary(single_qubit_gates[0], target_index) + return + if isinstance(control_indices, int): control_indices = [control_indices] @@ -4825,15 +4831,9 @@ def Multiplexor( # based on [2] if multiplexor_simplification: new_controls, single_qubit_gates = simplify(single_qubit_gates, num_controls) - control_indices = [qubits[len(control_indices) + 1 - i] for i in new_controls] + control_indices = [qubits[num_controls + 1 - i] for i in new_controls] control_indices.reverse() - # If there are no control indices, we use the ZYZ decomposition - # to apply the single qubit gate - if not control_indices: - self.unitary(single_qubit_gates[0], target_index) - return - # If there is at least one control qubit, we decompose the multiplexor # into a sequence of single-qubit gates and CX gates single_qubit_gates, diagonal = extract_single_qubits_and_diagonal( diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index ca28bb6..607f736 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -31,7 +31,6 @@ import numpy as np from numpy.typing import NDArray -import scipy.linalg # type: ignore """ Constants for decomposing multiplexed RZ gates from Bergholm et al. These are the (0, 0) and (1, 1) elements of the RZ gate matrix with angle -pi/2 @@ -186,28 +185,17 @@ def extract_uvr_matrices( `r` : NDArray[np.complex128] The diagonal matrix r. """ - print("a:", a) - print("b:", b) - # Hermitian conjugate of b (Eq 6) X = a @ b.conj().T - print("X:", X) - # Determinant and phase of x det_X = np.linalg.det(X) X_11 = X[0, 0] / np.sqrt(det_X) phi = np.angle(det_X) - print("det_X:", det_X) - print("X_11:", X_11) - print("phi:", phi) - # Compute the diagonal matrix r arg_X_11 = np.angle(X_11) - print("arg_X_11:", arg_X_11) - # The implementation of the diagonal matrix r is # given below, but it can be chosen freely r_1 = np.exp(1j / 2 * ((np.pi - phi)/2 - arg_X_11)) @@ -217,8 +205,6 @@ def extract_uvr_matrices( [0, r_2] ]) - print("r:", r) - # Eigendecomposition of r @ x @ r (Eq 8) # This is done via reforming Eq 6 to be similar to an eigenvalue decomposition rxr = r @ X @ r @@ -239,9 +225,6 @@ def extract_uvr_matrices( # Calculate v based on the decomposition (Eq 7) v = diagonal @ u.conj().T @ r.conj().T @ b - print("v:", v) - print("u:", u) - return v, u, r # type: ignore def extract_single_qubits_and_diagonal( diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_3qubits_01control_HXHX.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_3qubits_01control_HXHX.npy deleted file mode 100644 index 1e30cd2326f4c21d2194cffdc85214bf8d478bb9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUG7CH(RnmP)#3giN==$Z^9l3 zy#-36yBatDh;@D1yamtfPeEvk!-v}L`2~%?FJ#6)RR4Zhyx|TB;$&dvLEU3doGyea mn17(|p*XxxK;!QiRpM`g4Z@}5v7qkR4|Na8;e%@HggpSQT(FM- diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY.npy deleted file mode 100644 index f9200c3e05fc3e0165ca016336bf259407b91660..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4224 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGhGsenhGv>N3bhL40j>wrDjyn5e`cSt=GmonOPB0tKm#05@w*Up81iV! zhdehz!s^?*Rj=*myzPHK$8)7Ux`#OACLCGy>HdC=KXc}Z8qXc_;c*`tuM#-oUjQO* ze{Y1u{|;!pGDG7TJ^trG(#8HTNIDtv@sI95Qu+5{@1;n!ytUu)?8Ob?U#l>}BOuvp zieBWj{aQM^-PRS~z>p`^zQL~k$7l9u@k#IOJsRixTOD0(kM7<&m0LSD?z_DI#=Pss zx49<``S8$&#%lvKp3(hV07)124`h1d}xw;c?)7@55tOef#IIy?Q^b^8$uEx_LvL-}icI>>Sni z_6M?~6jzt8u}AmMmJ_U(H|3w%A8_nNRq@}>As-&oA@ORj0EuS||2}}G3s^cq50Bx- zPdEyWSNk21c*Y2i7tnM8ODDt4|D^iINk&GiCG@?$0PBWV#fve^4~M#VYnPL4`|n+E zT^I1~#E{P~gu=&X(H-rqviJ59^IS4o4`P-dM>tllzix1Bzrdse3zR)thJ1M3gv6^o p10=SMImQ40 diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY.npy deleted file mode 100644 index 33376d86ae7f4a392cbfed5fb6c2e9d63a9fad61..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4224 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGhGsenhGv>N3bhL40j>wrDjyn5e`cSt=GmonOPB2DfYNs%;`S~|Pk))n6v_US_# z>dywKzY8GYVE+ITF2mmcgwjWZgw?lqt6tmBdE5Vfj^|2y4!H?OR(-m^U*pf5d7{R1 zhkf|$fclde>hF1waM&LP3728-e?sZw$7l9u@k#IOJsRixTOD0(U#D_w$Hsk^_urUz z-S{^5q+uUE9P=Umtb+Qx0um1Uq2V&@{ZA-;=*-HC{B!%A{TFqw?gW!H`wzVDeR#~O zZ~q*&SMR5FUKsY_Gacejdj*KU?H@qH0TwQV!f&|CJITmswS>O67hv7+s(A65{SI~U z)-EU8_TRhSx-Q_|iD92UZbJNN&j9ha{T&uaIKaYXxW_*s|Mk6|8aqezz5RjgD8<#~ zYwWk2V7`(h05P#dhfQADsTnL5VaF_R4bVoa@?7h9j vJeQ2tgKO-MaI9Q^-Qd`Mfk_7zD0{RF`}8qkGT5K?dm#R{4}pe*7c^V|-nu!) diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_6qubits_31405control_RYRX_alternating.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_no_simplification_6qubits_31405control_RYRX_alternating.npy deleted file mode 100644 index ef7f2679f29096b18f990ebe303bb47683d74b56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65664 zcmeI5dsI}{8O1M_BxqSgX?Vm$9U2uhpaU3QqGtew5w)SZNWroq53x3jG8CsqgPQTN zH1QRpCAB7+S(OH8iI9|-LX|TWOH4x}qR=$Dg5==~Wf0#{YR7x-^zVGLrqj#Jo($EA=XktNROlY_!bg7}#SejS7*igEp z@WD4y^NN-i+P=PgSzbw@?enmhY2lifX_LY=Yc!6%Mo9AO^db3!u19AI<nyd%9Z^XQ4u?~5@8=*uiATZJV2JhZo zYd)jT_hk6!9RyTHAZ5(1ij9r7`xAfsZiVql2d)qF1ue!YnY?fCZgzKr1$v;e^NVsk zi@=3pKq-1daiNw>1Hm)?h9V=+Cu1o=WQW$>_!B>EV|Vc8)apd%R3g-wbHK-{KIZ9 zc&4n4TTVWi`?y~|^uUew23qkv0zOCINNwz}=|S(6afWdE^+8d~s)`Xunz&?MkXw8+ z1A5?jTL>Mih=3(X`1Z#XA5Sf!tj%;; z&JFlPkt0A4i2N`d1l);0Ue@i4t1k30^U4n<_Wx!BT@QTpH%3Z@yhwLsoxZ%CKHr^S zq8AVt;si2BWiAYAv)z9XGMy)cIq-brdF!UErx)-je!?%S<`DE?i0=oK@7V-e4Vk@B z`BqkMkiLEV8wairzS{rx@2eMc|GV8ces=p9^uV*X6S^Pb1U6mzHYM~k+x5Y^v1wixdJP4?WK#JYBUlKl^TAI07&J9?P$Pu6iRDc){ z0xBZVVfXDF!pBof8)=er16D0^1n2=3AjX4$8xeTn^doDk&RCgrGoqm>ivB#)TkmX0 z*|YCME^X=Wj2)N{J#nMGfmS?^Kv8r|fWc~I{b}D{{JWWSJ;+MSHgx&dbLm>|b?>}c z&;!rgLg?6y2#lINP`U3bn;!UX4xSK8*Msm&TX$4F^#PaK#y%1Ck96pP8|@9W;&}v` z2j?aozH0mX3rh?y2kYp1pt)!E`uOO(T*|My{BUH-)K0wACs0(WKK&Z>ovC+k6pUgQQ_l^j7mjiC|{00Ak0D6`StaG;e6FW!0-W}(F9-Q@!KeJ*E zf1|L@Tp7>+J#hIM!~sFTc?5FCYz{a$VEg?C(H|uq)H$FBdsZE;jY{SjDIfp&d_yht zz@JXsHN zqeO1-qLL%1rZE%(0wCaW0{Ir7pT2j&mL~?inxB(Ef1YXCTEC*%A%Ei1NBT&gBt3U| z12`ZEfPm8o%-sHpx#F6YjhtUTkdj2#gSe(!vHZh7a;bV+r*>x&^uTFn1`C0J>j~^E zI#gu3Y-O6PvFlqC>3UFpsBGJqmG5yW#QWC2C!|6TTz?92L=bQqf#a*R6#+M_%yeJM zj!mZPLD>G-rkuVSF5NG_JflPhJ#gBY!9pP50D(%`x3hb~$20N@!6G+UtKS+v>fB*=P2@IEgJF6Bxo~#EMN^VfE1Fqx7~lRXl+wVk^_41g7#*?JssDszdrt# zpZo!O;Jj0Vl|aDd1g3SKpZaK@O%K+roZB+P0X^vQP7O+!%}?cq?(uE5{aqHkKmY`W zECI{s+s6OJcK<>Bg~55|WC!%Xyt4nmzZjpD8!;tdob5gYyg&d1{%-{I!nZT2UH`zx zGio92TSaaVZ_g2^3t&75fPiWU7=>?V(o)&Sll8!)uW2)u{-XA8;t(cLpL!e@rfNtAd*4+TQ}4SS&Bw4abl;^a5%fyN`5=1rhD-cj8- He8UF-Gy-#N diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_3qubits_10control_HYHY.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_3qubits_10control_HYHY.npy deleted file mode 100644 index f6152f0fe5851439ef848bb2dab1f67232286621..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUG7CH(RnmP)#3giN=-~U12zWpxN3bhL40j>wrDjyn5e`cSt=GmonOPB0tKm#05@w*Up81iV! zhdehz!s^?*Rj=*myzPHK$8)7Ux`#OACLCGy>HdC=KXc}Z8qXc_;c*`tuM#-oUjQO* ze{Y1u{|;!pGDG7TJ^trG(#8HTNIDtv@sI95Qu+5{@1;n!ytUu)?8Ob?U#l>}BOuvp zieBWj{aQM^-PRS~z>p`^zQL~k$7l9u@k#IOJsRixTOD0(kM7<&m0LSD?z_DI#=Pss zx49<``S8$&#%lvKp3(hV07)124`h1d}xw;c?)7@55tOef#IIy?Q^b^8$uEx_LvL-}icI>>Sni z_6M?~6jzt8u}AmMmJ_U(H|3w%A8_nNRq@}>As-&oA@ORj0EuS||2}}G3s^cq50Bx- zPdEyWSNk21c*Y2i7tnM8ODDt4|D^iINk&GiCG@?$0PBWV#fve^4~M#VYnPL4`|n+E zT^I1~#E{P~gu=&X(H-rqviJ59^IS4o4`P-dM>tllzix1Bzrdse3zR)thJ1M3gv6^o p10=SMImQ40 diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_4qubits_213control_RXRYRXRYRXRY.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_4qubits_213control_RXRYRXRYRXRY.npy deleted file mode 100644 index 33376d86ae7f4a392cbfed5fb6c2e9d63a9fad61..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4224 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGhGsenhGv>N3bhL40j>wrDjyn5e`cSt=GmonOPB2DfYNs%;`S~|Pk))n6v_US_# z>dywKzY8GYVE+ITF2mmcgwjWZgw?lqt6tmBdE5Vfj^|2y4!H?OR(-m^U*pf5d7{R1 zhkf|$fclde>hF1waM&LP3728-e?sZw$7l9u@k#IOJsRixTOD0(U#D_w$Hsk^_urUz z-S{^5q+uUE9P=Umtb+Qx0um1Uq2V&@{ZA-;=*-HC{B!%A{TFqw?gW!H`wzVDeR#~O zZ~q*&SMR5FUKsY_Gacejdj*KU?H@qH0TwQV!f&|CJITmswS>O67hv7+s(A65{SI~U z)-EU8_TRhSx-Q_|iD92UZbJNN&j9ha{T&uaIKaYXxW_*s|Mk6|8aqezz5RjgD8<#~ zYwWk2V7`(h05P#dhfQADsTnL5VaF_R4bVoa@?7h9j vJeQ2tgKO-MaI9Q^-Qd`Mfk_7zD0{RF`}8qkGT5K?dm#R{4}pe*7c^V|-nu!) diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_6qubits_31405control_RYRX_alternating.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_diagonal_simplification_6qubits_31405control_RYRX_alternating.npy deleted file mode 100644 index adee71b7b8f374c4fc4c6cbf92829c58d8c85c97..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65664 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 wfDdGUM!*PqGl4X}Q))j<;Nc*i_@% diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_3qubits_01control_HXHX.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_3qubits_01control_HXHX.npy deleted file mode 100644 index 13aad2cb5b0ef4c6363838c6f42b1fdd6609b680..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUG7CH(RnmP)#3giN=>f(}cMK(bk5TL%h|l5SvlDknV3$D+pEG+<-3K!lyIyRfCm{aY y50N81{h*tP>OOQ?!hCS_|DV4HMibV7Uj=IXFMy^?{CbdOQNw2f#qm$=@Bsk+lZf*G diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_3qubits_10control_HYHY.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_3qubits_10control_HYHY.npy deleted file mode 100644 index 060bc316014a89b8005e8aae058aad56e81e0237..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUG7CH(RnmP)#3giN=6V~-<^AK^o(!`GHb=2?~wR|<`ZN&{Oq&f@NL@ziLW+_!v|s; JJUvnD9suhHlHdRU diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY.npy deleted file mode 100644 index ba5dd7003cf008c318f258552d84cbe5168cea85..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4224 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUGhGsenhGv>N3bhL40j|T=^=b1KJhNv2gClz&G&;G#<^V+9esr1P#-HO8 z@uF(mYiM{J!U&HK|3TnBG#n1v3=jWLusMVyeh;FgkKy54e7<-CiC<{^W2&5ml0J?N zllaH_ z_WW6zX|vN3bhL40j|T=^=b1KJhNv2gClz&^ah&)5b^yG{%|KJ*c^iT z_aI7m40nGM_Mc_1(fRBNZ=va92Rwa*tX#M3QSFTplRiSB{d=pP!!e9-4oSLDR>oJ%5&F+U)poVuYlR88+Xb{zVOs z6(c@@8X3(X(Q0$9}D)dp@hc)8vy*|%47fl diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_6qubits_31405control_RYRX_alternating.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_no_simplification_6qubits_31405control_RYRX_alternating.npy deleted file mode 100644 index df4180c7dcefa0b914cb6affb0a5c4b665073424..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65664 zcmeI5ZA=tL7{{+xO@l#A4N9xlGaA5#YDx(z-H1VK39+@QRWUK~ti0F=VvkBKid|c4 zYzj!KwO9?IFIs$~h=6Fe98y3KQdAU4#dz{ojzjQWo7z~s*-iGoIpdezng4xLZ}543 z^X#8_{xiF~^WDYoytnK*OPXb4U|f9aDtlnKHBeghdRSnvH83H?{)v6%+7&7GxcDdk z@Yc$t)cC=_PhGuoUHssmgTg|Ctzn_lf~^@=wvSK5v7uY0EWM)a3yay2HSxM)q5PbD z{Nyv>Aj9e;%HjAr2!Mcr2vm>XlKIV-0}-B(WEZs_#HNne{Bn3L(F3`Gdc1*9AQTV~ zo`7vZY)^Wbo9KbXO?^F=dDaKl)b+t>tPg~*C;%`sfjyD4hUOhn*9Q!GP!KgLZpF+h zRSznu^~B8V5IYDMjll1ue^-CM7f}Cx-c~q$|CtL;Y2$&8rt7`esmB|Q2jT$%kqIP` z{$16B66))@+si-brXDXcd*luRMk8>F^zW)3&_}mf?;pr6=m9-Ki~s?{5U3#iyQ&8= z_4Qn-_YdS=>hXpFfj~e21fH3|jyFF~$jk3m^`u8(`+FD(fk5=?zHfU^KI{6RL@E3!d6ZviCG@}ulZqGwCr~8iGxqOCz5E0CiGmjr z(&vIe0dxNz;O!q^e}Rj7KoVjTsFr3i_U~h}^!@?PBZ*x{$es@ZHOel={yjnOAK*L^ zALW27K!A}z@dr_3MyB*L?=QUKnlW>rwpsNN%h7Kz&I5*lfDs7zC+<#|U#&g=ppW`I zQretD?u;uHL=WUD>hVTkfEYjk1PFl%uGnGwLNq<-rM{l$&mYo}v$0WKAK>{1s4EaK zGXZg7A^p3m2gj)U|1dBA0D55lia-V;6YwMb zyQ&Ad)OwJk_YW*?>h*=l?2$VN@J8TFMfBTOd$jWh-PC$QcvL7!!*`y}$T{cWjSunx z0g(ud921wB(Wvb&bTR5dgEBGYwKqx*D$oOwiU+xJN5HoHLDTmis(N5yeICjYseJ!O zQu)I==mB>Xf`mjOaB=GSpR-yuJ)nOd3t?3tJvcs1IWX=d^gyKIL9PZRAS-E%{dvDC}hzi-m}2XYto8;v@}hzkUs zn!r)zE@S_GSMMLlcpl1ABgYp(fI|XpF-zROS2aJelX}1L*Ro|H-FYX7k7#vbeZgUk zASH1K9G=(dxHO=hKX6g&LFi8Bc%LyvsvfjK55y@QWXd6dC6W854eQj_2lV@m+4rnl z-BU`59@s8I4>+t5q$Cc3?B%QOOu4PC4{lJe52lBQmIl=vC3+y&Ko7(z9b{@i0zaJa z^zRX#&~COH5dT>E`oHaY7%dZ4_fSLjU6BGC+x#H}EJDMKQzkk7#y0bxPsarTCs`Lo- zz{ItH&^aMsYnEcRu=VdrdjA0TJ9APHNJD4>`L)vdPPYEtzh37b;QmIT%LvKyLZDt+ zlg-w@r+fPc)ccjZ5JVmzzzu=QGAX>Ct$!c8R_7nccwZYgHGxDxfPuiKcAuN$T$-Oq ze}Ci0qyBY&G5NUaBNk$P!7v9H0s=-Mu;8og*hkv_f=quNsdf90i~P$E6Ftapf*u%U zVi5-jFc3I2=Re=S`_%P;Ouyf_?arRk#T$;P>w`>%Ap{Ho0T3_p`8~KfwLYVipmy=7vB5>EDSS%q^$Z zgIX{DppSYyHz1J+2!Mbk;8T0C)vcXBV0^!`?Jwna?jP;Z^NJln3IZTtUIGnWFW#uR ztL-n)pFi+qKKh)pp>81hOv54Qfq6>-k%NHc{@zzx*7s}c1N!rc1Yxlfnf_98WQF=( z7H|at5HKZyfaK3QCg0cA2lVHWJW05-y%j%6J%?aQ5(pXucp;FlJimvne~ diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_3qubits_10control_HYHY.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_3qubits_10control_HYHY.npy deleted file mode 100644 index 711ae8420e9923bfff65f2eb89de8ec722d771a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1152 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlWb_FuA`uymS0p-l$aNvUzCyxl5k7RDNY57 z7iT0EqyqUG7CH(RnmP)#3giN=6V~-<^A@|}J;dw8t`gO}fp^bY zaQIBya|TLdcQql=J$ruq2Z8td38^F`Pi^<0hW7%JN3bhL40j|T=^=b1KJhNv2gClz&G&;G#<^V+9esr1P#-HO8 z@uF(mYiM{J!U&HK|3TnBG#n1v3=jWLusMVyeh;FgkKy54e7<-CiC<{^W2&5ml0J?N zllaH_ z_WW6zX|vN3bhL40j|T=^=b1KJhNv2gClz&^ah&)5b^yG{%|KJ*c^iT z_aI7m40nGM_Mc_1(fRBNZ=va92Rwa*tX#M3QSFTplRiSB{d=pP!!e9-4oSLDR>oJ%5&F+U)poVuYlR88+Xb{zVOs z6(c@@8X3(X(Q0$9}D)dp@hc)8vy*|%47fl diff --git a/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_6qubits_31405control_RYRX_alternating.npy b/tests/circuit/gate_matrix_checkers/UC_unitary_matrix_no_diagonal_simplification_6qubits_31405control_RYRX_alternating.npy deleted file mode 100644 index df4180c7dcefa0b914cb6affb0a5c4b665073424..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65664 zcmeI5ZA=tL7{{+xO@l#A4N9xlGaA5#YDx(z-H1VK39+@QRWUK~ti0F=VvkBKid|c4 zYzj!KwO9?IFIs$~h=6Fe98y3KQdAU4#dz{ojzjQWo7z~s*-iGoIpdezng4xLZ}543 z^X#8_{xiF~^WDYoytnK*OPXb4U|f9aDtlnKHBeghdRSnvH83H?{)v6%+7&7GxcDdk z@Yc$t)cC=_PhGuoUHssmgTg|Ctzn_lf~^@=wvSK5v7uY0EWM)a3yay2HSxM)q5PbD z{Nyv>Aj9e;%HjAr2!Mcr2vm>XlKIV-0}-B(WEZs_#HNne{Bn3L(F3`Gdc1*9AQTV~ zo`7vZY)^Wbo9KbXO?^F=dDaKl)b+t>tPg~*C;%`sfjyD4hUOhn*9Q!GP!KgLZpF+h zRSznu^~B8V5IYDMjll1ue^-CM7f}Cx-c~q$|CtL;Y2$&8rt7`esmB|Q2jT$%kqIP` z{$16B66))@+si-brXDXcd*luRMk8>F^zW)3&_}mf?;pr6=m9-Ki~s?{5U3#iyQ&8= z_4Qn-_YdS=>hXpFfj~e21fH3|jyFF~$jk3m^`u8(`+FD(fk5=?zHfU^KI{6RL@E3!d6ZviCG@}ulZqGwCr~8iGxqOCz5E0CiGmjr z(&vIe0dxNz;O!q^e}Rj7KoVjTsFr3i_U~h}^!@?PBZ*x{$es@ZHOel={yjnOAK*L^ zALW27K!A}z@dr_3MyB*L?=QUKnlW>rwpsNN%h7Kz&I5*lfDs7zC+<#|U#&g=ppW`I zQretD?u;uHL=WUD>hVTkfEYjk1PFl%uGnGwLNq<-rM{l$&mYo}v$0WKAK>{1s4EaK zGXZg7A^p3m2gj)U|1dBA0D55lia-V;6YwMb zyQ&Ad)OwJk_YW*?>h*=l?2$VN@J8TFMfBTOd$jWh-PC$QcvL7!!*`y}$T{cWjSunx z0g(ud921wB(Wvb&bTR5dgEBGYwKqx*D$oOwiU+xJN5HoHLDTmis(N5yeICjYseJ!O zQu)I==mB>Xf`mjOaB=GSpR-yuJ)nOd3t?3tJvcs1IWX=d^gyKIL9PZRAS-E%{dvDC}hzi-m}2XYto8;v@}hzkUs zn!r)zE@S_GSMMLlcpl1ABgYp(fI|XpF-zROS2aJelX}1L*Ro|H-FYX7k7#vbeZgUk zASH1K9G=(dxHO=hKX6g&LFi8Bc%LyvsvfjK55y@QWXd6dC6W854eQj_2lV@m+4rnl z-BU`59@s8I4>+t5q$Cc3?B%QOOu4PC4{lJe52lBQmIl=vC3+y&Ko7(z9b{@i0zaJa z^zRX#&~COH5dT>E`oHaY7%dZ4_fSLjU6BGC+x#H}EJDMKQzkk7#y0bxPsarTCs`Lo- zz{ItH&^aMsYnEcRu=VdrdjA0TJ9APHNJD4>`L)vdPPYEtzh37b;QmIT%LvKyLZDt+ zlg-w@r+fPc)ccjZ5JVmzzzu=QGAX>Ct$!c8R_7nccwZYgHGxDxfPuiKcAuN$T$-Oq ze}Ci0qyBY&G5NUaBNk$P!7v9H0s=-Mu;8og*hkv_f=quNsdf90i~P$E6Ftapf*u%U zVi5-jFc3I2=Re=S`_%P;Ouyf_?arRk#T$;P>w`>%Ap{Ho0T3_p`8~KfwLYVipmy=7vB5>EDSS%q^$Z zgIX{DppSYyHz1J+2!Mbk;8T0C)vcXBV0^!`?Jwna?jP;Z^NJln3IZTtUIGnWFW#uR ztL-n)pFi+qKKh)pp>81hOv54Qfq6>-k%NHc{@zzx*7s}c1N!rc1Yxlfnf_98WQF=( z7H|at5HKZyfaK3QCg0cA2lVHWJW05-y%j%6J%?aQ5(pXucp;FlJimvne~ None: - """ Test the `Multiplexor` gate without diagonal and without simplification. + """ Test the `Multiplexor` gate. Parameters ---------- @@ -386,395 +337,59 @@ def test_Multiplexor_no_diagonal_no_simplification( The quantum circuit framework. `single_qubit_gates` : list[NDArray[np.complex128]] The single-qubit gates. - `control_indices` : list[int] - The control qubits. - `target_index` : int - The target qubit. - `expected` : NDArray[np.complex128] - The expected unitary matrix. + `up_to_diagonal` : bool, optional, default=False + Determines if the gate is implemented up to a diagonal + or if it is decomposed completely. + `multiplexor_simplification` : bool, optional, default=True + Determines if the multiplexor is simplified. """ - # Define the quantum circuit - circuit = circuit_framework(len(control_indices) + 1) - - # Apply the Multiplexor gate - circuit.Multiplexor( - single_qubit_gates, - control_indices, - target_index, - up_to_diagonal=False, - multiplexor_simplification=False + from quick.circuit.circuit_utils import ( + extract_single_qubits_and_diagonal, + simplify ) - # Ensure the unitary matrix is correct - assert_almost_equal(circuit.get_unitary(), expected, 8) - - # @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) - # @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ - # [ - # [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], - # [0, 1], - # 2, - # UC_unitary_matrix_diagonal_no_simplification_3qubits_01control_HXHX - # ], - # [ - # [Hadamard().matrix, PauliY().matrix, Hadamard().matrix, PauliY().matrix], - # [1, 0], - # 2, - # UC_unitary_matrix_diagonal_no_simplification_3qubits_10control_HYHY - # ], - # [ - # [ - # RX(np.pi/2).matrix, - # RY(np.pi/3).matrix, - # RX(np.pi/4).matrix, - # RY(np.pi/5).matrix, - # RX(np.pi/6).matrix, - # RY(np.pi/7).matrix, - # RX(np.pi/8).matrix, - # RY(np.pi/9).matrix - # ], - # [0, 2, 3], - # 1, - # UC_unitary_matrix_diagonal_no_simplification_4qubits_023control_RXRYRXRYRXRY - # ], - # [ - # [ - # RX(np.pi/2).matrix, - # RY(np.pi/3).matrix, - # RX(np.pi/4).matrix, - # RY(np.pi/5).matrix, - # RX(np.pi/6).matrix, - # RY(np.pi/7).matrix, - # RX(np.pi/8).matrix, - # RY(np.pi/9).matrix - # ], - # [2, 1, 3], - # 0, - # UC_unitary_matrix_diagonal_no_simplification_4qubits_213control_RXRYRXRYRXRY - # ], - # [ - # [ - # RY(np.pi).matrix, - # RX(np.pi/2).matrix, - # RY(np.pi/3).matrix, - # RX(np.pi/4).matrix, - # RY(np.pi/5).matrix, - # RX(np.pi/6).matrix, - # RY(np.pi/7).matrix, - # RX(np.pi/8).matrix, - # RY(np.pi/9).matrix, - # RX(np.pi/10).matrix, - # RY(np.pi/11).matrix, - # RX(np.pi/12).matrix, - # RY(np.pi/13).matrix, - # RX(np.pi/14).matrix, - # RY(np.pi/15).matrix, - # RX(np.pi/16).matrix, - # RY(np.pi/17).matrix, - # RX(np.pi/18).matrix, - # RY(np.pi/19).matrix, - # RX(np.pi/20).matrix, - # RY(np.pi/21).matrix, - # RX(np.pi/22).matrix, - # RY(np.pi/23).matrix, - # RX(np.pi/24).matrix, - # RY(np.pi/25).matrix, - # RX(np.pi/26).matrix, - # RY(np.pi/27).matrix, - # RX(np.pi/28).matrix, - # RY(np.pi/29).matrix, - # RX(np.pi/30).matrix, - # RY(np.pi/31).matrix, - # RX(np.pi/32).matrix - # ], - # [3, 1, 4, 0, 5], - # 2, - # UC_unitary_matrix_diagonal_no_simplification_6qubits_31405control_RYRX_alternating - # ] - # ]) - # def test_Multiplexor_diagonal_no_simplification( - # self, - # circuit_framework: type[Circuit], - # single_qubit_gates: list[NDArray[np.complex128]], - # control_indices: list[int], - # target_index: int, - # expected: NDArray[np.complex128] - # ) -> None: - # """ Test the `Multiplexor` gate with diagonal and without simplification. - - # Parameters - # ---------- - # `circuit_framework` : type[quick.circuit.Circuit] - # The quantum circuit framework. - # `single_qubit_gates` : list[NDArray[np.complex128]] - # The single-qubit gates. - # `control_indices` : list[int] - # The control qubits. - # `target_index` : int - # The target qubit. - # `expected` : NDArray[np.complex128] - # The expected unitary matrix. - # """ - # # Define the quantum circuit - # circuit = circuit_framework(len(control_indices) + 1) - - # # Apply the Multiplexor gate - # circuit.Multiplexor( - # single_qubit_gates, - # control_indices, - # target_index, - # up_to_diagonal=True, - # multiplexor_simplification=False - # ) - - # # Ensure the unitary matrix is correct - # assert_almost_equal(circuit.get_unitary(), expected, 8) + num_controls = int( + np.log2( + len(single_qubit_gates) + ) + ) - @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) - @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ - [ - [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], - [0, 1], - 2, - UC_unitary_matrix_no_diagonal_simplification_3qubits_01control_HXHX - ], - [ - [Hadamard().matrix, PauliY().matrix, Hadamard().matrix, PauliY().matrix], - [1, 0], - 2, - UC_unitary_matrix_no_diagonal_simplification_3qubits_10control_HYHY - ], - [ - [ - RX(np.pi/2).matrix, - RY(np.pi/3).matrix, - RX(np.pi/4).matrix, - RY(np.pi/5).matrix, - RX(np.pi/6).matrix, - RY(np.pi/7).matrix, - RX(np.pi/8).matrix, - RY(np.pi/9).matrix - ], - [0, 2, 3], - 1, - UC_unitary_matrix_no_diagonal_simplification_4qubits_023control_RXRYRXRYRXRY - ], - [ - [ - RX(np.pi/2).matrix, - RY(np.pi/3).matrix, - RX(np.pi/4).matrix, - RY(np.pi/5).matrix, - RX(np.pi/6).matrix, - RY(np.pi/7).matrix, - RX(np.pi/8).matrix, - RY(np.pi/9).matrix - ], - [2, 1, 3], - 0, - UC_unitary_matrix_no_diagonal_simplification_4qubits_213control_RXRYRXRYRXRY - ], - [ - [ - RY(np.pi).matrix, - RX(np.pi/2).matrix, - RY(np.pi/3).matrix, - RX(np.pi/4).matrix, - RY(np.pi/5).matrix, - RX(np.pi/6).matrix, - RY(np.pi/7).matrix, - RX(np.pi/8).matrix, - RY(np.pi/9).matrix, - RX(np.pi/10).matrix, - RY(np.pi/11).matrix, - RX(np.pi/12).matrix, - RY(np.pi/13).matrix, - RX(np.pi/14).matrix, - RY(np.pi/15).matrix, - RX(np.pi/16).matrix, - RY(np.pi/17).matrix, - RX(np.pi/18).matrix, - RY(np.pi/19).matrix, - RX(np.pi/20).matrix, - RY(np.pi/21).matrix, - RX(np.pi/22).matrix, - RY(np.pi/23).matrix, - RX(np.pi/24).matrix, - RY(np.pi/25).matrix, - RX(np.pi/26).matrix, - RY(np.pi/27).matrix, - RX(np.pi/28).matrix, - RY(np.pi/29).matrix, - RX(np.pi/30).matrix, - RY(np.pi/31).matrix, - RX(np.pi/32).matrix - ], - [3, 1, 4, 0, 5], - 2, - UC_unitary_matrix_no_diagonal_simplification_6qubits_31405control_RYRX_alternating - ] - ]) - def test_Multiplexor_no_diagonal_simplification( - self, - circuit_framework: type[Circuit], - single_qubit_gates: list[NDArray[np.complex128]], - control_indices: list[int], - target_index: int, - expected: NDArray[np.complex128] - ) -> None: - """ Test the `Multiplexor` gate without diagonal and with simplification. + qubits = list(range(num_controls + 1)) + target_index = qubits[0] + control_indices = qubits[1:] - Parameters - ---------- - `circuit_framework` : type[quick.circuit.Circuit] - The quantum circuit framework. - `single_qubit_gates` : list[NDArray[np.complex128]] - The single-qubit gates. - `control_indices` : list[int] - The control qubits. - `target_index` : int - The target qubit. - `expected` : NDArray[np.complex128] - The expected unitary matrix. - """ # Define the quantum circuit - circuit = circuit_framework(len(control_indices) + 1) + circuit = circuit_framework(num_controls + 1) + expected = block_diag(*single_qubit_gates) # Apply the Multiplexor gate circuit.Multiplexor( single_qubit_gates, control_indices, target_index, - up_to_diagonal=False, - multiplexor_simplification=True + up_to_diagonal=up_to_diagonal, + multiplexor_simplification=multiplexor_simplification ) - # Ensure the unitary matrix is correct - assert_almost_equal(circuit.get_unitary(), expected, 8) + unitary = circuit.get_unitary() - # @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) - @pytest.mark.parametrize("single_qubit_gates, control_indices, target_index, expected", [ - [ - [Hadamard().matrix, PauliX().matrix, Hadamard().matrix, PauliX().matrix], - [0, 1], - 2, - UC_unitary_matrix_diagonal_simplification_3qubits_01control_HXHX - ], - [ - [Hadamard().matrix, PauliY().matrix, Hadamard().matrix, PauliY().matrix], - [1, 0], - 2, - UC_unitary_matrix_diagonal_simplification_3qubits_10control_HYHY - ], - [ - [ - RX(np.pi/2).matrix, - RY(np.pi/3).matrix, - RX(np.pi/4).matrix, - RY(np.pi/5).matrix, - RX(np.pi/6).matrix, - RY(np.pi/7).matrix, - RX(np.pi/8).matrix, - RY(np.pi/9).matrix - ], - [0, 2, 3], - 1, - UC_unitary_matrix_diagonal_simplification_4qubits_023control_RXRYRXRYRXRY - ], - [ - [ - RX(np.pi/2).matrix, - RY(np.pi/3).matrix, - RX(np.pi/4).matrix, - RY(np.pi/5).matrix, - RX(np.pi/6).matrix, - RY(np.pi/7).matrix, - RX(np.pi/8).matrix, - RY(np.pi/9).matrix - ], - [2, 1, 3], - 0, - UC_unitary_matrix_diagonal_simplification_4qubits_213control_RXRYRXRYRXRY - ], - [ - [ - RY(np.pi).matrix, - RX(np.pi/2).matrix, - RY(np.pi/3).matrix, - RX(np.pi/4).matrix, - RY(np.pi/5).matrix, - RX(np.pi/6).matrix, - RY(np.pi/7).matrix, - RX(np.pi/8).matrix, - RY(np.pi/9).matrix, - RX(np.pi/10).matrix, - RY(np.pi/11).matrix, - RX(np.pi/12).matrix, - RY(np.pi/13).matrix, - RX(np.pi/14).matrix, - RY(np.pi/15).matrix, - RX(np.pi/16).matrix, - RY(np.pi/17).matrix, - RX(np.pi/18).matrix, - RY(np.pi/19).matrix, - RX(np.pi/20).matrix, - RY(np.pi/21).matrix, - RX(np.pi/22).matrix, - RY(np.pi/23).matrix, - RX(np.pi/24).matrix, - RY(np.pi/25).matrix, - RX(np.pi/26).matrix, - RY(np.pi/27).matrix, - RX(np.pi/28).matrix, - RY(np.pi/29).matrix, - RX(np.pi/30).matrix, - RY(np.pi/31).matrix, - RX(np.pi/32).matrix - ], - [3, 1, 4, 0, 5], - 2, - UC_unitary_matrix_diagonal_simplification_6qubits_31405control_RYRX_alternating - ] - ]) - def test_Multiplexor_diagonal_simplification( - self, - # circuit_framework: type[Circuit], - single_qubit_gates: list[NDArray[np.complex128]], - control_indices: list[int], - target_index: int, - expected: NDArray[np.complex128] - ) -> None: - """ Test the `Multiplexor` gate with diagonal and simplification. - - Parameters - ---------- - `circuit_framework` : type[quick.circuit.Circuit] - The quantum circuit framework. - `single_qubit_gates` : list[NDArray[np.complex128]] - The single-qubit gates. - `control_indices` : list[int] - The control qubits. - `target_index` : int - The target qubit. - `expected` : NDArray[np.complex128] - The expected unitary matrix. - """ - from quick.circuit import QiskitCircuit + if multiplexor_simplification: + new_controls, single_qubit_gates = simplify(single_qubit_gates, num_controls) + control_indices = [qubits[len(qubits) - i] for i in new_controls] + control_indices.reverse() - # Define the quantum circuit - circuit = QiskitCircuit(len(control_indices) + 1) + if up_to_diagonal: + _, diagonal = extract_single_qubits_and_diagonal( + single_qubit_gates, len(control_indices) + 1 + ) + diagonal_circuit = circuit_framework(circuit.num_qubits) + diagonal_circuit.Diagonal(diagonal, [target_index] + control_indices) + diagonal_unitary = diagonal_circuit.get_unitary() - # Apply the Multiplexor gate - circuit.Multiplexor( - single_qubit_gates, - control_indices, - target_index, - up_to_diagonal=True, - multiplexor_simplification=True - ) + unitary = np.dot(diagonal_unitary, unitary) # Ensure the unitary matrix is correct - assert_almost_equal(circuit.get_unitary(), expected, 8) + assert_almost_equal(unitary, expected, 8) @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_PauliMultiplexor_invalid_num_angles( From a784b6e5c5934ad61b57c6d2fbd2f9321fb92a7c Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Sun, 13 Jul 2025 00:47:56 +0800 Subject: [PATCH 28/33] - Fixes style for E252. --- quick/backend/backend.py | 10 +++--- quick/backend/qiskit_backends/aer_backend.py | 8 ++--- .../qiskit_backends/fake_ibm_backend.py | 4 +-- quick/circuit/circuit.py | 32 +++++++++---------- quick/circuit/cirqcircuit.py | 2 +- quick/circuit/pennylanecircuit.py | 2 +- quick/circuit/qiskitcircuit.py | 2 +- quick/circuit/quimbcircuit.py | 2 +- quick/circuit/tketcircuit.py | 2 +- quick/compiler/compiler.py | 6 ++-- quick/predicates/predicates.py | 32 +++++++++---------- .../one_qubit_decomposition.py | 2 +- .../two_qubit_decomposition/weyl.py | 4 +-- quick/synthesis/statepreparation/isometry.py | 4 +-- quick/synthesis/statepreparation/mottonen.py | 4 +-- quick/synthesis/statepreparation/shende.py | 4 +-- .../statepreparation/statepreparation.py | 8 ++--- .../synthesis/unitarypreparation/diffusion.py | 10 +++--- .../qiskit_unitary_transpiler.py | 4 +-- .../shannon_decomposition.py | 4 +-- .../qiskit_backends/test_ibm_backend.py | 2 +- 21 files changed, 74 insertions(+), 74 deletions(-) diff --git a/quick/backend/backend.py b/quick/backend/backend.py index 9eec55a..f2c0028 100644 --- a/quick/backend/backend.py +++ b/quick/backend/backend.py @@ -58,7 +58,7 @@ class Backend(ABC): """ def __init__( self, - device: str="CPU" + device: str = "CPU" ) -> None: """ Initialize a `quick.backend.Backend` instance. """ @@ -176,7 +176,7 @@ def get_operator( def get_counts( self, circuit: Circuit, - num_shots: int=1024 + num_shots: int = 1024 ) -> dict[str, int]: """ Get the counts of the backend. @@ -331,7 +331,7 @@ def __init__( self, single_qubit_error: float, two_qubit_error: float, - device: str="CPU" + device: str = "CPU" ) -> None: """ Initialize a `quick.backend.NoisyBackend` instance. """ @@ -380,7 +380,7 @@ class FakeBackend(Backend, ABC): """ def __init__( self, - device: str="CPU" + device: str = "CPU" ) -> None: """ Initialize a `quick.backend.FakeBackend` instance. """ @@ -451,7 +451,7 @@ def get_operator( def get_counts( self, circuit: Circuit, - num_shots: int=1024 + num_shots: int = 1024 ) -> dict[str, int]: """ Get the counts of the backend. diff --git a/quick/backend/qiskit_backends/aer_backend.py b/quick/backend/qiskit_backends/aer_backend.py index ba08866..7d17be9 100644 --- a/quick/backend/qiskit_backends/aer_backend.py +++ b/quick/backend/qiskit_backends/aer_backend.py @@ -87,9 +87,9 @@ class AerBackend(NoisyBackend): """ def __init__( self, - single_qubit_error: float=0.0, - two_qubit_error: float=0.0, - device: str="CPU" + single_qubit_error: float = 0.0, + two_qubit_error: float = 0.0, + device: str = "CPU" ) -> None: super().__init__( @@ -189,7 +189,7 @@ def get_operator( def get_counts( self, circuit: Circuit, - num_shots: int=1024 + num_shots: int = 1024 ) -> dict[str, int]: if len(circuit.measured_qubits) == 0: diff --git a/quick/backend/qiskit_backends/fake_ibm_backend.py b/quick/backend/qiskit_backends/fake_ibm_backend.py index 1f1a4e6..e78d3d9 100644 --- a/quick/backend/qiskit_backends/fake_ibm_backend.py +++ b/quick/backend/qiskit_backends/fake_ibm_backend.py @@ -89,7 +89,7 @@ def __init__( self, hardware_name: str, qiskit_runtime: QiskitRuntimeService, - device: str="CPU" + device: str = "CPU" ) -> None: super().__init__(device=device) @@ -161,7 +161,7 @@ def get_operator( def get_counts( self, circuit: Circuit, - num_shots: int=1024 + num_shots: int = 1024 ) -> dict[str, int]: result = self._counts_backend.run([circuit.circuit], shots=num_shots).result() diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index 35b0fff..d9871c9 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -4718,8 +4718,8 @@ def Multiplexor( single_qubit_gates: list[NDArray[np.complex128]], control_indices: int | Sequence[int], target_index: int, - up_to_diagonal: bool=False, - multiplexor_simplification: bool=True, + up_to_diagonal: bool = False, + multiplexor_simplification: bool = True, control_state: str | None = None ) -> None: """ Apply a multiplexed/uniformly controlled gate to the circuit. @@ -4913,9 +4913,9 @@ def merge_global_phases(self) -> None: def QFT( self, qubit_indices: int | Sequence[int], - do_swaps: bool=True, - approximation_degree: int=0, - inverse: bool=False + do_swaps: bool = True, + approximation_degree: int = 0, + inverse: bool = False ) -> None: r""" Apply the Quantum Fourier Transform to the circuit. @@ -5089,7 +5089,7 @@ def vertical_reverse(self) -> None: @staticmethod def _horizontal_reverse( circuit_log: list[dict[str, Any]], - adjoint: bool=True + adjoint: bool = True ) -> list[dict[str, Any]]: """ Perform a horizontal reverse operation. @@ -5143,7 +5143,7 @@ def _horizontal_reverse( def horizontal_reverse( self, - adjoint: bool=True + adjoint: bool = True ) -> None: """ Perform a horizontal reverse operation. This is equivalent to the adjoint of the circuit if `adjoint=True`. Otherwise, it @@ -5368,7 +5368,7 @@ def get_unitary(self) -> NDArray[np.complex128]: def get_instructions( self, - include_measurements: bool=True + include_measurements: bool = True ) -> list[dict]: """ Get the instructions of the circuit. @@ -5585,7 +5585,7 @@ def remove_measurements( def remove_measurements( self, - inplace: bool=False + inplace: bool = False ) -> Circuit | None: """ Remove the measurement instructions from the circuit. @@ -5612,8 +5612,8 @@ def remove_measurements( def decompose( self, - reps: int=1, - full: bool=False + reps: int = 1, + full: bool = False ) -> Circuit: """ Decompose the gates in the circuit to their implementation gates. @@ -5684,7 +5684,7 @@ def decompose( def transpile( self, - direct_transpile: bool=True, + direct_transpile: bool = True, synthesis_method: UnitaryPreparation | None = None ) -> None: """ Transpile the circuit to U3 and CX gates. @@ -5953,7 +5953,7 @@ def update(self) -> None: @abstractmethod def to_qasm( self, - qasm_version: int=2 + qasm_version: int = 2 ) -> str: """ Convert the circuit to QASM. @@ -6215,7 +6215,7 @@ def draw(self): def plot_histogram( self, - non_zeros_only: bool=False + non_zeros_only: bool = False ) -> plt.Figure: """ Plot the histogram of the circuit. @@ -6338,8 +6338,8 @@ def __eq__( def is_equivalent( self, other_circuit: Circuit, - check_unitary: bool=True, - check_dag: bool=False + check_unitary: bool = True, + check_dag: bool = False ) -> bool: """ Check if the circuit is equivalent to another circuit. diff --git a/quick/circuit/cirqcircuit.py b/quick/circuit/cirqcircuit.py index b140a48..e758bd6 100644 --- a/quick/circuit/cirqcircuit.py +++ b/quick/circuit/cirqcircuit.py @@ -310,7 +310,7 @@ def reset_qubit( def to_qasm( self, - qasm_version: int=2 + qasm_version: int = 2 ) -> str: from quick.circuit import QiskitCircuit diff --git a/quick/circuit/pennylanecircuit.py b/quick/circuit/pennylanecircuit.py index 614214a..3dd5de9 100644 --- a/quick/circuit/pennylanecircuit.py +++ b/quick/circuit/pennylanecircuit.py @@ -306,7 +306,7 @@ def reset_qubit( def to_qasm( self, - qasm_version: int=2 + qasm_version: int = 2 ) -> str: from quick.circuit import QiskitCircuit diff --git a/quick/circuit/qiskitcircuit.py b/quick/circuit/qiskitcircuit.py index 4868d1e..358ae80 100644 --- a/quick/circuit/qiskitcircuit.py +++ b/quick/circuit/qiskitcircuit.py @@ -260,7 +260,7 @@ def reset_qubit( def to_qasm( self, - qasm_version: int=2 + qasm_version: int = 2 ) -> str: if qasm_version == 2: diff --git a/quick/circuit/quimbcircuit.py b/quick/circuit/quimbcircuit.py index a29b6ec..2db772a 100644 --- a/quick/circuit/quimbcircuit.py +++ b/quick/circuit/quimbcircuit.py @@ -279,7 +279,7 @@ def reset_qubit( def to_qasm( self, - qasm_version: int=2 + qasm_version: int = 2 ) -> str: from quick.circuit import QiskitCircuit diff --git a/quick/circuit/tketcircuit.py b/quick/circuit/tketcircuit.py index da4c257..3a6d88f 100644 --- a/quick/circuit/tketcircuit.py +++ b/quick/circuit/tketcircuit.py @@ -273,7 +273,7 @@ def reset_qubit( def to_qasm( self, - qasm_version: int=2 + qasm_version: int = 2 ) -> str: from quick.circuit import QiskitCircuit diff --git a/quick/compiler/compiler.py b/quick/compiler/compiler.py index 0446928..ba62b03 100644 --- a/quick/compiler/compiler.py +++ b/quick/compiler/compiler.py @@ -91,9 +91,9 @@ class Compiler: def __init__( self, circuit_framework: type[Circuit], - state_prep: type[StatePreparation]=Isometry, - unitary_prep: type[UnitaryPreparation]=ShannonDecomposition, - optimizer: Optimizer | None=None + state_prep: type[StatePreparation] = Isometry, + unitary_prep: type[UnitaryPreparation] = ShannonDecomposition, + optimizer: Optimizer | None = None ) -> None: """ Initialize a `quick.compiler.Compiler` object. """ diff --git a/quick/predicates/predicates.py b/quick/predicates/predicates.py index 92dbcc8..d59fc3a 100644 --- a/quick/predicates/predicates.py +++ b/quick/predicates/predicates.py @@ -60,7 +60,7 @@ def _is_power( def is_statevector( statevector: NDArray[np.complex128], - system_size: int=2 + system_size: int = 2 ) -> bool: """ Test if an array is a statevector. @@ -119,8 +119,8 @@ def is_square_matrix(matrix: NDArray[np.complex128]) -> bool: def is_diagonal_matrix( matrix: NDArray[np.complex128], - rtol: float=RTOL_DEFAULT, - atol: float=ATOL_DEFAULT + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT ) -> bool: """ Test if an array is a diagonal matrix. @@ -149,8 +149,8 @@ def is_diagonal_matrix( def is_symmetric_matrix( matrix: NDArray[np.complex128], - rtol: float=RTOL_DEFAULT, - atol: float=ATOL_DEFAULT + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT ) -> bool: """ Test if an array is a symmetric matrix. @@ -179,9 +179,9 @@ def is_symmetric_matrix( def is_identity_matrix( matrix: NDArray[np.complex128], - ignore_phase: bool=False, - rtol: float=RTOL_DEFAULT, - atol: float=ATOL_DEFAULT + ignore_phase: bool = False, + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT ) -> bool: """ Test if an array is an identity matrix. @@ -220,8 +220,8 @@ def is_identity_matrix( def is_unitary_matrix( matrix: NDArray[np.complex128], - rtol: float=RTOL_DEFAULT, - atol: float=ATOL_DEFAULT + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT ) -> bool: """ Test if an array is a unitary matrix. @@ -251,8 +251,8 @@ def is_unitary_matrix( def is_hermitian_matrix( matrix: NDArray[np.complex128], - rtol: float=RTOL_DEFAULT, - atol: float=ATOL_DEFAULT + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT ) -> bool: """ Test if an array is a Hermitian matrix. @@ -281,8 +281,8 @@ def is_hermitian_matrix( def is_positive_semidefinite_matrix( matrix: NDArray[np.complex128], - rtol: float=RTOL_DEFAULT, - atol: float=ATOL_DEFAULT + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT ) -> bool: """ Test if a matrix is positive semidefinite. @@ -316,8 +316,8 @@ def is_positive_semidefinite_matrix( def is_isometry( matrix: NDArray[np.complex128], - rtol: float=RTOL_DEFAULT, - atol: float=ATOL_DEFAULT + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT ) -> bool: """ Test if an array is an isometry. diff --git a/quick/synthesis/gate_decompositions/one_qubit_decomposition.py b/quick/synthesis/gate_decompositions/one_qubit_decomposition.py index 6987b54..d26c9ac 100644 --- a/quick/synthesis/gate_decompositions/one_qubit_decomposition.py +++ b/quick/synthesis/gate_decompositions/one_qubit_decomposition.py @@ -68,7 +68,7 @@ class OneQubitDecomposition(UnitaryPreparation): def __init__( self, output_framework: type[Circuit], - basis: Literal["zyz", "u3"]="u3" + basis: Literal["zyz", "u3"] = "u3" ) -> None: super().__init__(output_framework) diff --git a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py index b9edfef..10032f7 100644 --- a/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py +++ b/quick/synthesis/gate_decompositions/two_qubit_decomposition/weyl.py @@ -86,7 +86,7 @@ def transform_to_magic_basis( U: NDArray[np.complex128], - reverse: bool=False + reverse: bool = False ) -> NDArray[np.complex128]: """ Transform the 4x4 matrix `U` into the magic basis. @@ -186,7 +186,7 @@ def weyl_coordinates(U: NDArray[np.complex128]) -> NDArray[np.float64]: def partition_eigenvalues( eigenvalues: NDArray[np.complex128], - atol: float=1e-13 + atol: float = 1e-13 ) -> list[list[int]]: """ Group the indices of degenerate eigenvalues. diff --git a/quick/synthesis/statepreparation/isometry.py b/quick/synthesis/statepreparation/isometry.py index 4a46d76..f406cec 100644 --- a/quick/synthesis/statepreparation/isometry.py +++ b/quick/synthesis/statepreparation/isometry.py @@ -88,8 +88,8 @@ def apply_state( circuit: Circuit, state: NDArray[np.complex128] | Bra | Ket, qubit_indices: int | Sequence[int], - compression_percentage: float=0.0, - index_type: Literal["row", "snake"]="row" + compression_percentage: float = 0.0, + index_type: Literal["row", "snake"] = "row" ) -> Circuit: if not isinstance(state, (np.ndarray, Bra, Ket)): diff --git a/quick/synthesis/statepreparation/mottonen.py b/quick/synthesis/statepreparation/mottonen.py index 16dd907..94c8e09 100644 --- a/quick/synthesis/statepreparation/mottonen.py +++ b/quick/synthesis/statepreparation/mottonen.py @@ -72,8 +72,8 @@ def apply_state( circuit: Circuit, state: NDArray[np.complex128] | Bra | Ket, qubit_indices: int | Sequence[int], - compression_percentage: float=0.0, - index_type: Literal["row", "snake"]="row" + compression_percentage: float = 0.0, + index_type: Literal["row", "snake"] = "row" ) -> Circuit: if not isinstance(state, (np.ndarray, Bra, Ket)): diff --git a/quick/synthesis/statepreparation/shende.py b/quick/synthesis/statepreparation/shende.py index 2f3059e..51ea24a 100644 --- a/quick/synthesis/statepreparation/shende.py +++ b/quick/synthesis/statepreparation/shende.py @@ -70,8 +70,8 @@ def apply_state( circuit: Circuit, state: NDArray[np.complex128] | Bra | Ket, qubit_indices: int | Sequence[int], - compression_percentage: float=0.0, - index_type: Literal["row", "snake"]="row" + compression_percentage: float = 0.0, + index_type: Literal["row", "snake"] = "row" ) -> Circuit: if not isinstance(state, (np.ndarray, Bra, Ket)): diff --git a/quick/synthesis/statepreparation/statepreparation.py b/quick/synthesis/statepreparation/statepreparation.py index 97dbba8..e9d01a0 100644 --- a/quick/synthesis/statepreparation/statepreparation.py +++ b/quick/synthesis/statepreparation/statepreparation.py @@ -68,8 +68,8 @@ def __init__( def prepare_state( self, state: NDArray[np.complex128] | Bra | Ket, - compression_percentage: float=0.0, - index_type: Literal["row", "snake"]="row" + compression_percentage: float = 0.0, + index_type: Literal["row", "snake"] = "row" ) -> Circuit: """ Prepare the quantum state. @@ -112,8 +112,8 @@ def apply_state( circuit: Circuit, state: NDArray[np.complex128] | Bra | Ket, qubit_indices: int | Sequence[int], - compression_percentage: float=0.0, - index_type: Literal["row", "snake"]="row" + compression_percentage: float = 0.0, + index_type: Literal["row", "snake"] = "row" ) -> Circuit: """ Apply the quantum state to a quantum circuit. diff --git a/quick/synthesis/unitarypreparation/diffusion.py b/quick/synthesis/unitarypreparation/diffusion.py index 6e8de97..545c259 100644 --- a/quick/synthesis/unitarypreparation/diffusion.py +++ b/quick/synthesis/unitarypreparation/diffusion.py @@ -98,11 +98,11 @@ class Diffusion(UnitaryPreparation): def __init__( self, output_framework: type[Circuit], - model: str="Floki00/qc_unitary_3qubit", - prompt: str="Compile using: ['h', 'cx', 'z', 'ccx', 'swap']", - max_num_gates: int=12, - num_samples: int=128, - min_fidelity: float=0.99 + model: str = "Floki00/qc_unitary_3qubit", + prompt: str = "Compile using: ['h', 'cx', 'z', 'ccx', 'swap']", + max_num_gates: int = 12, + num_samples: int = 128, + min_fidelity: float = 0.99 ) -> None: super().__init__(output_framework) diff --git a/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.py b/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.py index e6cabcd..12684a2 100644 --- a/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.py +++ b/quick/synthesis/unitarypreparation/qiskit_unitary_transpiler.py @@ -97,8 +97,8 @@ class QiskitUnitaryTranspiler(UnitaryPreparation): def __init__( self, output_framework: type[Circuit], - ai_transpilation: bool=False, - unitary_synthesis_plugin: str="default", + ai_transpilation: bool = False, + unitary_synthesis_plugin: str = "default", service: QiskitRuntimeService | None = None, backend_name: str | None = None ) -> None: diff --git a/quick/synthesis/unitarypreparation/shannon_decomposition.py b/quick/synthesis/unitarypreparation/shannon_decomposition.py index 518f027..e5fccbb 100644 --- a/quick/synthesis/unitarypreparation/shannon_decomposition.py +++ b/quick/synthesis/unitarypreparation/shannon_decomposition.py @@ -142,7 +142,7 @@ def quantum_shannon_decomposition( circuit: Circuit, qubit_indices: list[int], unitary: NDArray[np.complex128], - recursion_depth: int=0 + recursion_depth: int = 0 ) -> None: """ Decompose n-qubit unitary into CX/RY/RZ/CX gates, preserving global phase. @@ -226,7 +226,7 @@ def demultiplexor( demux_qubits: list[int], unitary_1: NDArray[np.complex128], unitary_2: NDArray[np.complex128], - recursion_depth: int=0 + recursion_depth: int = 0 ) -> None: """ Decompose a multiplexor defined by a pair of unitary matrices operating on the same subspace per Theorem 12. diff --git a/tests/backend/qiskit_backends/test_ibm_backend.py b/tests/backend/qiskit_backends/test_ibm_backend.py index c7f73bd..b6380fe 100644 --- a/tests/backend/qiskit_backends/test_ibm_backend.py +++ b/tests/backend/qiskit_backends/test_ibm_backend.py @@ -97,7 +97,7 @@ def get_operator( def get_counts( self, circuit: Circuit, - num_shots: int=1024 + num_shots: int = 1024 ) -> dict[str, int]: # Create a copy of the circuit as measurement is applied inplace From 5c93153af1851143412cdd554c7b9eedea9c1b42 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 14 Jul 2025 19:15:11 +0800 Subject: [PATCH 29/33] - Added type hints to `repetition_verify` in `quick.circuit.circuit_utils`. - Removed deprecated `typing.Callable` and replaced with `Callable` from `collections.abc`. --- quick/circuit/circuit.py | 2 +- quick/circuit/circuit_utils.py | 45 ++++++++++--------- quick/circuit/cirqcircuit.py | 4 +- quick/circuit/from_framework/from_tket.py | 8 ++-- quick/circuit/pennylanecircuit.py | 4 +- quick/circuit/qiskitcircuit.py | 4 +- quick/circuit/quimbcircuit.py | 4 +- quick/circuit/tketcircuit.py | 12 ++--- quick/optimizer/tket2optimizer.py | 2 +- .../synthesis/unitarypreparation/diffusion.py | 5 ++- 10 files changed, 46 insertions(+), 44 deletions(-) diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index d9871c9..38dd217 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -35,7 +35,7 @@ import qiskit # type: ignore import cirq # type: ignore import pennylane as qml # type: ignore -import pytket +import pytket # type: ignore import quimb.tensor as qtn # type: ignore if TYPE_CHECKING: diff --git a/quick/circuit/circuit_utils.py b/quick/circuit/circuit_utils.py index 607f736..ef3721f 100644 --- a/quick/circuit/circuit_utils.py +++ b/quick/circuit/circuit_utils.py @@ -336,29 +336,28 @@ def simplify( ------- `new_controls` : set[int] The new set of controls. - `new_mux` : list[NDArray[np.complex128]] + `mux_copy` : list[NDArray[np.complex128]] The new list of single qubit gates. """ - c: set[int] = set() - nc: set[int] = set() + multiplexer_controls: set[int] = set() + removed_control_indices: set[int] = set() mux_copy = single_qubit_gates.copy() # Add the position of the multiplexer controls to the set c for i in range(num_controls): - c.add(i + 1) + multiplexer_controls.add(i + 1) # Identify repetitions in the array and return the unnecessary # controls and a copy of the array, marking the repeated operators # as null if len(single_qubit_gates) > 1: - nc, mux_copy = repetition_search(single_qubit_gates, num_controls) + removed_control_indices, mux_copy = repetition_search(single_qubit_gates, num_controls) # Remove the unnecessary controls and the marked operators, creating # a new set of controls and a new array representing the simplified multiplexer - controls_tree = {x for x in c if x not in nc} - mux_tree = [gate for gate in mux_copy if gate is not None] + controls_tree = {x for x in multiplexer_controls if x not in removed_control_indices} - return controls_tree, mux_tree + return controls_tree, mux_copy def repetition_search( multiplexor: list[NDArray[np.complex128]], @@ -384,13 +383,13 @@ def repetition_search( Returns ------- - `nc` : set[int] + `removed_control_indices` : set[int] The set of removed controls. `mux_copy` : list[NDArray[np.complex128]] The new list of gates. """ mux_copy = multiplexor.copy() - nc = set() + removed_control_indices = set() d = 1 # The positions of the multiplexer whose indices are a power of two @@ -433,16 +432,16 @@ def repetition_search( # and add it to the set of unnecessary controls if disentanglement: removed_control_index = level - np.log2(d) - nc.add(removed_control_index) + removed_control_indices.add(removed_control_index) d *= 2 - return nc, mux_copy + return removed_control_indices, mux_copy def repetition_verify( - base, - d, - multiplexor, - mux_copy + base: int, + d: int, + multiplexor: list[NDArray[np.complex128]], + mux_copy: list[NDArray[np.complex128]] ) -> tuple[bool, list[NDArray[np.complex128]]]: """ Verify if the repetitions are valid. This is done by comparing each pair of operators with a distance d between them. @@ -454,7 +453,7 @@ def repetition_verify( Notes ----- The implementation of this simplification is based on the paper - by by de Carvalho et al. [1]. The pseudocode is provided in Algorithm 3. + by de Carvalho et al. [1]. The pseudocode is provided in Algorithm 3. [1] de Carvalho, Batista, de Veras, Araujo, da Silva, Quantum multiplexer simplification for state preparation (2024). @@ -484,9 +483,11 @@ def repetition_verify( while i < d: if not np.allclose(multiplexor[base], multiplexor[next_base]): return False, mux_copy - mux_copy[next_base] = None + mux_copy[next_base] = None # type: ignore base, next_base, i = base + 1, next_base + 1, i + 1 + mux_copy = [gate for gate in mux_copy if gate is not None] + return True, mux_copy def flatten(array: Params) -> tuple[list[float], Params]: # pragma: no cover @@ -495,14 +496,14 @@ def flatten(array: Params) -> tuple[list[float], Params]: # pragma: no cover Parameters ---------- - `array` : Tree + `array` : Params The nested list of floats. Returns ------- `flattened` : list[float] The flattened list of parameters. - `shape` : Tree + `shape` : Params The shape of the original array. """ flattened: list[float] = [] @@ -537,12 +538,12 @@ def reshape( ---------- `flattened` : list[float] The flat list of floats. - `shape` : Tree + `shape` : Params The shape instruction. Returns ------- - `reshaped` : Tree + `reshaped` : Params The reshaped list of floats. """ reshaped: Params = [] diff --git a/quick/circuit/cirqcircuit.py b/quick/circuit/cirqcircuit.py index e758bd6..ec49b23 100644 --- a/quick/circuit/cirqcircuit.py +++ b/quick/circuit/cirqcircuit.py @@ -19,10 +19,10 @@ __all__ = ["CirqCircuit"] -from collections.abc import Sequence +from collections.abc import Callable, Sequence import numpy as np from numpy.typing import NDArray -from typing import Callable, TYPE_CHECKING +from typing import TYPE_CHECKING import cirq from cirq.ops import Rx, Ry, Rz, X, Y, Z, H, S, T, I diff --git a/quick/circuit/from_framework/from_tket.py b/quick/circuit/from_framework/from_tket.py index 5cd1249..98027c6 100644 --- a/quick/circuit/from_framework/from_tket.py +++ b/quick/circuit/from_framework/from_tket.py @@ -20,10 +20,10 @@ __all__ = ["FromTKET"] import numpy as np -from pytket import Circuit as TKCircuit -from pytket._tket.circuit import Command -from pytket import OpType -from pytket.passes import AutoRebase +from pytket import Circuit as TKCircuit # type: ignore +from pytket._tket.circuit import Command # type: ignore +from pytket import OpType # type: ignore +from pytket.passes import AutoRebase # type: ignore from typing import TYPE_CHECKING if TYPE_CHECKING: diff --git a/quick/circuit/pennylanecircuit.py b/quick/circuit/pennylanecircuit.py index 3dd5de9..fc75bee 100644 --- a/quick/circuit/pennylanecircuit.py +++ b/quick/circuit/pennylanecircuit.py @@ -19,11 +19,11 @@ __all__ = ["PennylaneCircuit"] -from collections.abc import Sequence +from collections.abc import Callable, Sequence import copy import numpy as np from numpy.typing import NDArray -from typing import Callable, TYPE_CHECKING +from typing import TYPE_CHECKING import pennylane as qml # type: ignore diff --git a/quick/circuit/qiskitcircuit.py b/quick/circuit/qiskitcircuit.py index 358ae80..d795454 100644 --- a/quick/circuit/qiskitcircuit.py +++ b/quick/circuit/qiskitcircuit.py @@ -19,10 +19,10 @@ __all__ = ["QiskitCircuit"] -from collections.abc import Sequence +from collections.abc import Callable, Sequence import numpy as np from numpy.typing import NDArray -from typing import Callable, TYPE_CHECKING +from typing import TYPE_CHECKING from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister # type: ignore from qiskit.circuit.library import ( # type: ignore diff --git a/quick/circuit/quimbcircuit.py b/quick/circuit/quimbcircuit.py index 2db772a..f1119b1 100644 --- a/quick/circuit/quimbcircuit.py +++ b/quick/circuit/quimbcircuit.py @@ -19,10 +19,10 @@ __all__ = ["QuimbCircuit"] -from collections.abc import Sequence +from collections.abc import Callable, Sequence import numpy as np from numpy.typing import NDArray -from typing import Callable, TYPE_CHECKING +from typing import TYPE_CHECKING import quimb.tensor as qtn # type: ignore from quimb.gates import I, X, Y, Z, H, S, T, RX, RY, RZ, U3 # type: ignore diff --git a/quick/circuit/tketcircuit.py b/quick/circuit/tketcircuit.py index 3a6d88f..111948f 100644 --- a/quick/circuit/tketcircuit.py +++ b/quick/circuit/tketcircuit.py @@ -19,15 +19,15 @@ __all__ = ["TKETCircuit"] -from collections.abc import Sequence +from collections.abc import Callable, Sequence import numpy as np from numpy.typing import NDArray -from typing import Callable, TYPE_CHECKING +from typing import TYPE_CHECKING -from pytket import Circuit as TKCircuit -from pytket import OpType -from pytket.circuit import Op, QControlBox -from pytket.extensions.qiskit import AerBackend, AerStateBackend +from pytket import Circuit as TKCircuit # type: ignore +from pytket import OpType # type: ignore +from pytket.circuit import Op, QControlBox # type: ignore +from pytket.extensions.qiskit import AerBackend, AerStateBackend # type: ignore if TYPE_CHECKING: from quick.backend import Backend diff --git a/quick/optimizer/tket2optimizer.py b/quick/optimizer/tket2optimizer.py index 89df9f7..efb5105 100644 --- a/quick/optimizer/tket2optimizer.py +++ b/quick/optimizer/tket2optimizer.py @@ -19,7 +19,7 @@ __all__ = ["TKET2Optimizer"] -from tket2.passes import badger_pass +from tket2.passes import badger_pass # type: ignore from quick.circuit import Circuit, TKETCircuit from quick.optimizer.optimizer import Optimizer diff --git a/quick/synthesis/unitarypreparation/diffusion.py b/quick/synthesis/unitarypreparation/diffusion.py index 545c259..4dfd21c 100644 --- a/quick/synthesis/unitarypreparation/diffusion.py +++ b/quick/synthesis/unitarypreparation/diffusion.py @@ -19,14 +19,15 @@ __all__ = ["Diffusion"] +from collections.abc import Sequence from genQC.pipeline.diffusion_pipeline import DiffusionPipeline # type: ignore from genQC.inference.infer_compilation import generate_comp_tensors, convert_tensors_to_circuits # type: ignore import genQC.util as util # type: ignore import numpy as np from numpy.typing import NDArray from qiskit.quantum_info import Operator as QiskitOperator # type: ignore -import torch -from typing import Sequence, SupportsIndex, TYPE_CHECKING +import torch # type: ignore +from typing import SupportsIndex, TYPE_CHECKING import quick if TYPE_CHECKING: From 7ee77a8ad10d49255ff0bb04bfd6235430461eec Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Sun, 20 Jul 2025 18:37:59 +0800 Subject: [PATCH 30/33] - Added `calculate_entanglement_entropy()` and `calculate_entanglement_entropy_slope()` to `quick.metrics`. - Added testers for `calculate_entanglement_entropy` and `calculate_entanglement_entropy_slope`. - Added `is_density_matrix()` to `quick.predicates`. - Added testers for `quick.predicates.is_density_matrix()`. - Added `generate_random_density_matrix()` to `quick.random`. - Added testers for `quick.random.generate_random_density_matrix()`. --- quick/metrics/__init__.py | 2 + quick/metrics/metrics.py | 186 ++++++++++++++++++++-------- quick/predicates/__init__.py | 6 +- quick/predicates/predicates.py | 53 +++++++- quick/random/__init__.py | 9 +- quick/random/random.py | 74 ++++++++++- tests/metrics/area_law_slope.npy | Bin 0 -> 136 bytes tests/metrics/test_metrics.py | 80 ++++++++++-- tests/metrics/volume_law_slope.npy | Bin 0 -> 136 bytes tests/predicates/test_predicates.py | 68 ++++++---- tests/random/test_random.py | 54 +++++++- 11 files changed, 435 insertions(+), 97 deletions(-) create mode 100644 tests/metrics/area_law_slope.npy create mode 100644 tests/metrics/volume_law_slope.npy diff --git a/quick/metrics/__init__.py b/quick/metrics/__init__.py index f496d44..2a80faa 100644 --- a/quick/metrics/__init__.py +++ b/quick/metrics/__init__.py @@ -16,6 +16,7 @@ "calculate_entanglement_range", "calculate_shannon_entropy", "calculate_entanglement_entropy", + "calculate_entanglement_entropy_slope", "calculate_hilbert_schmidt_test" ] @@ -23,5 +24,6 @@ calculate_entanglement_range, calculate_shannon_entropy, calculate_entanglement_entropy, + calculate_entanglement_entropy_slope, calculate_hilbert_schmidt_test ) \ No newline at end of file diff --git a/quick/metrics/metrics.py b/quick/metrics/metrics.py index 056fe1d..a6ba4c0 100644 --- a/quick/metrics/metrics.py +++ b/quick/metrics/metrics.py @@ -21,83 +21,68 @@ "calculate_entanglement_range", "calculate_shannon_entropy", "calculate_entanglement_entropy", + "calculate_entanglement_entropy_slope", "calculate_hilbert_schmidt_test" ] import numpy as np from numpy.typing import NDArray import quimb.tensor as qtn # type: ignore +from qiskit.quantum_info import partial_trace # type: ignore -from quick.predicates import is_unitary_matrix +from quick.predicates import is_density_matrix, is_statevector, is_unitary_matrix -def _get_submps_indices(mps: qtn.MatrixProductState) -> list[tuple[int, int]]: - """ Get the indices of contiguous blocks in the MPS. For testing purposes, - this method is static. +def _calculate_1d_entanglement_range(mps: qtn.MatrixProductState) -> list[tuple[int, int]]: + """ Get the entanglement range for entangled qubits in a 1D chain + by checking the virtual (bond) dimensions of the tensors at each + site in the MPS. - Notes - ----- - Certain sites may not be entangled with the rest, and thus we can simply apply - a single qubit gate to them as opposed to a two qubit gate. - - This reduces the overall cost of the circuit for a given layer. If all sites are - entangled, then the method will simply return the indices of the MPS, i.e., for - 10 qubit system [(0, 9)]. If sites 0 and 1 are not entangled at all with the rest, - the method will return [(0, 0), (1,1), (2, 9)]. - - The implementation is based on the analytical decomposition [1]. - - For more information, refer to the publication below: - [1] Shi-Ju. - Encoding of Matrix Product States into Quantum Circuits of One- and Two-Qubit Gates (2020). - https://arxiv.org/abs/1908.07958 + Parameters + ---------- + `mps` : qtn.MatrixProductState + The MPS representation of the quantum state. Returns ------- - `submps_indices` : list[tuple[int, int]] - The indices of the MPS contiguous blocks. - - Usage - ----- - >>> mps.get_submps_indices() + `entangled_blocks_indices` : list[tuple[int, int]] + The indices of the MPS entangled blocks. """ - sub_mps_indices: list[tuple[int, int]] = [] + entangled_blocks_indices: list[tuple[int, int]] = [] if mps.L == 1: return [(0, 0)] for site in range(mps.L): - # Reset the dimension variables for each iteration dim_left, dim_right = 1, 1 # Define the dimensions for each site # The first and last sites are connected to only one site # as opposed to the other sites in the middle which are connected # to two sites to their left and right - # # | # ●━━ `dim_right` if site == 0: _, dim_right = mps[site].shape # type: ignore - # + # | # `dim_left` ━━● elif site == (mps.L - 1): dim_left, _ = mps[site].shape # type: ignore - # + # | # `dim_left` ━━●━━ `dim_right` else: dim_left, _, dim_right = mps[site].shape # type: ignore if dim_left < 2 and dim_right < 2: - sub_mps_indices.append((site, site)) + entangled_blocks_indices.append((site, site)) elif dim_left < 2 and dim_right >= 2: temp = site elif dim_left >= 2 and dim_right < 2: - sub_mps_indices.append((temp, site)) + entangled_blocks_indices.append((temp, site)) - return sub_mps_indices + return entangled_blocks_indices def calculate_entanglement_range(statevector: NDArray[np.complex128]) -> list[tuple[int, int]]: """ Get the entanglements of the circuit. @@ -112,11 +97,18 @@ def calculate_entanglement_range(statevector: NDArray[np.complex128]) -> list[tu list[tuple[int, int]] The entanglements of the circuit. + Raises + ------ + ValueError + - If the input is not a statevector. + Usage ----- >>> entanglements = get_entanglements(statevector) """ - statevector = statevector.flatten() + if not is_statevector(statevector): + raise ValueError("The input must be a statevector.") + num_qubits = int(np.log2(statevector.size)) # We need to have the statevector in MSB order for @@ -127,15 +119,15 @@ def calculate_entanglement_range(statevector: NDArray[np.complex128]) -> list[tu .flatten() ) - return _get_submps_indices(qtn.MatrixProductState.from_dense(statevector)) + return _calculate_1d_entanglement_range(qtn.MatrixProductState.from_dense(statevector)) -def calculate_shannon_entropy(statevector: NDArray[np.complex128]) -> float: - """ Calculate the Shannon entropy. +def calculate_shannon_entropy(probability_vector: NDArray[np.complex128]) -> float: + """ Calculate the Shannon entropy of a probability vector. Parameters ---------- - `statevector` : NDArray[np.complex128] - The statevector of the circuit. + `probability_vector` : NDArray[np.complex128] + The probability vector. Returns ------- @@ -146,30 +138,124 @@ def calculate_shannon_entropy(statevector: NDArray[np.complex128]) -> float: ----- >>> shannon_entropy = calculate_shannon_entropy(statevector) """ - statevector = statevector[(0 < statevector) & (statevector < 1)] - return -np.sum(statevector * np.log2(statevector)).astype(float) + probability_vector = probability_vector[(0 < probability_vector) & (probability_vector < 1)] + return -np.sum(probability_vector * np.log2(probability_vector)).astype(float) -def calculate_entanglement_entropy(statevector: NDArray[np.complex128]) -> float: - """ Calculate the entanglement entropy of the circuit. +def calculate_entanglement_entropy(data: NDArray[np.complex128]) -> float: + """ Calculate the Von Neumann entanglement entropy from the + density matrix. In case of statevectors the entropy is simply + 0. Parameters ---------- - `statevector` : NDArray[np.complex128] - The statevector of the circuit. + `data` : NDArray[np.complex128] + The data, which can be a statevector or a density matrix. Returns ------- float - The entanglement entropy of the circuit. + The entanglement entropy of the data. + + Raises + ------ + ValueError + - Input dimension matches a statevector but is not a valid statevector. + - The input is not a valid density matrix. Usage ----- - >>> entanglement_entropy = calculate_entanglement_entropy(statevector) + >>> entanglement_entropy = calculate_entanglement_entropy(data) """ - density_matrix = np.outer(statevector, statevector.conj()) - eigenvalues = np.maximum(np.real(np.linalg.eigvals(density_matrix)), 0.0) + # Handle the case of statevectors + # and ensure the density matrix is + # valid + if data.ndim == 1: + if is_statevector(data): + return 0.0 + else: + raise ValueError( + "Input dimension matches a statevector " + "but is not a valid statevector." + ) + if data.ndim == 2: + if data.shape[1] == 1: + if is_statevector(data): + return 0.0 + else: + raise ValueError( + "Input dimension matches a statevector " + "but is not a valid statevector." + ) + else: + if not is_density_matrix(data): + raise ValueError("The input is not a valid density matrix.") + + eigenvalues = np.maximum(np.real(np.linalg.eigvals(data)), 0.0) return calculate_shannon_entropy(eigenvalues) +def calculate_entanglement_entropy_slope(statevector: NDArray[np.complex128]) -> float: + """ Calculate the slope of the entanglement entropy. This is + used to determine whether a state is area-law or volume-law + entangled, which is a measure of how the entanglement entropy + scales with the number of qubits. + + If the slope is 1, which is a straight line, then the state is + volume-law entangled. If the entropy decays after a while and + forms a decaying curve, then the state is area-law entangled. + + Parameters + ---------- + `statevector` : NDArray[np.complex128] + The statevector of the circuit. + + Returns + ------- + `slope` : float + The slope of the entanglement entropy of the circuit. + + Raises + ------ + ValueError + - The input must be a statevector. + + Usage + ----- + >>> entanglement_entropy_slope = calculate_entanglement_entropy_slope(statevector) + """ + if not is_statevector(statevector): + raise ValueError("The input must be a statevector.") + + num_qubits = int( + np.ceil( + np.log2(len(statevector)) + ) + ) + + max_k = num_qubits // 2 + entropies = np.empty(max_k, dtype=np.float64) + + for k in range(1, max_k + 1): + # Trace out rest of the qubits to extract the + # reduced density matrix for the first k qubits + rho_A = partial_trace(statevector, list(range(k, num_qubits))) # type: ignore + S = calculate_entanglement_entropy(rho_A.data) + entropies[k - 1] = S + + # We use half of the entropies to calculate the slope + # for efficiency + entropies = entropies[len(entropies) // 2:] + x = np.arange(1, len(entropies) + 1) + + x_mean = np.mean(x) + y_mean = np.mean(entropies) + + numerator = np.sum((x - x_mean) * (entropies - y_mean)) + denominator = np.sum((x - x_mean) ** 2) + + slope = numerator / denominator if denominator != 0 else 0 + + return float(slope) + def calculate_hilbert_schmidt_test( unitary_1: NDArray[np.complex128], unitary_2: NDArray[np.complex128] diff --git a/quick/predicates/__init__.py b/quick/predicates/__init__.py index 7f7729e..7423163 100644 --- a/quick/predicates/__init__.py +++ b/quick/predicates/__init__.py @@ -21,7 +21,8 @@ "is_unitary_matrix", "is_hermitian_matrix", "is_positive_semidefinite_matrix", - "is_isometry" + "is_isometry", + "is_density_matrix" ] from quick.predicates.predicates import ( @@ -33,5 +34,6 @@ is_unitary_matrix, is_hermitian_matrix, is_positive_semidefinite_matrix, - is_isometry + is_isometry, + is_density_matrix ) \ No newline at end of file diff --git a/quick/predicates/predicates.py b/quick/predicates/predicates.py index d59fc3a..16fbfff 100644 --- a/quick/predicates/predicates.py +++ b/quick/predicates/predicates.py @@ -26,7 +26,8 @@ "is_unitary_matrix", "is_hermitian_matrix", "is_positive_semidefinite_matrix", - "is_isometry" + "is_isometry", + "is_density_matrix" ] import numpy as np @@ -60,7 +61,9 @@ def _is_power( def is_statevector( statevector: NDArray[np.complex128], - system_size: int = 2 + system_size: int = 2, + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT ) -> bool: """ Test if an array is a statevector. @@ -72,6 +75,10 @@ def is_statevector( The size of the quantum memory. If the size is 2, then the system uses qubits. If the size is 3, then the system uses qutrits, and so on. + `rtol` : float, optional, default=RTOL_DEFAULT + The relative tolerance parameter. + `atol` : float, optional, default=ATOL_DEFAULT + The absolute tolerance parameter. Returns ------- @@ -93,7 +100,11 @@ def is_statevector( if not _is_power(system_size, len(statevector)): return False - return np.linalg.norm(statevector) == 1 and statevector.ndim == 1 and len(statevector) > 1 + return ( + bool(np.isclose(np.linalg.norm(statevector), 1.0, rtol=rtol, atol=atol)) + and statevector.ndim == 1 + and len(statevector) > 1 + ) def is_square_matrix(matrix: NDArray[np.complex128]) -> bool: """ Test if an array is a square matrix. @@ -344,4 +355,38 @@ def is_isometry( identity = np.eye(matrix.shape[1]) matrix = matrix.conj().T @ matrix - return np.allclose(matrix, identity, rtol=rtol, atol=atol) \ No newline at end of file + return np.allclose(matrix, identity, rtol=rtol, atol=atol) + +def is_density_matrix( + rho: NDArray[np.complex128], + rtol: float = RTOL_DEFAULT, + atol: float = ATOL_DEFAULT + ) -> bool: + """ Test if an array is a density matrix. + + Parameters + ---------- + `rho` : NDArray[np.complex128] + The input matrix. + `rtol` : float, optional, default=RTOL_DEFAULT + The relative tolerance parameter. + `atol` : float, optional, default=ATOL_DEFAULT + The absolute tolerance parameter. + + Returns + ------- + bool + True if the matrix is a density matrix, False otherwise. + + Usage + ----- + >>> is_density_matrix(np.eye(2)) + """ + if not ( + is_hermitian_matrix(rho, rtol=rtol, atol=atol) + and is_positive_semidefinite_matrix(rho, rtol=rtol, atol=atol) + and np.isclose(np.trace(rho), 1.0, rtol=rtol, atol=atol) + ): + return False + + return True \ No newline at end of file diff --git a/quick/random/__init__.py b/quick/random/__init__.py index b2f6305..ad5e766 100644 --- a/quick/random/__init__.py +++ b/quick/random/__init__.py @@ -14,7 +14,12 @@ __all__ = [ "generate_random_state", - "generate_random_unitary" + "generate_random_unitary", + "generate_random_density_matrix" ] -from quick.random.random import generate_random_state, generate_random_unitary \ No newline at end of file +from quick.random.random import ( + generate_random_state, + generate_random_unitary, + generate_random_density_matrix +) \ No newline at end of file diff --git a/quick/random/random.py b/quick/random/random.py index d848c93..a37f29d 100644 --- a/quick/random/random.py +++ b/quick/random/random.py @@ -16,14 +16,39 @@ __all__ = [ "generate_random_state", - "generate_random_unitary" + "generate_random_unitary", + "generate_random_density_matrix" ] import numpy as np from numpy.typing import NDArray from scipy.stats import unitary_group # type: ignore +from typing import Literal +def _generate_ginibre_matrix( + num_rows: int, + num_columns: int, + ) -> NDArray[np.complex128]: + """ Return a normally distributed complex random matrix. + + Parameters + ---------- + `num_rows` : int + Number of rows in output matrix. + `num_columns` : int + Number of columns in output matrix. + + Returns + ------- + `ginibre_ensemble` : NDArray[np.complex128] + A complex rectangular matrix where each real and imaginary + entry is sampled from the normal distribution. + """ + rng = np.random.default_rng() + ginibre_ensemble = rng.normal(size=(num_rows, num_columns)) + 1j * rng.normal(size=(num_rows, num_columns)) + return ginibre_ensemble + def generate_random_state(num_qubits: int) -> NDArray[np.complex128]: """ Generate a random state vector for the given number of qubits. @@ -53,4 +78,49 @@ def generate_random_unitary(num_qubits: int) -> NDArray[np.complex128]: `NDArray[np.complex128]` The random unitary matrix. """ - return unitary_group.rvs(2 ** num_qubits).astype(np.complex128) \ No newline at end of file + return unitary_group.rvs(2 ** num_qubits).astype(np.complex128) + +def generate_random_density_matrix( + num_qubits: int, + rank: int | None = None, + generator: Literal["hilbert-schmidt", "bures"] = "hilbert-schmidt" + ) -> NDArray[np.complex128]: + """ Generate a random density matrix. + + Parameters + ---------- + `num_qubits` : int + The number of qubits in the density matrix. + `rank` : int, optional, default=None + The rank of the density matrix. If None, the matrix is full-rank, + where rank is set to the number of qubits. + `generator` : Literal["hilbert-schmidt", "bures"], optional, default="hilbert-schmidt" + The method to use for generating the density matrix. + Options are "hilbert-schmidt" or "bures". + + Returns + ------- + NDArray[np.complex128] + The generated random density matrix. + + Raises + ------ + ValueError + - If the `generator` is not recognized. + """ + if not rank: + rank = num_qubits + + if generator not in ["hilbert-schmidt", "bures"]: + raise ValueError(f"Unrecognized generator method: {generator}") + + ginibre_ensemble = _generate_ginibre_matrix(2**num_qubits, 2**rank) + + if generator == "hilbert-schmidt": + density_matrix = ginibre_ensemble @ ginibre_ensemble.conj().T + elif generator == "bures": + density_matrix = np.eye(2**num_qubits) + generate_random_unitary(num_qubits) + density_matrix = density_matrix @ ginibre_ensemble + density_matrix = density_matrix @ density_matrix.conj().T + + return density_matrix / np.trace(density_matrix) \ No newline at end of file diff --git a/tests/metrics/area_law_slope.npy b/tests/metrics/area_law_slope.npy new file mode 100644 index 0000000000000000000000000000000000000000..05fffd809a8be329f3c7e2d7a3632d2268465da7 GIT binary patch literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I%6ItsN46ag-k^|@D8Ts>(I06O6x*Z=?k literal 0 HcmV?d00001 diff --git a/tests/metrics/test_metrics.py b/tests/metrics/test_metrics.py index 3487d8d..733e2a2 100644 --- a/tests/metrics/test_metrics.py +++ b/tests/metrics/test_metrics.py @@ -56,26 +56,86 @@ def test_calculate_shannon_entropy(self) -> None: assert_almost_equal(1.7736043871504037, calculate_shannon_entropy(data)) - @pytest.mark.parametrize("data", [ - np.array([1, 0]), - np.array([0, 1, 0, 0]), - np.array([0.5, 0.5, 0.5, 0.5]), - np.array([0.5, 0.5j, -0.5j, 0.5]), - np.array([1/np.sqrt(2), 0, 0, -1/np.sqrt(2)* 1j]), - np.array([1/np.sqrt(2)] + (14 * [0]) + [1/np.sqrt(2) * 1j]), + @pytest.mark.parametrize("data, expected", [ + (np.array([1, 0]), 0.0), + (np.array([0, 1, 0, 0]), 0.0), + (np.array([0.5, 0.5, 0.5, 0.5]), 0.0), + (np.array([0.5, 0.5j, -0.5j, 0.5]), 0.0), + (np.array([1/np.sqrt(2), 0, 0, -1/np.sqrt(2)* 1j]), 0.0), + (np.array([1/np.sqrt(2)] + (14 * [0]) + [1/np.sqrt(2) * 1j]), 0.0), + ( + np.array([ + [0.84048555+0.j, 0.0510054-0.02325157j], + [0.0510054+0.02325157j, 0.15951445+0.j] + ], dtype=np.complex128), + 0.6220438669480641 + ), + ( + np.array([ + [0.16521093+0.j, -0.08915021+0.07244625j, -0.14670846-0.10748953j, -0.03544851+0.106916j], + [-0.08915021-0.07244625j, 0.28432666+0.j, -0.01778044+0.15666538j, -0.03049137-0.01306784j], + [-0.14670846+0.10748953j, -0.01778044-0.15666538j, 0.2941435 +0.j, -0.06158772-0.08660825j], + [-0.03544851-0.106916j, -0.03049137+0.01306784j, -0.06158772+0.08660825j, 0.2563189+0.j] + ], dtype=np.complex128), + 1.3705180586061732 + ) ]) def test_calculate_entanglement_entropy( self, - data: NDArray[np.complex128] + data: NDArray[np.complex128], + expected: float ) -> None: """ Test the `calculate_entanglement_entropy` method. Parameters ---------- - data : NDArray[np.complex128] + `data` : NDArray[np.complex128] The statevector of the circuit. + `expected` : float + The expected value. + """ + assert_almost_equal(expected, calculate_entanglement_entropy(data)) + + @pytest.mark.parametrize("data", [ + np.array([1, 2, 3], dtype=np.complex128), + np.array([1, 2, 3, 4], dtype=np.complex128), + np.array([ + [1, 2], + [3, 4] + ], dtype=np.complex128), + np.array([ + [1, 2, 3], + [4, 5, 6] + ], dtype=np.complex128) + ]) + def test_calculate_entanglement_entropy_invalid_data( + self, + data: NDArray[np.complex128] + ) -> None: + """ Test failure of `calculate_entanglement_entropy` with invalid + data values, which are neither density matrix nor statevector. + + Parameters + ---------- + `data` : NDArray[np.complex128] + The data to be tested. + """ + with pytest.raises(ValueError): + calculate_entanglement_entropy(data) + + def test_calculate_entanglement_entropy_slope_area_law_case(self) -> None: + """ Test the `calculate_entanglement_entropy_slope` method with an area-law + entangled state. + """ + area_law_slope = np.load("tests/metrics/area_law_slope.npy") + assert_almost_equal(0.20183287022097673, area_law_slope) + + def test_calculate_entanglement_entropy_slope_volume_law_case(self) -> None: + """ Test the `calculate_entanglement_entropy_slope` method with a volume-law + entangled state. """ - assert_almost_equal(0.0, calculate_entanglement_entropy(data)) + volume_law_slope = np.load("tests/metrics/volume_law_slope.npy") + assert_almost_equal(1.0, volume_law_slope) def test_calculate_hilbert_schmidt_test(self) -> None: """ Test the `calculate_hilbert_schmidt_test` method. diff --git a/tests/metrics/volume_law_slope.npy b/tests/metrics/volume_law_slope.npy new file mode 100644 index 0000000000000000000000000000000000000000..2ba801cb0383657ec50c7249c5069503f5229fd0 GIT binary patch literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= YXCxM+0{I%6ItsN46ag* None: """ Test the `.is_statevector()` method with invalid system size. @@ -104,7 +93,7 @@ def test_is_square_matrix( `expected` : bool The expected output of the function. """ - assert is_square_matrix(array) == expected + assert is_square_matrix(array) is expected @pytest.mark.parametrize("array, expected", [ (np.diag([1, 2, 3]), True), @@ -139,7 +128,7 @@ def test_is_diagonal_matrix( `expected` : bool The expected output of the function. """ - assert is_diagonal_matrix(array) == expected + assert is_diagonal_matrix(array) is expected @pytest.mark.parametrize("array, expected", [ (np.array([ @@ -187,7 +176,7 @@ def test_is_symmetric_matrix( `expected` : bool The expected output of the function. """ - assert is_symmetric_matrix(array) == expected + assert is_symmetric_matrix(array) is expected @pytest.mark.parametrize("array, expected", [ (np.eye(2), True), @@ -213,7 +202,7 @@ def test_is_identity_matrix( `expected` : bool The expected output of the function. """ - assert is_identity_matrix(array) == expected + assert is_identity_matrix(array) is expected @pytest.mark.parametrize("array, expected", [ (unitary_group.rvs(2), True), @@ -239,7 +228,7 @@ def test_is_unitary_matrix( `expected` : bool The expected output of the function. """ - assert is_unitary_matrix(array) == expected + assert is_unitary_matrix(array) is expected @pytest.mark.parametrize("array, expected", [ (np.array([ @@ -283,7 +272,7 @@ def test_is_hermitian_matrix( `expected` : bool The expected output of the function. """ - assert is_hermitian_matrix(array) == expected + assert is_hermitian_matrix(array) is expected @pytest.mark.parametrize("array, expected", [ (np.array([ @@ -325,7 +314,7 @@ def test_is_positive_semidefinite_matrix( `expected` : bool The expected output of the function. """ - assert is_positive_semidefinite_matrix(array) == expected + assert is_positive_semidefinite_matrix(array) is expected @pytest.mark.parametrize("array, expected", [ (np.array([ @@ -362,4 +351,39 @@ def test_is_isometry( `expected` : bool The expected output of the function. """ - assert is_isometry(array) == expected \ No newline at end of file + assert is_isometry(array) is expected + +@pytest.mark.parametrize("array, expected", [ + (np.array([ + [0.55282636+0.j, 0.19339888+0.1369917j], + [0.19339888-0.1369917j, 0.44717364+0.j] + ], dtype=np.complex128), True), + (np.array([ + [0.19834677+0.j, 0.21077084+0.08851485j, 0.07369894-0.03780167j], + [0.21077084-0.08851485j, 0.5556912 +0.j, 0.09721694-0.13294078j], + [0.07369894+0.03780167j, 0.09721694+0.13294078j, 0.24596203+0.j] + ], dtype=np.complex128), True), + (np.array([ + [1, 2 + 1j], + [2 + 1j, 3] + ]), False), + (np.array([ + [1, 2], + [3, 4] + ]), False), + (np.random.rand(2, 3, 3), False) +]) +def test_is_density_matrix( + array: NDArray[np.complex128], + expected: bool + ) -> None: + """ Test the `is_density_matrix` function with various matrices. + + Parameters + ---------- + `array` : NDArray[np.complex128] + The input array to check if it is an isometry. + `expected` : bool + The expected out of the function. + """ + assert is_density_matrix(array) is expected \ No newline at end of file diff --git a/tests/random/test_random.py b/tests/random/test_random.py index 1b8bc2b..6644b44 100644 --- a/tests/random/test_random.py +++ b/tests/random/test_random.py @@ -18,8 +18,12 @@ import pytest -from quick.predicates import is_unitary_matrix -from quick.random import generate_random_state, generate_random_unitary +from quick.predicates import is_unitary_matrix, is_statevector, is_density_matrix +from quick.random import ( + generate_random_state, + generate_random_unitary, + generate_random_density_matrix +) class TestRandom: @@ -40,8 +44,7 @@ def test_generate_random_state( """ state = generate_random_state(num_qubits) - assert state.shape == (2 ** num_qubits,) - assert pytest.approx(1.0) == abs(state @ state.conj()) + assert is_statevector(state) @pytest.mark.parametrize("num_qubits", [1, 2, 3, 4, 5]) def test_generate_random_unitary( @@ -58,4 +61,45 @@ def test_generate_random_unitary( unitary = generate_random_unitary(num_qubits) assert unitary.shape == (2 ** num_qubits, 2 ** num_qubits) - assert is_unitary_matrix(unitary) \ No newline at end of file + assert is_unitary_matrix(unitary) + + @pytest.mark.parametrize("num_qubits", [1, 2, 3, 4, 5]) + @pytest.mark.parametrize("generator", ["hilbert-schmidt", "bures"]) + @pytest.mark.parametrize("rank", [1, 2, 3]) + def test_generate_random_density_matrix( + self, + num_qubits: int, + generator: str, + rank: int + ) -> None: + """ Test the `generate_random_density_matrix` function. + + Parameters + ---------- + `num_qubits` : int + The number of qubits in the density matrix. + `generator` : str + The method to use for generating the density matrix. + `rank` : int + The rank of the density matrix. + """ + density_matrix = generate_random_density_matrix( + num_qubits=num_qubits, + rank=rank, + generator=generator # type: ignore + ) + + assert is_density_matrix(density_matrix) + + def test_generate_random_density_matrix_invalid_generator( + self + ) -> None: + """ Test the `generate_random_density_matrix` function with an + invalid generator. + """ + with pytest.raises(ValueError): + generate_random_density_matrix( + num_qubits=2, + rank=1, + generator="invalid-generator" # type: ignore + ) \ No newline at end of file From c7d5852c614cc6fa93088e91bb595403d71edab9 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 21 Jul 2025 20:56:43 +0800 Subject: [PATCH 31/33] - Added repeat and tensor to `quick.circuit` module via `__mul__` and `__matmul__` respectively. - Added testers for `quick.circuit.Circuit.__mul__` and `quick.circuit.Circuit.__matmul__`. --- quick/circuit/circuit.py | 101 +++++++++++++++++++++++++++++ tests/circuit/test_circuit_base.py | 69 ++++++++++++++++++++ 2 files changed, 170 insertions(+) diff --git a/quick/circuit/circuit.py b/quick/circuit/circuit.py index 38dd217..b0de8e0 100644 --- a/quick/circuit/circuit.py +++ b/quick/circuit/circuit.py @@ -6251,6 +6251,107 @@ def plot_histogram( return figure + def __mul__( + self, + multiplier: int + ) -> Circuit: + """ Multiply the circuit by an integer to repeat the circuit. + + Parameters + ---------- + `multiplier` : int + The number of times to repeat the circuit. + + Returns + ------- + `new_circuit` : quick.circuit.Circuit + The new circuit with the repeated operations. + + Raises + ------ + TypeError + - The multiplier must be an integer. + + Usage + ----- + >>> new_circuit = circuit * 3 + """ + if not isinstance(multiplier, int): + raise TypeError("The multiplier must be an integer.") + + new_circuit = type(self)(self.num_qubits) + for _ in range(multiplier): + new_circuit.add(self, list(range(self.num_qubits))) + + return new_circuit + + def __rmul__( + self, + multiplier: int + ) -> Circuit: + """ Multiply the circuit by an integer to repeat the circuit. + This is the right-hand side multiplication, allowing for + the syntax `3 * circuit`. + + Parameters + ---------- + `multiplier` : int + The number of times to repeat the circuit. + + Returns + ------- + `new_circuit` : quick.circuit.Circuit + The new circuit with the repeated operations. + + Raises + ------ + TypeError + - The multiplier must be an integer. + + Usage + ----- + >>> new_circuit = 3 * circuit + """ + return self.__mul__(multiplier) + + def __matmul__( + self, + other_circuit: Circuit + ) -> Circuit: + """ Tensor product two circuits together. This is + done by putting the circuits side by side. + + Parameters + ---------- + `other_circuit` : quick.circuit.Circuit + The circuit to tensor product with. + + Returns + ------- + `new_circuit` : quick.circuit.Circuit + The tensor product circuit. + + Raises + ------ + TypeError + - The other circuit must be a `quick.circuit.Circuit`. + + Usage + ----- + >>> new_circuit = circuit @ other_circuit + """ + if not isinstance(other_circuit, Circuit): + raise TypeError("The other circuit must be a `quick.circuit.Circuit`.") + + new_circuit = type(self)(self.num_qubits + other_circuit.num_qubits) + new_circuit.add(self, list(range(self.num_qubits))) + new_circuit.add( + other_circuit, + list(range(self.num_qubits, self.num_qubits + other_circuit.num_qubits)) + ) + + return new_circuit + def __getitem__( self, index: int | slice diff --git a/tests/circuit/test_circuit_base.py b/tests/circuit/test_circuit_base.py index d91c73a..298b2ba 100644 --- a/tests/circuit/test_circuit_base.py +++ b/tests/circuit/test_circuit_base.py @@ -1094,6 +1094,75 @@ def test_remove_measurement( # ensure they are equivalent assert circuit == no_measurement_circuit + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + def test_mul( + self, + circuit_framework: type[Circuit] + ) -> None: + """ Test the repeat of circuits via `__mul__` operator. + + Parameters + ---------- + `circuit_framework`: type[quick.circuit.Circuit] + The circuit framework to test. + """ + # Define the `quick.circuit.Circuit` instance + circuit = circuit_framework(2) + + # Apply a series of gates + circuit.H(0) + circuit.CX(0, 1) + + new_circuit = circuit * 3 + new_circuit_reverse = 3 * circuit + + # Define the equivalent `quick.circuit.Circuit` instance, and + # ensure they are equivalent + checker_circuit = circuit_framework(2) + checker_circuit.H(0) + checker_circuit.CX(0, 1) + checker_circuit.H(0) + checker_circuit.CX(0, 1) + checker_circuit.H(0) + checker_circuit.CX(0, 1) + + assert checker_circuit == new_circuit + assert checker_circuit == new_circuit_reverse + + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) + def test_matmul( + self, + circuit_framework: type[Circuit] + ) -> None: + """ Test the tensor product of circuits via `__matmul__` operator. + + Parameters + ---------- + `circuit_framework`: type[quick.circuit.Circuit] + The circuit framework to test. + """ + # Define the `quick.circuit.Circuit` instance + circuit_1 = circuit_framework(3) + circuit_2 = circuit_framework(2) + + # Apply a series of gates + circuit_1.H(0) + circuit_1.CX(0, 2) + circuit_2.Z(0) + circuit_2.CY(1, 0) + + new_circuit = circuit_1 @ circuit_2 + + # Define the equivalent `quick.circuit.Circuit` instance, and + # ensure they are equivalent + checker_circuit = circuit_framework(5) + checker_circuit.H(0) + checker_circuit.CX(0, 2) + checker_circuit.Z(3) + checker_circuit.CY(4, 3) + + assert checker_circuit == new_circuit + @pytest.mark.parametrize("circuit_framework", CIRCUIT_FRAMEWORKS) def test_getitem( self, From a04315f6092095fda23708434972b8818ada08b4 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Mon, 21 Jul 2025 22:38:52 +0800 Subject: [PATCH 32/33] - Updated to improve coverage. --- tests/metrics/area_law_slope.npy | Bin 136 -> 0 bytes tests/metrics/area_law_state.npy | Bin 0 -> 16512 bytes tests/metrics/test_metrics.py | 34 +++++++++++++++++++++++++---- tests/metrics/volume_law_slope.npy | Bin 136 -> 0 bytes tests/metrics/volume_law_state.npy | Bin 0 -> 524416 bytes tests/random/test_random.py | 2 +- 6 files changed, 31 insertions(+), 5 deletions(-) delete mode 100644 tests/metrics/area_law_slope.npy create mode 100644 tests/metrics/area_law_state.npy delete mode 100644 tests/metrics/volume_law_slope.npy create mode 100644 tests/metrics/volume_law_state.npy diff --git a/tests/metrics/area_law_slope.npy b/tests/metrics/area_law_slope.npy deleted file mode 100644 index 05fffd809a8be329f3c7e2d7a3632d2268465da7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136 zcmbR27wQ`j$;eQ~P_3SlTAW;@Zl$1ZlV+i=qoAIaUsO_*m=~X4l#&V(cT3DEP6dh= cXCxM+0{I%6ItsN46ag-k^|@D8Ts>(I06O6x*Z=?k diff --git a/tests/metrics/area_law_state.npy b/tests/metrics/area_law_state.npy new file mode 100644 index 0000000000000000000000000000000000000000..c227d455e225a6f1c141f19479b94cf9ff3bc112 GIT binary patch literal 16512 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 None: entanglements = calculate_entanglement_range(qc.get_statevector()) assert entanglements == [(0, 1), (2, 2), (3, 5)] + def test_calculate_entanglement_range_single_qubit(self) -> None: + """ Test the `calculate_entanglement_range` method with a single qubit. + """ + qc = QiskitCircuit(1) + qc.H(0) + + entanglements = calculate_entanglement_range(qc.get_statevector()) + assert entanglements == [(0, 0)] + def test_calculate_shannon_entropy(self) -> None: """ Test the `calculate_shannon_entropy` method. """ @@ -63,6 +73,15 @@ def test_calculate_shannon_entropy(self) -> None: (np.array([0.5, 0.5j, -0.5j, 0.5]), 0.0), (np.array([1/np.sqrt(2), 0, 0, -1/np.sqrt(2)* 1j]), 0.0), (np.array([1/np.sqrt(2)] + (14 * [0]) + [1/np.sqrt(2) * 1j]), 0.0), + ( + np.array([ + [0.5], + [0.5], + [0.5], + [0.5] + ]), + 0.0 + ), ( np.array([ [0.84048555+0.j, 0.0510054-0.02325157j], @@ -106,6 +125,11 @@ def test_calculate_entanglement_entropy( np.array([ [1, 2, 3], [4, 5, 6] + ], dtype=np.complex128), + np.array([ + [1], + [2], + [3] ], dtype=np.complex128) ]) def test_calculate_entanglement_entropy_invalid_data( @@ -127,15 +151,17 @@ def test_calculate_entanglement_entropy_slope_area_law_case(self) -> None: """ Test the `calculate_entanglement_entropy_slope` method with an area-law entangled state. """ - area_law_slope = np.load("tests/metrics/area_law_slope.npy") - assert_almost_equal(0.20183287022097673, area_law_slope) + area_law_state = np.load("tests/metrics/area_law_state.npy") + slope = calculate_entanglement_entropy_slope(area_law_state) + assert_almost_equal(0.20183287022097673, slope) def test_calculate_entanglement_entropy_slope_volume_law_case(self) -> None: """ Test the `calculate_entanglement_entropy_slope` method with a volume-law entangled state. """ - volume_law_slope = np.load("tests/metrics/volume_law_slope.npy") - assert_almost_equal(1.0, volume_law_slope) + volume_law_state = np.load("tests/metrics/volume_law_state.npy") + slope = calculate_entanglement_entropy_slope(volume_law_state) + assert_almost_equal(1.0, slope) def test_calculate_hilbert_schmidt_test(self) -> None: """ Test the `calculate_hilbert_schmidt_test` method. diff --git a/tests/metrics/volume_law_slope.npy b/tests/metrics/volume_law_slope.npy deleted file mode 100644 index 2ba801cb0383657ec50c7249c5069503f5229fd0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136 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 literal 0 HcmV?d00001 diff --git a/tests/random/test_random.py b/tests/random/test_random.py index 6644b44..765cdca 100644 --- a/tests/random/test_random.py +++ b/tests/random/test_random.py @@ -65,7 +65,7 @@ def test_generate_random_unitary( @pytest.mark.parametrize("num_qubits", [1, 2, 3, 4, 5]) @pytest.mark.parametrize("generator", ["hilbert-schmidt", "bures"]) - @pytest.mark.parametrize("rank", [1, 2, 3]) + @pytest.mark.parametrize("rank", [1, 2, 3, None]) def test_generate_random_density_matrix( self, num_qubits: int, From f36dee199b7cd17fa6fa7ec17adc2aa9f32c72b3 Mon Sep 17 00:00:00 2001 From: ACE07-Sev Date: Tue, 22 Jul 2025 23:47:14 +0800 Subject: [PATCH 33/33] - Slight fix to `is_statevector` to handle 2 dimensional arrays where the second dimension is equal to 1. --- quick/predicates/predicates.py | 4 ++++ tests/predicates/test_predicates.py | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/quick/predicates/predicates.py b/quick/predicates/predicates.py index 16fbfff..c96ed7f 100644 --- a/quick/predicates/predicates.py +++ b/quick/predicates/predicates.py @@ -100,6 +100,10 @@ def is_statevector( if not _is_power(system_size, len(statevector)): return False + if statevector.ndim == 2: + if statevector.shape[1] == 1: + statevector = statevector.flatten() + return ( bool(np.isclose(np.linalg.norm(statevector), 1.0, rtol=rtol, atol=atol)) and statevector.ndim == 1 diff --git a/tests/predicates/test_predicates.py b/tests/predicates/test_predicates.py index 1b09b44..ddd19b5 100644 --- a/tests/predicates/test_predicates.py +++ b/tests/predicates/test_predicates.py @@ -39,7 +39,8 @@ (np.array([1, 0, 0]), 3, True), (np.array([1, 2]), 2, False), (np.array([1, 2, 3]), 3, False), - (np.array([1, 0, 0, 0]), 2, True) + (np.array([1, 0, 0, 0]), 2, True), + (np.array([[0.5], [0.5], [0.5], [0.5]]), 2, True) ]) def test_is_statevector( array: NDArray[np.complex128],