Get started with Orbit
See the API reference
Qiskit Functions are an experimental feature available only to IBM Quantum® Premium Plan, Flex Plan, and On-Prem (via IBM Quantum Platform API) Plan users. They are in preview release status and subject to change.
For an overview of this function, see the Orbit documentation home.
Set up
Authenticate using your IBM Quantum Platform API key, and select the Orbit function as follows. This snippet assumes you've already saved your account to your local environment.
from qiskit_ibm_catalog import QiskitFunctionsCatalog
from qiskit import QuantumCircuit
catalog = QiskitFunctionsCatalog(channel="ibm_quantum_platform")
# Verify that you have access to the function.
catalog.list()Output:
[QiskitFunction(quantum-elements/orbit-dev),
QiskitFunction(quantum-elements/orbit)]
# Load the function.
orbit = catalog.load("quantum-elements/orbit")
print("Orbit version:", orbit.version)Output:
Orbit version: 0.3.23
Example
The following example builds a Bernstein-Vazirani circuit and submits it to Orbit two ways in one job: raw execution and Orbit defaults.
Use this pattern when you want a direct raw-versus-Orbit comparison.
Scaling Bernstein-Vazirani to larger sizes
Orbit can help preserve Bernstein-Vazirani success probability as the hidden-bitstring width grows. The representative scaling runs below compare raw execution with Orbit configurations on IBM Quantum hardware. On ibm_miami, raw success probability falls below 10% by a hidden-bitstring width of 9 (10 total qubits), while Orbit remains near 80–90%. On ibm_kingston, Orbit maintains non-trivial success probability at widths approaching 60, including approximately 15% at the largest size shown. Exact results depend on the backend calibration, circuit, and execution settings.
To preserve QPU time, this tutorial does not rerun a size sweep. Instead, the executable example below reports shot counts for one fixed hidden-bitstring width, N = 50 (51 total qubits in this circuit), comparing raw execution with Orbit defaults in a single job.
Build a Bernstein-Vazirani circuit
Choose the hidden bitstring and construct the logical circuit. The extra qubit is the oracle target, while the input qubits are measured.
def create_bv_circuit(hidden_bitstring: str) -> QuantumCircuit:
width = len(hidden_bitstring)
circuit = QuantumCircuit(width + 1, width)
circuit.x(width)
circuit.h(range(width + 1))
for input_qubit, bit in enumerate(reversed(hidden_bitstring)):
if bit == "1":
circuit.cx(input_qubit, width)
circuit.barrier()
circuit.h(range(width + 1))
circuit.measure(range(width), range(width))
return circuit
circuit_width = 50
hidden_bitstring = "1" * circuit_width
shots = 4096
bv_circuit = create_bv_circuit(hidden_bitstring)
bv_circuit.count_ops()Output:
OrderedDict([('h', 102),
('cx', 50),
('measure', 50),
('x', 1),
('barrier', 1)])
Prepare the Orbit workload
Create one Sampler PUB, then duplicate it for the two comparison modes. Orbit receives the logical circuit and maps it to the selected backend during execution.
base_pub = (bv_circuit, None, shots)
comparison_labels = ["raw", "orbit"]
pubs = [base_pub] * len(comparison_labels)Run raw and Orbit defaults
pub_options assigns the first PUB to raw mode, which skips Orbit optimization, DD, and MEM, and assigns the second PUB to Orbit defaults, which include optimized transpilation, automated dynamical decoupling (DD) insertion, and measurement error mitigation (MEM).
backend_name = "ibm_kingston"
job = orbit.run(
primitive="sampler",
pubs=pubs,
backend_name=backend_name,
options={
"pub_options": [
{"mode": "raw"},
{"mode": "orbit"},
],
"save_backend_info": True,
},
)
# Check the job ID and status
print(job.job_id)
print(job.status())Output:
2c427a80-4190-404c-b98a-6bbeb53fafcd
QUEUED
Retrieve the result and compare the raw and Orbit-enhanced circuits using counts. For Sampler results, get_counts() on the returned classical register provides the output histogram. When MEM succeeds for the Orbit PUB, this histogram is already MEM-corrected; the unmitigated counts remain available in the Orbit metadata under measurementErrorMitigation["rawCounts"].
def extract_counts(pub_result) -> dict[str, int]:
data = getattr(pub_result, "data", None)
if data is None:
raise TypeError("pub_result.data is missing")
for name in dir(data):
if name.startswith("_"):
continue
register = getattr(data, name)
get_counts = getattr(register, "get_counts", None)
if callable(get_counts):
counts = get_counts()
if counts:
return counts
raise TypeError(
"No classical register with get_counts() found in pub_result.data"
)
result = job.result()
if len(result) != len(comparison_labels):
raise RuntimeError(
f"Expected {len(comparison_labels)} PUB results, received {len(result)}."
)
comparison_counts = {
label: extract_counts(pub_result)
for label, pub_result in zip(comparison_labels, result, strict=True)
}
correct_counts = {
label: counts.get(hidden_bitstring, 0)
for label, counts in comparison_counts.items()
}
print(
{
"hidden_bitstring": hidden_bitstring,
"correct_counts": correct_counts,
"shots": shots,
}
)Output:
{'hidden_bitstring': '11111111111111111111111111111111111111111111111111', 'correct_counts': {'raw': 0, 'orbit': 370}, 'shots': 4096}
Make a visual comparison of the raw and Orbit-enhanced circuits.
import matplotlib.pyplot as plt
labels = list(correct_counts)
values = list(correct_counts.values())
fig, ax = plt.subplots(figsize=(7, 4))
bars = ax.bar(labels, values, width=0.55)
ax.bar_label(bars, labels=[f"{value:g}" for value in values], padding=3)
ax.set_xlabel("Circuit execution mode")
ax.set_ylabel("Correct hidden-bitstring count")
ax.set_title(f"{circuit_width}-qubit Bernstein-Vazirani on {backend_name}")
ax.set_ylim(0, max(1, 1.15 * max(values)))
ax.grid(axis="y", alpha=0.25)
fig.tight_layout()
plt.show()Output: