Transpile and run with Qiskit
examples/qiskit_transpile_and_run.py
The problem
Section titled “The problem”Run existing Qiskit code — provider, transpiler, primitives. The adapter puts the
platform behind the interfaces Qiskit code already speaks: QubitraProvider lists the
catalogue as BackendV2 instances, and the V2 primitives run PUBs. The example builds
a GHZ state, transpiles it against the backend’s reported capability, samples it, and
reads ⟨ZZ⟩ from it.
Needs the extra: pip install 'qubitra-sdk[qiskit]'.
The walkthrough
Section titled “The walkthrough”Provider and backend
Section titled “Provider and backend”from qiskit import QuantumCircuit, transpilefrom qiskit.quantum_info import SparsePauliOp
from qubitra.qiskit import QubitraEstimatorV2, QubitraProvider, QubitraSamplerV2 provider = QubitraProvider() # QUBITRA_API_KEY / QUBITRA_API_URL from the environment backend = provider.backend(backend_id) print(f"backend {backend.name}: {backend.num_qubits} qubits")QubitraProvider builds its client from the environment, and provider.backend()
returns a BackendV2 whose Target is built from the catalogue’s capability
metadata — basis gates, connectivity, qubit count.
Build and transpile
Section titled “Build and transpile” # A GHZ state on three qubits: H then a CX ladder, measured into "meas". ghz = QuantumCircuit(3, name="ghz") ghz.h(0) ghz.cx(0, 1) ghz.cx(1, 2) ghz.measure_all()
# Transpiling against the backend rewrites the circuit for its Target. Against a # backend with no reported capability this is a pass-through — the Target carries # no constraints to satisfy. transpiled = transpile(ghz, backend=backend)transpile(circuit, backend=backend) aims at what the backend reported: the Target’s
instruction set and coupling map come from the catalogue. A backend whose capability
metadata has yet to be populated yields a permissive Target, and the transpile passes
the circuit through.
The sampler
Section titled “The sampler” sampler = QubitraSamplerV2(backend) result = sampler.run([transpiled], shots=SHOTS).result() counts = result[0].data.meas.get_counts() print() print(f"counts ({SHOTS} shots):") for bitstring, count in sorted(counts.items()): print(f" {bitstring} {count:5d} {'█' * round(40 * count / SHOTS)}")QubitraSamplerV2.run takes a list and submits it as one job — a platform round trip
of seconds (submit, poll, read). Results carry one BitArray per classical register;
measure_all named this one meas.
The estimator
Section titled “The estimator” # The estimator reads an operator's average instead of sampling bitstrings, so its # circuit carries no measurements. <ZZI> on a GHZ state is +1: qubits 0 and 1 are # perfectly correlated. Labels are Qiskit-dense — "IZZ" is Z on qubits 0 and 1. bare = QuantumCircuit(3) bare.h(0) bare.cx(0, 1) bare.cx(1, 2) zz = SparsePauliOp.from_list([("IZZ", 1.0)])
estimator = QubitraEstimatorV2(backend) estimation = estimator.run([(bare, zz)]).result() print() print(f"<ZZ> on qubits 0,1 = {float(estimation[0].data.evs):+.4f} (GHZ gives +1)") return 0The estimator’s circuit is the same GHZ preparation without the measurements, and the
observable arrives as a SparsePauliOp. Qiskit’s dense labels read right to left:
"IZZ" puts Z on qubits 0 and 1. The adapter converts it to the platform’s sparse
Pauli form on the way through.
Running it
Section titled “Running it”QUBITRA_API_KEY=qpk_... python examples/qiskit_transpile_and_run.pyOutput from a live run, trimmed to the results:
counts (1024 shots): 000 503 ████████████████████ 111 521 ████████████████████
<ZZ> on qubits 0,1 = +1.0000 (GHZ gives +1)The counts split between 000 and 111 — every qubit agrees, which is the GHZ
state — and the estimator reads the qubit-0/qubit-1 correlation as exactly +1.
Where to go next
Section titled “Where to go next”- Qiskit — the full adapter: format selection, sweeps, precision, and per-call latency.
- Primitives and PUBs — what the primitives submit underneath.