Source code for pytest_quantum.assertions.unitary

"""Unitary-level assertions for quantum circuits.

These functions compare circuits at the level of their unitary matrix — the
most rigorous form of correctness check for deterministic quantum operations.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import numpy as np

from pytest_quantum.converters.to_unitary import (
    _is_cirq,
    _is_pytket,
    _is_qiskit,
    to_unitary,
)

if TYPE_CHECKING:
    from numpy.typing import NDArray


[docs] def assert_unitary( circuit: object, expected: NDArray[np.complex128], *, atol: float = 1e-6, allow_global_phase: bool = True, ) -> None: """Assert that *circuit* implements the expected unitary matrix. Args: circuit: Any supported quantum circuit (Qiskit, Cirq, Braket, PennyLane). expected: Target unitary as a numpy array, shape ``(2**n, 2**n)``. atol: Absolute tolerance for element-wise comparison (default ``1e-6``). allow_global_phase: If ``True`` (default), circuits that differ only by a global phase ``e^{iθ}`` are considered equivalent. This is physically correct because global phase is not observable. Raises: AssertionError: If the circuit's unitary does not match *expected*. TypeError: If *circuit* is not a recognised framework type. Example:: import numpy as np from pytest_quantum import assert_unitary HADAMARD = np.array([[1, 1], [1, -1]]) / np.sqrt(2) def test_h_gate(): from qiskit import QuantumCircuit qc = QuantumCircuit(1) qc.h(0) assert_unitary(qc, HADAMARD) """ actual = to_unitary(circuit) expected_arr = np.asarray(expected, dtype=np.complex128) if actual.shape != expected_arr.shape: raise AssertionError( f"Unitary shape mismatch.\n" f" Circuit produces : {actual.shape}\n" f" Expected : {expected_arr.shape}\n" f" Hint: check qubit count and ordering." ) if np.allclose(actual, expected_arr, atol=atol): return if allow_global_phase: # Find the element with largest magnitude in expected, use it to # extract the global phase, then compare. flat_idx = int(np.argmax(np.abs(expected_arr))) e_val = expected_arr.flat[flat_idx] a_val = actual.flat[flat_idx] if abs(e_val) > 1e-10 and abs(a_val) > 1e-10: phase = a_val / e_val if np.allclose(actual, phase * expected_arr, atol=atol): return # equal up to global phase max_diff = float(np.max(np.abs(actual - expected_arr))) raise AssertionError( f"Circuit does not implement the expected unitary.\n" f" Max |difference| : {max_diff:.2e} (tolerance: {atol:.2e})\n" f" allow_global_phase = {allow_global_phase}\n" f" Hint: use allow_global_phase=True if you only care about " f"physical equivalence." )
[docs] def assert_circuits_equivalent( circuit_a: object, circuit_b: object, *, atol: float = 1e-6, ) -> None: """Assert that two circuits implement the same unitary, up to global phase. Works across frameworks — you can compare a Qiskit circuit against a Cirq circuit, a Braket circuit, or a PennyLane QNode. For two Qiskit circuits, ``mqt.qcec`` is used automatically when installed (faster, exact verification via decision diagrams / ZX-calculus). For cross-framework comparison the circuits are both converted to numpy matrices and compared numerically. Args: circuit_a: First circuit (any supported framework). circuit_b: Second circuit (any supported framework). atol: Absolute tolerance for the numpy fallback comparison (default ``1e-6``). Raises: AssertionError: If the circuits implement different unitaries. TypeError: If either argument is not a recognised circuit type. Example:: from pytest_quantum import assert_circuits_equivalent def test_cnot_cross_framework(): import cirq from qiskit import QuantumCircuit qc = QuantumCircuit(2) qc.cx(0, 1) q0, q1 = cirq.LineQubit.range(2) cc = cirq.Circuit(cirq.CNOT(q0, q1)) assert_circuits_equivalent(qc, cc) """ type_a = type(circuit_a).__module__ type_b = type(circuit_b).__module__ # Fast path: both Qiskit → use mqt.qcec (exact, scalable) if type_a.startswith("qiskit") and type_b.startswith("qiskit"): result = _qcec_verify(circuit_a, circuit_b) if result == "not_equivalent": raise AssertionError("Circuits are NOT equivalent (verified by mqt.qcec).") if result == "equivalent": return # result == "no_information" → fall through to numpy comparison # General path: convert both to unitary matrices u_a = to_unitary(circuit_a) u_b = to_unitary(circuit_b) # Normalize qubit ordering for cross-framework comparison. # Qiskit = little-endian, Cirq = big-endian, Pytket = big-endian. big_endian_a = _is_cirq(circuit_a) or _is_pytket(circuit_a) big_endian_b = _is_cirq(circuit_b) or _is_pytket(circuit_b) from pytest_quantum.converters.to_unitary import _reverse_qubit_order if _is_qiskit(circuit_a) and big_endian_b: u_a = _reverse_qubit_order(u_a) elif big_endian_a and _is_qiskit(circuit_b): u_b = _reverse_qubit_order(u_b) if u_a.shape != u_b.shape: raise AssertionError( f"Circuits act on different-sized Hilbert spaces.\n" f" circuit_a: {u_a.shape} circuit_b: {u_b.shape}" ) if np.allclose(u_a, u_b, atol=atol): return # Check up to global phase flat_idx = int(np.argmax(np.abs(u_a))) a_val = u_a.flat[flat_idx] b_val = u_b.flat[flat_idx] if abs(a_val) > 1e-10 and abs(b_val) > 1e-10: phase = a_val / b_val if np.allclose(u_a, phase * u_b, atol=atol): return max_diff = float(np.max(np.abs(u_a - u_b))) raise AssertionError( f"Circuits are NOT equivalent.\n" f" Max |U_a - U_b|: {max_diff:.2e} (tolerance: {atol:.2e})" )
[docs] def assert_transpilation_preserves_semantics( circuit: object, backend: object, *, optimization_level: int = 1, atol: float = 1e-6, ) -> None: """Assert that transpiling a Qiskit circuit preserves its unitary. Transpiles *circuit* for *backend* and verifies the resulting circuit implements the same unitary (up to global phase). Args: circuit: Qiskit QuantumCircuit (must be unitary, no measurements). backend: Qiskit backend or ``FakeBackend`` target. optimization_level: Qiskit transpiler optimisation level 0-3 (default 1). atol: Tolerance for unitary comparison (default 1e-6). Raises: AssertionError: If transpiled circuit has different unitary. NotImplementedError: For non-Qiskit circuits. ImportError: If qiskit is not installed. Example:: from qiskit import QuantumCircuit from qiskit.providers.fake_provider import GenericBackendV2 from pytest_quantum import assert_transpilation_preserves_semantics qc = QuantumCircuit(2) qc.h(0) qc.cx(0, 1) backend = GenericBackendV2(num_qubits=2) assert_transpilation_preserves_semantics(qc, backend) """ module = type(circuit).__module__ if not module.startswith("qiskit"): raise NotImplementedError( f"assert_transpilation_preserves_semantics only supports Qiskit circuits; " f"got {module!r}" ) try: from qiskit import transpile original_U = to_unitary(circuit) transpiled = transpile( circuit, backend=backend, optimization_level=optimization_level, ) assert_unitary(transpiled, original_U, atol=atol, allow_global_phase=True) except ImportError as exc: raise ImportError( "qiskit is required: pip install pytest-quantum[qiskit]" ) from exc
# --------------------------------------------------------------------------- # Internal helper # --------------------------------------------------------------------------- def _qcec_verify(circuit_a: object, circuit_b: object) -> str: """Run mqt.qcec if available. Returns 'equivalent', 'not_equivalent', or 'no_information'.""" try: from mqt import qcec except ImportError: return "no_information" result = qcec.verify(circuit_a, circuit_b) result_str = str(result.equivalence).lower() if "not_equivalent" in result_str: return "not_equivalent" if "equivalent" in result_str: return "equivalent" return "no_information"