Source code for qtest.assertions.resources

"""Resource / cost assertions — guard a circuit's *structure*, not its output.

Where :func:`qtest.assert_circuit_equivalent` answers "does the transpiled
circuit still compute the right thing?", these assertions answer the
complementary question: "did it get *cheaper*?". They check the quantities an
optimiser or transpiler is supposed to reduce — circuit depth, total gate
count, two-qubit-gate count, and T-count — and are the natural way to lock in
an optimisation and catch regressions.

All checks are pure static analysis (no simulation), so they are fast and work
on circuits of any width. Metrics are extracted via
:meth:`Backend.get_resources`; backends that cannot introspect their native
circuit type raise :class:`NotImplementedError`.

Gate-name-based checks (:func:`assert_max_gate_count` with a ``gate`` filter,
and :func:`assert_max_t_count`) rely on backend gate naming, which qtest
normalises to lowercase (e.g. ``"t"``, ``"cx"``, ``"h"``).
"""

from __future__ import annotations

from typing import Any

from qtest.backends import Backend
from qtest.backends.base import CircuitResources
from qtest.backends.registry import get_backend
from qtest.config import get_config

__all__ = [
    "assert_max_depth",
    "assert_max_gate_count",
    "assert_max_t_count",
    "assert_max_two_qubit_count",
]


# --------------------------------------------------------------------------- #
# Public API                                                                  #
# --------------------------------------------------------------------------- #


[docs] def assert_max_depth( circuit: Any, max_depth: int, backend: Backend | None = None, msg: str | None = None, ) -> None: """Assert that *circuit* has depth at most *max_depth*. Parameters ---------- circuit A circuit object native to the chosen backend. max_depth Maximum permitted circuit depth (non-negative integer). backend Backend used to analyse the circuit. Defaults to the configured one. msg Optional prefix prepended to the assertion failure message. Raises ------ ValueError If ``max_depth`` is not a non-negative integer. AssertionError If the circuit's depth exceeds ``max_depth``. """ _validate_limit(max_depth, "max_depth") res = _resources(circuit, backend) if res.depth <= max_depth: return raise AssertionError( _format_failure( "Circuit depth exceeds the allowed maximum", metric="depth", actual=res.depth, limit=max_depth, res=res, user_msg=msg, ) )
[docs] def assert_max_gate_count( circuit: Any, max_count: int, gate: str | None = None, backend: Backend | None = None, msg: str | None = None, ) -> None: """Assert that *circuit* contains at most *max_count* gates. Parameters ---------- circuit A circuit object native to the chosen backend. max_count Maximum permitted gate count (non-negative integer). gate If given, count only gates with this (lowercased) name, e.g. ``"cx"`` or ``"h"``. If ``None`` (the default), count every operation. backend Backend used to analyse the circuit. Defaults to the configured one. msg Optional prefix prepended to the assertion failure message. Raises ------ ValueError If ``max_count`` is not a non-negative integer, or ``gate`` is not a non-empty string / ``None``. AssertionError If the (optionally filtered) gate count exceeds ``max_count``. """ _validate_limit(max_count, "max_count") if gate is not None and (not isinstance(gate, str) or not gate): raise ValueError(f"gate must be a non-empty string or None, got {gate!r}") res = _resources(circuit, backend) if gate is None: actual = res.size metric = "total gate count" else: key = gate.lower() actual = res.gate_counts.get(key, 0) metric = f"{key!r} gate count" if actual <= max_count: return raise AssertionError( _format_failure( "Circuit gate count exceeds the allowed maximum", metric=metric, actual=actual, limit=max_count, res=res, user_msg=msg, ) )
[docs] def assert_max_two_qubit_count( circuit: Any, max_count: int, backend: Backend | None = None, msg: str | None = None, ) -> None: """Assert that *circuit* contains at most *max_count* two-qubit gates. Two-qubit gates dominate error rates on real hardware, so this is one of the most useful regression guards after a transpilation pass. Raises ------ ValueError If ``max_count`` is not a non-negative integer. AssertionError If the two-qubit-gate count exceeds ``max_count``. """ _validate_limit(max_count, "max_count") res = _resources(circuit, backend) if res.two_qubit_count <= max_count: return raise AssertionError( _format_failure( "Circuit two-qubit-gate count exceeds the allowed maximum", metric="two-qubit-gate count", actual=res.two_qubit_count, limit=max_count, res=res, user_msg=msg, ) )
[docs] def assert_max_t_count( circuit: Any, max_count: int, backend: Backend | None = None, msg: str | None = None, ) -> None: """Assert that *circuit* uses at most *max_count* T / T-dagger gates. T-count is the dominant cost metric for fault-tolerant (surface-code) execution, where T gates require expensive magic-state distillation. Raises ------ ValueError If ``max_count`` is not a non-negative integer. AssertionError If the T-count exceeds ``max_count``. """ _validate_limit(max_count, "max_count") res = _resources(circuit, backend) if res.t_count <= max_count: return raise AssertionError( _format_failure( "Circuit T-count exceeds the allowed maximum", metric="T-count", actual=res.t_count, limit=max_count, res=res, user_msg=msg, ) )
# --------------------------------------------------------------------------- # # Helpers # # --------------------------------------------------------------------------- # def _validate_limit(value: int, name: str) -> None: if not isinstance(value, int) or isinstance(value, bool) or value < 0: raise ValueError(f"{name} must be a non-negative integer, got {value!r}") def _resources(circuit: Any, backend: Backend | None) -> CircuitResources: if circuit is None: raise ValueError("circuit must not be None") if backend is None: backend = get_backend(get_config().default_backend) return backend.get_resources(circuit) def _format_failure( headline: str, *, metric: str, actual: int, limit: int, res: CircuitResources, user_msg: str | None, ) -> str: lines: list[str] = [] if user_msg: lines.append(user_msg) lines.append("") lines.append(headline) lines.append("") lines.append(f" {metric}: {actual} (limit: {limit}, over by {actual - limit})") lines.append("") lines.append(" Circuit resources:") lines.append(f" qubits: {res.num_qubits}") lines.append(f" depth: {res.depth}") lines.append(f" total gates: {res.size}") lines.append(f" two-qubit gates: {res.two_qubit_count}") lines.append(f" T-count: {res.t_count}") if res.gate_counts: breakdown = ", ".join(f"{name}={count}" for name, count in sorted(res.gate_counts.items())) lines.append(f" by gate: {breakdown}") return "\n".join(lines)