Documentation

The Quantuva SDK.

A small Python library for writing circuits, compiling them against a backend, simulating them, and reading back a calibrated expectation value. This page covers the path from install to a mitigated measurement.

Installation#

Quantuva targets Python 3.10 and later. Install from PyPI; the local simulators ship with the package, so there is no separate runtime to set up.

terminal
# install the SDK
$ pip install quantuva

# verify it imports and reports the simulator
$ python -c "import quantuva; print(quantuva.__version__)"
No account required. Everything on this page runs against the built-in statevector simulator. Hardware submission needs a backend token, covered under Backends.

Quickstart#

Build a GHZ state, transpile it, run it, and read the ⟨Z₀⊗Z₁⟩ stabilizer — with readout mitigation on.

quickstart.pypython
from quantuva import Circuit, Observable, transpile

# 1. build the circuit
qc = Circuit(3)
qc.h(0)
qc.cx(0, 1)
qc.cx(0, 2)
qc.measure_all()

# 2. compile against a target topology and gate set
compiled = transpile(qc, target="superconducting-v2", opt_level=2)

# 3. run — locally by default, hardware with a token
obs = Observable("Z(0) @ Z(1)")
result = compiled.run(shots=2048, mitigate="zne")

# 4. read the calibrated expectation value
print(result.expectation(obs))

That's the whole loop: build → compile → run → read. The circuit, the observable, and the mitigation are all explicit arguments — nothing is hidden in a default you didn't choose.

Circuit#

A Circuit holds qubits, gates, and measurements. Gates are named after the operations you'd write on paper — H, X, Y, Z, Rₓ, R_z, CX, SWAP — plus parameterized rotations for variational work.

build.pypython
qc = Circuit(4)
qc.h(0)
qc.rx(0.5, 1)
qc.cx(0, 1)
qc.cz(1, 2)
qc.swap(2, 3)
qc.measure_all()

# the circuit knows its own depth and gate counts
qc.depth()      # -> int
qc.num_qubits() # -> int

Parameterized circuits

Bind a circuit to parameters once and re-run it across a sweep without rebuilding. Useful for variational algorithms and for the noise-scaled circuits that ZNE needs.

parameterized.pypython
qc = Circuit(2)
qc.rx("θ", 0)
qc.ry("φ", 1)
qc.cx(0, 1)

for θ, φ in [(0.1, 0.2), (0.3, 0.4)]:
    bound = qc.bind(θ=θ, φ=φ)
    bound.run(shots=1024)

Observable#

An Observable is a Pauli string over a subset of qubits. You declare what you mean to measure, and the SDK handles the basis rotations behind it.

Observable(spec)
observable.pypython
obs = Observable("Z(0) @ Z(1)")        # two-point correlator
obs = Observable("X(0) + X(1) + X(2)")   # a sum, measured term by term
obs = Observable("Z(0) @ Z(1) @ Z(2)")   # three-point, for GHZ stabilizers

Transpilation#

transpile rewrites a logical circuit for a physical backend: it decomposes multi-qubit gates into the native set, maps logical qubits onto the device topology, and inserts SWAP gates where the connectivity demands them. The output is a circuit the backend can actually schedule.

transpile(circuit, target, opt_level=2) → CompiledCircuit
  • gate decomposition — multi-controlled and arbitrary-angle gates become native single-qubit rotations and two-qubit gates.
  • qubit mapping — logical qubits are placed on physical qubits to minimise the routing work that follows.
  • SWAP routing — where the connectivity is sparse, SWAP gates move states so entangling gates can act on adjacent qubits.
  • scheduling — gates are aligned to the backend's timing, not left as a bare dependency graph.

Simulation#

Three simulators, chosen by what you need to model. The default is a fast statevector simulator; add a noise model and you get the density-matrix path; shallow wide circuits can use the tensor-network backend.

simulate.pypython
# exact statevector
result = compiled.run(shots=0)               # 0 -> return the statevector

# noisy density-matrix with a decoherence model
result = compiled.run(noise="depolarizing(0.01)", shots=4096)

# tensor-network path for shallow, wide circuits
result = compiled.run(backend="mps", shots=2048)
Decoherence is modelled, not assumed away. The density-matrix simulator tracks amplitude damping and dephasing separately, so a mitigation strategy you test offline behaves like it will on hardware.

Error mitigation#

Pass a mitigate argument to run. Each method trades classical sampling budget for a less biased answer, and each has regimes where it stops helping — see the lab notes for where.

mitigate.pypython
# zero-noise extrapolation
result = compiled.run(shots=2048, mitigate="zne")

# Pauli twirling before sampling
result = compiled.run(shots=2048, mitigate="twirl")

# readout correction from a calibration matrix
result = compiled.run(shots=2048, mitigate="readout")

# or compose them
result = compiled.run(shots=2048, mitigate=["twirl", "zne"])

Backends#

By default everything runs locally. To submit to hardware, pass a target name and a token; the same compiled circuit is serialized and scheduled on the partner device.

backends.pypython
compiled = transpile(qc, target="superconducting-v2")
result = compiled.run(shots=2048, token="...")

Supported target families: superconducting and trapped-ion backends, plus the local simulators. Each target advertises its native gate set and connectivity, so transpilation can be genuinely backend-aware rather than a fixed rewrite.