Appearance
Quantum Circuits
Building circuits
Circuit follows Qiskit's builder conventions — gate methods take parameters first, then qubits, and calls chain:
python
from lightrider import Circuit
circ = Circuit(3) # 3 qubits, 3 classical bits
circ.h(0)
circ.rx(0.5, 1) # params first, qubits last
circ.ccx(0, 1, 2)
circ.measure_all()The primitive gate set:
| Group | Gates |
|---|---|
| Single-qubit | id x y z h s sdg t tdg sx |
| Single-qubit, parameterized | rx ry rz p r u |
| Two-qubit | cx cy cz ch swap cp rxx ryy rzz |
| Three-qubit | ccx cswap |
| Pauli errors | x_error y_error z_error |
| Noise channels | depolarize1 depolarize2 pauli_channel_1 |
| Basis measurements | measure measure_x measure_y |
| Basis resets | reset reset_x reset_y |
Errors, noise channels, and basis measurements/resets in action:
python
from lightrider import Circuit, get_backend
circuit = Circuit(1)
circuit.h(0)
circuit.depolarize1(1e-4, 0)
circuit.measure_x(0)
result = get_backend("stabilizer").run(
circuit,
shots=100_000,
seed=7,
).result()
print(result.counts)Composite gates are defined as macros that expand to primitives at append time:
python
from lightrider import custom_gate
@custom_gate(num_qubits=2)
def bell_pair(c, qubits, params):
a, b = qubits
c.h(a)
c.cx(a, b)
circ = Circuit(3)
circ.append(bell_pair, [0, 1])Choosing a backend
Every backend declares the gate set it supports, and run() validates the circuit up front — a job that submits will also execute. Inspect all backends programmatically with list_backends().
| Backend name | Aliases | Where | Gate set | Best for |
|---|---|---|---|---|
lightrider_statevector | statevector, sv | local | full | Exact simulation up to 24 qubits. Shots are sampled in one vectorized pass |
lightrider_stabilizer | stabilizer, stim | local | Clifford gates plus Pauli noise, basis measurement, and reset | Large Clifford circuits and surface-code QEC |
iqm | cloud | cloud | full, transpiled server-side to IQM-native r (prx) + cz | Real-hardware runs via the Light Rider IQM proxy |
Running locally
python
from lightrider import get_backend
result = get_backend("statevector").run(circ, shots=10_000, seed=7).result()
result.counts # {'000': 4980, '111': 5020}
result.probabilities() # {'000': 0.498, '111': 0.502}The stabilizer backend trades gate-set generality for scale — a 100-qubit GHZ state samples at ~6 ms/shot:
python
n = 100
ghz = Circuit(n)
ghz.h(0)
for q in range(n - 1):
ghz.cx(q, q + 1)
ghz.measure_all()
counts = get_backend("stabilizer").run(ghz, shots=1000).result().countsSubmitting a non-Clifford gate to the stabilizer backend (or an unsupported gate to any backend) raises UnsupportedGateError before anything runs.
For circuit-level QEC experiments — encoded circuits, noise injection, and decoding — see Quantum Error Correction.
Running on IQM hardware
Cloud jobs go through the Light Rider IQM proxy and authenticate with a Light Rider lr_ API key — you never handle IQM credentials directly. The circuit is transpiled to the QPU's native gates server-side.
Getting a key: lr_ API keys are issued internally by Light Rider — request one from your administrator. There is intentionally no public self-registration; IQMBackend.register() exists for administrators only and requires the deployment's admin token.
python
iqm = get_backend("iqm",
endpoint="https://quantum.lightrider.example", # or LR_QUANTUM_ENDPOINT
api_key="lr_...") # or LR_QUANTUM_API_KEY
job = iqm.run(circ, shots=1000) # returns immediately
job.status() # WAITING | PROCESSING | COMPLETED | FAILED | ABORTED
job.result() # polls until the job is terminal, then returns countsMock deployments: if the proxy is backed by one of IQM's :mock QPU endpoints, run() emits a MockBackendWarning: mock QPUs execute the full job lifecycle but return canned mock entropy instead of running your circuit.
Serialization
Circuits serialize to the lr-circuit/v1 JSON payload shared with the Light Rider proxy and lr-entropy SDK, and to a Stim-flavored text format:
python
payload = circ.to_payload() # dict, JSON-safe
circ2 = Circuit.from_payload(payload)
print(circ.to_text()) # H 0 / CX 0 1 / M 0 -> 0 ...
circ3 = Circuit.from_text(circ.to_text())
