Fixtures Reference¶
pytest-quantum provides 25 fixtures covering every major quantum SDK, real hardware backends, and testing utilities. All fixtures are auto-discovered with no import needed; just declare them as test parameters.
def test_bell(aer_simulator): # ← injected automatically
...
Fixtures that require an uninstalled SDK are automatically skipped with a helpful message pointing to the install command.
Simulators¶
aer_simulator¶
Session-scoped AerSimulator() for shot-based Qiskit simulation.
def test_bell(aer_simulator):
from qiskit import QuantumCircuit, transpile
qc = QuantumCircuit(2)
qc.h(0); qc.cx(0, 1); qc.measure_all()
counts = aer_simulator.run(transpile(qc, aer_simulator), shots=1024) \
.result().get_counts()
assert "00" in counts
aer_statevector_simulator¶
Session-scoped AerSimulator(method="statevector") for exact statevectors with
no shot noise.
def test_exact(aer_statevector_simulator):
result = aer_statevector_simulator.run(transpile(qc, aer_statevector_simulator)) \
.result()
sv = result.get_statevector()
aer_noise_simulator¶
Function-scoped factory: make_simulator(error_rate), an AerSimulator with
configurable depolarizing noise on all gates.
def test_noisy(aer_noise_simulator):
sim = aer_noise_simulator(error_rate=0.01) # 1% per-gate depolarizing error
counts = sim.run(transpile(qc, sim), shots=2000).result().get_counts()
assert_measurement_distribution(counts, {"00": 0.5, "11": 0.5}, significance=0.001)
cirq_simulator¶
Session-scoped cirq.Simulator().
def test_cirq(cirq_simulator):
import cirq
q = cirq.LineQubit.range(1)
circuit = cirq.Circuit(cirq.H(q[0]))
sv = cirq_simulator.simulate(circuit).final_state_vector
assert sv.shape == (2,)
cirq_sampler¶
Returns a callable run(circuit, repetitions=1024) → dict[str, int] that
runs a Cirq circuit with measurements and returns a count dict.
def test_cirq_bell(cirq_sampler):
import cirq
q = cirq.LineQubit.range(2)
circuit = cirq.Circuit(
cirq.H(q[0]), cirq.CNOT(q[0], q[1]),
cirq.measure(*q, key="result"),
)
counts = cirq_sampler(circuit, repetitions=2000)
assert_measurement_distribution(counts, {"00": 0.5, "11": 0.5})
braket_simulator¶
Session-scoped Amazon Braket LocalSimulator().
def test_braket(braket_simulator):
from braket.circuits import Circuit
circ = Circuit().h(0).cnot(0, 1).probability()
result = braket_simulator.run(circ, shots=1000).result()
pennylane_device¶
Factory: make_device(wires, shots=None), creates a default.qubit device.
def test_pl(pennylane_device):
import pennylane as qml
dev = pennylane_device(wires=2)
@qml.qnode(dev)
def circuit():
qml.Hadamard(0); qml.CNOT([0, 1])
return qml.state()
state = circuit()
assert state.shape == (4,)
pytket_circuit_factory¶
Returns the pytket.Circuit class for building circuits.
def test_pytket(pytket_circuit_factory):
Circuit = pytket_circuit_factory
c = Circuit(2)
c.H(0); c.CX(0, 1)
assert c.n_qubits == 2
stim_sampler¶
Returns a callable sample(circuit, shots=1024) → dict[str, int] for Stim
stabilizer circuits.
def test_stim(stim_sampler):
import stim
c = stim.Circuit("H 0\nCNOT 0 1\nM 0 1")
counts = stim_sampler(c, shots=10000)
assert_measurement_distribution(counts, {"00": 0.5, "11": 0.5})
graphix_backend¶
Backend for running graphix MBQC patterns. Returns a _GraphixBackend with
.run_pattern(pattern) method.
def test_graphix(graphix_backend):
from graphix.transpiler import Circuit
circuit = Circuit(1); circuit.h(0)
pattern = circuit.transpile().pattern
sv = graphix_backend.run_pattern(pattern)
assert sv.shape == (2,)
Qiskit Primitives¶
qiskit_sampler¶
Session-scoped StatevectorSampler (Qiskit 1.0+ primitives API).
def test_sampler(qiskit_sampler):
from qiskit.circuit import QuantumCircuit
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})
qiskit_estimator¶
Session-scoped StatevectorEstimator.
def test_estimator(qiskit_estimator):
from qiskit.circuit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
qc = QuantumCircuit(1) # |0⟩: ⟨Z⟩ = 1.0
result = qiskit_estimator.run([(qc, SparsePauliOp("Z"))]).result()
assert_estimator_close(result, expected=1.0, atol=0.01)
Open-System Solvers¶
qutip_solver¶
Returns a callable solve(H, psi0, tlist, c_ops=None) → np.ndarray that runs
the Lindblad master equation via QuTiP.
def test_qubit_decay(qutip_solver):
import qutip, numpy as np
H = qutip.sigmaz() / 2
psi0 = qutip.basis(2, 0)
gamma = 0.1
tlist = np.linspace(0, 10, 100)
rho_final = qutip_solver(H, psi0, tlist, c_ops=[np.sqrt(gamma)*qutip.sigmam()])
assert_purity_above(rho_final, min_purity=0.0)
tequila_backend¶
Returns the tequila module for quantum chemistry / VQE tests.
def test_tequila_h(tequila_backend):
tq = tequila_backend
U = tq.gates.H(target=0)
result = tq.simulate(U)
assert abs(result[0])**2 == pytest.approx(0.5, abs=1e-6)
Real Hardware¶
All hardware fixtures require --quantum-real to be passed on the CLI
and skip automatically otherwise.
ibm_backend¶
Real IBM Quantum backend. Reads IBM_QUANTUM_TOKEN env var.
Uses SamplerV2 primitives.
export IBM_QUANTUM_TOKEN=your_token
pytest --quantum-real tests/hardware/
@pytest.mark.quantum_real
def test_ibm(ibm_backend):
from qiskit_ibm_runtime import SamplerV2
from pytest_quantum import assert_measurement_distribution
sampler = SamplerV2(ibm_backend)
job = sampler.run([bell_circuit], shots=1024)
counts = dict(job.result()[0].data.c.get_counts())
assert_measurement_distribution(counts, {"00": 0.5, "11": 0.5})
Environment variables:
IBM_QUANTUM_TOKEN(required)IBM_QUANTUM_BACKEND(optional, defaults to least-busy)IBM_QUANTUM_INSTANCE(optional, defaults toibm-q/open/main)
ionq_backend¶
Real IonQ backend. Reads IONQ_API_KEY env var.
export IONQ_API_KEY=your_key
pytest --quantum-real
quantinuum_backend¶
Real Quantinuum backend via pytket-quantinuum. Reads
QUANTINUUM_USERNAME and QUANTINUUM_PASSWORD env vars.
braket_cloud_device¶
AWS Braket cloud device. Reads BRAKET_DEVICE_ARN and uses AWS credentials
from environment.
quantum_hardware_info¶
Returns a dict of credential availability, useful for dynamic test skipping.
def test_info(quantum_hardware_info):
print(quantum_hardware_info)
# {'ibm_available': True, 'ionq_available': False, ...}
Utilities¶
quantum_benchmark¶
Quantum-aware benchmark fixture. Uses pytest-benchmark if installed, otherwise a simple timer.
def test_circuit_speed(quantum_benchmark):
result = quantum_benchmark(run_circuit, my_circuit, shots=1024, n_qubits=5)
benchmark_suite¶
Collects per-assertion timing data within a test session. (v1.0.0)
def test_assertion_overhead(benchmark_suite):
import time
with benchmark_suite.record("assert_unitary"):
assert_unitary(qc, expected_matrix)
benchmark_suite.print_summary()
multi_backend_runner¶
Runs the same circuit on multiple backends in parallel (thread-based) and
returns a dict[backend_name, counts]. (v1.0.0)
def test_cross_backend(multi_backend_runner):
def make_bell():
qc = QuantumCircuit(2, 2)
qc.h(0); qc.cx(0, 1); qc.measure([0,1],[0,1])
return qc
results = multi_backend_runner.run_all(make_bell, shots=1024)
for backend, counts in results.items():
assert_measurement_distribution(counts, {"00": 0.5, "11": 0.5})
shot_budget¶
Track and enforce total shot usage within a single test.
def test_budget(aer_simulator, shot_budget):
budget = shot_budget(max_shots=10_000)
shots = budget.allocate(2000) # raises if over budget
# ... run tests ...
assert budget.remaining == 8000
quantum_shots / quantum_significance¶
Returns the --quantum-shots / --quantum-significance CLI override values.
def test_with_global_shot_override(quantum_shots):
shots = quantum_shots or 1024 # use CLI override if provided
@pytest.mark.quantum_backends: parametrised multi-framework tests¶
Run the same test on multiple backends automatically:
@pytest.mark.quantum_backends("qiskit", "cirq", "pennylane")
def test_h_gate_all_frameworks(quantum_backend, quantum_backend_name):
# quantum_backend = simulator for current framework
# quantum_backend_name = "qiskit" | "cirq" | "pennylane"
print(f"Testing on {quantum_backend_name}")