Submit a Bell pair
examples/submit_bell_pair.py
The problem
Section titled “The problem”Submit a first circuit and read counts back. The example runs the whole execution path — SDK to the Qubitra API to a Temporal workflow to whichever backend the identifier resolves to — so a failure here says something real about the stack. It also shows format negotiation: the circuit ships in whichever OpenQASM version the backend accepts.
The walkthrough
Section titled “The walkthrough”The circuit, in two formats
Section titled “The circuit, in two formats”from qubitra import CircuitFormat, QubitraClient
# A Bell pair: entangle two qubits, measure both. On a simulator the counts land almost# entirely on "00" and "11", so a result that is spread across all four outcomes is a# visible sign something ran the wrong circuit.BELL = { # `include "stdgates.inc";` is not decoration: OpenQASM 3 defines no gates of its own, # so `h` and `cx` are undefined identifiers without it and a conformant importer # rejects the program. Every OpenQASM 3 circuit in this SDK's documentation carries it. CircuitFormat.OPENQASM_3: """OPENQASM 3.0;include "stdgates.inc";qubit[2] q;bit[2] c;h q[0];cx q[0], q[1];c = measure q;""", # Backends differ in what they accept, and not every one has caught up to OpenQASM 3 — # `client.backends.list()` reports each one's `supported_formats`. CircuitFormat.OPENQASM_2: """OPENQASM 2.0;include "qelib1.inc";qreg q[2];creg c[2];h q[0];cx q[0],q[1];measure q -> c;""",}The same Bell pair is written twice, keyed by CircuitFormat, because the format a job
ships in is negotiated per backend. A circuit is a string in every format; the
CircuitFormat value beside it tells the platform how to parse it.
Format negotiation
Section titled “Format negotiation”DEFAULT_BACKEND = "sim-statevector-26q"SHOTS = 1024
# Preferred first: OpenQASM 3 where a backend takes it, OpenQASM 2 otherwise.PREFERENCE = (CircuitFormat.OPENQASM_3, CircuitFormat.OPENQASM_2)
def pick_format(client: QubitraClient, backend_id: str) -> CircuitFormat: """The best format this backend accepts, out of the ones this circuit is written in.
A backend reports what it accepts, and a backend that reports nothing places no constraint of its own — so an empty list means the newest format is worth trying rather than that nothing is. """ accepted = set(client.backends.get(backend_id).supported_formats) if not accepted: return PREFERENCE[0] for candidate in PREFERENCE: if candidate in accepted: return candidate raise SystemExit( f"{backend_id} accepts {sorted(accepted)}, and this example writes only " f"{[f.value for f in PREFERENCE]}" )client.backends.get(backend_id).supported_formats is the backend’s own report of what
it takes, and pick_format walks the preference order against it. An empty report
places no constraint, so the newest format is worth trying.
Submit, wait, check
Section titled “Submit, wait, check” job = client.jobs.submit( backend_id=backend_id, circuit=BELL[circuit_format], circuit_format=circuit_format, shots=SHOTS, name="bell-pair example", ) # submit returns once the platform has accepted the job, not once it has run, so the # first status is PENDING and the id is what everything else is addressed by. print(f"submitted {job.id} to {backend_id} — {job.status}")
job = client.jobs.wait(job.id, timeout=120, poll_interval=2) print(f"finished {job.id} — {job.status}")
if job.status.value != "COMPLETED": # A failed job is a result too, and its error is the useful part. print(f"job did not complete: {job.error or '(no error recorded)'}", file=sys.stderr) return 1submit returns as soon as the platform has accepted the job; wait polls until it
reaches a terminal status. The status check matters because wait returning says the
job finished, and a failed job finishes too — its error field carries the reason.
Read the counts
Section titled “Read the counts” result = client.jobs.result(job.id) counts = result.counts or {} total = sum(counts.values()) or 1 print() for outcome, count in sorted(counts.items(), key=lambda kv: -kv[1]): share = 100 * count / total print(f" {outcome} {count:6} {share:5.1f}% {'█' * round(share / 2)}") return 0result.counts maps measured bitstrings to how often each appeared. A Bell pair’s
counts land almost entirely on 00 and 11, so the printed histogram is checkable at
a glance.
Running it
Section titled “Running it”QUBITRA_API_KEY=qpk_... python examples/submit_bell_pair.pyOutput from a live run at 1024 shots, trimmed to the counts:
11 521 50.9% █████████████████████████ 00 503 49.1% █████████████████████████The counts concentrate on the two correlated outcomes in a near-even split — the Bell
pair’s signature. A backend id as the first argument selects a different backend, and a
CircuitFormat value as the second forces a format past the negotiation.
Where to go next
Section titled “Where to go next”- Primitives and PUBs — what a job carries and what comes back.
- Circuit formats — every format the platform accepts.