Metadata-Version: 2.4
Name: qkd
Version: 0.1.0
Summary: Continuous-variable quantum optics and CV-QKD simulator: Gaussian symplectic core, pilot-assisted DSP, and hardware-parameterised key rates.
Home-page: https://github.com/plutoniumm/qkd
Author: plutoniumm
Author-email: haskell-game@manav.ch
License: MIT
Keywords: quantum,continuous-variable,quantum-optics,cvqkd,qkd,gaussian,simulator
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Rust
Classifier: Topic :: Scientific/Engineering :: Physics
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy
Provides-Extra: dev
Requires-Dist: setuptools-rust; extra == "dev"
Requires-Dist: twine; extra == "dev"
Requires-Dist: black; extra == "dev"
Requires-Dist: wheel; extra == "dev"
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: keywords
Dynamic: license
Dynamic: license-file
Dynamic: provides-extra
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

<p align="center">
  <img src="https://raw.githubusercontent.com/plutoniumm/qkd/main/icon.svg" width="120" alt="qkd">
</p>

<h1 align="center">qkd</h1>

<p align="center">
  A QKD protocol simulator with the hardware in the loop, over a native Rust core.
</p>


**Documentation: <https://plutoniumm.github.io/qkd/>**

## What it is

Most CV toolkits let you *set* the channel transmittance `T` and the excess
noise `ξ`, then compute a key rate from the same numbers you set. That is the
textbook derivation, not a protocol simulation.

qkd inverts it. Excess noise is an **output**, assembled from parameters you
can measure on a bench — laser linewidth, detector electronic noise, quantum
efficiency, DAC and ADC bit depth, pilot power ratio, symbol rate, fibre length
— with the DSP chain in the loop: pilot-assisted phase recovery, carrier-offset
estimation, frame rotation. The residual phase error those estimators leave
behind feeds back into the noise budget as `ξ_phase`. Parameter estimation then
works from finite samples with confidence bounds, the way Alice and Bob have to.

```python
import qkd as q

link = q.Link(
    modulation=q.GaussianModulation(v_a=4.0),
    channel=q.Fiber(length=15.0, alpha=0.2),          # hardware, not T
    alice=q.Alice(
        laser=q.Laser(linewidth=10e3),
        pilots=q.Pilots(power_db=12.0, freq=180e6),
        symbol_rate=100e6,
    ),
    bob=q.Bob(
        detector=q.Heterodyne(eta=0.6, v_el=0.1, trusted=True),
        lo=q.LocalLO(linewidth=10e3),
    ),
    dsp=q.DSP(cfo=q.PilotCFO(), phase=q.PilotPhase(), block=32),
    security=q.FiniteSize(beta=0.95, n=1e9),
)
res = link.run(symbols=200_000, seed=7)

res.dsp.v_err       # 0.0075742    rad^2 left by the pilot estimator
res.budget.total    # 0.0304118    SNU at the channel input -- derived from it
res.est.xi          # 0.0142987    what Alice and Bob estimate from the samples
res.key_rate        # 0.0285553    bit/symbol
```

No line above sets `ξ`. It is what the linewidth, the pilot ratio, the symbol
rate and the recovered phase produce.

Two rules follow the thesis everywhere. **Every noise quantity names its plane**
— `ξ` at the channel input and at Bob's side differ by a factor of `T`, so the
API refuses to guess which you meant. And **trusted versus untrusted detector
noise is an explicit flag**, because it is a security-model decision, not a
physics one.

Underneath sits an ordinary Gaussian CV simulator (symplectic covariance engine,
thermal-loss channels, homodyne/heterodyne detection) which is useful on its
own, with a truncated-Fock layer beside it for the states a covariance matrix
cannot carry.

## Status

**0.1.0. Never published.** `pip install qkd` does not work and will 404 —
the wheel pipeline runs locally and nothing has been uploaded. Build from source
(below).

`qkd` today is 26 Rust files (18,035 lines) plus 6 WGSL shaders (472 lines)
under `src/`, 9,196 lines of Python under `qkd/`, and 49 test scripts building
1,444 exams across 241 `Exam`s. `qkd.__all__` lists 82 public names and is the
authoritative API index; `qkd._core` exposes 199 — 192 functions and 7 classes.
Counted 2026-09-04, and stale the moment an engine lands.

Configurations the library cannot compute **raise, naming the restriction**,
rather than approximating. How well each protocol is checked is graded rather
than asserted — see
[Validation](https://plutoniumm.github.io/qkd/guide/validation), which
scores every protocol on two axes: what the theory reproduces (the same number,
a correctly signed inequality, or behaviour only) and, separately, whether a
published experiment was reproduced.

## Protocols

Discrete and continuous variable, prepare-and-measure and entanglement-based.
Every row runs end to end and is covered by exams.

| Reached through | Protocol |
| --- | --- |
| `q.Link` + `q.GaussianModulation` | Gaussian-modulation CV-QKD: homodyne and heterodyne, trusted and untrusted, asymptotic and finite-size, pilot DSP in the loop |
| `q.Link` + `q.PhaseShiftKeying` | *M*-PSK discrete modulation |
| `q.Link` + `q.BasisKeying` | BB84 with weak coherent pulses and decoy states; `bases=3` is six-state, `announce="pair"` is SARG04 |
| `q.Link` + `q.PolarisationKeying` | the same, with the polarisation reference frame named |
| `q.Link` + `q.TwoStateKeying` | B92; `reference=True` selects the strong-reference variant |
| `q.Link` + `q.DifferentialPhase` | DPS, with the QBER derived from the delay interferometer |
| `q.Link` + `q.IntensityKeying` | COW |
| `q.PairLink` + `q.SymmetryBound` | BBM92 over a photon-pair source |
| `q.PairLink` + `q.ViolationBound` | E91, priced by an observed CHSH violation instead of a phase error |
| `q.Swap` + `q.BellDetector` | CV-MDI, the untrusted-relay topology |
| `q.Swap` + `q.BellAnalyser` | MDI-BB84, the same topology with a counting midpoint. Asymptotic |
| `q.Network` | the graph layer: trusted nodes, `q.Hop` edges, widest-path routing |

Four more engines ship with exams but no `q.*` wrapper yet, and are reached on
`qkd._core` directly: round-robin differential phase shift (`rrdps_*`),
mode-pairing / asynchronous MDI (`pairing_*`), a hand-rolled semidefinite phase
bound for COW′ (`sdp_*`), and the loss-tolerant analysis of source flaws
(`flaw_*`). So does the Winick–Lütkenhaus relative-entropy proof for discrete
modulation (`dm_secure`, on the complex-Hermitian numerics `herm_*`) — a second
solver, on a different cone from `sdp_*`.

Three exported components describe hardware and are **refused by `q.Link`** rather
than approximated: `q.ThresholdArray` and `q.PnrDetector` build a
photon-number-resolving POVM no shipped bound can read, and `q.TransmittedLO`
models the oscillator that travels with the signal, where every `q.Link` runs a
locally generated one. Each refusal names what is missing.

Two Python modules sit beside the protocols. `qkd.attacks` holds the
detector-side attacks — saturation, calibration, blinding, time-shift, blanking
— reporting what Alice and Bob observe and what Eve holds as **separate** books
of numbers, and refusing to be asked for a key rate, because under every attack
modelled there the observables stay at values an unattacked link would produce.
`qkd.reconcile` is the classical layer: LDPC and Cascade reconciliation,
privacy amplification, authentication cost.

Four exclusions are settled decisions rather than pending work: MDI-DPS
(patented), twin-field / phase-matching / sending-or-not-sending, satellite and
free-space channels, and qudits.

## Layers

| Module | What |
| --- | --- |
| `qkd.gaussian` | symplectic covariance engine, thermal-loss channels, homodyne/heterodyne, Wigner and Husimi grids |
| `qkd.fock` | truncated number basis: cat, GKP, squeezed, explicit density matrices, and the Wigner-negativity witness |
| `qkd.budget` | `ξ` assembled from hardware, referred between four named planes |
| `qkd.impairments` | hardware descriptors and the closed forms behind them |
| `qkd.pairs` | photon-pair coincidence gains, BBM92 and CHSH |
| `qkd.attacks` | detector-side attacks, as observables against Eve's holdings |
| `qkd.reconcile` | error correction, privacy amplification, authentication |

## Build from source

```sh
git clone https://github.com/plutoniumm/qkd
cd qkd
./do develop        # compiles the Rust core in place -- this is the import path
```

You need Python ≥ 3.11 with `numpy`, plus a Rust toolchain (`cargo`, `rustc`).
`./do develop` runs `setup.py build_ext --inplace` and installs its own build
dependencies. `numpy` is a hard requirement, not an optional extra — every bulk
numeric return crosses the native boundary as an `ndarray`.

**A bare `cargo build` will fail to link.** A PyO3 `extension-module` cdylib has
no Python symbols to resolve against; `setup.py build_ext` supplies
`-undefined dynamic_lookup`. That failure is expected. Always go through
`./do develop`.

`./do build` produces `cp311-abi3` wheels for macOS (arm64, x86_64), Linux
`manylinux_2_28` (aarch64, x86_64) and Windows x86_64, plus an sdist. It has
been run; `./do deploy` has not.

## Compute backends

The core resolves a backend at runtime and reports it:

```python
>>> import qkd
>>> qkd.backend_info()
('gpu', 'f32', 'Apple M2 (Metal)')
```

That tuple is machine-specific; `('cpu', 'f64', ...)` on a machine with no
adapter is a correct result, not a failure.

GPU support is on by default, via `wgpu` (Metal / Vulkan / DX12). It needs no
vendor SDK at build time, which is what keeps the local cross-compile matrix
working from a single Apple Silicon machine — a CUDA dependency would break it
outright and could never be tested here.

**Dispatch asks for a precision, not a device.** WGSL has no `f64`, so the GPU
path is single-precision, permanently. That suits the DSP layer, which runs over
ADC samples carrying 12–16 real bits, where f32's 24-bit mantissa is already
past the hardware's resolution. Covariance matrices, symplectic maps and the
Holevo bound are 2×2 and 4×4, negligible to compute and sensitive to
cancellation, and stay in f64 on the CPU whatever hardware is present.

**No `q.Link` run touches the GPU.** The WGSL kernels exist, dispatch, and are
measured — Threefry is bit-exact against the Rust past counters of 2³², and DSP
statistics agree with the f64 CPU path to about 1e-7 — but they compute the
*oracle* branch, not the DSP-derotated one: the pilot DSP across blocks and the
phase-walk carry scan are sequential and stay on the host, where a monolithic
f32 scan errs 4.0e-2 rad, the same size as the residual phase noise the
simulation exists to measure. `run_symbols`, and therefore every `q.Link` run,
is CPU. The measured in-kernel throughput figures (313–335 M symbol/s against
10.3 M symbol/s for the CPU pipeline, crossover at 1e3–3e3 symbols) come from a
machine under heavy load, so the CPU rows are roughly 2× slow and no single
speedup number is claimed.

## Development

Everything goes through `./do`. There is no Makefile, no `pyproject.toml`, and
no CI.

```sh
./do develop           # compile the Rust core in place (debug) -- the import path
./do check             # cargo check + python compileall, fast, no link
./do test [name ...]   # develop + run test/*.py, emit markdown reports
./do bench [name ...]  # release build + bench/*.py through the MDB harness
./do lint [--fix]      # view.lint + black --check + eastwood + svelte-check
./do docs              # vitepress dev server on :3000
./do build             # every wheel -> wheelhouse/, sdist -> dist/  (no upload)
./do deploy            # twine check + upload what build produced
```

`./do` needs `python` on `PATH`; on a machine that has only `python3`, activate
an environment first. `./do test` and `./do bench` take file selectors, so
`./do test anchors budget` re-runs those two alone.

Tests are standalone scripts, not pytest, and emit markdown report tables. To
publish them into the docs site:

```sh
MDR_OUT="$(pwd)/docs/tests" ./do test
```

See [RELEASING.md](https://github.com/plutoniumm/qkd/blob/main/RELEASING.md)
for the wheel matrix, how the cross-compiles work, and the three release gates,
and [docs/architecture.md](https://github.com/plutoniumm/qkd/blob/main/docs/architecture.md)
for the layering.

## License

MIT
