Source code for qtest.assertions.commutation
"""``assert_commutes`` — operator commutation / anticommutation assertion.
Checks whether two operators commute (``AB == BA``) or anticommute
(``AB == -BA``) by comparing the (anti)commutator to the zero matrix. Operands
may be raw matrices, gate objects (anything with ``to_matrix()``), or circuits
(whose unitary is extracted via the backend) — the same coercion used by
:func:`qtest.assert_unitary`.
"""
from __future__ import annotations
from typing import Any
import numpy as np
from qtest.assertions.unitary import _to_matrix
from qtest.backends import Backend
_FP_SLACK = 1e-14
[docs]
def assert_commutes(
a: Any,
b: Any,
tolerance: float = 1e-9,
anti: bool = False,
backend: Backend | None = None,
msg: str | None = None,
) -> None:
r"""Assert that operators *a* and *b* commute (or anticommute).
Parameters
----------
a, b
Operators as ``np.ndarray``, gate objects with ``to_matrix()``, or
circuits. Must have identical shape.
tolerance
Maximum permitted entry-wise magnitude of the (anti)commutator
:math:`\max_{ij}|C_{ij}|`, where ``C = AB - BA`` (commutator) or
``C = AB + BA`` (anticommutator when ``anti=True``).
anti
When ``True`` check anticommutation (``AB + BA ≈ 0``) instead of
commutation.
backend
Backend used to extract unitaries from circuit-shaped operands.
msg
Optional prefix prepended to the assertion failure message.
Raises
------
ValueError
For malformed or mismatched operand shapes.
AssertionError
When *a* and *b* do not (anti)commute within *tolerance*.
"""
if not isinstance(tolerance, (int, float)) or isinstance(tolerance, bool) or tolerance < 0.0:
raise ValueError(f"tolerance must be a non-negative real number, got {tolerance!r}")
mat_a = _to_matrix(a, backend)
mat_b = _to_matrix(b, backend)
for name, m in (("a", mat_a), ("b", mat_b)):
if m.ndim != 2 or m.shape[0] != m.shape[1] or m.size == 0:
raise ValueError(f"operand {name} must be a non-empty square matrix, got {m.shape}")
if mat_a.shape != mat_b.shape:
raise ValueError(f"operands must have the same shape; got {mat_a.shape} and {mat_b.shape}.")
ab = mat_a @ mat_b
ba = mat_b @ mat_a
commutator = ab + ba if anti else ab - ba
deviation = float(np.max(np.abs(commutator)))
if deviation <= tolerance + _FP_SLACK:
return
raise AssertionError(
_format_failure(a=a, b=b, anti=anti, deviation=deviation, tolerance=tolerance, user_msg=msg)
)
# --------------------------------------------------------------------------- #
# Helpers #
# --------------------------------------------------------------------------- #
def _operand_summary(op: Any) -> str:
if isinstance(op, np.ndarray):
return f"<ndarray shape={op.shape}>"
name = getattr(op, "name", None)
return str(name) if name else f"<{type(op).__name__}>"
def _format_failure(
*,
a: Any,
b: Any,
anti: bool,
deviation: float,
tolerance: float,
user_msg: str | None,
) -> str:
kind = "anticommute" if anti else "commute"
symbol = "AB + BA" if anti else "AB - BA"
lines: list[str] = []
if user_msg:
lines.extend([user_msg, ""])
lines.append(f"Operators do not {kind}")
lines.append("")
lines.append(f" A: {_operand_summary(a)}")
lines.append(f" B: {_operand_summary(b)}")
lines.append(f" max|{symbol}|: {deviation:.3e}")
lines.append(f" Tolerance: {tolerance:g}")
return "\n".join(lines)