Metadata-Version: 2.4
Name: lightrider
Version: 1.0.1
Summary: Quantum circuit simulation (statevector + stabilizer) with IQM cloud submission, quantum random numbers from IQM bit pools, and QRNG synthetic data with provable provenance.
Author: Light Rider
License: Apache-2.0
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: numpy>=1.21
Provides-Extra: pandas
Requires-Dist: pandas>=1.5; extra == "pandas"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pandas>=1.5; 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 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](#quantum-circuits) | `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](#quantum-random-numbers) | `IQM_sirius` | Draw random numbers from a bundled pool of real IQM hardware bits — fully offline |
| [Synthetic data with provenance](#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

```bash
pip install lightrider             # core (NumPy only)
pip install "lightrider[pandas]"   # + pandas DataFrame support
```

Requires Python ≥ 3.9.

> **Live EMS entropy** additionally needs the `lr-entropy` SDK, which ships
> with the Light Rider platform rather than on PyPI. Install it from the
> platform repository (`pip install ./sdk-python`) and `EntropySource`
> becomes available automatically.

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

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

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

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

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

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

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

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

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