Quantum circuit simulation (statevector + stabilizer) with IQM cloud submission, quantum random numbers from IQM bit pools, and QRNG synthetic data with provable provenance.
Project description
lightrider
Quantum circuit simulation, quantum random numbers, and QRNG-driven synthetic data — in one lightweight, NumPy-only package.
lightrider provides three capabilities:
| Capability | Entry point | What it does |
|---|---|---|
| Circuit simulation & cloud jobs | Circuit, get_backend |
Build arbitrary circuits with a Qiskit-style API and run them on a fast local statevector simulator, a Stim-style stabilizer simulator, or IQM hardware in the cloud |
| Quantum random numbers | IQM_sirius |
Draw random numbers from a bundled pool of real IQM hardware bits — fully offline |
| Synthetic data with provenance | Synthesizer |
Generate tabular synthetic data where every random draw is quantum, certified by a signed manifest |
The only required dependency is NumPy. Simulators and the QRNG pool run locally with no network access; cloud execution and live attested entropy are opt-in upgrades.
Installation
pip install lightrider # core (NumPy only)
pip install "lightrider[pandas]" # + pandas DataFrame support
Requires Python ≥ 3.9.
Live EMS entropy additionally needs the
lr-entropySDK, which ships with the Light Rider platform rather than on PyPI. Install it from the platform repository (pip install ./sdk-python) andEntropySourcebecomes available automatically.
Quickstart
from lightrider import Circuit, get_backend
# 1. Build a Bell-pair circuit
circ = Circuit(2)
circ.h(0)
circ.cx(0, 1)
circ.measure_all()
# 2. Run it on the local statevector simulator
job = get_backend("statevector").run(circ, shots=1000, seed=42)
# 3. Read the counts (Qiskit convention: clbit 0 is the rightmost character)
print(job.result().counts) # {'00': 507, '11': 493}
Quantum circuits
Building circuits
Circuit follows Qiskit's builder conventions — gate methods take parameters
first, then qubits, and calls chain:
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 |
Composite gates are defined as macros that expand to primitives at append time:
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, so large shot counts are effectively free (1M shots of a 20-qubit circuit in ~1.4 s) |
lightrider_stabilizer |
stabilizer, stim |
local | Clifford subset (x y z h s sdg sx cx cy cz swap) |
Clifford circuits at hundreds of qubits; supports mid-circuit measurement |
iqm |
cloud |
cloud | full, transpiled server-side to IQM-native r (prx) + cz |
Real-hardware runs via the Light Rider IQM proxy |
Running locally
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:
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().counts
Submitting a non-Clifford gate to the stabilizer backend (or an unsupported
gate to any backend) raises UnsupportedGateError before anything runs.
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.
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 counts
Mock deployments. If the proxy is backed by one of IQM's
:mockQPU endpoints,run()emits aMockBackendWarning: mock QPUs execute the full job lifecycle but return canned mock entropy (all measured bits set to one coin flip) instead of running your circuit. Use the local simulators when the counts need to be physically meaningful.
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:
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())
Quantum random numbers
IQM_sirius draws from a bundled pool of ~2 million bits captured from IQM
hardware (Hadamard coin-flip circuits across 10 qubits, SHA-256 debiased) —
no network required. Output is unbiased on any range via rejection sampling.
from lightrider import IQM_sirius
IQM_sirius(5, 1, 100) # 5 quantum random ints in [1, 100]
IQM_sirius(3, 0.0, 1.0, step=0.1) # 3 quantum random floats on a 0.1 grid
Capture metadata for the bundled pool lives in the repository under
iqm_capture_20260507_181448/metadata.json.
Synthetic data with provenance
Synthesizer fits a Gaussian copula to tabular data and generates new rows
whose every random draw comes from a quantum source. Each dataset ships with
a provenance manifest binding it to the entropy that produced it.
from lightrider import Synthesizer
synth = Synthesizer(dataset_id="customers_v3").fit(df) # DataFrame / dict / records
rows = synth.generate(10_000)
synth.manifest.write("customers_v3.provenance.json")
print(synth.certificate())
How it works
fit: data ─▶ marginals (empirical CDF / category freqs)
─▶ normal scores z = Φ⁻¹(rank)
─▶ correlation Σ = corr(z), Cholesky Σ = L Lᵀ
gen: QRNG ─▶ U(0,1) (quantum draws, recorded on the manifest)
─▶ Z₀ = Φ⁻¹(U) (iid standard normals)
─▶ Z = Z₀ Lᵀ (impose learned correlation)
─▶ U' = Φ(Z) (back to uniform, per column)
─▶ x = F⁻¹(U') (inverse marginal → synthetic value)
The copula reproduces each column's marginal distribution and inter-column
correlations; the randomness selecting each synthetic row is quantum, not a
PRNG. The full mathematical treatment is in the repository under
docs/qrng-synthetic-data.pdf.
Entropy modes
| Mode | Provider | Provenance |
|---|---|---|
| bundled-qrng (default) | BundledQrng over the packaged IQM pool |
Real quantum bits, SHA-256 debiased, offline, unsigned |
| live-attested | EntropySource against a Light Rider EMS |
Multi-source extraction over GF(2¹²⁸), SP 800-90B health-tested, post-quantum-signed receipts |
from lightrider import EntropySource, Synthesizer # requires the lr-entropy SDK
src = EntropySource("http://localhost:7081", dataset_id="customers_v3")
synth = Synthesizer(entropy=src).fit(df)
rows = synth.generate(10_000) # every draw carries a signed receipt
EntropySource(allow_failover=True) (the default) falls back to the OS
CSPRNG on any EMS error so a long job never blocks. Failover draws are
flagged in the manifest and excluded from the certificate's source list — the
certificate never overstates its provenance.
The manifest
{
"dataset_id": "customers_v3", "model": "qrng-copula",
"rows": 10000, "columns": ["age", "income", "tier", "region"],
"entropy_mode": "live-attested", "fully_attested": true,
"signature_alg": "ML-DSA-65", "post_quantum_signed": true,
"sources_used": ["curby_q_jila_001", "qispace_kds_001"],
"min_quality_score": 90, "health_all_pass": true,
"extractors": ["SHAKE256"], "failover_used": false
}
In the offline default the same manifest reports
entropy_mode: "bundled-qrng" and post_quantum_signed: false — honest by
construction.
Demo and development
# generate a synthetic dataset with its provenance certificate
python -m lightrider.demo --rows 2000 --out synthetic.csv --manifest cert.json
# against a live EMS
python -m lightrider.demo --endpoint http://localhost:7081 --rows 2000
# run the test suite
pytest tests -q
License
Apache-2.0. Built by Light Rider.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lightrider-1.0.1.tar.gz.
File metadata
- Download URL: lightrider-1.0.1.tar.gz
- Upload date:
- Size: 532.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0da8a88bab8a1cad92d20a33a51da390dca5d2ebeba6ab6c5b92e4a8ba25197d
|
|
| MD5 |
9c0a0f963d6691b2c154772fb8cee1a6
|
|
| BLAKE2b-256 |
21b6f37e78fcc091d39c6e01bd6afdc2526634bbd8eebf24dbf18cf0467ea506
|
File details
Details for the file lightrider-1.0.1-py3-none-any.whl.
File metadata
- Download URL: lightrider-1.0.1-py3-none-any.whl
- Upload date:
- Size: 657.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b31eeb54c30f5132202c277e5da13cc2895055dc90944f8580e17d5bfe5ffe3d
|
|
| MD5 |
2c4dfb0e75b9e8a222078c362bf3777a
|
|
| BLAKE2b-256 |
32f40e299850cf5bd5d47571a5a58ca0b37fb2fe6d175f8bcae4cefbb951b2f9
|