Metadata-Version: 2.4
Name: lightrider
Version: 1.5.2
Summary: Light Rider Python SDK: quantum circuits, tensor network simulation, QPU cloud jobs, attested entropy, QRNG, and synthetic data.
Author: Light Rider
License: Apache-2.0
Project-URL: Documentation, https://ems.lightriderinc.com/docs/tensor-network
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: numpy>=1.21
Requires-Dist: httpx>=0.27
Requires-Dist: websockets>=12
Requires-Dist: cryptography>=42
Requires-Dist: matplotlib>=3.5
Provides-Extra: pandas
Requires-Dist: pandas>=1.5; extra == "pandas"
Provides-Extra: pqc
Requires-Dist: dilithium-py>=1.1; extra == "pqc"
Provides-Extra: decoder
Requires-Dist: pymatching>=2.0; extra == "decoder"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pandas>=1.5; extra == "dev"
Requires-Dist: pymatching>=2.0; extra == "dev"

# lightrider

[![PyPI](https://img.shields.io/pypi/v/lightrider.svg)](https://pypi.org/project/lightrider/)
[![Python](https://img.shields.io/pypi/pyversions/lightrider.svg)](https://pypi.org/project/lightrider/)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)

**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](#quantum-circuits) | `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-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](#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:

```python
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](https://docs.astral.sh/uv/getting-started/installation/) if it is
not already available. On macOS or Linux:

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

On Windows PowerShell:

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

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

```bash
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](https://docs.astral.sh/uv/concepts/projects/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:

```bash
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:

```bash
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](https://ems.lightriderinc.com/docs/tensor-network).

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

```python
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](#qpu-cloud-jobs)).

## Quickstart

```python
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:

```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` |

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, 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

```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().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+):**

```bash
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:**

```bash
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:**

```python
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](
https://ems.lightriderinc.com/docs/tensor-network) 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` |

```python
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)](
https://doi.org/10.1103/PhysRevResearch.5.043137). 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:

```python
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`](
examples/stabilizer_surface_code_demo.py):

```bash
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.

```python
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:

```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())
```

## 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:

```python
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:

```python
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:

```python
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.

```python
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.

```python
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 |

```python
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

```json
{
  "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:

```bash
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:

```bash
# 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](https://lightriderinc.com).
