Rigetti Cepheus-1-108Q
Submitting gate-model and timing-aware programs to Rigetti's Cepheus-1-108Q through the QbraidProvider, from any qBraid-supported framework.
Device ID: rigetti:rigetti:qpu:cepheus-1-108q · 108 qubits ·
superconducting · accessed through
QbraidProvider
Cepheus-1-108Q is Rigetti’s 108-qubit superconducting processor, available on qBraid with no Rigetti account of your own. You write a circuit in whichever framework you already use, hand it to device.run(), and qBraid handles conversion, compilation, submission and results.
This page covers the one thing about Cepheus that surprises people: it has two different compilation paths, and which one your program takes depends on whether it contains timing instructions. Everything else follows from that.
This is the direct Rigetti device, billed per minute of execution. Cepheus-1-108Q is also offered through Amazon Braket (aws:rigetti:qpu:cepheus-1-108q), which is billed per task + per shot and has different compilation behavior. If you are working with delays or other timing operations, you want the direct device documented here, since Braket has no gate-level delay.
For direct QCS access using your own Rigetti credentials, see RigettiProvider instead.
Quick start
A plain gate-model circuit needs nothing special. Write it however you like and submit:
from qbraid.runtime import QbraidProvider
from qiskit import QuantumCircuit
provider = QbraidProvider()
device = provider.get_device("rigetti:rigetti:qpu:cepheus-1-108q")
circuit = QuantumCircuit(2, 2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure([0, 1], [0, 1])
job = device.run(circuit, shots=10)
job.wait_for_final_state()
print(job.result().data.get_counts())
# {'00': 2, '01': 1, '11': 7}H and CX are not gates Cepheus can execute. You did not have to care, because this program took the compiled path.
The two compilation paths
Whether your program is compiled for you, or has to arrive ready to run, depends entirely on whether it contains Quil-T instructions: Rigetti’s timing and pulse-level operations, of which DELAY is the one most people meet first.
| Your program | Path | Compiler | You must write |
|---|---|---|---|
| Gates only | compiled | quilc nativizes it | any gates you like |
| Contains a delay or other Quil-T instruction | direct | quilc is skipped | native gates only |
The reason is not a qBraid limitation. quilc is a gate-model compiler, and as Rigetti’s docs put it plainly:
Quil-T instructions are not supported by quilc or the QVM.
So a program containing DELAY cannot go through quilc at all. qBraid detects Quil-T automatically and routes such programs straight to the QCS translation service, which does accept timing instructions. Nothing is dropped and you do not set a flag. But because quilc is what would normally have nativized your gates, that job is now yours.
If a program contains a delay and uses a non-native gate, it fails at translation. Rigetti names the instruction it could not execute:
Translation failed for quantum processor 'Cepheus-1-108Q': ... message:
"input program error: Failed to schedule Quil program: at instruction 0 ("X 0"):
this instruction must be replaced or decomposed prior to compilation"This is the single most common error on this device: your timing program reached the QPU with a gate it cannot execute, here X 0. See Native gates below.
Native gates
When you take the direct path, these are the only instructions Cepheus-1-108Q accepts. The set comes from the device’s own ISA:
| Instruction | Accepts | Notes |
|---|---|---|
RX(θ) | only ±π/2 and ±π | any other angle has no calibration |
RZ(θ) | any angle | parametric, so no angle restriction |
CZ | — | the only two-qubit gate |
I | — | identity |
MEASURE | — | readout |
The RX restriction is the one that catches people, and it follows from the calibrations: Rigetti publishes DEFCALs for RX at exactly four angles (±π/2 and ±π), while RZ is defined parametrically (DEFCAL RZ(%theta)) and so takes any angle. A pulse only exists for those RX angles, which is why RX(0), RX(2π) and RX(π/4) are rejected.
RX(π) is an X; RX(π/2) is a half rotation. Arbitrary single-qubit rotations are built from these plus RZ, which is exactly the decomposition quilc performs for you on the compiled path.
Reaching for X? Use RX(π): same operation, and it is native:
circuit.rx(np.pi, 0) # native
circuit.x(0) # NOT native: fails at translation if the program has a delayNativizing a Qiskit circuit automatically
For anything beyond a few gates, let Qiskit do the decomposition. The basis choice is the whole trick: use ["rz", "sx", "x", "cz"], because sx and x map exactly onto the calibrated RX(π/2) and RX(π) pulses. The seemingly natural ["rx", "rz", "cz"] basis does not work, since Qiskit then emits RX at arbitrary angles, which the QPU rejects. Delays pass through transpilation untouched:
from qiskit import QuantumCircuit, transpile
qc = QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
qc.delay(50, 0, unit="us")
qc.ry(0.7, 1)
qc.measure([0, 1], [0, 1])
native = transpile(qc, basis_gates=["rz", "sx", "x", "cz"], optimization_level=3)
job = device.run(native, shots=100) # conversion to Quil happens automaticallyEvery gate in the transpiled circuit now lands on a calibrated pulse, so the program passes translation on the direct path with the delay intact. To inspect the exact Quil that will run, convert it yourself first: qbraid.transpile(native, "pyquil").
Choosing qubits with calibration data
Cepheus is not uniform. On a recent calibration snapshot, two-qubit CZ error ranged from 0.4% on the best edge to 50% on the worst, and readout error varied by an order of magnitude across the lattice. Which physical qubits your program lands on is worth controlling, and the calibrations API provides the data to control it with.
What happens to the qubit indices you wrote depends, once again, on the compilation path:
- Compiled path: quilc is free to move your circuit. If the indices you wrote already satisfy the device’s connectivity, quilc’s default is
NAIVErewiring, which keeps them where they are. If they do not, it falls back toPARTIALrewiring, which chooses a placement using the gate fidelities Rigetti publishes in the device ISA. An unpinned circuit therefore already gets fidelity-aware placement; manual selection is for when you want it deterministic, or want to fold in metrics quilc does not weigh the way you do. - Direct path: no rewiring of any kind. A Quil-T program executes on exactly the physical qubits it names, which makes it the strongest placement guarantee available.
Scoring edges
device.get_calibrations() reports readout error and single-qubit randomized-benchmarking error per qubit, plus two-qubit gate error per edge. Fold all three into the score, because the edge with the lowest CZ error is often not the best place to run:
calibration = device.get_calibrations()
qubits = calibration.qubits
def score(edge):
"""Estimated success probability of a two-qubit circuit on this edge."""
a, b = qubits[str(edge.source)], qubits[str(edge.target)]
return (
(1 - edge.value)
* (1 - a.readout_error) * (1 - b.readout_error)
* (1 - a.gate_error["rb"]) ** 4 * (1 - b.gate_error["rb"]) ** 4
)
best = max(calibration.edges["gateError"]["cz"], key=score)
q0, q1 = best.source, best.targetOn the snapshot used for the run below, the readout-aware score mattered: the edge with the lowest raw CZ error (0.40%) sat on a qubit with weak readout, and the score selected a different edge, (92, 101), with a slightly higher CZ error (0.89%) but readout errors of 1.4% and 1.0%. For chains longer than a pair, and for plotting the graph you are choosing from, see Choosing the best qubits.
Pinning the circuit
Three ways to make the placement stick, in decreasing order of guarantee:
# A clock-aligned DELAY makes this Quil-T, so quilc is bypassed and the
# program runs on exactly the qubits it names. Native gates required.
from pyquil import Program
program = Program(f"""DECLARE ro BIT[2]
RZ(pi/2) {q0}
RX(pi/2) {q0}
RZ(pi/2) {q0}
RZ(pi/2) {q1}
RX(pi/2) {q1}
RZ(pi/2) {q1}
CZ {q0} {q1}
RZ(pi/2) {q1}
RX(pi/2) {q1}
RZ(pi/2) {q1}
DELAY {q1} 3.2e-8
MEASURE {q0} ro[0]
MEASURE {q1} ro[1]
""")
job = device.run(program, shots=1000)# Physical indices survive conversion when the circuit has a single
# register: qubit i maps to physical qubit i. The 32 ns delay makes the
# converted program Quil-T, same guarantee as the pyQuil version.
from qiskit import QuantumCircuit, transpile
qc = QuantumCircuit(max(q0, q1) + 1, 2)
qc.h(q0)
qc.cx(q0, q1)
qc.delay(32, q1, unit="ns")
qc.measure(q0, 0)
qc.measure(q1, 1)
native = transpile(qc, basis_gates=["rz", "sx", "x", "cz"], optimization_level=3)
job = device.run(native, shots=1000)# PRAGMA INITIAL_REWIRING pins placement without leaving the compiled
# path: quilc still nativizes your gates but keeps the identity mapping,
# inserting SWAPs only if you request a pair that is not coupled. Must be
# the first instruction. For a hard no-touch guarantee on a region, wrap
# it in PRAGMA PRESERVE_BLOCK ... PRAGMA END_PRESERVE_BLOCK instead.
from pyquil import Program
program = Program(f"""PRAGMA INITIAL_REWIRING "NAIVE"
DECLARE ro BIT[2]
H {q0}
CNOT {q0} {q1}
MEASURE {q0} ro[0]
MEASURE {q1} ro[1]
""")
job = device.run(program, shots=1000)DELAY durations must line up with the QPU’s sequencer clock, or translation
rejects the program with duration not aligned to sequencer clock: 1e-8. A 10
ns delay is rejected; 32 ns is accepted. When using a delay purely as a Quil-T
marker, 3.2e-8 seconds is a safe choice.
Run on hardware, the Bell pair on the score-selected edge (92, 101) returned
job.result().data.get_counts()
# {'00': 480, '11': 423, '01': 58, '10': 39}a correlated fraction of 90.3% at 1000 shots, against roughly 95% predicted from the calibration data alone. The gap is decoherence and crosstalk the snapshot does not capture, which is the right way to read these scores: a ranking, not a forecast.
Calibration data is refreshed roughly hourly and the ranking moves with it, so select your qubits shortly before you submit, not once per project.
Reading the counts
Bitstrings from this device follow declared-register order, left to right: the first character of each counts key is ro[0], the second is ro[1], and so on. A program that excites only the qubit measured into ro[0] returns {'10': N}.
This is the opposite convention from Braket-backed devices (aws:*), where
qBraid counts are little-endian with c[0] as the rightmost bit. If you
run the same circuit on rigetti:rigetti:qpu:cepheus-1-108q and
aws:rigetti:qpu:cepheus-1-108q, asymmetric outcomes appear bit-reversed
relative to each other. Account for this before comparing distributions across
the two routes.
If your program declares multiple readout registers, they are concatenated in alphabetical register-name order.
The compiled program
For completed jobs, the native Quil that quilc actually produced — after nativization and any rewiring — is served by the GET /jobs/{job_qrn}/compiled-program endpoint, in the same {format, data} shape as the submitted program (here always quil):
job.wait_for_final_state()
compiled = provider.client.get_job_compiled_program(job.id)
print(compiled.data)The endpoint returns 404 for jobs with no stored compiled program (not yet complete, or submitted before this feature).
On the compiled path this is the authoritative record of which physical qubits your circuit ran on and what quilc did to your gates. On the direct (Quil-T) path it is your program as submitted, since quilc is bypassed.
Timing operations
A delay is what makes a program Quil-T. This is a T1 (energy relaxation) measurement: excite the qubit, wait, and see whether it is still excited:
import numpy as np
from qbraid.runtime import QbraidProvider
from qiskit import QuantumCircuit
provider = QbraidProvider()
device = provider.get_device("rigetti:rigetti:qpu:cepheus-1-108q")
circuit = QuantumCircuit(1, 1)
circuit.rx(np.pi, 0) # native, NOT circuit.x(0)
circuit.delay(500, 0, unit="us") # makes this a Quil-T program
circuit.measure(0, 0)
job = device.run(circuit, shots=10)
job.wait_for_final_state()
print(job.result().data.get_counts())
# {'0': 9, '1': 1} -- mostly relaxed to |0> during the 500 us delayfrom pyquil import Program
from qbraid.runtime import QbraidProvider
provider = QbraidProvider()
device = provider.get_device("rigetti:rigetti:qpu:cepheus-1-108q")
program = Program(
"DECLARE ro BIT[1]",
"RX(pi) 0", # native, NOT "X 0"
"DELAY 0 0.0005", # 0.0005 seconds = 500 us
"MEASURE 0 ro[0]",
)
job = device.run(program, shots=10)
job.wait_for_final_state()
print(job.result().data.get_counts())
# {'0': 9, '1': 1}Both submit the same thing. In Quil, DELAY 0 0.0005 delays qubit 0 by 0.0005 seconds; Qiskit’s delay(500, 0, unit="us") converts to exactly that.
Relaxation is probabilistic, so your counts will differ run to run. A stray '1' at 500 us is the physics, not a bug. That variation is exactly what a T1 sweep measures.
Passing each instruction as its own string, as above, sidesteps a common trap: pyQuil rejects a flat instruction that starts with whitespace (expected a command or a gate), so an indented triple-quoted block of plain instructions fails even though it looks fine.
The exception is a block that is meant to be indented, such as a DEFCAL or DEFFRAME body. Those must arrive as one string, indentation included; passed as separate arguments they fail with failed to parse arguments for DEFCAL. See prepend_default_calibrations for that shape.
To sweep T1, vary the delay and submit one job per point:
delays_us = [0, 50, 100, 200, 400, 800]
jobs = []
for t in delays_us:
circuit = QuantumCircuit(1, 1)
circuit.rx(np.pi, 0)
circuit.delay(t, 0, unit="us") # keep this even at t=0 -- see below
circuit.measure(0, 0)
jobs.append((t, device.run(circuit, shots=1000)))
for t, job in jobs:
job.wait_for_final_state()
counts = job.result().data.get_counts()
p_excited = counts.get("1", 0) / sum(counts.values())
print(f"{t:>4} us -> P(1) = {p_excited:.3f}")Keep the delay call at t = 0 rather than skipping it. A circuit with no
delay is not a Quil-T program, so your zero point would be nativized by quilc
while every other point bypasses it, leaving a baseline compiled differently
from the data it anchors. delay(0) emits DELAY 0 0 and keeps every point
on the same path.
Cepheus-1-108Q is billed per minute of execution, prorated with no minimum. A delay is execution time, so a long delay costs more than a short one. Individual jobs on this device typically run for tens to hundreds of milliseconds. See Pricing.
Which framework?
The device itself accepts three run input types: pyQuil, OpenQASM 2 and OpenQASM 3. Qiskit appears throughout this page only because it is the most widely used, not because it is special. It is converted to QASM like anything else.
That means any framework qBraid can convert to QASM works here, not just the two shown. Cirq, Amazon Braket, PennyLane, pytket and the rest all reach qasm2/qasm3 on the ConversionGraph, and device.run() handles the conversion:
import cirq
q = cirq.LineQubit.range(2)
circuit = cirq.Circuit([cirq.H(q[0]), cirq.CNOT(q[0], q[1]), cirq.measure(*q, key="m")])
job = device.run(circuit, shots=10)from braket.circuits import Circuit
# .measure() is required here -- see the note below
circuit = Circuit().h(0).cnot(0, 1).measure(0).measure(1)
job = device.run(circuit, shots=10)import pennylane as qml
with qml.tape.QuantumTape() as circuit:
qml.Hadamard(wires=0)
qml.CNOT(wires=[0, 1])
qml.sample(wires=[0, 1])
job = device.run(circuit, shots=10)If your framework has a path to qasm2 or qasm3 on the ConversionGraph, it will run on Cepheus. Write in whichever one you already use.
Measure explicitly. Some frameworks measure implicitly when run on their own simulators, and that implicitness does not survive conversion. An Amazon Braket circuit written as Circuit().h(0).cnot(0, 1) converts to QASM with no measure statement, so the program reaches the QPU with nothing to read out. It runs, then fails when the results are parsed:
No declared registers found in ro_sources. ro_sources keys: []Add the measurements yourself and the job returns counts as expected.
QPROGRAM_REGISTRY is the authoritative answer to “what type do I pass?”. It maps each framework to the exact program type device.run() accepts, which is not always the object you would guess (PennyLane, for instance, takes a QuantumTape rather than a QNode):
from qbraid.programs import QPROGRAM_REGISTRY
print(QPROGRAM_REGISTRY["pennylane"]) # <class 'pennylane.tape.tape.QuantumTape'>
print(QPROGRAM_REGISTRY["cirq"]) # <class 'cirq.circuits.circuit.Circuit'>
print(sorted(QPROGRAM_REGISTRY)) # every framework qBraid knowsWhen to reach for pyQuil
The one thing QASM cannot express is Quil-T beyond a simple delay: FENCE, PULSE, frame-level DELAY 0 "rf" 1e-6, or your own DEFCAL calibrations. Those have no vocabulary in Qiskit or QASM, so there is nothing for the transpiler to convert. pyQuil is Rigetti’s native language, so nothing is translated and those features have first-class syntax.
An ordinary delay is the exception: it converts cleanly from Qiskit, so a T1 or T2 experiment does not require pyQuil.
Not sure which path your program will take? Ask before you spend a job:
from qbraid.runtime.rigetti.device import contains_quil_t
from pyquil import Program
contains_quil_t(Program("RX(pi) 0\nDELAY 0 0.0005")) # True -> direct path, native gates required
contains_quil_t(Program("H 0\nCNOT 0 1")) # False -> compiled path, quilc handles itRuntime options
Rigetti’s translation service, the stage that turns your native-gate program into pulses, takes two options, passed as a dict through runtime_options. Unrecognized keys are silently ignored.
| Option | Type | Default | Effect |
|---|---|---|---|
passive_reset_delay_seconds | float | Rigetti | How long to wait for qubits to relax to |0⟩ before each shot. |
prepend_default_calibrations | bool | True | If False, Rigetti’s default calibrations are not prepended. |
passive_reset_delay_seconds
Between shots, qubits are left to relax back to |0⟩ on their own, a passive reset. This option sets how long the QPU waits for that.
Rigetti documents it as “the delay between passive resets, in seconds” and does not publish a default or a recommended range, so the figures below are measurements from a single run on one qubit, included to show the shape of the trade-off. Treat them as illustrative; your own numbers will differ with the qubit, the calibration and the day.
The delay is paid once per shot, and you are billed for it. The same 20-shot program, changing nothing else:
passive_reset_delay_seconds | Execution observed | Cost observed |
|---|---|---|
0.000001 (1 µs) | 15 ms | ~3 credits |
0.01 (10 ms) | 215 ms | ~43 credits |
The 200 ms difference is exactly 20 shots × 10 ms, which is the part that generalizes: the delay is per shot, and this device is billed per minute of execution. Multiply by your shot count before raising it.
What the wait buys is a cleaner starting state. Relaxation is what clears the previous shot. Measuring a qubit at shot start, after a program that deliberately leaves it excited:
| Reset before each shot | Still excited at shot start (observed) |
|---|---|
| 1 µs | ~5% (the previous shot bleeding through) |
| 1 ms | ~1.5% (indistinguishable from baseline) |
A control that never excited the qubit read 0%, so the 5% was leftover state rather than readout error. Again: one qubit, one run. The effect is the point, not the percentages.
So the trade runs both ways:
- Raise it for circuits sensitive to initialization, where a few percent of shots starting in the wrong state would matter.
- Lower it to finish sooner and pay less. The floor is what your circuit can tolerate.
job = device.run(
circuit,
shots=1000,
runtime_options={"passive_reset_delay_seconds": 0.0001}, # 100 us
)The translation service rejects delays above roughly 67 ms, reporting
waveform duration of 1e-1s exceeds maximum allowed duration of 6.7108864e-2s. That ceiling comes from the service’s own error rather than
published documentation, so do not rely on the exact figure.
prepend_default_calibrations
By default Rigetti prepends its calibration set to every program: the DEFFRAME and DEFCAL definitions that give RX, RZ, CZ and MEASURE their actual pulse shapes. Those definitions are the only reason a gate means anything on hardware.
Leave this one alone. It enables nothing, and setting it to False only breaks things.
To override a calibration, include your own DEFCAL and keep this option at its default. Your definition is used in place of Rigetti’s. A DEFCAL that turns RX(pi) into a no-op shows this plainly: the qubit is left in |0⟩ rather than excited.
from pyquil import Program
# A DEFCAL body must be indented, so pass the block as ONE string --
# not as separate arguments (see the note under Timing operations).
program = Program(
"""DEFCAL RX(pi) 0:
NOP
DECLARE ro BIT[1]
RX(pi) 0
DELAY 0 0
MEASURE 0 ro[0]
"""
)
job = device.run(program, shots=20) # no runtime_options needed
job.wait_for_final_state()
print(job.result().data.get_counts())
# {'0': 19, '1': 1} -- the RX did nothing.
# Without the custom DEFCAL the same program reads {'0': 1, '1': 19}.Setting prepend_default_calibrations=False only removes the defaults. Your program must then carry a complete calibration set of its own, and there is no practical way to do that here: Rigetti’s set defines frames across far more qubits than Cepheus exposes, so submitting it is rejected before it reaches the QPU.
ProgramValidationError: Number of qubits in the circuit (300) exceeds the device's capacity (108)Without a calibration set, translation fails instead, with Rigetti’s reason:
input program error: program has no defined framesCustom DEFFRAMEs are constrained regardless: only INITIAL-FREQUENCY and
CHANNEL-DELAY may differ from Rigetti’s defaults. Changing anything else,
such as SAMPLE-RATE or HARDWARE-OBJECT, is rejected (frame 0 "Transmon-0_charge_tx" differs from Rigetti-provided definition). Rigetti
restricts the flag that lifts this to certain
users,
and it is not enabled for qBraid. Treat the default frames as fixed and
confine your customization to DEFCALs.
Troubleshooting
Every failure at the translation stage arrives as a RigettiJobError naming the processor, followed by Rigetti’s own reason, which is the part that tells you what to fix:
Translation failed for quantum processor 'Cepheus-1-108Q': ... message:
"input program error: Failed to schedule Quil program: at instruction 0 ("X 0"):
this instruction must be replaced or decomposed prior to compilation"Read to the end of the line. Rigetti names the offending instruction, here X 0, which is not native.
| Rigetti’s reason | What happened |
|---|---|
at instruction N (...): must be replaced or decomposed | A non-native gate reached the QPU, because your program contains a delay and so skipped quilc. Replace X with RX(π), H/CX with RX/RZ/CZ decompositions, or drop the delay to get the compiled path back. Also covers RX at a non-native angle. |
program has no defined frames | You set prepend_default_calibrations=False without supplying a calibration set. Drop the option; see above. |
duration not aligned to sequencer clock | A DELAY duration the hardware clock cannot represent, such as 10 ns. Use a coarser value; 32 ns (3.2e-8) is a safe marker delay. See Pinning the circuit. |
waveform duration ... exceeds maximum allowed duration | passive_reset_delay_seconds is above the 67.1 ms cap. |
frame ... differs from Rigetti-provided definition | A DEFFRAME of yours changes something other than INITIAL-FREQUENCY or CHANNEL-DELAY. |
Failures elsewhere in the pipeline:
| Message | Cause |
|---|---|
quilc failed to compile the program ... | The gate-model path could not nativize your circuit. The message carries quilc’s own reason. |
Number of qubits in the circuit (300) exceeds the device's capacity | You are submitting Rigetti’s full calibration set. See prepend_default_calibrations. |
| Delay silently missing from results | You are on aws:rigetti:qpu:cepheus-1-108q, not the direct device. Braket has no gate-level delay. Use rigetti:rigetti:qpu:cepheus-1-108q. |
Related links
- Pricing: Cepheus-1-108Q is billed per minute of execution
- Device calibrations: coupling maps, calibration data, and best-chain search
- QbraidProvider: installation, authentication, runtime options
- RigettiProvider: direct QCS access with your own credentials
- Rigetti: Quil-T: the timing/pulse extension to Quil
- Rigetti: Getting started with Quil-T: why Quil-T programs must be nativized by hand
- Rigetti: The Quil compiler: what quilc does, and qubit rewiring
- Rigetti: The lifecycle of a program: compilation vs translation on QCS
- Rigetti: TranslationOptions: the full set of translation options
- quilc: the open-source Quil compiler
Thanks for your feedback.

