Skip to main content

lightrider

PyPI Python License

Quantum circuits, QPU cloud jobs, attested entropy, quantum random numbers, and QRNG-driven synthetic data in one Python SDK.

lightrider provides three capabilities:

Capability Entry point What it does
Circuit simulation & QPU cloud jobs Circuit, get_backend Build circuits with a Qiskit-style API and run them on local statevector, MPS tensor network, or stabilizer simulators, or submit QPU cloud jobs through a supported backend
Quantum random numbers quantum_rng, BundledQrng A numpy.random-style generator backed by quantum hardware bits, with an offline bundled pool and selectable entropy sources
Synthetic data with provenance Synthesizer Generate tabular synthetic data where every random draw is quantum, certified by a signed manifest

Local simulators and the bundled QRNG pool run without network access. Cloud execution and live attested entropy use the same installed SDK and activate only when their clients are called.

The canonical Python namespace is lightrider. Attested entropy is grouped under lightrider.entropy; quantum circuits and backends remain at the SDK root:

from lightrider import Circuit, get_backend
from lightrider.entropy import EntropyClient, Policy

Installation

Requires Python ≥ 3.9. The examples below use Python 3.11. Install the single lightrider package for circuits, QPU cloud jobs, local simulators, QRNG, synthetic data, and lightrider.entropy.

Set up with uv

Install uv if it is not already available. On macOS or Linux:

curl -LsSf https://astral.sh/uv/install.sh | sh

On Windows PowerShell:

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

Open a new terminal after installation, then create a project:

uv init --python 3.11 lightrider-demo
cd lightrider-demo
uv add "lightrider==1.5.2"
uv run python -c "import lightrider; print(lightrider.__version__)"
uv run python -m lightrider.tensor_network_demo

The version should be 1.5.2. These commands work in PowerShell, bash, and zsh. uv manages Python and the project's .venv; activation is unnecessary when using uv run. Keep the generated pyproject.toml, .python-version, and uv.lock with your application. On another machine, run uv sync --locked in that project to install the locked dependencies. See uv project dependencies.

For an existing uv project, start at uv add. For an existing virtual environment, activate it and use uv pip install "lightrider==1.5.2" instead; the pip interface does not update a project's dependency declarations or lockfile.

Optional packages

Install target Adds
lightrider All local simulators, QPU cloud client, entropy client, QRNG, and synthetic data
lightrider[pandas] pandas DataFrame support
lightrider[pqc] ML-DSA-65 receipt verification
lightrider[decoder] PyMatching decoding for surface-code experiments
lightrider[dev] pytest, pandas, and PyMatching for SDK development

Combine extras in one requirement, for example:

uv add "lightrider[pandas,pqc,decoder]==1.5.2"

MPS simulation needs no extra. Choose pqc when your EMS uses ML-DSA-65 signatures. Optional packages may have their own Python/platform constraints.

Set up with pip

From a directory for your work:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade "lightrider==1.5.2"
python -m lightrider.tensor_network_demo

On Windows, create the environment with py -3 -m venv .venv and activate it with .venv\Scripts\Activate.ps1. Use the same quoted extras as above with python -m pip install, such as "lightrider[pandas,pqc]==1.5.2".

New in SDK 1.5.0: local MPS tensor network simulation with configurable bond dimensions and compression diagnostics. Install with python -m pip install --upgrade "lightrider==1.5.2". See the step-by-step MPS guides and fundamentals.

Live EMS entropy is part of the same SDK under lightrider.entropy; no second Python package is required.

from lightrider.entropy import EntropyClient, Policy

Migrating from lr_entropy? The legacy lr_entropy package is retired and has been removed from the repository. lightrider.entropy is a drop-in replacement for its client and receipt APIs — change from lr_entropy import ... to from lightrider.entropy import .... The old QuantumClient is superseded by get_backend("iqm", ...) (see QPU cloud jobs).

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}

Measure before you run. Counts are samples of measured classical bits, so every circuit needs measure_all() (or explicit measure() calls) before run() — otherwise run() raises BackendError: circuit has no measurements. In notebooks, build and run the circuit in the same cell: Circuit methods mutate in place, so re-running only the run() cell reuses whatever state the circuit last had.

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
lightrider_tensor_network tensor_network, mps, tn local CPU full Larger circuits with manageable entanglement; configurable MPS truncation, terminal measurements
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.

Tensor network simulation

Step 1 — Install the release in a fresh environment (Python 3.9+):

python3 -m venv .venv-mps
source .venv-mps/bin/activate
python -m pip install --upgrade "lightrider==1.5.2"

Step 2 — Reproduce the bundled benchmark:

python -m lightrider.tensor_network_demo

This command compares a four-qubit non-Clifford circuit against the dense simulator at bond caps 1, 2, 4, and 8, then runs a 100-qubit GHZ-like circuit. Expect sampled total variation distance to fall from roughly 0.33 at bond one to roughly 0.01 at bond four. The larger circuit produces only all-zero and all-one strings and uses bond dimension two. Sampled counts can vary with the numerical environment. No repository clone or cloud account is needed.

Step 3 — Run your own circuit:

from lightrider import Circuit, get_backend

circuit = Circuit(2).h(0).cx(0, 1).t(1).measure_all()
result = get_backend("mps", max_bond_dim=64, cutoff=1e-12).run(
    circuit, shots=4096, seed=7
).result()
print(result.counts)
print(result.metadata)  # Retained bond dimension and discarded weight

The native NumPy MPS engine supports every primitive gate and terminal X/Y/Z measurement. A finite bond cap can introduce approximation: increase max_bond_dim and lower cutoff to check convergence against small statevector runs. discarded_weight is a local compression diagnostic, not a global error bound. Highly entangled circuits can still be expensive. Sampling uses NumPy pseudorandomness, separately from hardware QRNG APIs.

How it works: an MPS factors the quantum state's amplitudes into a chain of tensors, each with shape (left_bond, 2, right_bond). Contracting their shared bond indices reconstructs amplitudes. With bond dimension χ, storage is approximately O(n χ²) instead of O(2**n). QR sweeps establish orthonormal environments; gate blocks are split by SVD, retaining the largest Schmidt coefficients allowed by the cap and cutoff. Distant gates use routing SWAPs, which can also introduce truncation. Sampling draws conditional outcomes from the final MPS using the Born rule, without allocating every basis probability.

Read the installation, accuracy-tuning, and fundamentals guide for options and limitations.

Stabilizer noise and surface-code QEC

The local stabilizer backend includes the Stim-style operations needed for circuit-level QEC experiments:

Kind Light Rider circuit methods Stim text
Pauli noise x_error, y_error, z_error X_ERROR, Y_ERROR, Z_ERROR
Depolarizing noise depolarize1, depolarize2 DEPOLARIZE1, DEPOLARIZE2
General 1q Pauli channel pauli_channel_1 PAULI_CHANNEL_1
Basis measurement measure, measure_x, measure_y M, MX, MY
Basis reset reset, reset_x, reset_y R, RX, RY
from lightrider import Circuit, get_backend

circuit = Circuit(1)
circuit.h(0)
circuit.depolarize1(1e-4, 0)
circuit.measure_x(0)

counts = get_backend("stim").run(
    circuit, shots=100_000, seed=7
).result().counts

SurfaceCode9 implements the measurement-free, fault-tolerant [[9,1,3]] encoder of Goto, Ho, and Kanao, Phys. Rev. Research 5, 043137 (2023). It includes the exact two-stage encoder, transversal logical Hadamard with virtual 90-degree relabeling, X/Z syndrome decoding, and batched Pauli-frame Monte Carlo:

from lightrider import PauliNoiseModel, SurfaceCode9

code = SurfaceCode9()
result = code.simulate_logical_h(
    PauliNoiseModel(one_qubit_error=1e-4, two_qubit_error=1e-4),
    shots=1_000_000,
    seed=7,
    noisy_encoder=True,
)
print(result.as_dict())

The complete three-part reproduction is examples/stabilizer_surface_code_demo.py:

PYTHONPATH=lightrider python3 \
  lightrider/examples/stabilizer_surface_code_demo.py

The SDK implements these core stabilizer/QEC operations natively; it does not yet claim wire-format compatibility with every advanced Stim annotation such as DETECTOR, OBSERVABLE_INCLUDE, or detector error models.

QPU cloud jobs

Build a Circuit, select a supported cloud backend, submit it with backend.run(), and retrieve counts with job.result(). The same Circuit / Backend / Job / Result interface is used for local simulation and cloud execution. Device selection, native gates, credentials, and job limits depend on the backend and deployment.

Cloud jobs authenticate through Light Rider with an lr_ API key. Inspect backend.list_remote_backends() for the targets exposed by your deployment and select one authorized for your key.

Current adapter example: IQM

The SDK currently ships an IQM cloud adapter; cloud is an alias for that adapter. This example uses IQM Garnet through the Light Rider proxy, which handles provider credentials and transpiles circuits to the device'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.

backend = get_backend("cloud",
                      endpoint="https://lightriderapp.vercel.app/api/quantum",
                      api_key="lr_...",  # Key authorized for the selected target
                      backend_id="iqm_garnet")

job = backend.run(circ, shots=100)  # low-cost Bell smoke test; returns immediately
job.status()                      # WAITING | PROCESSING | COMPLETED | FAILED | ABORTED
result = job.result()             # counts + receipt in result.metadata["receipt"]
job.receipt()                     # provider credits + Light Rider token charge

Mock 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 (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 the rest of the 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

numpy-style: quantum_rng()

quantum_rng() is the quantum counterpart of numpy.random.default_rng() — the same calling conventions, but every draw comes from a quantum entropy source, with no PRNG in the sampling path:

from lightrider import quantum_rng

rng = quantum_rng()                      # default source: "iqm_sirius"
rng.random(5)                            # uniform floats in [0, 1)
rng.integers(1, 6, size=10, endpoint=True)   # quantum dice
rng.normal(loc=0.0, scale=1.0, size=100)     # Box–Muller on quantum uniforms
rng.choice(["a", "b", "c"], 5, p=[0.5, 0.3, 0.2])
rng.shuffle(my_list)                     # quantum Fisher–Yates
rng.bytes(32)                            # raw quantum entropy

The entropy backend is selectable. "iqm_sirius" (default) is the bundled IQM hardware pool; any object with a uniform(shape) method also works — pass an EntropySource for live, signed EMS entropy, or a BundledQrng to record every draw on a provenance manifest:

from lightrider import BundledQrng, quantum_rng

provider = BundledQrng(dataset_id="my_experiment")
rng = quantum_rng(provider)              # draws are logged on provider.manifest

numpy interop: when you need numpy's full distribution zoo or bulk PRNG throughput, rng.numpy_generator() returns a genuine numpy.random.Generator seeded from quantum bytes — quantum-seeded rather than quantum-drawn, and the honest label matters:

g = rng.numpy_generator()                # a real np.random.Generator
g.binomial(10, 0.5, size=100_000)        # anything numpy can do

Two deliberate design points: there is no seed parameter (the stream is physical entropy, not a reproducible algorithm — for reproducibility, seed a numpy_generator() and store the seed), and the bundled pool cycles after ~1.9M bits, so it is statistically quantum but not suitable for cryptographic key material.

Classic: IQM_sirius

IQM_sirius draws from the same bundled pool (~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

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

For SDK contributors, from the platform repository root:

cd lightrider
uv sync --locked --extra dev --extra pqc
uv run --no-sync pytest tests -q
uv run --no-sync python -m lightrider.tensor_network_demo

The SDK's uv.lock records the development dependency resolution; uv sync installs the local package in editable mode. Use uv lock after intentional dependency changes and include the updated lockfile in the change. Without uv, install with python -m pip install -e ".[dev,pqc]" in an activated virtual environment. The following commands also work in that environment:

# generate a synthetic dataset with its provenance certificate
# (installed as the `lightrider-demo` console script)
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

Repository layout

Path Contents
lightrider/ The package itself (data/ holds the bundled QRNG pool shipped in the wheel)
examples/ Runnable examples: Bell circuit on Garnet, Colab cloud submission (script + notebook), surface-code QEC demo
tests/ Offline test suite (pytest tests -q); live-EMS smoke tests gate on LR_EMS_LIVE=1
docs/ The QRNG-copula mathematical treatment (qrng-synthetic-data.pdf + LaTeX source)
fibonacci_gates/ Fibonacci-gate research notebooks and captured results across IQM Garnet, Emerald, Sirius, and Cepheus
iqm_capture_20260507_181448/ Capture record for the bundled pool: metadata, experiment log, and raw measurements

License

Apache-2.0. Built by Light Rider.

Release files for lightrider 1.5.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for lightrider 1.5.2
File Size Uploaded
lightrider-1.5.2.tar.gz 846.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for lightrider 1.5.2
File Interpreter ABI Platform
lightrider-1.5.2-py3-none-any.whl Python 3 none any Details

Total release size: 1.6 MB

Release files / lightrider-1.5.2.tar.gz

Download URL lightrider-1.5.2.tar.gz
Size 846.7 kB
Tags Source
SHA-256 checksum
How to use checksums
5271601af8f8648b51dea618eb62ac2368847c1c913979841ad0e12a4ff8273a
BLAKE2b-256 checksum
How to use checksums
08e877ab6f63babdfa82a3f87acb2445b8d2764702df967ba8265182a00cf6b7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.10.12

Release files / lightrider-1.5.2-py3-none-any.whl

Download URL lightrider-1.5.2-py3-none-any.whl
Size 734.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3623fe4c8a52acbc1432facb1788907622a3a80325f9f3af1b6be273eb5583e8
BLAKE2b-256 checksum
How to use checksums
f5fa727f8ae1d0af05cf9d97cf3cde9fafffa46a9ac7d41367f2a32e2adb2cf5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.10.12

Release history Release notifications | RSS feed

This release

1.5.2 This release

2 release files

1.5.1

2 release files

1.5.0

2 release files

1.4.3

2 release files

1.4.2

2 release files

1.3.1

2 release files

1.1.0

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.3.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page