perf(photonlayer): M1 — cached + in-place Propagator (1.70x, bit-identical)

Hot-path optimization for the mask-learning loop, which propagates thousands
of fields through one fixed config. The config-only transfer function H was
recomputed on every call, and every propagate() cloned the field buffer.

- Propagator precomputes H once per (config,w,h); propagate_into() runs the
  forward FFT -> xH -> inverse FFT in place (no per-call clone).
- Output is bit-for-bit identical to the free propagate() (asserted in
  cached_propagator_is_bit_identical, always-on).
- Measured 1.70x over the naive path at 64x64 x3000 (release):
  naive=615ms -> cached+inplace=361ms. Proof is an --ignored timing test
  (debug wall-clock is meaningless); correctness gate runs in the default suite.

Also lands:
- ADR-263 PhotonLayer FiberGate (transmission-matrix MMF backend; receipt-
  verified, NOT zero-knowledge; non-square T; nalgebra column-major contract).
- docs/research/photonlayer/APPLICATIONS.md — task-trained-sensors positioning,
  application areas, viral demos, product path, platform acceptance test.

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
ruv 2026-06-17 23:28:39 -04:00
parent e1429af65c
commit c3a553e7fc
5 changed files with 461 additions and 18 deletions

View file

@ -62,7 +62,7 @@ pub mod prelude {
accuracy, compression_ratio, frame_spectrum_embedding, input_frame_similarity, mse, psnr,
MetricReport,
};
pub use crate::propagate::propagate;
pub use crate::propagate::{propagate, Propagator};
pub use crate::receipt::{build_receipt, verify_receipt, ExperimentReceipt, Provenance};
pub use crate::simulator::{OpticalSimulator, ScalarSimulator, SimulationTrace};
}

View file

@ -66,19 +66,17 @@ enum TransferKind {
AngularSpectrum,
}
fn transfer_fn(field: &OpticalField, config: &OpticalConfig, kind: TransferKind) -> Result<OpticalField> {
let (w, h) = (field.width, field.height);
/// Build the config-only transfer function H (length `w*h`, row-major). H depends
/// solely on (w, h, λ, z, d, kind) — never on the field — so it can be computed
/// once and reused across many propagations (see [`Propagator`]).
fn transfer_kernel(w: usize, h: usize, config: &OpticalConfig, kind: TransferKind) -> Vec<Complex> {
let lambda = config.wavelength_m();
let z = config.distance_m();
let d = config.pixel_pitch_m();
let fx = fftfreq(w, d);
let fy = fftfreq(h, d);
let mut data = field.data.clone();
fft_2d(&mut data, w, h, false);
let k = 2.0 * PI / lambda;
let mut hk = vec![Complex::ZERO; w * h];
for row in 0..h {
for col in 0..w {
let fxx = fx[col];
@ -86,8 +84,7 @@ fn transfer_fn(field: &OpticalField, config: &OpticalConfig, kind: TransferKind)
let h_val = match kind {
TransferKind::Fresnel => {
// Drop constant exp(i k z); keep quadratic phase.
let phase = -PI * lambda * z * (fxx * fxx + fyy * fyy);
Complex::from_phase(phase)
Complex::from_phase(-PI * lambda * z * (fxx * fxx + fyy * fyy))
}
TransferKind::AngularSpectrum => {
let arg = 1.0 - (lambda * fxx).powi(2) - (lambda * fyy).powi(2);
@ -99,17 +96,108 @@ fn transfer_fn(field: &OpticalField, config: &OpticalConfig, kind: TransferKind)
}
}
};
let idx = row * w + col;
data[idx] = data[idx] * h_val;
hk[row * w + col] = h_val;
}
}
hk
}
/// Apply a precomputed transfer kernel: forward FFT → ×H → inverse FFT.
fn apply_transfer(field: &OpticalField, w: usize, h: usize, hk: &[Complex]) -> OpticalField {
let mut data = field.data.clone();
fft_2d(&mut data, w, h, false);
for (dv, hv) in data.iter_mut().zip(hk.iter()) {
*dv = *dv * *hv;
}
fft_2d(&mut data, w, h, true);
OpticalField { width: w, height: h, data }
}
fn transfer_fn(field: &OpticalField, config: &OpticalConfig, kind: TransferKind) -> Result<OpticalField> {
let (w, h) = (field.width, field.height);
let hk = transfer_kernel(w, h, config, kind);
Ok(apply_transfer(field, w, h, &hk))
}
/// A precomputed propagation operator. Build once per `(config, width, height)`
/// and reuse across many fields — the config-only transfer function is computed
/// a single time instead of on every call. This is the hot path in mask-learning
/// loops (thousands of propagations share one config). Output is bit-identical to
/// the free [`propagate`] function.
pub struct Propagator {
width: usize,
height: usize,
kind: PropKind,
}
enum PropKind {
Fraunhofer,
/// Precomputed transfer function H (length `width*height`).
Transfer(Vec<Complex>),
}
impl Propagator {
/// Precompute the operator for a fixed grid + config.
pub fn new(width: usize, height: usize, config: &OpticalConfig) -> Result<Self> {
if !is_pow2(width) {
return Err(PhotonError::NotPowerOfTwo(width));
}
if !is_pow2(height) {
return Err(PhotonError::NotPowerOfTwo(height));
}
let kind = match config.propagation {
PropagationMode::Fraunhofer => PropKind::Fraunhofer,
PropagationMode::Fresnel => {
PropKind::Transfer(transfer_kernel(width, height, config, TransferKind::Fresnel))
}
PropagationMode::AngularSpectrum => PropKind::Transfer(transfer_kernel(
width,
height,
config,
TransferKind::AngularSpectrum,
)),
};
Ok(Self { width, height, kind })
}
/// Propagate a field through the precomputed operator.
pub fn propagate(&self, field: &OpticalField) -> Result<OpticalField> {
if field.width != self.width || field.height != self.height {
return Err(PhotonError::NotPowerOfTwo(field.width));
}
match &self.kind {
PropKind::Fraunhofer => fraunhofer(field),
PropKind::Transfer(hk) => Ok(apply_transfer(field, self.width, self.height, hk)),
}
}
fft_2d(&mut data, w, h, true);
Ok(OpticalField {
width: w,
height: h,
data,
})
/// **In-place** propagation — forward FFT → ×H → inverse FFT, mutating `data`
/// directly (no per-call field clone). Bit-identical to [`Propagator::propagate`];
/// this is the batch hot path (mask-learning loops over many samples).
pub fn propagate_into(&self, data: &mut [Complex]) -> Result<()> {
let (w, h) = (self.width, self.height);
if data.len() != w * h {
return Err(PhotonError::NotPowerOfTwo(data.len()));
}
match &self.kind {
PropKind::Fraunhofer => {
fft_2d(data, w, h, false);
fftshift_2d(data, w, h);
let norm = 1.0 / (w as f32 * h as f32).sqrt();
for c in data.iter_mut() {
*c = c.scale(norm);
}
}
PropKind::Transfer(hk) => {
fft_2d(data, w, h, false);
for (dv, hv) in data.iter_mut().zip(hk.iter()) {
*dv = *dv * *hv;
}
fft_2d(data, w, h, true);
}
}
Ok(())
}
}
#[cfg(test)]

View file

@ -0,0 +1,88 @@
//! M1 proof: the cached + in-place `Propagator` is faster than the naive free
//! `propagate()` (which recomputes the transfer function H and clones the field
//! every call), and produces **bit-identical** output. No speedup claim without
//! this measured number.
//!
//! Run: `cargo test -p photonlayer-core --release --test propagation_speedup -- --ignored --nocapture`
use std::time::Instant;
use photonlayer_core::config::OpticalConfig;
use photonlayer_core::field::{InputImage, OpticalField};
use photonlayer_core::propagate::{propagate, Propagator};
const N: usize = 64; // grid (learn-loop regime where H-recompute is a large fraction)
const ITERS: usize = 3000;
fn test_field(n: usize) -> OpticalField {
// Deterministic non-trivial pattern.
let px: Vec<f32> = (0..n * n)
.map(|i| {
let (x, y) = ((i % n) as f32, (i / n) as f32);
0.5 + 0.5 * ((x * 0.3).sin() * (y * 0.2).cos())
})
.collect();
let img = InputImage::from_norm_f32(n, n, px).unwrap();
OpticalField::from_image(&img, n, n).unwrap()
}
/// Always-on correctness gate: the cached + in-place path is bit-for-bit
/// identical to the free `propagate()`. Cheap; runs in the default suite.
#[test]
fn cached_propagator_is_bit_identical() {
let field = test_field(N);
let config = OpticalConfig::demo(N, N);
let reference = propagate(&field, &config).unwrap();
let prop = Propagator::new(N, N, &config).unwrap();
let via_struct = prop.propagate(&field).unwrap();
let mut buf = field.data.clone();
prop.propagate_into(&mut buf).unwrap();
assert_eq!(via_struct.data, reference.data, "Propagator::propagate must match free propagate");
assert_eq!(buf, reference.data, "propagate_into must be bit-identical to free propagate");
}
/// Timing proof (M1). Release-only — wall-clock is meaningless in debug. Run:
/// `cargo test -p photonlayer-core --release --test propagation_speedup -- --ignored --nocapture`
#[test]
#[ignore = "timing benchmark — run with --release --ignored"]
fn cached_propagator_is_faster() {
let field = test_field(N);
let config = OpticalConfig::demo(N, N);
// Warm up.
for _ in 0..64 {
let _ = propagate(&field, &config).unwrap();
}
// Naive: free propagate (recompute H + clone) every call.
let t = Instant::now();
let mut sink = 0.0f32;
for _ in 0..ITERS {
let out = propagate(&field, &config).unwrap();
sink += out.data[0].re;
}
let naive = t.elapsed().as_secs_f64();
// Optimized: build operator once; in-place propagate into a reused buffer.
let prop = Propagator::new(N, N, &config).unwrap();
let mut scratch = vec![photonlayer_core::complex::Complex::ZERO; N * N];
let t = Instant::now();
for _ in 0..ITERS {
scratch.copy_from_slice(&field.data);
prop.propagate_into(&mut scratch).unwrap();
sink += scratch[0].re;
}
let opt = t.elapsed().as_secs_f64();
std::hint::black_box(sink);
let speedup = naive / opt;
eprintln!(
"propagation {N}x{N} x{ITERS}: naive={:.1}ms cached+inplace={:.1}ms speedup={speedup:.2}x",
naive * 1e3,
opt * 1e3
);
assert!(
speedup >= 1.5,
"cached+in-place propagator must be >= 1.5x the naive path; got {speedup:.2}x"
);
}

View file

@ -0,0 +1,206 @@
---
adr: 263
title: "PhotonLayer FiberGate — transmission-matrix optical compression for drift-bound privacy verification"
status: proposed
date: 2026-06-18
authors: [ruvnet, claude-flow]
related: [ADR-260, ADR-261, ADR-262]
tags: [photonlayer, fiber-optics, multimode-fiber, transmission-matrix, mmf, privacy, receipts, drift, ruvector, wasm]
---
# ADR-263 — PhotonLayer FiberGate
> **Decision in one line.** Add a **multimode-fiber (MMF) propagation backend** to
> `photonlayer-core` based on a **calibrated complex transmission matrix (T)**, a
> deterministic intensity-sensor projection, **drift-aware calibration receipts**,
> and ruVector-backed calibration memory — moving PhotonLayer from free-space
> diffraction to guided-wave optics.
## Context
PhotonLayer today models **free-space** scalar diffraction (Fresnel / Fraunhofer /
angular-spectrum; ADR-260). The cutting edge of optical computing is moving to
**guided-wave** substrates — e.g. the Fiber-based Diffractive Deep Neural Network
(Fiber-D2NN, *Opt. Lett.* 50(17):5254) shows high ML accuracy from linear optical
relations **inside** fibers.
The right framing (and the bounded claim we will defend):
> **PhotonLayer Fiber treats the multimode fiber as a calibrated, drifting,
> complex *linear* optical operator. The learned mask shapes the input field so
> that the drifting fiber + sensor produce task-useful compressed measurements.**
This is stronger than "the fiber is just another propagation model." Below
nonlinear power thresholds, the input→output **field** relationship of an MMF is
well modeled by a transmission matrix **T**; the observed **sensor** image is an
intensity speckle pattern. Both deep-learning and intensity-matrix methods are
established for MMF image recovery/classification (arXiv:1805.05134). Bending and
temperature **drift** of T is the central real-world challenge and requires
recalibration (Nat. Commun. s42005-023-01410-x).
### Claim hygiene (no slop)
- **This is NOT zero-knowledge.** The design proves a private input was transformed
under a specific mask + calibrated fiber state into a specific low-dimensional
measurement. That is a **cryptographic receipt**, not a ZK proof. We use:
**receipt-verified privacy gate** / **non-reconstructive verification** /
**optical biometric commitment** / **fiber-bound biometric proof**. Safe claim:
*"the server verifies a receipt bound to the current fiber calibration and
receives only the compressed optical measurement, not the source image."*
- **Consented verification only** — no gallery identification.
## Decision
Add a fiber backend and the supporting machinery, in this **build order** (the
moat is the physics + receipts + memory, not the browser):
1. `photonlayer-core` fiber backend (T-matrix propagation + deterministic sensor).
2. Fiber **drift** benchmark in `photonlayer-bench`.
3. ruVector **calibration memory** in `photonlayer-ruvector`.
4. `photonlayer-wasm` client bindings.
### Mathematical model
**Level 1 — practical T-matrix simulator (implementation target):**
```
E_in' = E_in ⊙ e^{iΦ} (apply learned phase mask)
E_out = T · E_in' (linear mode mixing; T may be non-square)
I_sensor = B · |E_out|² + ε (intensity-only, binned, deterministic seeded noise ε)
```
**Level 2 — mode-basis simulator (later, for physical interpretability):**
```
E(x,y,z) = Σ_m a_m(z) ψ_m(x,y) e^{iβ_m z}
a_m(0) = ∬ E_in(x,y) e^{iΦ(x,y)} ψ_m*(x,y) dx dy
a(L) = C(L, θ, T_env) a(0) (separates ideal propagation from drift)
```
### Rust design (determinism-first)
Three correctness rules the implementation MUST honor:
1. **Layout contract**`nalgebra::DMatrix` is column-major; define and document a
fixed (row-major image) ⇄ (column-vector) mapping so flatten/reshape never
silently reorders the spatial layout.
2. **Non-square T** — input modes, output field samples, and sensor bins have
different dimensions (e.g. 256 → 64 → 4). `T` is `output_len × input_len`.
3. **Determinism is not free from `nalgebra`** — pin operation order, no
uncontrolled parallel reductions, finite checks on every value, stable
serialization. Bit-identical output across Linux/macOS/WASM is an invariant
(ADR-261).
Core type (shape per the corrected design):
```rust
pub struct FiberTransmissionMatrix {
pub t: DMatrix<Complex64>, // output_len × input_len (non-square allowed)
pub input_len: usize,
pub output_len: usize,
pub version: u64,
pub calibration_hash: [u8; 32],
}
// propagate(input_field, mask) -> FiberOutput { field, intensity },
// validating input/mask length + matrix shape + finiteness (FiberError otherwise).
```
New `photonlayer-core` modules: `fiber.rs`, `fiber_matrix.rs`,
`fiber_calibration.rs`, `fiber_sensor.rs`, `fiber_receipt.rs`. Core structs:
`FiberTransmissionMatrix`, `FiberCalibrationState`, `FiberPilotPattern`,
`FiberDriftModel`, `FiberSensorProjector`, `FiberReceipt`.
### Drift-aware training (turns drift from liability into a training distribution)
```
min_{Φ,θ} E_{T ~ D_fiber} [ L(decoder(B|T(E ⊙ e^{iΦ})|²), y) ] + λ R(Φ) + γ L_privacy
```
Train the mask against a **family** of likely T states, not one matrix. `R(Φ)` =
smoothness/manufacturability; `L_privacy` = reconstruction/leakage penalty.
Drift metric and accuracy-vs-drift tracking:
```
Δ_T = ‖T_t T_{t1}‖_F / ‖T_{t1}‖_F A = f(Δ_T, SNR, bins, mask)
```
### ruVector calibration memory (`photonlayer-ruvector`)
Store each calibration as an experiment object (fiber_id, t_version,
calibration_hash, timestamp, temperature, bend_state, snr_db, drift_norm,
mask_id, task, eer, reconstruction_score) and use ruVector for: nearest-calibration
lookup, drift-regime clustering, recalibration prediction, finding masks robust
across multiple T states, and spectral failure explanation.
### Receipt schema + commitment
```rust
pub struct FiberReceipt {
pub photonlayer_version: String,
pub fiber_id_hash: [u8; 32],
pub t_version: u64,
pub t_hash: [u8; 32],
pub pilot_hash: [u8; 32],
pub phase_mask_id: [u8; 32],
pub input_commitment: [u8; 32], // salted: C = H(input_hash || nonce || purpose || session)
pub output_hash: [u8; 32],
pub decoder_id: [u8; 32],
pub nonce: [u8; 32],
pub timestamp_ms: u64,
}
```
Key design choice: **never store the raw biometric hash in a reusable form** — use
the salted commitment `C`.
### Threat model
| Threat | Risk | Mitigation |
|---|---|---|
| Replay old N-pixel output | Medium | bind receipt to T version + nonce + timestamp |
| T-matrix theft | Medium | rotate calibration, encrypt at rest, bind to device |
| Reconstruction attack | High | publish attack suite (ADR-262), train privacy penalty |
| Server spoofing T state | Medium | signed calibration state |
| Browser config DoS | Medium | existing input validation |
| Biometric misuse | High | consented verification only, no gallery |
| Model inversion | High | leakage tests vs attributes + identity |
## Consequences
### Positive
- Unlocks the flagship **medical-endoscope** (optical compression inside a needle-
thin fiber bundle — no bulky tip sensor) and **drone/edge** sensing paths.
- Drift becomes an **anti-replay signal**, not only a liability.
- The defensible moat: **drift-aware, receipt-verified optical compression with
experiment memory** — not the browser layer.
### Negative / risks
- Real fiber validation needs hardware + calibration; until then this is a
**simulator + receipt schema**, claimed as such.
- T-matrix calibration is a new operational burden (pilot wavefronts, drift
thresholds, recalibration triggers).
- Determinism across native + WASM with `nalgebra` complex linear algebra requires
care (see rule 3).
### Neutral
- Free-space backend (ADR-260) stays; fiber is an additional `PropagationKind`.
## Acceptance tests
1. Same input + mask + T + seed → identical output hash across Linux, macOS, WASM.
2. Learned mask beats random by ≥ **20 pp** on compressed fiber classification.
3. EER stays below target across ≥ **5 drift states**.
4. Reconstruction-attack similarity stays below the documented threshold.
5. Receipt verification **fails** if any of {T version, phase mask, decoder, nonce,
output, sensor config} changes.
6. Recalibration trigger fires when `Δ_T` crosses the configured threshold.
7. **Non-square** T passes end-to-end: 256 inputs → 64 output samples → 4 sensor bins.
## Links
- ADR-260 (free-space simulator), ADR-261 (mask exchange & determinism),
ADR-262 (privacy-preserving optical verification).
- Fiber-D2NN — *Opt. Lett.* 50(17):5254. MMF + deep learning — arXiv:1805.05134.
Single-ended T recovery / drift — *Nat. Commun.* s42005-023-01410-x.
- Product: **PhotonLayer FiberGate** — calibrated fiber as a physically-bound,
receipt-verified transformation layer for non-reconstructive verification.

View file

@ -0,0 +1,61 @@
# PhotonLayer — Applications & Strategy
> **The category:** not cameras, not neural nets, not "optical computing" — **task-trained sensors**.
> Strategic thesis: *AI created an infinite appetite for visual data; PhotonLayer goes the other way —
> **capture less, decide faster, leak less, and prove what happened.***
Companion to [ASSESSMENT.md](ASSESSMENT.md), ADR-260/261/262/263. Bounded-claim discipline applies:
no diagnosis claims, consented verification only, "receipt-verified" (not zero-knowledge).
## The core shift — image recognition without images
```
Traditional: scene → camera image → neural net → decision
PhotonLayer: scene → trained optical transform → tiny sensor measurement → small decoder → decision
```
Recognize / verify / classify / reject from a **compressed optical signature** instead of a stored
image. Aligned with learned optical encoders, lensless privacy cameras, meta-optics, and hybrid
optical-electronic neural nets (arXiv:2406.04129; ACS Photonics 5c02358; PMC12011376).
> **The system sees enough to decide, but not enough to reconstruct.**
## Application areas (positioned by market readiness)
| Area | PhotonLayer role | First market? |
|---|---|---|
| **Industrial inspection** | defect yes/no, barcode/label verify, tamper, scratch, sorting | **Yes — best first market** (low regulatory burden, clear ROI, controlled lighting) |
| Privacy-first machine vision | face *verification* without storage, occupancy without identity, PPE/posture, child-safe presence | High viral / commercial distinctiveness |
| Ultra-low-bandwidth sensors | tiny sensors, always-on vision, few-bin event verification, battery/edge | Reduces the expensive part of edge AI (pixel movement) |
| Drone / robotics pre-perception | landing-pad, horizon, obstacle, marker, motion-cue, terrain class | Medium (safer than full autonomy) |
| Medical imaging **research** | microscopy morphology, lesion compression, endoscopy, cell pre-sort, pathology triage | High risk — **research only, no diagnosis** |
| Fiber-bound security | tamper-evident links, device-bound auth, anti-replay, drift-liveness (ADR-263) | New product class |
| Scientific instruments | design instruments around the *question*, not the image | Biggest long-term idea |
## A new benchmark lane — "accuracy per captured photon / pixel / sensor bin"
Metrics: accuracy/sensor-pixel · accuracy/digital-MAC · EER under reconstruction constraint · drift
robustness · receipt reproducibility · calibration half-life · leakage score · failure boundary. The
receipt system is what makes these **reproducible optical experiments**, not just claims.
## Viral demos (for the Pages UI)
- **A — "The camera that cannot see you"**: face → 4-px measurement → same/different verdict. *It verified the person without storing the face.*
- **B — "The microscope learned what not to measure"**: full image → optical compression → morphology class → failed reconstruction. *The useful signal survived. The image did not.*
- **C — "Drone vision in 4 pixels"**: landing marker detected from a few optical bins. *The drone needs a decision, not a frame.*
- **D — "The fiber is the lock"** (ADR-263): verification fails when T drifts or the receipt is replayed. *The cable became part of the cryptographic boundary.*
## Product path (build order)
1. **PhotonLayer Studio** — browser optical-mask simulator with receipts.
2. **PhotonLayer Bench** — public optical-compression benchmark suite.
3. **PhotonLayer PrivacyGate** — consented verification + reconstruction attacks.
4. **PhotonLayer Industrial** — defect/barcode inspection SDK.
5. **PhotonLayer FiberGate** — drift-aware MMF simulator + lab bridge (ADR-263).
6. **PhotonLayer BioResearch** — microscopy/dermatology research simulator (no diagnostic claims).
## Sharp acceptance test (platform-grade)
> On **3 public datasets**: learned optical mask ≥ full-image baseline 2%; sensor pixels reduced
> ≥ **16×**; digital MACs reduced ≥ **10×**; reconstruction-attack similarity below threshold;
> receipt verification reproducible across Rust-native **and** WASM.