Writing assertions¶
qtest ships four assertions, each matched to a different question you might ask of a quantum program. This guide explains which assertion to reach for, how to choose a tolerance, and what the failure mode looks like when something is wrong.
Picking the right assertion¶
You want to check… |
Use |
Compare against |
|---|---|---|
A measurement distribution from a real run. |
|
Expected probabilities (or counts). |
The state vector produced by a (non-measured) circuit. |
|
Expected |
Whether an operator is unitary at all. |
|
\(U^\dagger U \approx I\). |
Whether two circuits compute the same unitary. |
|
Each other (up to global phase). |
Statistical vs. state-vector assertions¶
The most common mistake new users make is comparing measurement counts with strict equality, or comparing state vectors with statistical tolerances. They are not interchangeable.
Statistical assertions — assert_distribution_close() —
operate on shot counts produced by a real (or simulated) run. They take
shot noise into account: a distribution that is statistically
indistinguishable from the expected one passes, even if the raw counts
differ.
from qtest import assert_distribution_close
# ``assert_distribution_close`` runs the circuit internally on
# qtest's default backend — you pass the circuit, not pre-computed
# counts.
assert_distribution_close(
qc, expected={"0": 0.5, "1": 0.5}, shots=4096, tolerance=0.03
)
State-vector assertions — assert_state_close() — work on
exact Statevector objects. They support
named reference states ("plus", "minus", "bell", …) and are
phase-insensitive by default. Use them for tight, unambiguous gate-level
tests where you don’t want shot-noise variance.
from qtest import assert_state_close
# Pass the circuit directly — qtest computes the state vector for
# you. Named reference states make tight, readable gate tests.
assert_state_close(qc, expected_state="plus", tolerance=1e-10)
Choosing a tolerance¶
The right tolerance depends on which assertion you’re using:
Distribution tolerance is the maximum total-variation distance you will accept. With \(N\) shots, the natural floor is \(\mathcal{O}(1/\sqrt{N})\) — at 4096 shots,
tolerance=0.03is a reasonable, lightly-conservative default. The helperqtest.metrics.auto_tolerance()will compute one for you given a shot count and a target false-positive rate.State / unitary tolerance is a hard numerical bound.
1e-10is appropriate when comparing against an analytically known state on a noiseless simulator;1e-6is reasonable when noise or finite precision is in the loop.When in doubt, start strict and loosen on failure rather than the reverse. A test that passes with
tolerance=0.5is barely testing anything.
Failure messages¶
qtest assertions raise AssertionError with a structured message
that reports the metric value, the tolerance, and (where useful) the
top-contributing offending outcomes. For example:
AssertionError: distribution mismatch
metric: total_variation_distance
observed: 0.0871
tolerance: 0.030
worst offenders:
"00": observed=0.612, expected=0.500
"11": observed=0.388, expected=0.500
That is by design: when a test fails in CI, the message alone should be enough to diagnose whether the failure is a real bug, a tolerance that is too tight, or a shot count that is too low.
Global-phase insensitivity¶
Two state vectors \(\lvert\psi\rangle\) and
\(e^{i\theta}\lvert\psi\rangle\) are physically identical — every
measurement on them returns the same statistics. qtest’s state and
unitary assertions are global-phase-insensitive by default, computing
\(1 - |\langle\psi_1|\psi_2\rangle|^2\) (infidelity) rather than the
raw L2 distance. Pass global_phase=False to
assert_state_close() if you really do care about phase.
Where to go next¶
Property-based testing — turn the rules above into Hypothesis properties that hold over many random circuits.
pytest integration — wire these assertions into a real pytest configuration with shared fixtures, markers, and CLI flags.
Assertions — the full autogenerated API reference for every assertion.