API Reference

Complete auto-generated API reference for all public modules.


Top-level package

pytest-quantum: A cross-framework pytest plugin for quantum program testing.

Public API — import anything you need directly from pytest_quantum:

from pytest_quantum import (
    # Unitary
    assert_unitary,
    assert_circuits_equivalent,
    # States
    assert_normalized,
    assert_state_fidelity_above,
    assert_states_close,
    # Density matrices (v0.2.0)
    assert_density_matrix_close,
    assert_trace_distance_below,
    assert_purity_above,
    assert_partial_trace_close,
    # Observables / expectation values (v0.2.0)
    assert_expectation_value_close,
    assert_ground_state_energy_close,
    # VQE / variational optimization (v0.3.0)
    assert_vqe_converges,
    assert_cost_decreases,
    # Distributions
    assert_measurement_distribution,
    assert_counts_close,
    # Primitives (v0.2.0)
    assert_sampler_distribution,
    assert_estimator_close,
    # Structure
    assert_circuit_depth,
    assert_circuit_width,
    assert_gate_count,
    assert_gates_in_basis_set,
    assert_circuit_is_clifford,
    assert_has_diagram,
    assert_no_mid_circuit_measurement,
    # Snapshots (v0.2.0)
    assert_unitary_snapshot,
    assert_distribution_snapshot,
    # Channels / operators (v0.3.0)
    assert_hermitian,
    assert_positive_semidefinite,
    assert_commutes_with,
    assert_channel_is_cptp,
    assert_process_fidelity_above,
    assert_noise_fidelity_above,
    # Entanglement (v0.3.0)
    assert_entanglement_entropy_below,
    assert_bloch_sphere_close,
    assert_schmidt_rank_at_most,
    # Information theory (v0.3.0)
    assert_hellinger_close,
    assert_kl_divergence_below,
    assert_cross_entropy_below,
    # QASM / serialization round-trip (v0.3.0+)
    assert_qasm_roundtrip,
    assert_qasm2_roundtrip,
    # Transpilation (v0.3.0)
    assert_transpilation_preserves_semantics,
    # Stim / QEC (v0.3.0)
    assert_stim_logical_error_rate_below,
    assert_stim_detector_error_rate_below,
    assert_stabilizer_state,
    # Sweeps (v0.4.0)
    assert_circuit_sweep,
    assert_circuit_sweep_states,
    assert_parametrized_unitary_continuous,
    # Compilation (v0.4.0)
    assert_transpilation_equivalent,
    assert_transpilation_depth_below,
    assert_gate_count_after_transpilation,
    # Mitiq error mitigation (v0.4.0)
    assert_zne_expectation_close,
    assert_zne_reduces_error,
    assert_cdr_reduces_error,
    assert_mitigation_improves_fidelity,
    assert_pec_reduces_error,
    assert_pec_expectation_close,
    assert_error_mitigation_benchmark,
    # Benchmarking (v0.5.0)
    assert_quantum_volume,
    assert_randomized_benchmarking,
    assert_t1_above,
    assert_t2_above,
    assert_t2star_above,
    assert_interleaved_rb,
    assert_gate_fidelity_above,
    # Cross-platform equivalence (v0.5.0)
    assert_cross_platform_equivalent,
    assert_qiskit_cirq_equivalent,
    assert_qiskit_pytket_equivalent,
    # Noise channel assertions (v0.5.0)
    assert_depolarizing_channel,
    assert_amplitude_damping_channel,
    assert_dephasing_channel,
    assert_no_leakage,
    assert_channel_preserves_trace,
    assert_channel_diamond_norm_below,
    # Hardware assertions (v0.5.0)
    assert_backend_calibration,
    assert_backend_executes,
    assert_circuit_fits_backend,
    assert_mirror_fidelity,
    assert_real_counts_close,
    # Random generators (v0.3.0)
    random_statevector,
    random_density_matrix,
    random_unitary,
    random_kraus_channel,
    depolarizing_kraus,
    # Stats
    min_shots,
    recommended_shots,
    fidelity,
    tvd,
    tvd_from_counts,
    chi_square_test,
)

Fixtures (aer_simulator, cirq_simulator, cirq_sampler, qiskit_sampler, qiskit_estimator, pytket_circuit_factory, stim_sampler, quantum_benchmark, shot_budget, etc.) are injected automatically by pytest — no import needed, just declare them as test parameters.

pytest_quantum.assert_amplitude_damping_channel(channel_matrices, expected_gamma, *, atol=0.01)[source]

Assert Kraus operators represent an amplitude damping channel with parameter gamma.

The canonical amplitude damping Kraus operators are:

K0 = [[1, 0], [0, sqrt(1 - gamma)]]
K1 = [[0, sqrt(gamma)], [0, 0]]

The function estimates gamma from the Choi matrix of the channel. Specifically, the (1, 1) element of the Choi matrix (in the column-vectorised convention) encodes 1 - gamma, so:

gamma_est = 1 - Re(choi[1, 1]) / Re(choi[0, 0])
Parameters:
  • channel_matrices (list[ndarray[tuple[Any, ...], dtype[cdouble]]]) – List of Kraus operators for a single-qubit channel.

  • expected_gamma (float) – Expected decay parameter gamma in [0, 1].

  • atol (float) – Absolute tolerance (default 0.01).

Raises:
Return type:

None

pytest_quantum.assert_backend_calibration(backend, *, max_gate_error=0.01, max_readout_error=0.05)[source]

Assert backend calibration data meets quality thresholds.

Reads the latest calibration data from backend.properties() and checks that all readout errors and 2-qubit gate errors are within the specified bounds. Useful as a prerequisite test to skip an entire suite if the device is under-performing.

Parameters:
  • backend (Any) – IBM backend with a .properties() method.

  • max_gate_error (float) – Maximum allowed 2-qubit gate error rate (default 0.01 = 1%).

  • max_readout_error (float) – Maximum allowed per-qubit readout error (default 0.05 = 5%).

Raises:

AssertionError – If any error rate exceeds the threshold, or if the backend exposes no calibration data.

Return type:

None

Example:

from pytest_quantum import assert_backend_calibration


def test_device_quality(ibm_backend):
    assert_backend_calibration(
        ibm_backend,
        max_gate_error=0.005,
        max_readout_error=0.03,
    )
pytest_quantum.assert_backend_executes(circuit, backend, *, shots=1024, timeout=300.0, transpile=True, optimization_level=1)[source]

Assert a circuit executes successfully on a backend and return counts.

Submits the circuit (optionally transpiling it first), waits for the job to complete, and returns the measurement counts. Raises AssertionError if the job fails, is cancelled, or does not finish within timeout seconds.

Parameters:
  • circuit (Any) – Qiskit QuantumCircuit to execute.

  • backend (Any) – Qiskit-compatible backend (IBM, AerSimulator, …).

  • shots (int) – Number of shots (default 1024).

  • timeout (float) – Maximum seconds to wait for job completion (default 300).

  • transpile (bool) – Transpile to backend before running (default True).

  • optimization_level (int) – Qiskit transpilation level 0–3 (default 1).

Returns:

Measurement counts keyed by bitstring.

Return type:

dict[str, int]

Raises:

Example:

from pytest_quantum import assert_backend_executes


def test_h_gate(ibm_backend):
    from qiskit import QuantumCircuit

    qc = QuantumCircuit(1, 1)
    qc.h(0)
    qc.measure(0, 0)
    counts = assert_backend_executes(qc, ibm_backend, shots=2048)
    assert counts.get("0", 0) + counts.get("1", 0) == 2048
pytest_quantum.assert_bloch_sphere_close(statevector, expected_theta, expected_phi, *, atol=0.1)[source]

Assert single-qubit state is close to expected Bloch sphere position.

Bloch vector: (sin(theta)cos(phi), sin(theta)sin(phi), cos(theta)) theta in [0, pi]: polar angle (0=|0>, pi=|1>) phi in [0, 2pi): azimuthal angle

Parameters:
  • statevector (object) – Single-qubit state [alpha, beta] (length-2 array).

  • expected_theta (float) – Expected polar angle in radians.

  • expected_phi (float) – Expected azimuthal angle in radians.

  • atol (float) – Tolerance for Bloch vector Euclidean distance (default 0.1).

Raises:
Return type:

None

pytest_quantum.assert_cdr_reduces_error(circuit, executor, simulator, *, num_training_circuits=10, atol=None)[source]

Assert Clifford Data Regression (CDR) reduces estimation error.

CDR trains a regression model using near-Clifford circuits simulated classically, then uses the model to mitigate the noisy result.

Parameters:
  • circuit (object) – Quantum circuit (should contain near-Clifford gates).

  • executor (Callable[..., float]) – Callable(circuit) -> float for noisy execution.

  • simulator (Callable[..., float]) – Callable(circuit) -> float for exact classical simulation.

  • num_training_circuits (int) – Number of near-Clifford training circuits (default 10).

  • atol (float | None) – If provided, assert |mitigated - simulated| <= atol.

Return type:

tuple[float, float]

Returns:

Tuple of (unmitigated_value, mitigated_value).

Raises:
  • AssertionError – If atol provided and mitigated result is not within tolerance.

  • ImportError – If mitiq is not installed.

pytest_quantum.assert_channel_diamond_norm_below(channel_a_kraus, channel_b_kraus, max_diamond_norm, *, atol=0.0001)[source]

Assert the diamond-norm distance between two channels is below a threshold.

The diamond norm (completely bounded trace norm) is the operationally meaningful distance between quantum channels: it equals the maximum distinguishing advantage over all input states and ancillae.

When cvxpy is available the function solves the exact SDP formulation of Watrous (2009) and the result is precise.

Fallback (cvxpy not installed): the function uses the operator (spectral) norm of the difference of the normalised Choi matrices as a proxy distance. For single-qubit channels this proxy is within a constant factor of the true diamond norm and gives a conservative bound.

Parameters:
  • channel_a_kraus (list[ndarray[tuple[Any, ...], dtype[cdouble]]]) – Kraus operators for channel A (square arrays, equal shape).

  • channel_b_kraus (list[ndarray[tuple[Any, ...], dtype[cdouble]]]) – Kraus operators for channel B (same dimension as A).

  • max_diamond_norm (float) – Maximum acceptable diamond-norm distance.

  • atol (float) – Absolute tolerance added to the threshold (default 1e-4).

Raises:
  • AssertionError – If the diamond-norm distance exceeds max_diamond_norm + atol, showing the estimated distance and the method used.

  • ValueError – If the Kraus lists are empty or have mismatched dimensions.

Return type:

None

pytest_quantum.assert_channel_is_cptp(kraus_ops, *, atol=1e-08)[source]

Assert Kraus operators satisfy completeness: sum_i K_i† K_i == I.

This is the necessary and sufficient condition for a channel to be completely positive and trace-preserving (CPTP).

Parameters:
  • kraus_ops (list[Any]) – Non-empty list of Kraus operators (square matrices of equal shape).

  • atol (float) – Absolute tolerance for the completeness check (default 1e-8).

Raises:
  • AssertionError – If the completeness relation is violated, showing Frobenius norm of the deviation.

  • ValueError – If the list is empty, operators have different shapes, or operators are not square.

Return type:

None

pytest_quantum.assert_channel_preserves_trace(channel_matrices, *, atol=1e-06)[source]

Assert that Kraus operators satisfy the trace-preserving (TP) condition.

A quantum channel is trace-preserving iff:

sum_i  K_i^dagger @ K_i  ==  I

This is equivalent to demanding that the channel maps every valid density matrix to another density matrix with the same trace.

Parameters:
  • channel_matrices (list[ndarray[tuple[Any, ...], dtype[cdouble]]]) – Non-empty list of Kraus operators (square numpy arrays of equal shape).

  • atol (float) – Absolute tolerance for the completeness check (default 1e-6).

Raises:
  • AssertionError – If the completeness relation is violated, showing the Frobenius norm of the deviation and the tolerance.

  • ValueError – If the list is empty, operators have different shapes, or operators are not square.

Return type:

None

pytest_quantum.assert_circuit_depth(circuit, *, max_depth=None, min_depth=None)[source]

Assert that a circuit’s depth is within the specified bounds.

At least one of max_depth or min_depth must be provided.

Supported frameworks: Qiskit, Cirq, Amazon Braket.

Parameters:
  • circuit (object) – A quantum circuit from a supported framework.

  • max_depth (int | None) – If given, the circuit depth must be ≤ this value.

  • min_depth (int | None) – If given, the circuit depth must be ≥ this value.

Raises:
Return type:

None

Example:

def test_circuit_depth():
    from qiskit import QuantumCircuit

    qc = QuantumCircuit(2)
    qc.h(0)
    qc.cx(0, 1)
    assert_circuit_depth(qc, max_depth=3)
pytest_quantum.assert_circuit_fits_backend(circuit, backend, *, optimization_level=3, max_depth=None, max_2q_gates=None)[source]

Assert a circuit transpiles to the backend and stays within resource limits.

Transpiles circuit for backend and checks optional depth and 2-qubit gate count constraints. Even without constraints this is useful to assert the circuit is compatible with the backend’s basis gates and connectivity.

Parameters:
  • circuit (Any) – QuantumCircuit to check.

  • backend (Any) – Target backend.

  • optimization_level (int) – Qiskit transpilation level 0–3 (default 3).

  • max_depth (int | None) – If set, assert transpiled depth ≤ max_depth.

  • max_2q_gates (int | None) – If set, assert 2-qubit gate count ≤ max_2q_gates.

Return type:

dict[str, Any]

Returns:

dict with keys ‘depth’, ‘num_2q_gates’, ‘num_qubits’, ‘ops’.

Raises:

Example:

from pytest_quantum import assert_circuit_fits_backend


def test_grover_fits(ibm_backend):
    info = assert_circuit_fits_backend(
        grover_circuit,
        ibm_backend,
        max_depth=200,
        max_2q_gates=50,
    )
    print(
        f"Transpiled depth: {info['depth']}, 2Q gates: {info['num_2q_gates']}"
    )
pytest_quantum.assert_circuit_is_clifford(circuit)[source]

Assert a circuit uses only Clifford gates (H, S, S†, X, Y, Z, CNOT, CZ, SWAP).

Clifford circuits are classically efficiently simulable. Supported: Qiskit, Cirq.

Raises:
Parameters:

circuit (object)

Return type:

None

Example:

def test_is_clifford():
    from qiskit import QuantumCircuit
    from pytest_quantum import assert_circuit_is_clifford

    qc = QuantumCircuit(2)
    qc.h(0)
    qc.cx(0, 1)
    assert_circuit_is_clifford(qc)
Return type:

None

Parameters:

circuit (object)

pytest_quantum.assert_circuit_sweep(circuit_fn, param_values, expected_fn, *, atol=1e-06, allow_global_phase=True)[source]

Assert a parametrized circuit matches expected unitary for all parameter values.

Calls circuit_fn(**params) for every combination of param_values, computes the unitary, and compares to expected_fn(**params).

Parameters:
  • circuit_fn (Callable[..., Any]) – Callable that accepts keyword parameter arguments and returns a quantum circuit.

  • param_values (dict[str, list[float] | ndarray[tuple[Any, ...], dtype[double]]]) – Dict mapping parameter name to list/array of values. All combinations are tested (cartesian product).

  • expected_fn (Callable[..., ndarray[tuple[Any, ...], dtype[cdouble]]]) – Callable that accepts the same keyword arguments and returns the expected unitary as a numpy array.

  • atol (float) – Absolute tolerance for unitary comparison (default 1e-6).

  • allow_global_phase (bool) – If True, ignore global phase differences (default True).

Raises:

AssertionError – If any parameter combination fails.

Return type:

None

Example:

import numpy as np
from pytest_quantum import assert_circuit_sweep
from qiskit import QuantumCircuit


def rx_circuit(theta):
    qc = QuantumCircuit(1)
    qc.rx(theta, 0)
    return qc


def rx_expected(theta):
    c, s = np.cos(theta / 2), np.sin(theta / 2)
    return np.array([[c, -1j * s], [-1j * s, c]])


assert_circuit_sweep(
    rx_circuit,
    {"theta": np.linspace(0, 2 * np.pi, 8)},
    rx_expected,
)
pytest_quantum.assert_circuit_sweep_states(circuit_fn, initial_state, param_values, expected_fn, *, min_fidelity=0.99)[source]

Assert a parametrized circuit produces expected output states for all params.

Applies circuit_fn(**params) to initial_state and compares the output statevector fidelity to expected_fn(**params).

Parameters:
Raises:

AssertionError – If any combination fails the fidelity check.

Return type:

None

Example:

import numpy as np
from pytest_quantum import assert_circuit_sweep_states

psi0 = np.array([1, 0], dtype=complex)


def rz_circuit(phi):
    from qiskit import QuantumCircuit

    qc = QuantumCircuit(1)
    qc.rz(phi, 0)
    return qc


def expected_state(phi):
    return np.array([np.exp(-1j * phi / 2), 0])


assert_circuit_sweep_states(
    rz_circuit, psi0, {"phi": [0, np.pi / 2, np.pi]}, expected_state
)
pytest_quantum.assert_circuit_width(circuit, expected_qubits)[source]

Assert that a circuit acts on exactly expected_qubits qubits.

Supported frameworks: Qiskit, Cirq, Amazon Braket, PennyLane.

Parameters:
  • circuit (object) – A quantum circuit from a supported framework.

  • expected_qubits (int) – Expected number of qubits.

Raises:
Return type:

None

Example:

def test_circuit_width():
    from qiskit import QuantumCircuit

    qc = QuantumCircuit(3)
    qc.h(0)
    qc.cx(0, 1)
    qc.cx(1, 2)
    assert_circuit_width(qc, expected_qubits=3)
pytest_quantum.assert_circuits_equivalent(circuit_a, circuit_b, *, atol=1e-06)[source]

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.

Parameters:
  • circuit_a (object) – First circuit (any supported framework).

  • circuit_b (object) – Second circuit (any supported framework).

  • atol (float) – 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.

Return type:

None

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)
pytest_quantum.assert_commutes_with(op_a, op_b, *, atol=1e-08)[source]

Assert two square matrices commute: AB == BA.

Parameters:
  • op_a (Any) – First square matrix.

  • op_b (Any) – Second square matrix (same size as op_a).

  • atol (float) – Absolute tolerance for the commutator (default 1e-8).

Raises:
Return type:

None

pytest_quantum.assert_cost_decreases(cost_history, *, min_decrease=0.0, atol=1e-06)[source]

Assert an optimization cost history shows overall decrease.

Checks that cost_history[-1] < cost_history[0] - min_decrease.

Parameters:
  • cost_history (list[float]) – List of cost values recorded during optimization.

  • min_decrease (float) – Minimum required decrease from first to last value.

  • atol (float) – Tolerance for floating-point comparison (default 1e-6).

Raises:
Return type:

None

Example:

history = []
for step in range(50):
    cost = run_vqe_step(params)
    history.append(cost)

assert_cost_decreases(history, min_decrease=0.1)
pytest_quantum.assert_counts_close(counts_a, counts_b, *, max_tvd=0.05)[source]

Assert that two count dictionaries are statistically close.

Computes the Total Variation Distance (TVD) between the normalised distributions and fails if it exceeds max_tvd.

Useful for comparing two backends, or checking that transpilation has not changed a circuit’s output distribution.

Parameters:
  • counts_a (dict[str, int]) – First counts dict.

  • counts_b (dict[str, int]) – Second counts dict.

  • max_tvd (float) – Maximum acceptable TVD (default 0.05). TVD of 0 means identical distributions; 1 means disjoint support.

Raises:

AssertionError – If TVD exceeds max_tvd.

Return type:

None

Example:

def test_transpile_preserves_distribution(aer_simulator):
    from qiskit import QuantumCircuit, transpile

    qc = QuantumCircuit(2)
    qc.h(0)
    qc.cx(0, 1)
    qc.measure_all()

    # ideal vs noise-free transpiled
    qc_t = transpile(qc, aer_simulator, optimization_level=3)
    counts_ideal = aer_simulator.run(qc, shots=2000).result().get_counts()
    counts_transpiled = (
        aer_simulator.run(qc_t, shots=2000).result().get_counts()
    )
    assert_counts_close(counts_ideal, counts_transpiled, max_tvd=0.05)
pytest_quantum.assert_cross_entropy_below(counts, expected_probs, *, max_ce=1.0)[source]

Assert cross-entropy H(P, Q) = -sum_x P(x) log2 Q(x) <= max_ce.

Used in quantum supremacy experiments as XEB (cross-entropy benchmarking).

Parameters:
  • counts (dict[str, int]) – Observed count dictionary.

  • expected_probs (dict[str, float]) – Expected probability dictionary Q.

  • max_ce (float) – Maximum allowed cross-entropy in bits (default 1.0).

Raises:
Return type:

None

pytest_quantum.assert_cross_platform_equivalent(circuit_a, circuit_b, *, atol=1e-06, allow_global_phase=True, framework_a=None, framework_b=None)[source]

Assert that two circuits implement the same unitary, up to global phase.

Converts both circuits to numpy unitary matrices using pytest_quantum.converters.to_unitary.to_unitary() and compares them numerically, optionally ignoring a global phase factor e^{iθ}.

Qubit-ordering conventions are normalised automatically: Qiskit uses little-endian while Cirq and pytket use big-endian, so comparing a Qiskit circuit against a Cirq or pytket circuit applies the appropriate permutation before the element-wise check.

Parameters:
  • circuit_a (object) – First circuit (any supported framework).

  • circuit_b (object) – Second circuit (any supported framework).

  • atol (float) – Absolute tolerance for element-wise comparison (default 1e-6).

  • allow_global_phase (bool) – If True (default), circuits that differ only by a global phase e^{iθ} are considered equivalent.

  • framework_a (str | None) – Optional hint like "qiskit" or "cirq" used only to improve error messages.

  • framework_b (str | None) – Optional hint for the second circuit.

Raises:
  • AssertionError – If the circuits implement different unitaries, with a message showing the max element-wise difference and the framework names.

  • ValueError – If either circuit cannot be converted to a unitary (e.g. it contains measurements or is not a pure-unitary circuit).

Return type:

None

Example:

:rtype: :sphinx_autodoc_typehints_type:`\:py\:obj\:\`None\``

from qiskit import QuantumCircuit from pytest_quantum.assertions.cross_platform import (

assert_cross_platform_equivalent,

)

qc1 = QuantumCircuit(1) qc1.h(0)

qc2 = QuantumCircuit(1) qc2.h(0)

assert_cross_platform_equivalent(qc1, qc2)

pytest_quantum.assert_density_matrix_close(rho, sigma, *, atol=1e-06)[source]

Assert two density matrices are close element-wise after normalisation.

Parameters:
  • rho (object) – First density matrix, shape (d, d).

  • sigma (object) – Second density matrix, shape (d, d).

  • atol (float) – Absolute tolerance (default 1e-6).

Raises:
Return type:

None

Example:

import numpy as np
from pytest_quantum import assert_density_matrix_close

rho = np.array([[0.5, 0.5], [0.5, 0.5]], dtype=complex)
assert_density_matrix_close(rho, rho)
pytest_quantum.assert_dephasing_channel(channel_matrices, expected_rate, *, atol=0.01)[source]

Assert Kraus operators represent a dephasing (phase damping) channel.

The canonical dephasing Kraus operators are:

K0 = [[1, 0], [0, sqrt(1 - p)]]
K1 = [[0, 0], [0, sqrt(p)]]

The dephasing rate p controls how quickly off-diagonal coherences decay. It is estimated by applying the channel to the superposition state |+><+| and reading off the off-diagonal survival. The output off-diagonal element is 0.5 * sqrt(1 - p), so:

p_est = 1 - (2 * |rho_out[0, 1]|) ** 2
Parameters:
  • channel_matrices (list[ndarray[tuple[Any, ...], dtype[cdouble]]]) – List of Kraus operators for a single-qubit channel.

  • expected_rate (float) – Expected dephasing rate p in [0, 1].

  • atol (float) – Absolute tolerance (default 0.01).

Raises:
Return type:

None

pytest_quantum.assert_depolarizing_channel(channel_matrices, expected_error_rate, *, atol=0.01)[source]

Assert Kraus operators represent a depolarizing channel with a given error rate.

For the standard single-qubit depolarizing channel with error rate p:

K0 = sqrt(1 - p) * I
K1 = sqrt(p / 3) * X
K2 = sqrt(p / 3) * Y
K3 = sqrt(p / 3) * Z

The function recovers p from the average gate fidelity of the channel. Only K0 = sqrt(1-p)*I has nonzero trace, so:

F_proc = (1 - p)
F_avg  = (d * F_proc + 1) / (d + 1)  [Nielsen 2002]
       = (3 - 2p) / 3   for d = 2
p      = (1 - F_avg) * (d + 1) / 2   [for d=2: p = (1 - F_avg) * 3/2]
Parameters:
  • channel_matrices (list[ndarray[tuple[Any, ...], dtype[cdouble]]]) – List of Kraus operators (square numpy arrays of equal shape).

  • expected_error_rate (float) – Expected depolarizing error rate p in [0, 1].

  • atol (float) – Absolute tolerance for the error-rate comparison (default 0.01).

Raises:
Return type:

None

pytest_quantum.assert_distribution_snapshot(counts, name, *, update=False, max_tvd=0.05)[source]

Assert that a measurement distribution matches its saved snapshot.

Saves the normalised probability distribution derived from counts. Comparison uses Total Variation Distance (TVD).

Parameters:
  • counts (dict[str, int]) – Measurement count dict e.g. {“00”: 512, “11”: 512}.

  • name (str) – Unique snapshot name.

  • update (bool) – If True, overwrite existing snapshot.

  • max_tvd (float) – Maximum allowed TVD from snapshot (default 0.05).

Raises:
Return type:

None

Example:

def test_distribution_stable(aer_simulator):
    counts = run_bell(aer_simulator, shots=4000)
    from pytest_quantum import assert_distribution_snapshot

    assert_distribution_snapshot(counts, "bell_distribution")
pytest_quantum.assert_entanglement_capability_above(ansatz_fn, num_qubits, num_params, target_capability, *, num_samples=200)[source]

Assert Meyer-Wallach entanglement capability of a parameterised ansatz.

Computes the average Meyer-Wallach entanglement measure Q over random parameter samples. Q ∈ [0, 1]: 0 = no entanglement, 1 = maximally entangled.

Parameters:
  • ansatz_fn (Callable[..., Any]) – Callable (params: np.ndarray) -> QuantumCircuit.

  • num_qubits (int) – Number of qubits.

  • num_params (int) – Number of parameters.

  • target_capability (float) – Minimum average Q value (0.0–1.0).

  • num_samples (int) – Random parameter samples (default 200).

Returns:

Average Meyer-Wallach Q value.

Return type:

float

Raises:
pytest_quantum.assert_entanglement_entropy_below(statevector, partition, max_entropy, *, n_qubits=None)[source]

Assert von Neumann entanglement entropy S(rho_A) <= max_entropy.

For a pure state |psi>, partition qubits into subsystem A (partition indices) and subsystem B (rest). Computes S(rho_A) = -Tr(rho_A log2 rho_A).

max_entropy=0 means the state is separable (product state) on this partition. max_entropy=1 means at most 1 ebit of entanglement.

Parameters:
  • statevector (object) – Pure state amplitudes (1D complex array of length 2^n).

  • partition (list[int]) – Qubit indices to keep for subsystem A (big-endian).

  • max_entropy (float) – Maximum entanglement entropy in bits (nats if log2 is replaced — here we use log2 so units are bits).

  • n_qubits (int | None) – Total qubit count (inferred from len if not given).

Raises:
Return type:

None

pytest_quantum.assert_error_mitigation_benchmark(circuit, ideal_executor, noisy_executor, *, methods=None, atol=0.1)[source]

Benchmark multiple ZNE mitigation methods against the ideal value.

Runs ZNE with each specified method and asserts all methods achieve results within atol of the ideal value.

Parameters:
  • circuit (object) – Quantum circuit.

  • ideal_executor (Callable[..., float]) – Callable(circuit) -> float for ideal (noiseless) execution.

  • noisy_executor (Callable[..., float]) – Callable(circuit) -> float for noisy execution.

  • methods (list[str] | None) – List of method names to benchmark. Supported values: “zne_richardson”, “zne_linear”. Defaults to both.

  • atol (float) – Absolute tolerance from ideal value (default 0.1).

Return type:

dict[str, float]

Returns:

Dict mapping method_name -> mitigated_value.

Raises:

Example:

from pytest_quantum import assert_error_mitigation_benchmark

results = assert_error_mitigation_benchmark(
    circuit, ideal_executor, noisy_executor, atol=0.05
)
pytest_quantum.assert_estimator_close(result, expected, *, atol=0.1, pub_idx=0)[source]

Assert a Qiskit Estimator result is close to the expected value.

Parameters:
  • result (Any) – PrimitiveResult from StatevectorEstimator.run().

  • expected (float) – Expected expectation value.

  • atol (float) – Absolute tolerance (default 0.1).

  • pub_idx (int) – Which pub result to check (default 0).

Raises:

AssertionError – If |actual - expected| > atol.

Return type:

None

Example:

def test_estimator_z(qiskit_estimator):
    from qiskit.circuit import QuantumCircuit
    from qiskit.quantum_info import SparsePauliOp
    from pytest_quantum import assert_estimator_close

    qc = QuantumCircuit(1)  # |0>, <Z> = 1.0
    obs = SparsePauliOp("Z")
    result = qiskit_estimator.run([(qc, obs)]).result()
    assert_estimator_close(result, expected=1.0, atol=0.01)
pytest_quantum.assert_expectation_value_close(result_or_value, expected, *, atol=0.1)[source]

Assert a measured expectation value is close to expected.

Accepts: plain float/int, numpy scalar, Qiskit EstimatorResult/PrimitiveResult, or PennyLane measurement result.

Parameters:
  • result_or_value (Any) – Measured expectation value or result object.

  • expected (float) – Expected value.

  • atol (float) – Absolute tolerance (default 0.1).

Raises:
Return type:

None

Example:

from pytest_quantum import assert_expectation_value_close

assert_expectation_value_close(0.95, expected=1.0, atol=0.1)
pytest_quantum.assert_expressibility_above(ansatz_fn, num_qubits, num_params, target_expressibility, *, num_samples=200, num_bins=75)[source]

Assert circuit expressibility (frame potential / KL divergence from Haar).

Measures how uniformly an ansatz samples the unitary group compared to the Haar measure. Expressibility is quantified as:

expr = 1 - KL(P_ansatz || P_Haar) (normalized to [0, 1])

Higher = more expressive. A Haar-random circuit has expressibility ≈ 1.0.

Parameters:
  • ansatz_fn (Callable[..., Any]) – Callable (params: np.ndarray) -> QuantumCircuit. Returns a parameterised Qiskit QuantumCircuit.

  • num_qubits (int) – Number of qubits in the ansatz.

  • num_params (int) – Number of parameters the ansatz accepts.

  • target_expressibility (float) – Minimum required expressibility score (0.0–1.0).

  • num_samples (int) – Number of random parameter samples (default 200).

  • num_bins (int) – Histogram bins for fidelity distribution (default 75).

Returns:

Expressibility score (higher = better, 1.0 = Haar-equivalent).

Return type:

float

Raises:
pytest_quantum.assert_gate_count(circuit, gate_name, expected)[source]

Assert that a circuit contains exactly expected occurrences of gate_name.

Supported frameworks: Qiskit, Cirq, PennyLane.

Parameters:
  • circuit (object) – A quantum circuit from a supported framework.

  • gate_name (str) – Gate name as a string, e.g. "cx", "h", "t", "CNOT", "Hadamard". Case-insensitive for Qiskit; Cirq and PennyLane match case-insensitively by gate class name.

  • expected (int) – Expected count.

Raises:
Return type:

None

Example:

def test_t_count():
    from qiskit import QuantumCircuit

    qc = QuantumCircuit(2)
    qc.t(0)
    qc.t(1)
    qc.cx(0, 1)
    assert_gate_count(qc, "t", 2)
    assert_gate_count(qc, "cx", 1)
pytest_quantum.assert_gate_count_after_transpilation(circuit, gate_name, *, max_count=None, min_count=None, basis_gates=None, optimization_level=3)[source]

Assert the count of a specific gate after transpilation is within bounds.

Parameters:
  • circuit (object) – Quantum circuit to transpile.

  • gate_name (str) – Gate to count (e.g. “cx”, “t”, “rz”).

  • max_count (int | None) – Maximum allowed count (optional).

  • min_count (int | None) – Minimum required count (optional).

  • basis_gates (list[str] | None) – Target basis (optional).

  • optimization_level (int) – Qiskit transpile optimization level (default 3).

Return type:

int

Returns:

The actual gate count after transpilation.

Raises:
  • AssertionError – If count is outside [min_count, max_count].

  • ValueError – If neither max_count nor min_count is provided.

Example:

from pytest_quantum import assert_gate_count_after_transpilation

# Assert Toffoli is decomposed into at most 6 CNOT gates
assert_gate_count_after_transpilation(
    toffoli_circuit, "cx", max_count=6, basis_gates=["cx", "rz", "sx", "x"]
)
pytest_quantum.assert_gate_fidelity_above(backend, gate_name, qubits, target_fidelity, *, shots=2048, timeout=300.0)[source]

Assert gate fidelity (from calibration data) is at least target_fidelity.

Reads the gate error rate from backend.properties() for the specified gate on the specified qubits, computes fidelity = 1 - error_rate, and asserts the result meets the threshold. This is a fast, passive check against existing calibration data — no circuit is executed.

Parameters:
  • backend (Any) – IBM backend (or any backend with a .properties() method that exposes per-gate error rates).

  • gate_name (str) – Name of the gate (e.g. "cx", "ecr", "x").

  • qubits (list[int] | tuple[int, ...]) – Qubit indices the gate acts on (e.g. [0, 1]).

  • target_fidelity (float) – Minimum required fidelity (0.0–1.0).

  • shots (int) – Unused; kept for API consistency with other assertions.

  • timeout (float) – Unused; kept for API consistency with other assertions.

Returns:

Measured gate fidelity (1 - error_rate).

Return type:

float

Raises:

AssertionError – If fidelity < target_fidelity, or if gate/qubit data is not available in calibration properties.

Example:

from pytest_quantum.assertions.benchmarking import assert_gate_fidelity_above


def test_cx_fidelity(ibm_backend):
    fidelity = assert_gate_fidelity_above(
        ibm_backend,
        gate_name="cx",
        qubits=[0, 1],
        target_fidelity=0.99,
    )
    print(f"CX fidelity: {fidelity:.4f}")
pytest_quantum.assert_gates_in_basis_set(circuit, basis_gates, *, case_sensitive=False)[source]

Assert every gate in the circuit belongs to the specified basis gate set.

Useful for verifying that a transpiled circuit only uses a target backend’s native gate set (e.g. after qiskit.transpile with basis_gates=[...]).

Parameters:
  • circuit (object) – Qiskit, Cirq, Braket, or Pytket circuit.

  • basis_gates (set[str]) – Set of allowed gate names.

  • case_sensitive (bool) – If False (default), comparison is case-insensitive.

Raises:
Return type:

None

Example:

from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator
from pytest_quantum import assert_gates_in_basis_set

qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
transpiled = transpile(qc, basis_gates=["cx", "u3"])
assert_gates_in_basis_set(transpiled, {"cx", "u3"})
pytest_quantum.assert_ground_state_energy_close(result_or_value, expected_energy, *, atol=0.1)[source]

Assert VQE/QAOA result is close to the known ground state energy.

Parameters:
  • result_or_value (Any) – Measured energy or EstimatorResult.

  • expected_energy (float) – Known ground state energy (e.g. from exact diagonalisation).

  • atol (float) – Absolute tolerance in energy units (default 0.1).

Raises:

AssertionError – If measured energy differs from expected by more than atol.

Return type:

None

Example:

from pytest_quantum import assert_ground_state_energy_close

# H2 ground state energy (Hartree)
assert_ground_state_energy_close(-1.85, expected_energy=-1.8572, atol=0.05)
pytest_quantum.assert_has_diagram(circuit, expected, *, strict=False)[source]

Assert circuit’s text representation contains expected pattern.

For Qiskit: uses circuit.draw('text'). For Cirq: uses str(circuit) (circuit.to_text_diagram()).

Parameters:
  • circuit (object) – Any supported framework circuit.

  • expected (str) – Expected string (exact if strict is True, substring otherwise).

  • strict (bool) – If True, require exact match after stripping leading / trailing whitespace. If False (default), just check that expected is a substring of the diagram.

Raises:
Return type:

None

Example:

from qiskit import QuantumCircuit
from pytest_quantum import assert_has_diagram

qc = QuantumCircuit(1)
qc.h(0)
assert_has_diagram(qc, "H")
pytest_quantum.assert_hellinger_close(counts_a, counts_b, *, max_distance=0.1)[source]

Assert Hellinger distance H(p,q) <= max_distance.

H(p,q) = (1/sqrt(2)) * ||sqrt(p) - sqrt(q)||_2 Range: [0, 1]. H=0 identical, H=1 disjoint support.

More symmetric and bounded than KL divergence.

Parameters:
  • counts_a (dict[str, int]) – First count dictionary.

  • counts_b (dict[str, int]) – Second count dictionary.

  • max_distance (float) – Maximum allowed Hellinger distance (default 0.1).

Raises:
  • AssertionError – If H(p,q) > max_distance, with a per-key table.

  • ValueError – If both count dictionaries are empty.

Return type:

None

pytest_quantum.assert_hermitian(matrix, *, atol=1e-08)[source]

Assert matrix is Hermitian: A == A†.

Parameters:
  • matrix (Any) – Square matrix (any array-like convertible to numpy).

  • atol (float) – Absolute tolerance for element-wise comparison (default 1e-8).

Raises:
  • AssertionError – If the matrix is not Hermitian, with shape info and maximum deviation from Hermiticity.

  • ValueError – If the matrix is not square.

Return type:

None

pytest_quantum.assert_interleaved_rb(backend, qubit, gate_name, gate_circuit, *, clifford_lengths=None, num_sequences=20, shots=1024, min_gate_fidelity=0.999, timeout=300.0)[source]

Assert interleaved randomized benchmarking (IRB) gate fidelity.

Runs two RB experiments: 1. Standard RB to get reference decay rate p_ref. 2. Interleaved RB where each Clifford is followed by the target gate_circuit

to get p_irb.

Gate fidelity: F_gate = 1 - (d-1)/d * (1 - p_irb/p_ref) where d = 2 for single-qubit gates.

Parameters:
  • backend (Any) – Qiskit-compatible backend.

  • qubit (int) – Index of the qubit to benchmark.

  • gate_name (str) – Human-readable name for the gate (for error messages).

  • gate_circuit (Any) – A single-qubit QuantumCircuit implementing the gate.

  • clifford_lengths (list[int] | None) – Sequence lengths. Default: [1, 5, 10, 20, 50].

  • num_sequences (int) – Random sequences per length (default 20).

  • shots (int) – Shots per circuit (default 1024).

  • min_gate_fidelity (float) – Minimum acceptable gate fidelity (default 0.999).

  • timeout (float) – Maximum seconds for all jobs (default 300).

Return type:

dict[str, Any]

Returns:

dict with keys 'fidelity', 'p_ref', 'p_irb', 'lengths', 'ref_survival', 'irb_survival'.

Raises:
pytest_quantum.assert_kl_divergence_below(counts, expected_probs, *, max_kl=0.1)[source]

Assert KL divergence D_KL(observed || expected) <= max_kl.

D_KL(P||Q) = sum_x P(x) * log2(P(x) / Q(x))

Note: KL is asymmetric and infinite if Q(x)=0 but P(x)>0. Raises ValueError if expected_probs has zero probability for any observed outcome.

Parameters:
  • counts (dict[str, int]) – Observed count dictionary.

  • expected_probs (dict[str, float]) – Expected probability dictionary.

  • max_kl (float) – Maximum allowed KL divergence in bits (default 0.1).

Raises:
  • AssertionError – If D_KL > max_kl.

  • ValueError – If any outcome with non-zero observed count has zero expected probability (KL would be infinite).

Return type:

None

pytest_quantum.assert_measurement_distribution(counts, expected_probs, *, significance=0.05, min_expected_per_bucket=5)[source]

Assert that measured counts match the expected probability distribution.

Uses a chi-square goodness-of-fit test — the standard statistical tool for this exact problem. The test fails only when the deviation is statistically significant (p < significance), so occasional random fluctuations do not cause false failures.

Parameters:
  • counts (dict[str, int]) – Measured counts dict, e.g. {"00": 489, "11": 511}. Keys are bitstring labels; values are integer counts.

  • expected_probs (dict[str, float]) – Expected probability dict, e.g. {"00": 0.5, "11": 0.5}. Must sum to 1.0 (within 1e-6). Outcomes not present are assumed to have zero expected probability.

  • significance (float) – P-value threshold below which the test fails (default 0.05).

  • min_expected_per_bucket (int) – Chi-square requires expected count >= 5 per non-zero cell for valid results. A UserWarning is raised (but the test does not fail) if this is violated; consider increasing shots.

Raises:
  • AssertionError – If p_value < significance, with a per-state breakdown of observed vs expected probabilities.

  • ValueError – If expected_probs does not sum to 1.0, or counts is empty.

Return type:

None

Example:

:rtype: :sphinx_autodoc_typehints_type:`\:py\:obj\:\`None\``
def test_bell_distribution(aer_simulator):

from qiskit import QuantumCircuit, transpile from pytest_quantum import assert_measurement_distribution

qc = QuantumCircuit(2) qc.h(0) qc.cx(0, 1) qc.measure_all() qc_t = transpile(qc, aer_simulator) counts = aer_simulator.run(qc_t, shots=2000).result().get_counts()

assert_measurement_distribution(

counts, expected_probs={“00”: 0.5, “11”: 0.5},

)

pytest_quantum.assert_mirror_fidelity(circuit, backend, *, shots=4096, min_fidelity=0.5, timeout=300.0)[source]

Assert a circuit’s mirror achieves sufficient |0…0⟩ return fidelity.

Appends the circuit’s inverse to itself (circuit ∘ circuit†), measures all qubits, and checks that the |0…0⟩ outcome fraction meets min_fidelity. This is a lightweight form of mirror benchmarking that works for any invertible circuit without needing a classical simulator reference.

Parameters:
  • circuit (Any) – QuantumCircuit without measurements (must be invertible).

  • backend (Any) – Qiskit-compatible backend.

  • shots (int) – Number of shots (default 4096).

  • min_fidelity (float) – Minimum required |0…0⟩ return fraction (default 0.5). Reduce this for deeper circuits or noisier devices.

  • timeout (float) – Max seconds to wait for job completion (default 300).

Returns:

Measured |0…0⟩ fraction (fidelity proxy).

Return type:

float

Raises:

Example:

from pytest_quantum import assert_mirror_fidelity


def test_cx_mirror(ibm_backend):
    from qiskit import QuantumCircuit

    qc = QuantumCircuit(2)
    qc.h(0)
    qc.cx(0, 1)
    fidelity = assert_mirror_fidelity(qc, ibm_backend, min_fidelity=0.5)
    print(f"Mirror fidelity: {fidelity:.3f}")
pytest_quantum.assert_mitigation_improves_fidelity(noisy_dm, mitigated_dm, ideal_state, *, atol=0.0)[source]

Assert that a mitigated density matrix is closer to ideal than the noisy one.

Compares trace distances: T(mitigated, ideal) < T(noisy, ideal).

Parameters:
  • noisy_dm (object) – Density matrix from noisy (unmitigated) execution.

  • mitigated_dm (object) – Density matrix after error mitigation.

  • ideal_state (object) – Ideal target state (statevector or density matrix).

  • atol (float) – Require mitigated_distance <= noisy_distance - atol (default 0: any improvement accepted).

Raises:

AssertionError – If mitigated result is not closer to ideal.

Return type:

None

Example:

from pytest_quantum import assert_mitigation_improves_fidelity

assert_mitigation_improves_fidelity(noisy_dm, mitigated_dm, ideal_state)
pytest_quantum.assert_no_barren_plateau(ansatz_fn, num_qubits, num_params, observable=None, *, num_samples=100, min_gradient_variance=0.0001)[source]

Assert no barren plateau: gradient variance is above min_gradient_variance.

Detects barren plateaus by computing the variance of parameter-shift gradients over random parameter initializations. A barren plateau manifests as an exponentially vanishing gradient variance.

Parameters:
  • ansatz_fn (Callable[..., Any]) – Callable (params: np.ndarray) -> QuantumCircuit.

  • num_qubits (int) – Number of qubits.

  • num_params (int) – Number of parameters.

  • observable (Any | None) – Optional Qiskit SparsePauliOp. Defaults to Z⊗Z⊗…⊗Z.

  • num_samples (int) – Random parameter initializations (default 100).

  • min_gradient_variance (float) – Minimum acceptable variance (default 1e-4).

Returns:

Gradient variance (higher = no barren plateau).

Return type:

float

Raises:
pytest_quantum.assert_no_leakage(density_matrix, computational_subspace_dim, *, max_leakage=0.01)[source]

Assert a density matrix has negligible population outside the computational subspace.

Leakage is defined as the probability of being in states outside the first computational_subspace_dim basis states:

leakage = 1 - Tr(P_comp @ rho @ P_comp)
        = 1 - sum_{i=0}^{d_comp-1} rho[i, i]

where P_comp is the projector onto the first computational_subspace_dim basis states. This is particularly useful for qutrit or multi-level system simulations where leakage to non-computational levels is undesirable.

Parameters:
  • density_matrix (ndarray[tuple[Any, ...], dtype[cdouble]]) – Square density matrix of shape (d, d).

  • computational_subspace_dim (int) – Number of computational basis states (e.g. 2 for a qubit, 4 for two qubits).

  • max_leakage (float) – Maximum acceptable leakage probability (default 0.01).

Return type:

float

Returns:

The measured leakage as a float in [0, 1].

Raises:
  • AssertionError – If leakage > max_leakage, showing measured vs allowed leakage.

  • ValueError – If the density matrix is not square or computational_subspace_dim exceeds the matrix dimension.

pytest_quantum.assert_no_mid_circuit_measurement(circuit)[source]

Assert a circuit has no mid-circuit measurements (all measurements are terminal).

Mid-circuit measurements (measurements followed by further gate operations) are not supported on all hardware backends. This assertion verifies that all measurements occur after all gate operations — i.e., measurements only appear in the final layer.

Supported frameworks: Qiskit, Cirq.

Parameters:

circuit (object) – A quantum circuit from a supported framework.

Raises:
Return type:

None

Example:

from qiskit import QuantumCircuit
from pytest_quantum import assert_no_mid_circuit_measurement

qc = QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
qc.measure_all()
assert_no_mid_circuit_measurement(qc)  # passes — measurements are terminal
pytest_quantum.assert_noise_fidelity_above(noisy_dm, ideal_state, threshold=0.99)[source]

Assert state fidelity between noisy density matrix and ideal state.

F(rho, |psi>) = <psi|rho|psi> F(rho, sigma) = Tr(sigma @ rho)

Parameters:
  • noisy_dm (Any) – Density matrix (2D numpy array).

  • ideal_state (Any) – Pure statevector (1D) or density matrix (2D).

  • threshold (float) – Minimum acceptable fidelity (default 0.99).

Raises:
Return type:

None

pytest_quantum.assert_normalized(statevector, *, atol=1e-06)[source]

Assert statevector has unit norm: ||ψ||₂ = 1.

A common bug in manual statevector construction is forgetting to normalize.

Parameters:
  • statevector (object) – Complex array-like of any shape (flattened internally).

  • atol (float) – Absolute tolerance from 1.0 (default 1e-6).

Raises:

AssertionError – If ||sv||₂ is not within atol of 1.0, showing the actual norm.

Return type:

None

Example:

>>> import numpy as np
>>> sv = np.array([1, 0, 0, 0], dtype=complex)  # |00>
>>> assert_normalized(sv)  # passes

>>> sv_bad = np.array([1, 1], dtype=complex)  # NOT normalized
>>> assert_normalized(sv_bad)  # fails: norm = 1.4142
pytest_quantum.assert_parametrized_unitary_continuous(circuit_fn, param_name, param_range, *, n_samples=20, max_jump=0.5)[source]

Assert that a parametrized circuit’s unitary varies continuously.

Detects discontinuities (e.g. branch cut issues, phase jumps) by checking that consecutive unitary matrices are close in Frobenius norm.

Parameters:
  • circuit_fn (Callable[..., Any]) – Callable(param_name=value) -> circuit.

  • param_name (str) – Name of the parameter to sweep.

  • param_range (tuple[float, float]) – (start, end) range for the parameter.

  • n_samples (int) – Number of evenly-spaced sample points (default 20).

  • max_jump (float) – Maximum allowed Frobenius norm change between adjacent samples (default 0.5).

Raises:

AssertionError – If any discontinuity exceeds max_jump.

Return type:

None

Example:

assert_parametrized_unitary_continuous(
    lambda theta: rx_circuit(theta), "theta", (0, 2 * np.pi)
)
pytest_quantum.assert_partial_trace_close(rho, keep_qubits, expected, *, atol=1e-06)[source]

Assert the reduced density matrix (partial trace) is close to expected.

Traces out all qubits NOT in keep_qubits, then compares the resulting reduced density matrix to expected.

Parameters:
  • rho (object) – Full density matrix, shape (2**n, 2**n) for n qubits.

  • keep_qubits (list[int]) – List of qubit indices to keep (0-indexed, big-endian).

  • expected (object) – Expected reduced density matrix.

  • atol (float) – Absolute tolerance (default 1e-6).

Raises:
Return type:

None

Example:

import numpy as np
from pytest_quantum import assert_partial_trace_close

# Bell state: |00> + |11> / sqrt(2) — partial trace gives I/2
bell = np.array([[1], [0], [0], [1]], dtype=complex) / np.sqrt(2)
rho = bell @ bell.conj().T
mixed = np.eye(2, dtype=complex) / 2
assert_partial_trace_close(rho, keep_qubits=[0], expected=mixed)
pytest_quantum.assert_pec_expectation_close(circuit, executor, noise_model, expected, *, num_samples=100, atol=0.1)[source]

Assert PEC-mitigated expectation value is close to expected.

Parameters:
  • circuit (object) – Quantum circuit.

  • executor (Callable[..., float]) – Callable(circuit) -> float.

  • noise_model (object) – PEC representations.

  • expected (float) – Expected ideal expectation value.

  • num_samples (int) – Number of PEC samples (default 100).

  • atol (float) – Absolute tolerance (default 0.1).

Raises:
Return type:

None

Example:

from pytest_quantum import assert_pec_expectation_close

assert_pec_expectation_close(
    circuit, noisy_executor, representations, expected=1.0, atol=0.05
)
pytest_quantum.assert_pec_reduces_error(circuit, executor, noise_model, *, num_samples=100, threshold_improvement=0.0)[source]

Assert that Probabilistic Error Cancellation (PEC) produces a valid result.

Runs the circuit with and without PEC and returns both values.

Parameters:
  • circuit (object) – Quantum circuit.

  • executor (Callable[..., float]) – Callable(circuit) -> float for noisy execution.

  • noise_model (object) – PEC representations (quasi-probability representations).

  • num_samples (int) – Number of PEC samples (default 100).

  • threshold_improvement (float) – Unused threshold parameter (reserved for future use).

Return type:

tuple[float, float]

Returns:

Tuple of (unmitigated_value, mitigated_value).

Raises:

Example:

from pytest_quantum import assert_pec_reduces_error

unmitigated, mitigated = assert_pec_reduces_error(
    circuit, noisy_executor, representations
)
pytest_quantum.assert_positive_semidefinite(matrix, *, atol=1e-08)[source]

Assert matrix is positive semi-definite: all eigenvalues >= -atol.

Validates Hermiticity first, then checks that the smallest eigenvalue is >= -atol.

Parameters:
  • matrix (Any) – Square Hermitian matrix (any array-like).

  • atol (float) – Tolerance for eigenvalue non-negativity (default 1e-8).

Raises:
Return type:

None

pytest_quantum.assert_process_fidelity_above(channel_a, channel_b, threshold=0.99, *, atol=1e-08)[source]

Assert process fidelity F_process(A, B) >= threshold.

Supports: - List of numpy Kraus operators - Qiskit Kraus/Choi/SuperOp objects (uses qiskit.quantum_info.process_fidelity) - numpy unitary matrices (converts to single-Kraus channel)

Process fidelity for unitary channels: F = |Tr(A† B)|² / d² For general channels uses Choi matrix inner product.

Parameters:
  • channel_a (Any) – First channel (Kraus list, unitary matrix, or Qiskit object).

  • channel_b (Any) – Second channel (same supported types as channel_a).

  • threshold (float) – Minimum acceptable process fidelity (default 0.99).

  • atol (float) – Tolerance for unitary check (default 1e-8).

Raises:
Return type:

None

pytest_quantum.assert_purity_above(rho, *, min_purity=0.95)[source]

Assert purity Tr(ρ²) ≥ min_purity.

Pure state: Tr(ρ²)=1.0. Maximally mixed d×d state: Tr(ρ²)=1/d.

Parameters:
  • rho (object) – Density matrix, shape (d, d).

  • min_purity (float) – Minimum required purity (default 0.95).

Raises:

AssertionError – If Tr(ρ²) < min_purity.

Return type:

None

Example:

import numpy as np
from pytest_quantum import assert_purity_above

psi = np.array([[1], [0]], dtype=complex)
rho = psi @ psi.conj().T
assert_purity_above(rho, min_purity=0.99)
pytest_quantum.assert_qasm2_roundtrip(circuit, *, atol=1e-06, allow_global_phase=True)[source]

Assert a Qiskit circuit survives an OpenQASM 2.0 export/import round-trip.

Exports the circuit via qiskit.qasm2.dumps and re-imports it via qiskit.qasm2.loads, then compares unitaries.

Note

QASM 2.0 has limited gate support. Circuits with custom/parametrised gates that lack QASM 2 definitions may fail — use assert_qasm_roundtrip (QASM 3) for those circuits.

Parameters:
  • circuit (object) – Qiskit QuantumCircuit.

  • atol (float) – Tolerance for unitary comparison (default 1e-6).

  • allow_global_phase (bool) – Ignore global phase in comparison (default True).

Raises:
Return type:

None

Example:

from qiskit import QuantumCircuit
from pytest_quantum import assert_qasm2_roundtrip

qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
assert_qasm2_roundtrip(qc)
pytest_quantum.assert_qasm_roundtrip(circuit, *, atol=1e-06, allow_global_phase=True)[source]

Assert that a circuit survives an export/import round-trip.

For Qiskit: uses OpenQASM 3 (qiskit.qasm3.dumps / qasm3.loads). For Cirq: uses Cirq’s native JSON serialisation (cirq.to_json /

cirq.read_json), which provides an exact identity round-trip for all standard Cirq circuits.

Parameters:
  • circuit (object) – Qiskit QuantumCircuit or cirq.Circuit.

  • atol (float) – Tolerance for unitary comparison (default 1e-6).

  • allow_global_phase (bool) – Ignore global phase in comparison (default True).

Raises:
Return type:

None

pytest_quantum.assert_qiskit_cirq_equivalent(qiskit_circuit, cirq_circuit, *, atol=1e-06, allow_global_phase=True)[source]

Assert that a Qiskit circuit and a Cirq circuit implement the same unitary.

Convenience wrapper around assert_cross_platform_equivalent() with the framework labels pre-set to "qiskit" and "cirq".

Qubit-ordering is normalised automatically (Qiskit is little-endian, Cirq is big-endian), so you can compare circuits using their natural qubit indices in each framework without manual reversal.

Parameters:
  • qiskit_circuit (object) – A qiskit.QuantumCircuit instance.

  • cirq_circuit (object) – A cirq.Circuit instance.

  • atol (float) – Absolute tolerance for element-wise comparison (default 1e-6).

  • allow_global_phase (bool) – If True (default), circuits that differ only by a global phase are considered equivalent.

Raises:
  • AssertionError – If the circuits implement different unitaries.

  • ValueError – If either circuit cannot be converted to a unitary.

Return type:

None

Example:

import cirq
from qiskit import QuantumCircuit
from pytest_quantum.assertions.cross_platform import (
    assert_qiskit_cirq_equivalent,
)

qc = QuantumCircuit(1)
qc.h(0)

q = cirq.LineQubit.range(1)
cc = cirq.Circuit(cirq.H(q[0]))

assert_qiskit_cirq_equivalent(qc, cc)
pytest_quantum.assert_qiskit_pytket_equivalent(qiskit_circuit, pytket_circuit, *, atol=1e-06, allow_global_phase=True)[source]

Assert that a Qiskit circuit and a pytket circuit implement the same unitary.

Convenience wrapper around assert_cross_platform_equivalent() with the framework labels pre-set to "qiskit" and "pytket".

The pytket unitary is obtained via circuit.get_unitary() (exposed by pytest_quantum.converters.to_unitary.to_unitary()). Qubit-ordering is normalised automatically (Qiskit is little-endian, pytket is big-endian).

Parameters:
  • qiskit_circuit (object) – A qiskit.QuantumCircuit instance.

  • pytket_circuit (object) – A pytket.Circuit instance.

  • atol (float) – Absolute tolerance for element-wise comparison (default 1e-6).

  • allow_global_phase (bool) – If True (default), circuits that differ only by a global phase are considered equivalent.

Raises:
  • AssertionError – If the circuits implement different unitaries.

  • ValueError – If either circuit cannot be converted to a unitary.

Return type:

None

Example:

from pytket import Circuit as TketCircuit
from qiskit import QuantumCircuit
from pytest_quantum.assertions.cross_platform import (
    assert_qiskit_pytket_equivalent,
)

qc = QuantumCircuit(1)
qc.h(0)

tk = TketCircuit(1)
tk.H(0)

assert_qiskit_pytket_equivalent(qc, tk)
pytest_quantum.assert_quantum_volume(backend, target_qv, *, num_trials=100, shots=1024, confidence=0.97, timeout=300.0)[source]

Assert a backend achieves at least target_qv quantum volume.

Runs IBM’s quantum volume (QV) protocol: for each width n from 1 up to log2(target_qv), generate num_trials random square circuits of depth n x n using random SU(4) two-qubit unitaries, execute them, and check that the heavy output probability (HOP) exceeds 2/3 with the given statistical confidence (one-sided binomial test). The measured QV is 2 ** n for the largest n that passes.

For IBM backends (qiskit_ibm_runtime.IBMBackend) the circuits are run using SamplerV2; all other backends fall back to backend.run().

Parameters:
  • backend (Any) – Qiskit-compatible backend to benchmark.

  • target_qv (int) – Minimum required quantum volume (must be a power of 2).

  • num_trials (int) – Number of random circuits per width (default 100).

  • shots (int) – Shots per circuit (default 1024).

  • confidence (float) – Required statistical confidence for HOP > 2/3 test (default 0.97).

  • timeout (float) – Maximum seconds to wait for all jobs (default 300).

Returns:

The measured quantum volume (largest passing power of 2).

Return type:

int

Raises:
  • AssertionError – If the measured QV < target_qv, with details about heavy output probability, confidence, and trial count.

  • ImportError – If qiskit is not installed.

Example:

from pytest_quantum.assertions.benchmarking import assert_quantum_volume
from qiskit_aer import AerSimulator


def test_simulator_qv():
    backend = AerSimulator()
    qv = assert_quantum_volume(backend, target_qv=4, num_trials=20)
    assert qv >= 4
pytest_quantum.assert_randomized_benchmarking(backend, qubit, *, clifford_lengths=None, num_sequences=20, shots=1024, min_fidelity_per_clifford=0.999, timeout=300.0)[source]

Assert 1-qubit randomized benchmarking fidelity meets a minimum threshold.

For each sequence length m in clifford_lengths, generates num_sequences random 1-qubit Clifford sequences followed by the recovery Clifford that maps the state back to |0⟩. The survival probability (fraction of |0⟩ outcomes) is averaged across sequences for each length. An exponential decay A * p^m + B is fit to the length–survival-probability curve. The average gate fidelity is F = 1 - (1-p)/2.

For IBM backends the circuits are run using SamplerV2.

Parameters:
  • backend (Any) – Qiskit-compatible backend.

  • qubit (int) – Index of the qubit to benchmark.

  • clifford_lengths (list[int] | None) – List of Clifford sequence lengths to test. Default: [1, 10, 20, 50, 100].

  • num_sequences (int) – Number of random sequences per length (default 20).

  • shots (int) – Shots per circuit (default 1024).

  • min_fidelity_per_clifford (float) – Minimum average gate fidelity (default 0.999).

  • timeout (float) – Maximum seconds for all jobs (default 300).

Returns:

  • 'fidelity' (float): Measured average gate fidelity.

  • 'decay_rate' (float): Fitted decay parameter p.

  • 'lengths' (list[int]): Sequence lengths used.

  • 'survival_probs' (list[float]): Mean survival probability per length.

Return type:

dict[str, Any]

Raises:

Example:

from pytest_quantum.assertions.benchmarking import (
    assert_randomized_benchmarking,
)
from qiskit_aer import AerSimulator


def test_rb_fidelity():
    backend = AerSimulator()
    result = assert_randomized_benchmarking(
        backend, qubit=0, clifford_lengths=[1, 5, 10], num_sequences=5
    )
    assert result["fidelity"] >= 0.99
pytest_quantum.assert_real_counts_close(circuit, backend, expected_probs, *, shots=4096, max_tvd=0.15, timeout=300.0, optimization_level=1)[source]

Assert real hardware counts match expected probabilities within TVD tolerance.

Runs the circuit on the backend and compares the empirical distribution to expected_probs using Total Variation Distance (TVD). The default TVD threshold (0.15) is intentionally more lenient than simulator tests because real hardware has noise.

Parameters:
  • circuit (Any) – QuantumCircuit with measurements.

  • backend (Any) – Qiskit-compatible backend.

  • expected_probs (dict[str, float]) – Dict mapping bitstring → ideal probability (must sum to ~1).

  • shots (int) – Number of shots (default 4096; more = less sampling noise).

  • max_tvd (float) – Maximum allowed TVD (default 0.15).

  • timeout (float) – Max seconds to wait for job completion (default 300).

  • optimization_level (int) – Transpilation level (default 1).

Returns:

Actual measurement counts.

Return type:

dict[str, int]

Raises:

AssertionError – If TVD exceeds max_tvd.

Example:

from pytest_quantum import assert_real_counts_close


def test_bell_state(ibm_backend):
    from qiskit import QuantumCircuit

    qc = QuantumCircuit(2, 2)
    qc.h(0)
    qc.cx(0, 1)
    qc.measure_all()
    assert_real_counts_close(
        qc,
        ibm_backend,
        expected_probs={"00": 0.5, "11": 0.5},
        max_tvd=0.15,
    )
pytest_quantum.assert_sampler_distribution(result, expected_probs, *, pub_idx=0, significance=0.05)[source]

Assert a Qiskit Sampler result matches expected probability distribution.

Uses chi-square goodness-of-fit (same as assert_measurement_distribution).

Parameters:
  • result (Any) – PrimitiveResult from StatevectorSampler.run().

  • expected_probs (dict[str, float]) – Expected probability distribution e.g. {“00”: 0.5, “11”: 0.5}.

  • pub_idx (int) – Which pub result to check (default 0).

  • significance (float) – p-value threshold (default 0.05).

Raises:

AssertionError – If distribution doesn’t match.

Return type:

None

Example:

def test_sampler_bell(qiskit_sampler):
    from qiskit.circuit import QuantumCircuit
    from pytest_quantum import assert_sampler_distribution

    qc = QuantumCircuit(2, 2)
    qc.h(0)
    qc.cx(0, 1)
    qc.measure([0, 1], [0, 1])
    result = qiskit_sampler.run([(qc,)]).result()
    assert_sampler_distribution(result, {"00": 0.5, "11": 0.5})
pytest_quantum.assert_schmidt_rank_at_most(statevector, partition, max_rank, *, n_qubits=None, tol=1e-10)[source]

Assert Schmidt rank of bipartite pure state partition is at most max_rank.

Schmidt rank = 1 means separable (product state) on this partition.

Parameters:
  • statevector (object) – Pure state amplitudes (1D complex array of length 2^n).

  • partition (list[int]) – Qubit indices in subsystem A.

  • max_rank (int) – Maximum allowed Schmidt rank.

  • n_qubits (int | None) – Total qubit count (inferred if not given).

  • tol (float) – Singular value threshold for rank counting (default 1e-10).

Raises:
Return type:

None

pytest_quantum.assert_stabilizer_state(tableau_simulator, expected_stabilizers)[source]

Assert a Stim TableauSimulator is in the expected stabilizer state.

Parameters:
  • tableau_simulator (Any) – stim.TableauSimulator after running a circuit.

  • expected_stabilizers (list[str]) – List of Pauli strings, e.g. ["+XX", "+ZZ"].

Raises:
Return type:

None

Example:

import stim
from pytest_quantum import assert_stabilizer_state

sim = stim.TableauSimulator()
sim.h(0)
sim.cnot(0, 1)
assert_stabilizer_state(sim, ["+XX", "+ZZ"])
pytest_quantum.assert_state_fidelity_above(actual, target, threshold=0.99)[source]

Assert that two pure quantum states have fidelity at or above threshold.

Fidelity \(F = |\langle\text{actual}|\text{target}\rangle|^2\) equals 1.0 for identical states (up to global phase) and 0.0 for orthogonal states.

This is the primary assertion for MBQC / Graphix tests where the circuit does not have a fixed unitary representation.

Parameters:
Raises:
Return type:

None

Example:

import numpy as np
from pytest_quantum import assert_state_fidelity_above

BELL = np.array([1, 0, 0, 1], dtype=complex) / np.sqrt(2)


def test_bell_graphix(graphix_backend):
    from graphix.transpiler import Circuit

    circuit = Circuit(2)
    circuit.h(0)
    circuit.cnot(0, 1)
    pattern = circuit.transpile().pattern
    output = graphix_backend.run_pattern(pattern)
    assert_state_fidelity_above(output, BELL, threshold=0.999)
pytest_quantum.assert_states_close(actual, target, *, atol=1e-06)[source]

Assert that two statevectors are element-wise close, up to global phase.

Stricter than assert_state_fidelity_above() — use for exact simulator-to-simulator comparisons where you want bit-for-bit agreement.

Parameters:
Raises:

AssertionError – If any element differs by more than atol after removing the global phase.

Return type:

None

Example:

def test_plus_state(aer_statevector_simulator):
    from qiskit import QuantumCircuit, transpile

    qc = QuantumCircuit(1)
    qc.h(0)
    qc.save_statevector()
    qc_t = transpile(qc, aer_statevector_simulator)
    sv = aer_statevector_simulator.run(qc_t).result().get_statevector()
    PLUS = np.array([1, 1]) / np.sqrt(2)
    assert_states_close(sv.data, PLUS)
pytest_quantum.assert_stim_detector_error_rate_below(circuit, max_error_rate, *, shots=10000, seed=None)[source]

Assert that the mean detector error rate is below threshold.

Useful for verifying that a noise model produces errors at the expected rate.

Parameters:
  • circuit (Any) – stim.Circuit with DETECTOR instructions and noise.

  • max_error_rate (float) – Maximum allowed mean detector error rate.

  • shots (int) – Number of shots (default 10,000).

  • seed (int | None) – Optional random seed.

Raises:
Return type:

None

pytest_quantum.assert_stim_logical_error_rate_below(circuit, max_error_rate, *, shots=10000, seed=None)[source]

Assert that a Stim QEC circuit has logical error rate below threshold.

The circuit must contain DETECTOR and OBSERVABLE_INCLUDE instructions.

Parameters:
  • circuit (Any) – stim.Circuit with detectors and observables.

  • max_error_rate (float) – Maximum allowed logical error rate.

  • shots (int) – Number of shots (default 10,000).

  • seed (int | None) – Optional random seed.

Raises:
Return type:

None

Example:

import stim
from pytest_quantum import assert_stim_logical_error_rate_below

c = stim.Circuit.generated(
    "repetition_code:memory",
    rounds=3,
    distance=3,
    after_clifford_depolarization=0.001,
)
assert_stim_logical_error_rate_below(c, max_error_rate=0.05, shots=1000)
pytest_quantum.assert_t1_above(backend, qubit, target_t1_us, *, shots=1024, timeout=300.0)[source]

Assert qubit T1 relaxation time is at least target_t1_us microseconds.

Prepares the qubit in |1⟩ and waits variable delay durations (using Qiskit’s Delay gate), then measures. The |1⟩ survival probability is fit to an exponential decay exp(-t / T1) to extract T1.

Note

Accurate T1 measurement requires pulse-level backend support and a backend that honours Delay gates (e.g. a real IBM device or AerSimulator with a noise model). On an ideal simulator T1 will appear infinite.

Parameters:
  • backend (Any) – Qiskit-compatible backend with delay gate support.

  • qubit (int) – Index of the qubit to measure.

  • target_t1_us (float) – Minimum required T1 in microseconds.

  • shots (int) – Shots per delay point (default 1024).

  • timeout (float) – Maximum seconds for all jobs (default 300).

Returns:

Measured T1 in microseconds.

Return type:

float

Raises:

Example:

from pytest_quantum.assertions.benchmarking import assert_t1_above


def test_t1(ibm_backend):
    t1 = assert_t1_above(ibm_backend, qubit=0, target_t1_us=50.0)
    print(f"Measured T1: {t1:.1f} µs")
pytest_quantum.assert_t2_above(backend, qubit, target_t2_us, *, shots=1024, timeout=300.0)[source]

Assert qubit T2 (Hahn echo) coherence time is at least target_t2_us µs.

Implements a Hahn echo sequence: X/2 – delay/2 – X – delay/2 – X/2 – measure. Fits |0⟩ survival probability to exp(-t / T2) to extract T2.

Parameters:
  • backend (Any) – Qiskit-compatible backend.

  • qubit (int) – Index of the qubit to measure.

  • target_t2_us (float) – Minimum required T2 in microseconds.

  • shots (int) – Shots per delay point (default 1024).

  • timeout (float) – Maximum seconds for all jobs (default 300).

Returns:

Measured T2 in microseconds.

Return type:

float

Raises:
pytest_quantum.assert_t2star_above(backend, qubit, target_t2star_us, *, shots=1024, timeout=300.0)[source]

Assert qubit T2* (free induction decay) is at least target_t2star_us µs.

Implements a Ramsey sequence: Rx(π/2) – delay – Rx(π/2) – measure. Fits |0⟩ survival to a decaying cosine; the envelope gives T2*.

Parameters:
  • backend (Any) – Qiskit-compatible backend.

  • qubit (int) – Index of the qubit.

  • target_t2star_us (float) – Minimum required T2* in microseconds.

  • shots (int) – Shots per delay point (default 1024).

  • timeout (float) – Maximum seconds for all jobs (default 300).

Returns:

Measured T2* in microseconds.

Return type:

float

Raises:
pytest_quantum.assert_trace_distance_below(rho, sigma, *, max_distance=0.01)[source]

Assert trace distance T(ρ,σ) = ½ Tr(|ρ-σ|) is at most max_distance.

T=0 means identical states, T=1 means perfectly distinguishable. Physical meaning: maximum probability of distinguishing the two states in any single measurement equals T(ρ,σ).

Parameters:
  • rho (object) – First density matrix.

  • sigma (object) – Second density matrix.

  • max_distance (float) – Maximum allowed trace distance (default 0.01).

Raises:

AssertionError – If T(ρ,σ) > max_distance.

Return type:

None

Example:

import numpy as np
from pytest_quantum import assert_trace_distance_below

rho = np.eye(2, dtype=complex) / 2
assert_trace_distance_below(rho, rho, max_distance=0.01)
pytest_quantum.assert_transpilation_depth_below(circuit, max_depth, basis_gates=None, *, optimization_level=3)[source]

Assert that a circuit transpiled to a basis set has depth <= max_depth.

Useful as a regression test to detect when compiler changes silently increase circuit depth.

Parameters:
  • circuit (object) – Qiskit QuantumCircuit to transpile.

  • max_depth (int) – Maximum allowed depth after transpilation.

  • basis_gates (list[str] | None) – Target basis gate set (default: no restriction).

  • optimization_level (int) – Qiskit transpile optimization level 0-3 (default 3).

Raises:
Return type:

None

Example:

from pytest_quantum import assert_transpilation_depth_below

assert_transpilation_depth_below(
    qc, max_depth=5, basis_gates=["cx", "rz", "sx", "x"]
)
pytest_quantum.assert_transpilation_equivalent(circuit, basis_gates_a, basis_gates_b=None, *, optimization_level=1, atol=1e-06, allow_global_phase=True)[source]

Assert that a circuit compiled to different basis sets is unitarily equivalent.

Transpiles the circuit to basis_gates_a (and optionally basis_gates_b), then verifies the unitaries match.

Parameters:
  • circuit (object) – Qiskit QuantumCircuit to transpile.

  • basis_gates_a (list[str]) – First target basis gate set.

  • basis_gates_b (list[str] | None) – Second target basis gate set (default: original circuit).

  • optimization_level (int) – Qiskit transpile optimization level 0-3 (default 1).

  • atol (float) – Absolute tolerance for unitary comparison (default 1e-6).

  • allow_global_phase (bool) – If True, ignore global phase differences (default True).

Raises:
Return type:

None

Example:

from pytest_quantum import assert_transpilation_equivalent
from qiskit import QuantumCircuit

qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)

assert_transpilation_equivalent(
    qc,
    basis_gates_a=["cx", "u"],
    basis_gates_b=["ecr", "rz", "sx", "x"],
)
pytest_quantum.assert_transpilation_preserves_semantics(circuit, backend, *, optimization_level=1, atol=1e-06)[source]

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).

Parameters:
  • circuit (object) – Qiskit QuantumCircuit (must be unitary, no measurements).

  • backend (object) – Qiskit backend or FakeBackend target.

  • optimization_level (int) – Qiskit transpiler optimisation level 0-3 (default 1).

  • atol (float) – Tolerance for unitary comparison (default 1e-6).

Raises:
Return type:

None

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)
pytest_quantum.assert_unitary(circuit, expected, *, atol=1e-06, allow_global_phase=True)[source]

Assert that circuit implements the expected unitary matrix.

Parameters:
  • circuit (object) – Any supported quantum circuit (Qiskit, Cirq, Braket, PennyLane).

  • expected (ndarray[tuple[Any, ...], dtype[cdouble]]) – Target unitary as a numpy array, shape (2**n, 2**n).

  • atol (float) – Absolute tolerance for element-wise comparison (default 1e-6).

  • allow_global_phase (bool) – 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.

Return type:

None

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)
pytest_quantum.assert_unitary_snapshot(circuit, name, *, update=False, atol=1e-06, allow_global_phase=True)[source]

Assert that a circuit’s unitary matches its saved snapshot.

First run: saves the unitary to .pytest-quantum-snapshots/<name>.npy. Subsequent runs: loads and compares. Pass –quantum-update-snapshots on the CLI to regenerate all snapshots.

Parameters:
  • circuit (object) – Any supported quantum circuit (Qiskit, Cirq, Braket, PennyLane).

  • name (str) – Unique snapshot name (used as filename, no path separators).

  • update (bool) – If True, overwrite existing snapshot.

  • atol (float) – Absolute tolerance (default 1e-6).

  • allow_global_phase (bool) – If True (default), ignore global phase differences.

Raises:
Return type:

None

Example:

def test_compiler_stable(compiled_circuit):
    from pytest_quantum import assert_unitary_snapshot

    assert_unitary_snapshot(compiled_circuit, "my_compiler_bell")
pytest_quantum.assert_vqe_converges(cost_function, initial_params, *, method='COBYLA', max_iterations=200, expected_minimum=None, atol=0.1, rtol=0.0)[source]

Assert a VQE / variational optimization converges.

Runs a full optimization loop and checks: 1. The final cost is lower than the initial cost (energy decreased). 2. If expected_minimum is given, the result is within atol of it.

Parameters:
  • cost_function (Any) – Callable mapping parameter array -> float (e.g. a QNode returning qml.expval(H)).

  • initial_params (Any) – Starting parameter vector (list or numpy array).

  • method (str) – SciPy optimizer (default "COBYLA"; gradient-free, good for noisy quantum hardware).

  • max_iterations (int) – Maximum optimizer iterations (default 200).

  • expected_minimum (float | None) – Known ground-state energy to compare against.

  • atol (float) – Absolute tolerance when comparing against expected_minimum.

  • rtol (float) – Relative tolerance (default 0, i.e. absolute only).

Raises:

AssertionError – If energy did not decrease or result misses expected_minimum.

Return type:

None

Example:

import pennylane as qml
import numpy as np
from pytest_quantum import assert_vqe_converges

dev = qml.device("default.qubit", wires=1)


@qml.qnode(dev)
def circuit(theta):
    qml.RY(theta, wires=0)
    return qml.expval(qml.PauliZ(0))


# Ground state of Z is |1>, energy = -1
assert_vqe_converges(circuit, [0.5], expected_minimum=-1.0, atol=0.05)
pytest_quantum.assert_xeb_fidelity_above(backend, num_qubits, target_fidelity, *, num_circuits=20, depth=10, shots=1024)[source]

Assert cross-entropy benchmarking (XEB) fidelity is at least target_fidelity.

XEB measures how well a noisy backend reproduces the ideal output distribution of random circuits. For each random circuit, the XEB fidelity is:

F_XEB = D * <P_ideal(x)>_measured - 1

where D = 2**num_qubits and <P_ideal(x)> is the average ideal probability of observed bitstrings. Perfect fidelity = 1.0; random noise = 0.0.

Parameters:
  • backend (Any) – Qiskit-compatible backend (AerSimulator, IBM, etc.).

  • num_qubits (int) – Number of qubits to benchmark.

  • target_fidelity (float) – Minimum acceptable XEB fidelity (0.0–1.0).

  • num_circuits (int) – Number of random circuits to average over (default 20).

  • depth (int) – Circuit depth in random layers (default 10).

  • shots (int) – Shots per circuit (default 1024).

Returns:

Average XEB fidelity across all circuits.

Return type:

float

Raises:

Example:

from pytest_quantum.assertions.quantum_ml import assert_xeb_fidelity_above
from qiskit_aer import AerSimulator


def test_xeb(aer_simulator):
    fidelity = assert_xeb_fidelity_above(
        aer_simulator, num_qubits=2, target_fidelity=0.9
    )
    assert fidelity >= 0.9
pytest_quantum.assert_zne_expectation_close(circuit, executor, expected, *, scale_factors=None, atol=0.1, noise_extrapolation='richardson')[source]

Assert ZNE-mitigated expectation value is close to expected.

Parameters:
  • circuit (object) – Quantum circuit.

  • executor (Callable[..., float]) – Callable(circuit) -> float.

  • expected (float) – Expected ideal expectation value.

  • scale_factors (list[float] | None) – Noise scale factors (default [1.0, 2.0, 3.0]).

  • atol (float) – Absolute tolerance (default 0.1).

  • noise_extrapolation (str) – Extrapolation method (default “richardson”).

Raises:
Return type:

None

Example:

from pytest_quantum import assert_zne_expectation_close

assert_zne_expectation_close(circuit, noisy_executor, expected=1.0, atol=0.05)
pytest_quantum.assert_zne_reduces_error(circuit, executor, observable=None, *, scale_factors=None, noise_extrapolation='richardson', threshold_improvement=0.0)[source]

Assert that Zero-Noise Extrapolation (ZNE) reduces the estimation error.

Runs the circuit at multiple noise scale factors, extrapolates to zero noise, and asserts the mitigated result is closer to the ideal than the unmitigated.

Parameters:
  • circuit (object) – Quantum circuit (Qiskit QuantumCircuit supported).

  • executor (Callable[..., float]) – Callable that takes a circuit and returns a float expectation value. Should handle noise internally.

  • observable (object | None) – Observable to measure (optional, passed to executor).

  • scale_factors (list[float] | None) – Noise scale factors (default [1.0, 2.0, 3.0]).

  • noise_extrapolation (str) – Extrapolation method: “richardson”, “linear”, “poly2” (default “richardson”).

  • threshold_improvement (float) – Minimum required improvement ratio (default 0 = any improvement is accepted).

Return type:

tuple[float, float]

Returns:

Tuple of (unmitigated_value, mitigated_value).

Raises:

Example:

from pytest_quantum import assert_zne_reduces_error


def noisy_executor(circuit):
    # run circuit on noisy simulator and return expectation value
    ...


mitigated, unmitigated = assert_zne_reduces_error(
    circuit, noisy_executor, ideal_value=0.0
)
pytest_quantum.chi_square_test(observed, expected_probs, total_shots=None)[source]

Chi-square goodness-of-fit test for quantum measurement distributions.

Tests whether observed counts are consistent with expected_probs.

Parameters:
  • observed (dict[str, int] | ndarray[tuple[Any, ...], dtype[double]]) – Either a count dict {"00": 489, "11": 511} or a 1-D numpy array of observed counts.

  • expected_probs (dict[str, float] | ndarray[tuple[Any, ...], dtype[double]]) – Either a probability dict {"00": 0.5, "11": 0.5} (must sum to 1) or a 1-D numpy array of expected probabilities. When using numpy arrays, provide total_shots so expected counts can be computed.

  • total_shots (int | None) – Required when both inputs are numpy arrays. Ignored when dict inputs are used (total is inferred from observed).

Return type:

tuple[float, float]

Returns:

(statistic, pvalue) — the chi-square statistic and the p-value. Reject the null hypothesis (distributions match) when pvalue < significance.

Raises:

ValueError – If inputs are inconsistent (different keys, missing total_shots for array inputs, etc.).

Example:

>>> stat, p = chi_square_test({"00": 495, "11": 505}, {"00": 0.5, "11": 0.5})
>>> p > 0.05  # consistent with Bell state
True
pytest_quantum.depolarizing_kraus(n_qubits, error_rate)[source]

Return Kraus operators for the single-qubit depolarising channel.

Channel definition:

E(ρ) = (1−p)ρ + (p/3)(XρX + YρY + ZρZ)
Parameters:
  • n_qubits (int) – Must be 1; the depolarising channel is defined per qubit.

  • error_rate (float) – Depolarisation probability p in [0, 1].

Returns:

[√(1−p)·I, √(p/3)·X, √(p/3)·Y, √(p/3)·Z].

Return type:

list[ndarray[tuple[Any, ...], dtype[cdouble]]]

Raises:

ValueError – If n_qubits ≠ 1 or error_rate is not in [0, 1].

Example:

from pytest_quantum.random import depolarizing_kraus
from pytest_quantum import assert_channel_is_cptp

assert_channel_is_cptp(depolarizing_kraus(1, 0.1))
pytest_quantum.fidelity(psi, phi)[source]

Pure-state fidelity \(|\langle\psi|\phi\rangle|^2\).

Both arrays are flattened and normalised before computation, so minor normalisation errors from simulators do not affect the result.

Parameters:
Return type:

float

Returns:

Float in [0.0, 1.0]. 1.0 means the states are identical (up to global phase). 0.0 means orthogonal.

Raises:

ValueError – If psi and phi have different sizes.

Example:

>>> import numpy as np
>>> zero = np.array([1, 0], dtype=complex)
>>> plus = np.array([1, 1], dtype=complex) / np.sqrt(2)
>>> fidelity(zero, plus)
0.5
pytest_quantum.min_shots(epsilon, alpha=0.05, power=0.8)[source]

Minimum shots to detect a total-variation distance of epsilon.

Based on two-sample statistical power analysis:

\[N = \lceil (z_{1-\alpha/2} + z_{\text{power}})^2 \; / \; (2\varepsilon^2) \rceil\]
Parameters:
  • epsilon (float) – Minimum detectable total variation distance (TVD). For example, 0.01 means the test can reliably catch a 1 % deviation from the expected distribution.

  • alpha (float) – Significance level (default 0.05 → 95 % confidence).

  • power (float) – Statistical power, i.e. probability of detecting a real error (default 0.80 → 80 % power).

Return type:

int

Returns:

Minimum recommended shot count as an integer.

Raises:

ValueError – If any argument is outside its valid range.

Examples:

>>> min_shots(0.01)   # detect 1% TVD
7299
>>> min_shots(0.05)   # detect 5% TVD
293
>>> min_shots(0.10)   # detect 10% TVD
74
>>> min_shots(0.01, alpha=0.01, power=0.90)  # stricter
11282
pytest_quantum.random_density_matrix(n_qubits, rank=None, *, seed=None)[source]

Generate a random valid density matrix (PSD, trace 1).

Parameters:
  • n_qubits (int) – Number of qubits (matrix size = 2**n_qubits × 2**n_qubits).

  • rank (int | None) – Matrix rank (default: full rank = 2**n_qubits). Use rank=1 for a random pure state density matrix.

  • seed (int | None) – Optional random seed.

Return type:

ndarray[tuple[Any, ...], dtype[cdouble]]

Returns:

Complex128 array of shape (2**n_qubits, 2**n_qubits).

Example:

from pytest_quantum.random import random_density_matrix

rho = random_density_matrix(1, seed=42)
assert rho.shape == (2, 2)
pytest_quantum.random_kraus_channel(n_qubits, n_kraus=4, *, seed=None)[source]

Generate random valid Kraus operators for a CPTP channel.

Constructs a random Stinespring isometry and extracts Kraus operators that satisfy the completeness relation K†K = I.

Parameters:
  • n_qubits (int) – Number of qubits.

  • n_kraus (int) – Number of Kraus operators (default 4).

  • seed (int | None) – Optional random seed.

Return type:

list[ndarray[tuple[Any, ...], dtype[cdouble]]]

Returns:

List of n_kraus complex128 arrays of shape (2**n_qubits, 2**n_qubits).

Example:

from pytest_quantum.random import random_kraus_channel
from pytest_quantum import assert_channel_is_cptp

kraus = random_kraus_channel(1, seed=7)
assert_channel_is_cptp(kraus)
pytest_quantum.random_statevector(n_qubits, *, seed=None)[source]

Generate a random normalised statevector (Haar-random pure state).

Parameters:
  • n_qubits (int) – Number of qubits (statevector length = 2**n_qubits).

  • seed (int | None) – Optional random seed for reproducibility.

Return type:

ndarray[tuple[Any, ...], dtype[cdouble]]

Returns:

Normalised complex128 array of shape (2**n_qubits,).

Example:

from pytest_quantum.random import random_statevector

sv = random_statevector(2, seed=0)
assert sv.shape == (4,)
assert abs(sum(abs(sv) ** 2) - 1) < 1e-12
pytest_quantum.random_unitary(n_qubits, *, seed=None)[source]

Generate a Haar-random unitary matrix (CUE).

Uses QR decomposition of the Ginibre ensemble with phase correction to guarantee an exact Haar distribution.

Parameters:
  • n_qubits (int) – Number of qubits (matrix size = 2**n_qubits × 2**n_qubits).

  • seed (int | None) – Optional random seed.

Return type:

ndarray[tuple[Any, ...], dtype[cdouble]]

Returns:

Unitary complex128 array of shape (2**n_qubits, 2**n_qubits).

Example:

from pytest_quantum.random import random_unitary
import numpy as np

U = random_unitary(2, seed=0)
assert np.allclose(U @ U.conj().T, np.eye(4), atol=1e-12)
pytest_quantum.recommended_shots(expected_probs, min_expected_per_bucket=5)[source]

Recommend shots so every bucket gets enough expected counts for chi-square.

The chi-square goodness-of-fit test requires each cell to have an expected count of at least 5 (a common rule of thumb) to be statistically valid. This function returns the shot count that satisfies that requirement for the rarest outcome in expected_probs.

Parameters:
  • expected_probs (dict[str, float]) – Dict mapping outcome labels to probabilities. Must sum to 1. Zero-probability outcomes are ignored.

  • min_expected_per_bucket (int) – Minimum expected count per non-zero bucket (default 5).

Return type:

int

Returns:

Recommended shot count as an integer.

Raises:

ValueError – If expected_probs is empty or all probabilities are zero.

Example:

>>> recommended_shots({"00": 0.499, "01": 0.001, "11": 0.5})
5000
>>> recommended_shots({"0": 0.5, "1": 0.5})
10
pytest_quantum.tvd(p, q)[source]

Total Variation Distance between two probability distributions.

\[\text{TVD}(p, q) = \frac{1}{2} \sum_x |p(x) - q(x)|\]
Parameters:
Return type:

float

Returns:

Float in [0.0, 1.0]. 0.0 means identical distributions; 1.0 means disjoint support.

Example:

>>> import numpy as np
>>> tvd(np.array([0.5, 0.5]), np.array([0.6, 0.4]))
0.1
pytest_quantum.tvd_from_counts(counts_a, counts_b)[source]

Compute TVD between two shot-count dictionaries.

Normalises each dict to a probability distribution before computing TVD.

Parameters:
  • counts_a (dict[str, int]) – First counts dict, e.g. {"00": 489, "11": 511}.

  • counts_b (dict[str, int]) – Second counts dict, e.g. {"00": 501, "11": 499}.

Return type:

float

Returns:

Float in [0.0, 1.0].

Raises:

ValueError – If either dict is empty.


Assertions

Unitary assertions

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.

pytest_quantum.assertions.unitary.assert_unitary(circuit, expected, *, atol=1e-06, allow_global_phase=True)[source]

Assert that circuit implements the expected unitary matrix.

Parameters:
  • circuit (object) – Any supported quantum circuit (Qiskit, Cirq, Braket, PennyLane).

  • expected (ndarray[tuple[Any, ...], dtype[cdouble]]) – Target unitary as a numpy array, shape (2**n, 2**n).

  • atol (float) – Absolute tolerance for element-wise comparison (default 1e-6).

  • allow_global_phase (bool) – 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.

Return type:

None

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)
pytest_quantum.assertions.unitary.assert_circuits_equivalent(circuit_a, circuit_b, *, atol=1e-06)[source]

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.

Parameters:
  • circuit_a (object) – First circuit (any supported framework).

  • circuit_b (object) – Second circuit (any supported framework).

  • atol (float) – 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.

Return type:

None

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)
pytest_quantum.assertions.unitary.assert_transpilation_preserves_semantics(circuit, backend, *, optimization_level=1, atol=1e-06)[source]

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).

Parameters:
  • circuit (object) – Qiskit QuantumCircuit (must be unitary, no measurements).

  • backend (object) – Qiskit backend or FakeBackend target.

  • optimization_level (int) – Qiskit transpiler optimisation level 0-3 (default 1).

  • atol (float) – Tolerance for unitary comparison (default 1e-6).

Raises:
Return type:

None

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)

State assertions

Statevector-level assertions for quantum tests.

Use these when you have the full output statevector from a simulator (Aer statevector mode, Cirq simulator, or the Graphix backend) and want to verify the quantum state directly — more informative than comparing shot distributions.

pytest_quantum.assertions.states.assert_state_fidelity_above(actual, target, threshold=0.99)[source]

Assert that two pure quantum states have fidelity at or above threshold.

Fidelity \(F = |\langle\text{actual}|\text{target}\rangle|^2\) equals 1.0 for identical states (up to global phase) and 0.0 for orthogonal states.

This is the primary assertion for MBQC / Graphix tests where the circuit does not have a fixed unitary representation.

Parameters:
Raises:
Return type:

None

Example:

import numpy as np
from pytest_quantum import assert_state_fidelity_above

BELL = np.array([1, 0, 0, 1], dtype=complex) / np.sqrt(2)


def test_bell_graphix(graphix_backend):
    from graphix.transpiler import Circuit

    circuit = Circuit(2)
    circuit.h(0)
    circuit.cnot(0, 1)
    pattern = circuit.transpile().pattern
    output = graphix_backend.run_pattern(pattern)
    assert_state_fidelity_above(output, BELL, threshold=0.999)
pytest_quantum.assertions.states.assert_normalized(statevector, *, atol=1e-06)[source]

Assert statevector has unit norm: ||ψ||₂ = 1.

A common bug in manual statevector construction is forgetting to normalize.

Parameters:
  • statevector (object) – Complex array-like of any shape (flattened internally).

  • atol (float) – Absolute tolerance from 1.0 (default 1e-6).

Raises:

AssertionError – If ||sv||₂ is not within atol of 1.0, showing the actual norm.

Return type:

None

Example:

>>> import numpy as np
>>> sv = np.array([1, 0, 0, 0], dtype=complex)  # |00>
>>> assert_normalized(sv)  # passes

>>> sv_bad = np.array([1, 1], dtype=complex)  # NOT normalized
>>> assert_normalized(sv_bad)  # fails: norm = 1.4142
pytest_quantum.assertions.states.assert_states_close(actual, target, *, atol=1e-06)[source]

Assert that two statevectors are element-wise close, up to global phase.

Stricter than assert_state_fidelity_above() — use for exact simulator-to-simulator comparisons where you want bit-for-bit agreement.

Parameters:
Raises:

AssertionError – If any element differs by more than atol after removing the global phase.

Return type:

None

Example:

def test_plus_state(aer_statevector_simulator):
    from qiskit import QuantumCircuit, transpile

    qc = QuantumCircuit(1)
    qc.h(0)
    qc.save_statevector()
    qc_t = transpile(qc, aer_statevector_simulator)
    sv = aer_statevector_simulator.run(qc_t).result().get_statevector()
    PLUS = np.array([1, 1]) / np.sqrt(2)
    assert_states_close(sv.data, PLUS)

Distribution assertions

Shot-distribution assertions for quantum tests.

These assertions test the statistical output of a circuit — the probability distribution over measurement outcomes — using principled chi-square tests rather than ad-hoc tolerances.

pytest_quantum.assertions.distributions.assert_measurement_distribution(counts, expected_probs, *, significance=0.05, min_expected_per_bucket=5)[source]

Assert that measured counts match the expected probability distribution.

Uses a chi-square goodness-of-fit test — the standard statistical tool for this exact problem. The test fails only when the deviation is statistically significant (p < significance), so occasional random fluctuations do not cause false failures.

Parameters:
  • counts (dict[str, int]) – Measured counts dict, e.g. {"00": 489, "11": 511}. Keys are bitstring labels; values are integer counts.

  • expected_probs (dict[str, float]) – Expected probability dict, e.g. {"00": 0.5, "11": 0.5}. Must sum to 1.0 (within 1e-6). Outcomes not present are assumed to have zero expected probability.

  • significance (float) – P-value threshold below which the test fails (default 0.05).

  • min_expected_per_bucket (int) – Chi-square requires expected count >= 5 per non-zero cell for valid results. A UserWarning is raised (but the test does not fail) if this is violated; consider increasing shots.

Raises:
  • AssertionError – If p_value < significance, with a per-state breakdown of observed vs expected probabilities.

  • ValueError – If expected_probs does not sum to 1.0, or counts is empty.

Return type:

None

Example:

:rtype: :sphinx_autodoc_typehints_type:`\:py\:obj\:\`None\``
def test_bell_distribution(aer_simulator):

from qiskit import QuantumCircuit, transpile from pytest_quantum import assert_measurement_distribution

qc = QuantumCircuit(2) qc.h(0) qc.cx(0, 1) qc.measure_all() qc_t = transpile(qc, aer_simulator) counts = aer_simulator.run(qc_t, shots=2000).result().get_counts()

assert_measurement_distribution(

counts, expected_probs={“00”: 0.5, “11”: 0.5},

)

pytest_quantum.assertions.distributions.assert_counts_close(counts_a, counts_b, *, max_tvd=0.05)[source]

Assert that two count dictionaries are statistically close.

Computes the Total Variation Distance (TVD) between the normalised distributions and fails if it exceeds max_tvd.

Useful for comparing two backends, or checking that transpilation has not changed a circuit’s output distribution.

Parameters:
  • counts_a (dict[str, int]) – First counts dict.

  • counts_b (dict[str, int]) – Second counts dict.

  • max_tvd (float) – Maximum acceptable TVD (default 0.05). TVD of 0 means identical distributions; 1 means disjoint support.

Raises:

AssertionError – If TVD exceeds max_tvd.

Return type:

None

Example:

def test_transpile_preserves_distribution(aer_simulator):
    from qiskit import QuantumCircuit, transpile

    qc = QuantumCircuit(2)
    qc.h(0)
    qc.cx(0, 1)
    qc.measure_all()

    # ideal vs noise-free transpiled
    qc_t = transpile(qc, aer_simulator, optimization_level=3)
    counts_ideal = aer_simulator.run(qc, shots=2000).result().get_counts()
    counts_transpiled = (
        aer_simulator.run(qc_t, shots=2000).result().get_counts()
    )
    assert_counts_close(counts_ideal, counts_transpiled, max_tvd=0.05)

Structure assertions

Circuit structure assertions.

These assertions check static properties of a circuit — depth, gate counts, qubit width — without executing it. Useful for catching regressions in compiler output or ensuring a circuit meets hardware constraints.

pytest_quantum.assertions.structure.assert_circuit_depth(circuit, *, max_depth=None, min_depth=None)[source]

Assert that a circuit’s depth is within the specified bounds.

At least one of max_depth or min_depth must be provided.

Supported frameworks: Qiskit, Cirq, Amazon Braket.

Parameters:
  • circuit (object) – A quantum circuit from a supported framework.

  • max_depth (int | None) – If given, the circuit depth must be ≤ this value.

  • min_depth (int | None) – If given, the circuit depth must be ≥ this value.

Raises:
Return type:

None

Example:

def test_circuit_depth():
    from qiskit import QuantumCircuit

    qc = QuantumCircuit(2)
    qc.h(0)
    qc.cx(0, 1)
    assert_circuit_depth(qc, max_depth=3)
pytest_quantum.assertions.structure.assert_circuit_width(circuit, expected_qubits)[source]

Assert that a circuit acts on exactly expected_qubits qubits.

Supported frameworks: Qiskit, Cirq, Amazon Braket, PennyLane.

Parameters:
  • circuit (object) – A quantum circuit from a supported framework.

  • expected_qubits (int) – Expected number of qubits.

Raises:
Return type:

None

Example:

def test_circuit_width():
    from qiskit import QuantumCircuit

    qc = QuantumCircuit(3)
    qc.h(0)
    qc.cx(0, 1)
    qc.cx(1, 2)
    assert_circuit_width(qc, expected_qubits=3)
pytest_quantum.assertions.structure.assert_gate_count(circuit, gate_name, expected)[source]

Assert that a circuit contains exactly expected occurrences of gate_name.

Supported frameworks: Qiskit, Cirq, PennyLane.

Parameters:
  • circuit (object) – A quantum circuit from a supported framework.

  • gate_name (str) – Gate name as a string, e.g. "cx", "h", "t", "CNOT", "Hadamard". Case-insensitive for Qiskit; Cirq and PennyLane match case-insensitively by gate class name.

  • expected (int) – Expected count.

Raises:
Return type:

None

Example:

def test_t_count():
    from qiskit import QuantumCircuit

    qc = QuantumCircuit(2)
    qc.t(0)
    qc.t(1)
    qc.cx(0, 1)
    assert_gate_count(qc, "t", 2)
    assert_gate_count(qc, "cx", 1)
pytest_quantum.assertions.structure.assert_gates_in_basis_set(circuit, basis_gates, *, case_sensitive=False)[source]

Assert every gate in the circuit belongs to the specified basis gate set.

Useful for verifying that a transpiled circuit only uses a target backend’s native gate set (e.g. after qiskit.transpile with basis_gates=[...]).

Parameters:
  • circuit (object) – Qiskit, Cirq, Braket, or Pytket circuit.

  • basis_gates (set[str]) – Set of allowed gate names.

  • case_sensitive (bool) – If False (default), comparison is case-insensitive.

Raises:
Return type:

None

Example:

from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator
from pytest_quantum import assert_gates_in_basis_set

qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
transpiled = transpile(qc, basis_gates=["cx", "u3"])
assert_gates_in_basis_set(transpiled, {"cx", "u3"})
pytest_quantum.assertions.structure.assert_circuit_is_clifford(circuit)[source]

Assert a circuit uses only Clifford gates (H, S, S†, X, Y, Z, CNOT, CZ, SWAP).

Clifford circuits are classically efficiently simulable. Supported: Qiskit, Cirq.

Raises:
Parameters:

circuit (object)

Return type:

None

Example:

def test_is_clifford():
    from qiskit import QuantumCircuit
    from pytest_quantum import assert_circuit_is_clifford

    qc = QuantumCircuit(2)
    qc.h(0)
    qc.cx(0, 1)
    assert_circuit_is_clifford(qc)
Return type:

None

Parameters:

circuit (object)

pytest_quantum.assertions.structure.assert_no_mid_circuit_measurement(circuit)[source]

Assert a circuit has no mid-circuit measurements (all measurements are terminal).

Mid-circuit measurements (measurements followed by further gate operations) are not supported on all hardware backends. This assertion verifies that all measurements occur after all gate operations — i.e., measurements only appear in the final layer.

Supported frameworks: Qiskit, Cirq.

Parameters:

circuit (object) – A quantum circuit from a supported framework.

Raises:
Return type:

None

Example:

from qiskit import QuantumCircuit
from pytest_quantum import assert_no_mid_circuit_measurement

qc = QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
qc.measure_all()
assert_no_mid_circuit_measurement(qc)  # passes — measurements are terminal
pytest_quantum.assertions.structure.assert_has_diagram(circuit, expected, *, strict=False)[source]

Assert circuit’s text representation contains expected pattern.

For Qiskit: uses circuit.draw('text'). For Cirq: uses str(circuit) (circuit.to_text_diagram()).

Parameters:
  • circuit (object) – Any supported framework circuit.

  • expected (str) – Expected string (exact if strict is True, substring otherwise).

  • strict (bool) – If True, require exact match after stripping leading / trailing whitespace. If False (default), just check that expected is a substring of the diagram.

Raises:
Return type:

None

Example:

from qiskit import QuantumCircuit
from pytest_quantum import assert_has_diagram

qc = QuantumCircuit(1)
qc.h(0)
assert_has_diagram(qc, "H")

Statistics

Shot calculators

Shot-count calculators.

Quantum tests require running a circuit many times (shots). Choosing the right shot count is surprisingly nuanced:

  • Too few shots → test is flaky (random failures unrelated to correctness).

  • Too many shots → test is slow and wasteful.

This module provides principled formulas so you never have to guess.

pytest_quantum.stats.shots.min_shots(epsilon, alpha=0.05, power=0.8)[source]

Minimum shots to detect a total-variation distance of epsilon.

Based on two-sample statistical power analysis:

\[N = \lceil (z_{1-\alpha/2} + z_{\text{power}})^2 \; / \; (2\varepsilon^2) \rceil\]
Parameters:
  • epsilon (float) – Minimum detectable total variation distance (TVD). For example, 0.01 means the test can reliably catch a 1 % deviation from the expected distribution.

  • alpha (float) – Significance level (default 0.05 → 95 % confidence).

  • power (float) – Statistical power, i.e. probability of detecting a real error (default 0.80 → 80 % power).

Return type:

int

Returns:

Minimum recommended shot count as an integer.

Raises:

ValueError – If any argument is outside its valid range.

Examples:

>>> min_shots(0.01)   # detect 1% TVD
7299
>>> min_shots(0.05)   # detect 5% TVD
293
>>> min_shots(0.10)   # detect 10% TVD
74
>>> min_shots(0.01, alpha=0.01, power=0.90)  # stricter
11282
pytest_quantum.stats.shots.recommended_shots(expected_probs, min_expected_per_bucket=5)[source]

Recommend shots so every bucket gets enough expected counts for chi-square.

The chi-square goodness-of-fit test requires each cell to have an expected count of at least 5 (a common rule of thumb) to be statistically valid. This function returns the shot count that satisfies that requirement for the rarest outcome in expected_probs.

Parameters:
  • expected_probs (dict[str, float]) – Dict mapping outcome labels to probabilities. Must sum to 1. Zero-probability outcomes are ignored.

  • min_expected_per_bucket (int) – Minimum expected count per non-zero bucket (default 5).

Return type:

int

Returns:

Recommended shot count as an integer.

Raises:

ValueError – If expected_probs is empty or all probabilities are zero.

Example:

>>> recommended_shots({"00": 0.499, "01": 0.001, "11": 0.5})
5000
>>> recommended_shots({"0": 0.5, "1": 0.5})
10

Statistical tests

Statistical test primitives for quantum output validation.

All functions are pure numpy/scipy — no quantum SDK required.

pytest_quantum.stats.tests.fidelity(psi, phi)[source]

Pure-state fidelity \(|\langle\psi|\phi\rangle|^2\).

Both arrays are flattened and normalised before computation, so minor normalisation errors from simulators do not affect the result.

Parameters:
Return type:

float

Returns:

Float in [0.0, 1.0]. 1.0 means the states are identical (up to global phase). 0.0 means orthogonal.

Raises:

ValueError – If psi and phi have different sizes.

Example:

>>> import numpy as np
>>> zero = np.array([1, 0], dtype=complex)
>>> plus = np.array([1, 1], dtype=complex) / np.sqrt(2)
>>> fidelity(zero, plus)
0.5
pytest_quantum.stats.tests.tvd(p, q)[source]

Total Variation Distance between two probability distributions.

\[\text{TVD}(p, q) = \frac{1}{2} \sum_x |p(x) - q(x)|\]
Parameters:
Return type:

float

Returns:

Float in [0.0, 1.0]. 0.0 means identical distributions; 1.0 means disjoint support.

Example:

>>> import numpy as np
>>> tvd(np.array([0.5, 0.5]), np.array([0.6, 0.4]))
0.1
pytest_quantum.stats.tests.tvd_from_counts(counts_a, counts_b)[source]

Compute TVD between two shot-count dictionaries.

Normalises each dict to a probability distribution before computing TVD.

Parameters:
  • counts_a (dict[str, int]) – First counts dict, e.g. {"00": 489, "11": 511}.

  • counts_b (dict[str, int]) – Second counts dict, e.g. {"00": 501, "11": 499}.

Return type:

float

Returns:

Float in [0.0, 1.0].

Raises:

ValueError – If either dict is empty.

pytest_quantum.stats.tests.chi_square_test(observed, expected_probs, total_shots=None)[source]

Chi-square goodness-of-fit test for quantum measurement distributions.

Tests whether observed counts are consistent with expected_probs.

Parameters:
  • observed (dict[str, int] | ndarray[tuple[Any, ...], dtype[double]]) – Either a count dict {"00": 489, "11": 511} or a 1-D numpy array of observed counts.

  • expected_probs (dict[str, float] | ndarray[tuple[Any, ...], dtype[double]]) – Either a probability dict {"00": 0.5, "11": 0.5} (must sum to 1) or a 1-D numpy array of expected probabilities. When using numpy arrays, provide total_shots so expected counts can be computed.

  • total_shots (int | None) – Required when both inputs are numpy arrays. Ignored when dict inputs are used (total is inferred from observed).

Return type:

tuple[float, float]

Returns:

(statistic, pvalue) — the chi-square statistic and the p-value. Reject the null hypothesis (distributions match) when pvalue < significance.

Raises:

ValueError – If inputs are inconsistent (different keys, missing total_shots for array inputs, etc.).

Example:

>>> stat, p = chi_square_test({"00": 495, "11": 505}, {"00": 0.5, "11": 0.5})
>>> p > 0.05  # consistent with Bell state
True