A small, backend-agnostic quantum circuit API. Write a circuit once against a minimal intermediate representation (IR), and run it unmodified on Qiskit Aer, Cirq, or PennyLane (lightning.qubit) — with results returned in one normalized format.
from qiberis import Circuit
from qiberis.backends.qiskit_backend import QiskitAerBackend
from qiberis.backends.cirq_backend import CirqSimulatorBackend
from qiberis.backends.pennylane_backend import PennyLaneLightningBackend
# Build a 3-qubit GHZ state, once.
circuit = Circuit(3)
circuit.h(0).cx(0, 1).cx(1, 2).measure_all()
for backend_cls in (QiskitAerBackend, CirqSimulatorBackend, PennyLaneLightningBackend):
backend = backend_cls()
result = backend.run(circuit, shots=1000)
print(result)Qiskit, Cirq, and PennyLane each have their own circuit objects, gate names,
execution APIs, and — critically — their own bit-ordering conventions for
returned counts. Comparing results across them, or writing code that should
work regardless of which simulator is installed, means re-solving the same
translation problem every time. Qiberis solves it once.
pip install -e ".[all]" # all three backends
pip install -e ".[qiskit]" # just Qiskit Aer
pip install -e ".[cirq]" # just Cirq
pip install -e ".[pennylane]" # just PennyLaneEach backend is an optional dependency — the core package
(qiberis.Circuit, qiberis.Result) has zero hard dependencies, and each
adapter raises a clear ImportError telling you what to install if you try
to use a backend you haven't installed.
qiberis/
circuit.py # Circuit + Instruction: the backend-agnostic IR
result.py # Result: normalized counts/probabilities
backends/
base.py # Backend ABC — one method: run(circuit, shots) -> Result
qiskit_backend.py
cirq_backend.py
pennylane_backend.py
tests/
test_cross_backend.py # runs the same circuits on every installed backend
# and checks they agree with each other
Supported gates: h, x, y, z, s, t, rx, ry, rz, cx, cz, swap, ccx, barrier, measure. Extending to a new gate means adding one line to each adapter's
translation table.
This is the part that silently breaks most "universal circuit" projects.
Qiskit's native count strings are little-endian (clbit 0 is the
rightmost character); Cirq and PennyLane's qml.counts(wires=...) are
naturally ordered with the first wire/clbit leftmost.
Qiberis's convention: in every Result.counts key, the leftmost
character is always clbit/qubit 0. The Qiskit adapter reverses its native
strings to match; Cirq and PennyLane need no adjustment. This is covered by
a dedicated regression test, test_bit_ordering, which runs an asymmetric
circuit (X on qubit 0 only) through every backend and checks all three
report the same string.
- Create
qiberis/backends/your_backend.py - Subclass
Backend, implementrun(self, circuit, shots) -> Result - Translate
Circuit.instructionsinto your library's native circuit object - Normalize your library's raw output into Qiberis's bit-ordering convention
- Add your backend to the
BACKENDSlist intests/test_cross_backend.py— the existing GHZ, bit-ordering, and cross-agreement tests will immediately validate it against the others
- Real hardware backends (IBM Quantum, AWS Braket QPUs) behind the same interface
- A transpiler/optimization pass operating directly on the IR
- Parametric circuits / variational workflows (bind params without rebuilding)
- Statevector + expectation-value result modes, not just counts
- More gates:
u,crx/cry/crz,iswap, multi-controlled gates
MIT