qcoherence
A native pytest plugin for testing and verifying quantum circuits — across Qiskit, Cirq, and PennyLane (via OpenQASM), on simulators and real IBM Quantum hardware.
Why this exists
Only 31% of quantum developers use any quantum-specific testing tooling — most still debug with print statements and circuit visualizations, because observing a quantum state collapses it, so classical debugging habits don't transfer. Several academic tools have tried to close this gap (Muskit, QuCAT, Quito, MorphQ, QuCheck, QUTest), but each requires writing tests as custom classes inheriting from a project-specific runner, each is tied to Qiskit only, and none validates against real hardware.
qcoherence is different on all three counts:
- Native pytest. Install it, write
def test_x():, runpytest. No base class, no custom runner. - Cross-framework. Accepts Qiskit
QuantumCircuits, OpenQASM 2/3 strings/files, Cirq circuits, and PennyLane circuits (via QASM export). - Real hardware, opt-in. Re-run your golden tests on actual IBM Quantum devices with
--qc-hardware, with noise-realistic tolerances.
Install
pip install qcoherence # core (Qiskit + Aer)
pip install qcoherence[cirq] # + Cirq ingestion
pip install qcoherence[pennylane] # + PennyLane examples
pip install qcoherence[hardware] # + IBM Quantum real-device validation
pip install qcoherence[all] # everything
Quickstart
import numpy as np
from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector
from qcoherence import assert_state_equal, assert_entangled, run_statevector
def bell_circuit():
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
return qc
def test_bell_state():
expected = Statevector(np.array([1, 0, 0, 1]) / np.sqrt(2))
actual = run_statevector(bell_circuit())
assert_state_equal(actual, expected)
assert_entangled(actual, qubits=[0])
Run it like any other test suite: pytest.
From Cirq or PennyLane
from qcoherence import to_qiskit, run_statevector
# Cirq: pass the circuit object directly
qc = to_qiskit(my_cirq_circuit)
# PennyLane: export to OpenQASM, pass the string
qasm = qml.workflow.construct_tape(my_qnode)().to_openqasm(measure_all=False)
qc = to_qiskit(qasm)
What's proven, not just claimed
tests/test_bug_demo.py demonstrates the core value proposition: a Bell-state
circuit missing its entangling gate is a bug that looks completely fine under
naive inspection (it's a valid, normalized quantum state) but is caught
immediately by assert_entangled and assert_state_equal:
pytest tests/test_bug_demo.py -v
Assertion API
| Function | Checks |
|---|---|
assert_state_equal(actual, expected, tol=1e-6) |
State fidelity ≈ 1 (equal up to global phase) |
assert_distribution_close(counts, probs, tol=0.05) |
Total variation distance, simple tolerance |
assert_distribution_fits(counts, probs, alpha=0.01) |
Chi-square goodness-of-fit with a real p-value |
assert_entangled(state, qubits) |
Reduced-state purity < 1 |
assert_separable(state, qubits) |
Reduced-state purity ≈ 1 |
holm_bonferroni(pvalues, alpha) |
Family-wise error control across a suite of statistical tests |
pytest integration
pytest -m quantum_test— run only qcoherence-marked tests.pytest --qc-hardware— also run@pytest.mark.hardwaretests on real IBM Quantum devices (requires a free IBM Quantum account; skipped by default so CI stays green).--qc-shots N/--qc-seed N— shot count and reproducibility seed.
Real-hardware validation
import pytest
from qcoherence.hardware import run_counts_on_hardware, assert_hardware_distribution_close
@pytest.mark.hardware
def test_bell_on_real_device():
counts = run_counts_on_hardware(bell_circuit(), shots=4096)
assert_hardware_distribution_close(counts, {"00": 0.5, "11": 0.5}) # tol=0.15, NISQ-realistic
Extending qcoherence
Register custom assertions/backends programmatically or from a separate pip
package via entry points — see qcoherence/registry.py:
from qcoherence.registry import register_assertion
@register_assertion("assert_w_state_like")
def assert_w_state_like(state, *, tol=1e-6):
...
# in another package's pyproject.toml
[project.entry-points."qcoherence.assertions"]
assert_my_thing = "mypkg.assertions:assert_my_thing"
Golden benchmarks
tests/golden/ contains verified reference circuits used as both examples
and a regression suite: Bell state, GHZ state (3-qubit entanglement),
and a VQE ground-state search checked against exact numpy
diagonalization computed in-test.
Prior art
This project builds on ideas published in the quantum software testing research literature, particularly Muskit (mutation testing), QuCAT (combinatorial testing), Quito (coverage-guided generation), MorphQ (metamorphic testing), QuCheck (property-based testing and the statistical multiple-testing insight), and QUTest (native assertion testing). Its contribution is a native pytest integration layer with cross-framework ingestion and opt-in hardware validation, not a new testing paradigm.
License
MIT
Metadata
Release files for qcoherence 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| qcoherence-1.0.0.tar.gz | 18.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| qcoherence-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 34.6 kB
Release files / qcoherence-1.0.0.tar.gz
| Download URL | qcoherence-1.0.0.tar.gz |
|---|---|
| Size | 18.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
429dfaa85a023e59551f72938514aa634fe79911d07a37f3d485e62327d6cc9c
|
|
BLAKE2b-256 checksum How to use checksums |
9a0c31951e366e10d595c6da7de5a48e146c3677b44a672e8f47773223070706
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.9
|
Release files / qcoherence-1.0.0-py3-none-any.whl
| Download URL | qcoherence-1.0.0-py3-none-any.whl |
|---|---|
| Size | 16.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c5ffe9b8cafec8f7422933decaf74ddb4aa9f2dc83479fd9f660f98ec07c238a
|
|
BLAKE2b-256 checksum How to use checksums |
97658c162b01f6594b1a8fa7e64e8f41d03a591ea7fdfb046f3671eb92a05a3e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.9
|