Files
ruvnet--RuView/v2/crates/wifi-densepose-train/src/geometry.rs
T
rUv 1d12e8831a refactor(beyond-sota): ADR-155 M2 — host-verifiable §8 closeout (7 de-magic, 9 boundary tests, native-conv honest-null) (#1059)
* refactor(train): ADR-155 M2 §8 — de-magic train non-tch tuning constants + boundary tests

Lift bare numeric literals used as thresholds / guard epsilons in the
non-tch (host-verifiable) train surface into named, documented consts and
pin each set with a *_consts_unchanged_from_literals test. Values are
bit-identical to the prior inline literals — cleanup, no behaviour change.

De-magicked (const + pin test):
- metrics_core.rs: VISIBILITY_THRESHOLD (0.5), MIN_REFERENCE_EXTENT (1e-6),
  OKS_FALLBACK_SIGMA (0.07)
- ruview_metrics.rs: NUM_KEYPOINTS (17), VISIBILITY_THRESHOLD (0.5),
  PCK_THRESHOLD (0.2), MIN_BBOX_DIAG (1e-3), MIN_DURATION_MINUTES (1e-6)
- subcarrier.rs: SPARSE_BASIS_SIGMA (0.15), SPARSE_BASIS_THRESHOLD (1e-4),
  SPARSE_REGULARIZATION_LAMBDA (0.1), SPARSE_COO_PRUNE_EPS (1e-8),
  SPARSE_SOLVER_TOL (1e-5 f64), SPARSE_SOLVER_MAX_ITERS (500)
- eval.rs: MIN_POSITIVE_MPJPE (1e-10)
- domain.rs: LAYER_NORM_EPS (1e-5)
- virtual_aug.rs: BOX_MULLER_U1_FLOOR (1e-10), MIN_ROOM_SCALE (1e-10)

Boundary / characterization tests (pin CURRENT behaviour):
- visibility_threshold_boundary_is_inclusive (>= 0.5 at the edge)
- degenerate_extent_below_floor_is_unscoreable ((0,0,0.0)/0.0, not perfect)
- tracking_zero_duration_does_not_divide_by_zero
- oks_short_array_is_bounded_at_keypoint_count (16 rows, no panic)
- compute_interp_weights_single_target_is_index_zero (target_sc==1)
- sparse_interp_single_target_is_finite
- domain_gap_infinite_when_in_domain_perfect_but_cross_nonzero
- domain_gap_unity_when_everything_perfect
- augment_frame_zero_room_scale_passes_amplitude_finite

Doc-only (no behaviour change):
- rapid_adapt.rs: correct module-doc O(eps) -> O(eps^2) for central differences
- geometry.rs: add # Panics to DeepSets::encode (documents existing assert!)

train --no-default-features: 191 lib (was 176), 303 total (was 288), 0 failed.

Co-Authored-By: claude-flow <ruv@ruv.net>

* feat(nn): ADR-155 M2 §3 — pure-Rust LinearHead::try_new input guard + de-magic softplus threshold

ADR-155 §3 found rf_encoder.rs has no adversarial checkpoint-deserialization
assert — its assert_eq!s in LinearHead::new are construction-time API contracts
on programmer-supplied vectors. This adds the honest, in-scope improvement the
M2 task allows: a pure-Rust *fallible* constructor so weights from an untrusted /
deserialized checkpoint can be shape-validated without panicking.

- Add RfHeadError (WeightShape / BiasShape / VarWeightShape) + Display + Error.
- Add LinearHead::try_new returning Result<Self, RfHeadError>; on success the
  head is byte-identical to LinearHead::new. new() is unchanged (still asserts;
  now documents # Panics and points to try_new) — no behaviour change for
  existing callers.
- De-magic softplus's bare 20.0 overflow threshold into
  SOFTPLUS_LINEAR_THRESHOLD (value unchanged) + pin test.

Tests: try_new_accepts_valid_and_rejects_each_bad_shape (valid == new forward;
each bad shape → typed error), softplus_threshold_unchanged_from_literal.

nn --no-default-features lib: 37 passed (was 35), 0 failed.

Co-Authored-By: claude-flow <ruv@ruv.net>

* perf(nn): ADR-155 M2 §4 — native-conv bench-first → MEASURED-INCONCLUSIVE (no perf change shipped)

The §8 "native-conv naive-loop rewrite" backlog item: DensePoseHead::
apply_conv_layer is a pure-Rust 6-nested-loop conv (benchable on this host, not
tch/ort-gated). Bench-first per the §0 PROOF discipline.

- Add committed criterion bench benches/native_conv_bench.rs measuring forward()
  through the naive conv on representative single-layer configs (--no-default-
  features; no ort download).
- Prototyped a bit-identical range-clamped variant (hoist the per-tap in-bounds
  branch by pre-clamping kh/kw ranges; same ic→kh→kw MAC order ⇒ bit-identical).
  MEASURED before/after on this host: ~35% faster on padding-heavy small-channel
  maps (4.40→2.84 ms) but a ~3% *regression* on channel-heavy maps (11.09→11.48
  ms), all inside a ±20% run-to-run noise floor. Verdict: INCONCLUSIVE — the
  benefit is not robustly positive, so the rewrite is NOT shipped and NOT a
  fabricated speedup. Reverted to the naive loop; honestly deferred (ADR-155 §8).
- Add native_conv_matches_reference: a hand-computed characterization anchor
  (1×1 = scalar MAC; same-padded 3×3 ones = truncated-window sums 9/6/4) pinning
  CURRENT conv behaviour for any future rewrite.

nn --no-default-features lib: 38 passed (was 37), 0 failed. No behaviour change.

Co-Authored-By: claude-flow <ruv@ruv.net>

* docs(adr-155): M2 §8.2 — enumerated host-verifiable P3 backlog clearance + CHANGELOG

Replace the §8 bulk "~40 lower-severity findings" line with the real, enumerated
M2 resolution (§8.2): 7 de-magicked (const + pin == prior literal), 9 boundary
tests, 1 input guard (rf_encoder try_new), 2 doc-only, 1 perf bench-first
MEASURED-INCONCLUSIVE (not shipped). Mark native-conv + rf_encoder RESOLVED;
state which §8 items stay data-gated (GraphPose-Fi/INT4/CSI-JEPA) or tch-gated
(proof/trainer/model panic sites, metrics *_v2 dead code) and ONNX read-lock
upstream-gated — blocked, not dropped. Declare the non-tch-verifiable subset of
§8 cleared.

Validation: train --no-default-features 303 passed (was 288); nn lib 38 (was 35);
workspace --no-default-features 3,293 passed, 0 failed; Python proof VERDICT PASS,
hash f8e76f21…46f7a UNCHANGED bit-exact.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-06-14 00:07:56 -04:00

475 lines
14 KiB
Rust

//! MERIDIAN Phase 3 -- Geometry Encoder with FiLM Conditioning (ADR-027).
//!
//! Permutation-invariant encoding of AP positions into a 64-dim geometry
//! vector, plus FiLM layers for conditioning backbone features on room
//! geometry. Pure Rust, no external dependencies beyond the workspace.
use serde::{Deserialize, Serialize};
const GEOMETRY_DIM: usize = 64;
const NUM_COORDS: usize = 3;
// ---------------------------------------------------------------------------
// Linear layer (pure Rust)
// ---------------------------------------------------------------------------
/// Fully-connected layer: `y = x W^T + b`. Row-major weights `[out, in]`.
#[derive(Debug, Clone)]
struct Linear {
weights: Vec<f32>,
bias: Vec<f32>,
in_f: usize,
#[allow(dead_code)]
out_f: usize,
}
impl Linear {
/// Kaiming-uniform init: U(-k, k), k = sqrt(1/in_f).
fn new(in_f: usize, out_f: usize, seed: u64) -> Self {
let k = (1.0 / in_f as f32).sqrt();
Linear {
weights: det_uniform(in_f * out_f, -k, k, seed),
bias: vec![0.0; out_f],
in_f,
out_f,
}
}
fn forward(&self, x: &[f32]) -> Vec<f32> {
debug_assert_eq!(x.len(), self.in_f);
let mut y = self.bias.clone();
for (j, yj) in y.iter_mut().enumerate() {
let off = j * self.in_f;
let s: f32 = x
.iter()
.zip(self.weights[off..off + self.in_f].iter())
.map(|(&xi, &wi)| xi * wi)
.sum();
*yj += s;
}
y
}
}
/// Deterministic xorshift64 uniform in `[lo, hi)`.
/// Uses 24-bit precision (matching f32 mantissa) for uniform distribution.
fn det_uniform(n: usize, lo: f32, hi: f32, seed: u64) -> Vec<f32> {
let r = hi - lo;
let mut s = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
(0..n)
.map(|_| {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
lo + (s >> 40) as f32 / (1u64 << 24) as f32 * r
})
.collect()
}
fn relu(v: &mut [f32]) {
for x in v.iter_mut() {
if *x < 0.0 {
*x = 0.0;
}
}
}
// ---------------------------------------------------------------------------
// MeridianGeometryConfig
// ---------------------------------------------------------------------------
/// Configuration for the MERIDIAN geometry encoder and FiLM layers.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MeridianGeometryConfig {
/// Number of Fourier frequency bands (default 10).
pub n_frequencies: usize,
/// Spatial scale factor, 1.0 = metres (default 1.0).
pub scale: f32,
/// Output embedding dimension (default 64).
pub geometry_dim: usize,
/// Random seed for weight init (default 42).
pub seed: u64,
}
impl Default for MeridianGeometryConfig {
fn default() -> Self {
MeridianGeometryConfig {
n_frequencies: 10,
scale: 1.0,
geometry_dim: GEOMETRY_DIM,
seed: 42,
}
}
}
// ---------------------------------------------------------------------------
// FourierPositionalEncoding
// ---------------------------------------------------------------------------
/// Fourier positional encoding for 3-D coordinates.
///
/// Per coordinate: `[sin(2^0*pi*x), cos(2^0*pi*x), ..., sin(2^(L-1)*pi*x),
/// cos(2^(L-1)*pi*x)]`. Zero-padded to `geometry_dim`.
pub struct FourierPositionalEncoding {
n_frequencies: usize,
scale: f32,
output_dim: usize,
}
impl FourierPositionalEncoding {
/// Create from config.
pub fn new(cfg: &MeridianGeometryConfig) -> Self {
FourierPositionalEncoding {
n_frequencies: cfg.n_frequencies,
scale: cfg.scale,
output_dim: cfg.geometry_dim,
}
}
/// Encode `[x, y, z]` into a fixed-length vector of `geometry_dim` elements.
pub fn encode(&self, coords: &[f32; 3]) -> Vec<f32> {
let raw = NUM_COORDS * 2 * self.n_frequencies;
let mut enc = Vec::with_capacity(raw.max(self.output_dim));
for &c in coords {
let sc = c * self.scale;
for l in 0..self.n_frequencies {
let f = (2.0f32).powi(l as i32) * std::f32::consts::PI * sc;
enc.push(f.sin());
enc.push(f.cos());
}
}
enc.resize(self.output_dim, 0.0);
enc
}
}
// ---------------------------------------------------------------------------
// DeepSets
// ---------------------------------------------------------------------------
/// Permutation-invariant set encoder: phi each element, mean-pool, then rho.
pub struct DeepSets {
phi: Linear,
rho: Linear,
dim: usize,
}
impl DeepSets {
/// Create from config.
pub fn new(cfg: &MeridianGeometryConfig) -> Self {
let d = cfg.geometry_dim;
DeepSets {
phi: Linear::new(d, d, cfg.seed.wrapping_add(1)),
rho: Linear::new(d, d, cfg.seed.wrapping_add(2)),
dim: d,
}
}
/// Encode a set of embeddings (each of length `geometry_dim`) into one vector.
///
/// # Panics
///
/// Panics if `ap_embeddings` is empty — a permutation-invariant mean-pool
/// over zero elements is undefined. Callers with optional AP sets must guard
/// for the empty case before calling (no behaviour change; documents the
/// existing `assert!`).
pub fn encode(&self, ap_embeddings: &[Vec<f32>]) -> Vec<f32> {
assert!(
!ap_embeddings.is_empty(),
"DeepSets: input set must be non-empty"
);
let n = ap_embeddings.len() as f32;
let mut pooled = vec![0.0f32; self.dim];
for emb in ap_embeddings {
debug_assert_eq!(emb.len(), self.dim);
let mut t = self.phi.forward(emb);
relu(&mut t);
for (p, v) in pooled.iter_mut().zip(t.iter()) {
*p += *v;
}
}
for p in pooled.iter_mut() {
*p /= n;
}
let mut out = self.rho.forward(&pooled);
relu(&mut out);
out
}
}
// ---------------------------------------------------------------------------
// GeometryEncoder
// ---------------------------------------------------------------------------
/// End-to-end encoder: AP positions -> 64-dim geometry vector.
pub struct GeometryEncoder {
pos_embed: FourierPositionalEncoding,
set_encoder: DeepSets,
}
impl GeometryEncoder {
/// Build from config.
pub fn new(cfg: &MeridianGeometryConfig) -> Self {
GeometryEncoder {
pos_embed: FourierPositionalEncoding::new(cfg),
set_encoder: DeepSets::new(cfg),
}
}
/// Encode variable-count AP positions `[x,y,z]` into a fixed-dim vector.
pub fn encode(&self, ap_positions: &[[f32; 3]]) -> Vec<f32> {
let embs: Vec<Vec<f32>> = ap_positions
.iter()
.map(|p| self.pos_embed.encode(p))
.collect();
self.set_encoder.encode(&embs)
}
}
// ---------------------------------------------------------------------------
// FilmLayer
// ---------------------------------------------------------------------------
/// Feature-wise Linear Modulation: `output = gamma(g) * h + beta(g)`.
pub struct FilmLayer {
gamma_proj: Linear,
beta_proj: Linear,
}
impl FilmLayer {
/// Create a FiLM layer. Gamma bias is initialised to 1.0 (identity).
pub fn new(cfg: &MeridianGeometryConfig) -> Self {
let d = cfg.geometry_dim;
let mut gamma_proj = Linear::new(d, d, cfg.seed.wrapping_add(3));
for b in gamma_proj.bias.iter_mut() {
*b = 1.0;
}
FilmLayer {
gamma_proj,
beta_proj: Linear::new(d, d, cfg.seed.wrapping_add(4)),
}
}
/// Modulate `features` by `geometry`: `gamma(geometry) * features + beta(geometry)`.
pub fn modulate(&self, features: &[f32], geometry: &[f32]) -> Vec<f32> {
let gamma = self.gamma_proj.forward(geometry);
let beta = self.beta_proj.forward(geometry);
features
.iter()
.zip(gamma.iter())
.zip(beta.iter())
.map(|((&f, &g), &b)| g * f + b)
.collect()
}
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
fn cfg() -> MeridianGeometryConfig {
MeridianGeometryConfig::default()
}
#[test]
fn fourier_output_dimension_is_64() {
let c = cfg();
let out = FourierPositionalEncoding::new(&c).encode(&[1.0, 2.0, 3.0]);
assert_eq!(out.len(), c.geometry_dim);
}
#[test]
fn fourier_different_coords_different_outputs() {
let enc = FourierPositionalEncoding::new(&cfg());
let a = enc.encode(&[0.0, 0.0, 0.0]);
let b = enc.encode(&[1.0, 0.0, 0.0]);
let c = enc.encode(&[0.0, 1.0, 0.0]);
let d = enc.encode(&[0.0, 0.0, 1.0]);
assert_ne!(a, b);
assert_ne!(a, c);
assert_ne!(a, d);
assert_ne!(b, c);
}
#[test]
fn fourier_values_bounded() {
let out = FourierPositionalEncoding::new(&cfg()).encode(&[5.5, -3.2, 0.1]);
for &v in &out {
assert!(v.abs() <= 1.0 + 1e-6, "got {v}");
}
}
#[test]
fn deepsets_permutation_invariant() {
let c = cfg();
let enc = FourierPositionalEncoding::new(&c);
let ds = DeepSets::new(&c);
let (a, b, d) = (
enc.encode(&[1.0, 0.0, 0.0]),
enc.encode(&[0.0, 2.0, 0.0]),
enc.encode(&[0.0, 0.0, 3.0]),
);
let abc = ds.encode(&[a.clone(), b.clone(), d.clone()]);
let cba = ds.encode(&[d.clone(), b.clone(), a.clone()]);
let bac = ds.encode(&[b.clone(), a.clone(), d.clone()]);
for i in 0..c.geometry_dim {
assert!(
(abc[i] - cba[i]).abs() < 1e-5,
"dim {i}: abc={} cba={}",
abc[i],
cba[i]
);
assert!(
(abc[i] - bac[i]).abs() < 1e-5,
"dim {i}: abc={} bac={}",
abc[i],
bac[i]
);
}
}
#[test]
fn deepsets_variable_ap_count() {
let c = cfg();
let enc = FourierPositionalEncoding::new(&c);
let ds = DeepSets::new(&c);
let one = ds.encode(&[enc.encode(&[1.0, 0.0, 0.0])]);
assert_eq!(one.len(), c.geometry_dim);
let three = ds.encode(&[
enc.encode(&[1.0, 0.0, 0.0]),
enc.encode(&[0.0, 2.0, 0.0]),
enc.encode(&[0.0, 0.0, 3.0]),
]);
assert_eq!(three.len(), c.geometry_dim);
let six = ds.encode(&[
enc.encode(&[1.0, 0.0, 0.0]),
enc.encode(&[0.0, 2.0, 0.0]),
enc.encode(&[0.0, 0.0, 3.0]),
enc.encode(&[-1.0, 0.0, 0.0]),
enc.encode(&[0.0, -2.0, 0.0]),
enc.encode(&[0.0, 0.0, -3.0]),
]);
assert_eq!(six.len(), c.geometry_dim);
assert_ne!(one, three);
assert_ne!(three, six);
}
#[test]
fn geometry_encoder_end_to_end() {
let c = cfg();
let g =
GeometryEncoder::new(&c).encode(&[[1.0, 0.0, 2.5], [0.0, 3.0, 2.5], [-2.0, 1.0, 2.5]]);
assert_eq!(g.len(), c.geometry_dim);
for &v in &g {
assert!(v.is_finite());
}
}
#[test]
fn geometry_encoder_single_ap() {
let c = cfg();
assert_eq!(
GeometryEncoder::new(&c).encode(&[[0.0, 0.0, 0.0]]).len(),
c.geometry_dim
);
}
#[test]
fn film_identity_when_geometry_zero() {
let c = cfg();
let film = FilmLayer::new(&c);
let feat = vec![1.0f32; c.geometry_dim];
let out = film.modulate(&feat, &vec![0.0f32; c.geometry_dim]);
assert_eq!(out.len(), c.geometry_dim);
// gamma_proj(0) = bias = [1.0], beta_proj(0) = bias = [0.0] => identity
for i in 0..c.geometry_dim {
assert!(
(out[i] - feat[i]).abs() < 1e-5,
"dim {i}: expected {}, got {}",
feat[i],
out[i]
);
}
}
#[test]
fn film_nontrivial_modulation() {
let c = cfg();
let film = FilmLayer::new(&c);
let feat: Vec<f32> = (0..c.geometry_dim).map(|i| i as f32 * 0.1).collect();
let geom: Vec<f32> = (0..c.geometry_dim)
.map(|i| (i as f32 - 32.0) * 0.01)
.collect();
let out = film.modulate(&feat, &geom);
assert_eq!(out.len(), c.geometry_dim);
assert!(out
.iter()
.zip(feat.iter())
.any(|(o, f)| (o - f).abs() > 1e-6));
for &v in &out {
assert!(v.is_finite());
}
}
#[test]
fn film_explicit_gamma_beta() {
let c = MeridianGeometryConfig {
geometry_dim: 4,
..cfg()
};
let mut film = FilmLayer::new(&c);
film.gamma_proj.weights = vec![0.0; 16];
film.gamma_proj.bias = vec![2.0, 3.0, 0.5, 1.0];
film.beta_proj.weights = vec![0.0; 16];
film.beta_proj.bias = vec![10.0, 20.0, 30.0, 40.0];
let out = film.modulate(&[1.0, 2.0, 3.0, 4.0], &[999.0; 4]);
let exp = [12.0, 26.0, 31.5, 44.0];
for i in 0..4 {
assert!((out[i] - exp[i]).abs() < 1e-5, "dim {i}");
}
}
#[test]
fn config_defaults() {
let c = MeridianGeometryConfig::default();
assert_eq!(c.n_frequencies, 10);
assert!((c.scale - 1.0).abs() < 1e-6);
assert_eq!(c.geometry_dim, 64);
assert_eq!(c.seed, 42);
}
#[test]
fn config_serde_round_trip() {
let c = MeridianGeometryConfig {
n_frequencies: 8,
scale: 0.5,
geometry_dim: 32,
seed: 123,
};
let j = serde_json::to_string(&c).unwrap();
let d: MeridianGeometryConfig = serde_json::from_str(&j).unwrap();
assert_eq!(d.n_frequencies, 8);
assert!((d.scale - 0.5).abs() < 1e-6);
assert_eq!(d.geometry_dim, 32);
assert_eq!(d.seed, 123);
}
#[test]
fn linear_forward_dim() {
assert_eq!(Linear::new(8, 4, 0).forward(&[1.0; 8]).len(), 4);
}
#[test]
fn linear_zero_input_gives_bias() {
let lin = Linear::new(4, 3, 0);
let out = lin.forward(&[0.0; 4]);
for (oi, bi) in out.iter().zip(lin.bias.iter()) {
assert!((oi - bi).abs() < 1e-6);
}
}
}