Property-based testing

Quantum software is unusually amenable to property-based testing: laws like unitarity, reversibility, and Bell-state symmetry hold for every valid circuit, not just one example. qtest builds on Hypothesis to make those laws cheap to express and to shrink failing inputs down to minimal reproducers.

Note

The qtest.strategies subpackage requires the optional Hypothesis extra:

pip install "qtest-quantum[hypothesis]"

The strategies at a glance

Strategy

Generates

quantum_circuits()

Random QuantumCircuit instances.

random_gates()

Single gate descriptors from a configurable gate set.

pauli_strings()

\(n\)-qubit Pauli strings over {I, X, Y, Z}.

random_states()

Haar-random pure state vectors.

product_states()

Tensor-product (separable) pure states.

random_density_matrices()

Random mixed states (density matrices).

Your first property test

Every unitary circuit, by definition, has a unitary matrix representation. Hypothesis makes that one-liner:

from hypothesis import given, settings
from qtest import assert_unitary
from qtest.strategies import quantum_circuits

@given(circuit=quantum_circuits(min_qubits=2, max_qubits=4, max_depth=8))
@settings(deadline=None, max_examples=50)
def test_random_circuits_are_unitary(circuit):
    assert_unitary(circuit, tolerance=1e-9)

The deadline=None matters: Hypothesis’ default per-example deadline is 200 ms, which random circuit simulation can blow past for larger qubit counts. Disabling it (or setting an explicit, generous value) is recommended for quantum properties.

Reversibility — a circuit composed with its inverse is the identity

from hypothesis import given, settings
from qiskit import QuantumCircuit
from qtest import assert_circuit_equivalent
from qtest.strategies import quantum_circuits

@given(circuit=quantum_circuits(min_qubits=1, max_qubits=3, max_depth=6))
@settings(deadline=None, max_examples=30)
def test_circuit_inverse_round_trip(circuit):
    round_trip = circuit.compose(circuit.inverse())
    identity = QuantumCircuit(circuit.num_qubits)  # empty == identity
    assert_circuit_equivalent(round_trip, identity, tolerance=1e-9)

When Hypothesis finds a counter-example it will shrink it — the failing circuit you actually see is typically the smallest circuit, in qubits and in depth, that still reproduces the bug. That is the single biggest debugging win of property-based testing in quantum: a flaky-looking failure in an 8-qubit, 30-deep circuit shrinks to a 2-qubit, 2-deep one you can read by eye.

Shrinking in practice

Suppose you introduce a bug in your gate decomposition — a subtle phase error in the implementation of RY. A standard test on a Bell state might pass (the bug cancels at measurement) and a hand-rolled test on a single gate might miss it entirely. A property test like the one above will find a failing circuit and shrink it down to roughly:

QuantumCircuit(1)
qc.ry(0.5, 0)

The minimal reproducer is handed to you with a stack trace; you do not need to bisect, instrument, or guess.

Tuning Hypothesis settings for quantum

A few defaults are worth overriding for quantum properties:

from hypothesis import HealthCheck, settings

QUANTUM = settings(
    deadline=None,           # circuit simulation is slow; no per-example timeout
    max_examples=50,         # 50 random examples per property is a good default
    suppress_health_check=[
        HealthCheck.too_slow,
        HealthCheck.data_too_large,
    ],
    print_blob=True,         # show the shrunk failing input as a copy-pasteable blob
)

@QUANTUM
@given(circuit=quantum_circuits(min_qubits=2, max_qubits=4))
def test_whatever(circuit):
    ...

For long-running CI properties, register a Hypothesis profile (e.g. "ci") with higher max_examples and pick it up in conftest.py.

What makes a good quantum property?

The best properties are ones that hold exactly, by mathematical definition, regardless of the specific circuit:

  • Unitarity: \(U^\dagger U = I\).

  • Reversibility: qc.compose(qc.inverse()) is the identity.

  • Commutation: gates that commute analytically commute numerically.

  • Symmetry: a circuit that is symmetric in two qubits produces a distribution that is symmetric in those bits.

  • Conservation laws: a circuit that preserves total \(Z\) parity always produces outcomes with the expected parity.

Properties that don’t work well as property tests:

  • Anything that depends on a specific, hand-crafted output state — that is what unit tests are for.

  • Anything whose tolerance needs to grow with circuit depth — Hypothesis will happily find a 30-deep circuit where your generous tolerance finally bites.

Where to go next

  • Writing assertions — the assertions you’ll combine with Hypothesis strategies.

  • pytest integration — how to gate slow property tests behind markers and CI profiles.

  • Strategies — the full autogenerated API reference for every strategy.