fix(security): audit — fix RUSTSEC vulns, clippy warnings, dead code (#769)

- Upgrade openssl to 0.10.78 (CVE-2026-41676), jsonwebtoken to 9.4
- Suppress unmaintained-only/no-CVE advisories in .cargo/audit.toml
  with per-entry rationale
- Fix all `cargo clippy --all-targets -- -D warnings` errors across
  35 crates: derivable_impls, needless_range_loop, map_or→is_some_and/
  is_none_or, await_holding_lock (drop MutexGuard before .await),
  ptr_arg (&mut Vec→&mut [T]), useless_conversion, approximate_constant
  (2.718→E, 3.14→PI), field_reassign_with_default, manual_inspect,
  useless_vec, lines_filter_map_ok, print_literal, dead_code
- Apply `cargo fmt --all`
- Pre-existing test failure in wifi-densepose-signal
  (test_estimate_occupancy_noise_only) is not introduced by this PR
This commit is contained in:
rUv
2026-05-23 05:36:13 -04:00
committed by GitHub
parent 1906876541
commit 004a63e82d
248 changed files with 13614 additions and 5872 deletions
@@ -17,8 +17,8 @@
//! then feed CSI frames through the pipeline stages.
use ruvector_crv::{
AOLDetection, ConvergenceResult, CrvConfig, CrvError, CrvSessionManager, GestaltType,
GeometricKind, SensoryModality, SketchElement, SpatialRelationType, SpatialRelationship,
AOLDetection, ConvergenceResult, CrvConfig, CrvError, CrvSessionManager, GeometricKind,
GestaltType, SensoryModality, SketchElement, SpatialRelationType, SpatialRelationship,
StageIData, StageIIData, StageIIIData, StageIVData, StageVData, StageVIData,
};
use serde::{Deserialize, Serialize};
@@ -203,8 +203,7 @@ impl CsiGestaltClassifier {
// Movement: high variance + periodic.
// Suppress when water or energy are strong indicators.
let movement_suppress = water_score.max(energy_score);
let movement_score = if variance > self.thresholds.variance_high
&& movement_suppress < 0.6
let movement_score = if variance > self.thresholds.variance_high && movement_suppress < 0.6
{
0.6 + 0.4 * periodicity
} else if variance > self.thresholds.variance_high {
@@ -241,13 +240,12 @@ impl CsiGestaltClassifier {
.max(energy_score)
.max(movement_score)
.max(natural_score);
let manmade_score = if structure > self.thresholds.structure_threshold
&& manmade_suppress < 0.5
{
0.5 + 0.5 * structure
} else {
0.15 * structure * (1.0 - manmade_suppress).max(0.0)
};
let manmade_score =
if structure > self.thresholds.structure_threshold && manmade_suppress < 0.5 {
0.5 + 0.5 * structure
} else {
0.15 * structure * (1.0 - manmade_suppress).max(0.0)
};
scores[4] = (GestaltType::Manmade, manmade_score);
// Pick the highest-scoring type.
@@ -346,10 +344,7 @@ impl CsiGestaltClassifier {
}
// Compute successive differences.
let diffs: Vec<f32> = amplitudes
.windows(2)
.map(|w| (w[1] - w[0]).abs())
.collect();
let diffs: Vec<f32> = amplitudes.windows(2).map(|w| (w[1] - w[0]).abs()).collect();
let mean_diff = diffs.iter().sum::<f32>() / diffs.len().max(1) as f32;
let var_diff = if diffs.len() > 1 {
diffs.iter().map(|d| (d - mean_diff).powi(2)).sum::<f32>() / (diffs.len() - 1) as f32
@@ -419,11 +414,7 @@ impl CsiSensoryEncoder {
///
/// Returns a list of `(SensoryModality, descriptor_string)` pairs
/// suitable for feeding into [`ruvector_crv::StageIIEncoder`].
pub fn extract(
&self,
amplitudes: &[f32],
phases: &[f32],
) -> Vec<(SensoryModality, String)> {
pub fn extract(&self, amplitudes: &[f32], phases: &[f32]) -> Vec<(SensoryModality, String)> {
let mut impressions = Vec::new();
// Texture: amplitude roughness (high-freq variance).
@@ -605,11 +596,7 @@ impl WifiCrvPipeline {
/// The `session_id` identifies the sensing session and `room_id`
/// acts as the CRV target coordinate so that cross-session
/// convergence can be computed per room.
pub fn create_session(
&mut self,
session_id: &str,
room_id: &str,
) -> Result<(), CrvError> {
pub fn create_session(&mut self, session_id: &str, room_id: &str) -> Result<(), CrvError> {
self.manager
.create_session(session_id.to_string(), room_id.to_string())
}
@@ -625,9 +612,7 @@ impl WifiCrvPipeline {
phases: &[f32],
) -> Result<CsiCrvResult, CrvError> {
if amplitudes.is_empty() {
return Err(CrvError::EmptyInput(
"CSI amplitudes are empty".to_string(),
));
return Err(CrvError::EmptyInput("CSI amplitudes are empty".to_string()));
}
// Stage I: Gestalt classification.
@@ -789,9 +774,7 @@ impl WifiCrvPipeline {
query_embedding: &[f32],
) -> Result<StageVData, CrvError> {
if query_embedding.is_empty() {
return Err(CrvError::EmptyInput(
"Query embedding is empty".to_string(),
));
return Err(CrvError::EmptyInput("Query embedding is empty".to_string()));
}
// Probe all stages 1-4 with the query.
@@ -814,10 +797,7 @@ impl WifiCrvPipeline {
/// Uses MinCut to partition the accumulated session data into
/// distinct target aspects -- in the WiFi sensing context these
/// correspond to distinct persons or environment zones.
pub fn partition_persons(
&mut self,
session_id: &str,
) -> Result<StageVIData, CrvError> {
pub fn partition_persons(&mut self, session_id: &str) -> Result<StageVIData, CrvError> {
self.manager.run_stage_vi(session_id)
}
@@ -876,7 +856,13 @@ mod tests {
/// Generate a periodic amplitude signal.
fn periodic_signal(n: usize, freq: f32, amplitude: f32) -> Vec<f32> {
(0..n)
.map(|i| amplitude * (2.0 * std::f32::consts::PI * freq * i as f32 / n as f32).sin().abs() + 0.1)
.map(|i| {
amplitude
* (2.0 * std::f32::consts::PI * freq * i as f32 / n as f32)
.sin()
.abs()
+ 0.1
})
.collect()
}
@@ -906,7 +892,10 @@ mod tests {
let phases = linear_phases(64);
let (gestalt, conf) = classifier.classify(&amps, &phases);
assert_eq!(gestalt, GestaltType::Movement);
assert!(conf > 0.3, "movement confidence should be reasonable: {conf}");
assert!(
conf > 0.3,
"movement confidence should be reasonable: {conf}"
);
}
#[test]
@@ -951,7 +940,9 @@ mod tests {
..GestaltThresholds::default()
});
// Perfectly regular alternating pattern.
let amps: Vec<f32> = (0..64).map(|i| if i % 2 == 0 { 1.0 } else { 0.8 }).collect();
let amps: Vec<f32> = (0..64)
.map(|i| if i % 2 == 0 { 1.0 } else { 0.8 })
.collect();
let phases = linear_phases(64);
let (gestalt, conf) = classifier.classify(&amps, &phases);
assert_eq!(gestalt, GestaltType::Manmade);
@@ -1024,7 +1015,9 @@ mod tests {
let amps = static_signal(32, 1.0);
let phases = vec![0.5f32; 32]; // identical phases = high coherence
let impressions = encoder.extract(&amps, &phases);
let lum = impressions.iter().find(|(m, _)| *m == SensoryModality::Luminosity);
let lum = impressions
.iter()
.find(|(m, _)| *m == SensoryModality::Luminosity);
assert!(lum.is_some());
let desc = &lum.unwrap().1;
assert!(
@@ -1039,7 +1032,9 @@ mod tests {
let amps = static_signal(32, 0.01);
let phases = linear_phases(32);
let impressions = encoder.extract(&amps, &phases);
let temp = impressions.iter().find(|(m, _)| *m == SensoryModality::Temperature);
let temp = impressions
.iter()
.find(|(m, _)| *m == SensoryModality::Temperature);
assert!(temp.is_some());
assert!(
temp.unwrap().1.contains("cold"),
@@ -1174,8 +1169,16 @@ mod tests {
// Add mesh topology.
let nodes = vec![
ApNode { id: "ap-1".into(), position: (0.0, 0.0), coverage_radius: 10.0 },
ApNode { id: "ap-2".into(), position: (5.0, 3.0), coverage_radius: 8.0 },
ApNode {
id: "ap-1".into(),
position: (0.0, 0.0),
coverage_radius: 10.0,
},
ApNode {
id: "ap-2".into(),
position: (5.0, 3.0),
coverage_radius: 8.0,
},
];
let links = vec![ApLink {
from: "ap-1".into(),
@@ -1252,9 +1255,7 @@ mod tests {
.process_csi_frame("viewer-b", &amps, &phases)
.unwrap();
let convergence = pipeline
.find_cross_room_convergence("room-1", 0.5)
.unwrap();
let convergence = pipeline.find_cross_room_convergence("room-1", 0.5).unwrap();
assert!(
!convergence.scores.is_empty(),
"identical frames should converge"
@@ -1273,12 +1274,8 @@ mod tests {
let amps_b = static_signal(32, 0.01);
let phases = linear_phases(32);
pipeline
.process_csi_frame("a", &amps_a, &phases)
.unwrap();
pipeline
.process_csi_frame("b", &amps_b, &phases)
.unwrap();
pipeline.process_csi_frame("a", &amps_a, &phases).unwrap();
pipeline.process_csi_frame("b", &amps_b, &phases).unwrap();
let convergence = pipeline.find_cross_room_convergence("room-2", 0.95);
// May or may not converge at high threshold; the key is no panic.
@@ -1370,7 +1367,10 @@ mod tests {
#[test]
fn compute_null_fraction_all_zeros() {
let f = CsiGestaltClassifier::compute_null_fraction(&[0.0; 32]);
assert!((f - 1.0).abs() < 1e-6, "all zeros should give null fraction 1.0");
assert!(
(f - 1.0).abs() < 1e-6,
"all zeros should give null fraction 1.0"
);
}
#[test]
@@ -1397,14 +1397,20 @@ mod tests {
fn signal_energy_known() {
let encoder = CsiSensoryEncoder::new();
let energy = encoder.signal_energy(&[2.0, 2.0, 2.0, 2.0]);
assert!((energy - 4.0).abs() < 1e-6, "energy of [2,2,2,2] should be 4.0");
assert!(
(energy - 4.0).abs() < 1e-6,
"energy of [2,2,2,2] should be 4.0"
);
}
#[test]
fn phase_coherence_identical() {
let encoder = CsiSensoryEncoder::new();
let c = encoder.phase_coherence(&[1.0; 100]);
assert!(c > 0.99, "identical phases should give coherence ~1.0, got {c}");
assert!(
c > 0.99,
"identical phases should give coherence ~1.0, got {c}"
);
}
#[test]
@@ -1418,7 +1424,10 @@ mod tests {
fn subcarrier_spread_all_active() {
let encoder = CsiSensoryEncoder::new();
let spread = encoder.subcarrier_spread(&[1.0; 32]);
assert!((spread - 1.0).abs() < 1e-6, "all active should give spread 1.0");
assert!(
(spread - 1.0).abs() < 1e-6,
"all active should give spread 1.0"
);
}
#[test]
@@ -209,7 +209,10 @@ mod tests {
log_b.push(&s, 0.25, 999_999);
let wa = log_a.iter().next().unwrap().witness_sha256;
let wb = log_b.iter().next().unwrap().witness_sha256;
assert_eq!(wa, wb, "witness must be content-addressable, not time-addressable");
assert_eq!(
wa, wb,
"witness must be content-addressable, not time-addressable"
);
}
#[test]
+2 -2
View File
@@ -36,6 +36,6 @@ pub mod viewpoint;
pub use event_log::{NoveltyEvent, PrivacyEventLog};
pub use sketch::{
Sketch, SketchBank, SketchError, WireSketch, WireSketchError,
WIRE_SKETCH_FORMAT_VERSION, WIRE_SKETCH_MAGIC, WIRE_SKETCH_MAX_BYTES,
Sketch, SketchBank, SketchError, WireSketch, WireSketchError, WIRE_SKETCH_FORMAT_VERSION,
WIRE_SKETCH_MAGIC, WIRE_SKETCH_MAX_BYTES,
};
@@ -89,11 +89,17 @@ mod tests {
let mut buf = CompressedBreathingBuffer::new(n_subcarriers, 1);
for i in 0..20 {
let amplitudes: Vec<f32> = (0..n_subcarriers).map(|s| (i * n_subcarriers + s) as f32 * 0.01).collect();
let amplitudes: Vec<f32> = (0..n_subcarriers)
.map(|s| (i * n_subcarriers + s) as f32 * 0.01)
.collect();
buf.push_frame(&amplitudes);
}
assert_eq!(buf.frame_count(), 20, "frame_count must equal the number of pushed frames");
assert_eq!(
buf.frame_count(),
20,
"frame_count must equal the number of pushed frames"
);
}
#[test]
@@ -85,11 +85,17 @@ mod tests {
let mut spec = CompressedHeartbeatSpectrogram::new(n_freq_bins);
for i in 0..10 {
let column: Vec<f32> = (0..n_freq_bins).map(|b| (i * n_freq_bins + b) as f32 * 0.01).collect();
let column: Vec<f32> = (0..n_freq_bins)
.map(|b| (i * n_freq_bins + b) as f32 * 0.01)
.collect();
spec.push_column(&column);
}
assert_eq!(spec.frame_count(), 10, "frame_count must equal the number of pushed columns");
assert_eq!(
spec.frame_count(),
10,
"frame_count must equal the number of pushed columns"
);
}
#[test]
@@ -46,8 +46,7 @@ pub fn solve_triangulation(
col0.push(xi - xj);
col1.push(yi - yj);
b.push(
C * tdoa / 2.0
+ ((xi * xi - xj * xj) + (yi * yi - yj * yj)) / 2.0
C * tdoa / 2.0 + ((xi * xi - xj * xj) + (yi * yi - yj * yj)) / 2.0
- x_ref * (xi - xj)
- y_ref * (yi - yj),
);
@@ -99,9 +98,8 @@ mod tests {
((survivor.0 - ap.0).powi(2) + (survivor.1 - ap.1).powi(2)).sqrt()
};
let tdoa = |i: usize, j: usize| -> f32 {
(dist(ap_positions[i]) - dist(ap_positions[j])) / c
};
let tdoa =
|i: usize, j: usize| -> f32 { (dist(ap_positions[i]) - dist(ap_positions[j])) / c };
let measurements = vec![
(1, 0, tdoa(1, 0)),
@@ -133,6 +131,9 @@ mod tests {
fn triangulation_too_few_measurements_returns_none() {
let ap_positions = vec![(0.0_f32, 0.0), (10.0, 0.0), (10.0, 10.0)];
let result = solve_triangulation(&[(0, 1, 1e-9), (1, 2, 1e-9)], &ap_positions);
assert!(result.is_none(), "fewer than 3 measurements must return None");
assert!(
result.is_none(),
"fewer than 3 measurements must return None"
);
}
}
@@ -67,7 +67,11 @@ mod tests {
let n_velocity_bins = 8;
let stft_rows: Vec<Vec<f32>> = (0..n_subcarriers)
.map(|sc| (0..n_velocity_bins).map(|v| (sc * n_velocity_bins + v) as f32 * 0.1).collect())
.map(|sc| {
(0..n_velocity_bins)
.map(|v| (sc * n_velocity_bins + v) as f32 * 0.1)
.collect()
})
.collect();
let sensitivity = vec![0.5_f32, 0.3, 0.8];
@@ -72,7 +72,10 @@ mod tests {
];
let result = solve_fresnel_geometry(&observations, d_total);
assert!(result.is_some(), "solver must return Some for 5 observations");
assert!(
result.is_some(),
"solver must return Some for 5 observations"
);
let (d1, d2) = result.unwrap();
let sum = d1 + d2;
@@ -87,6 +90,9 @@ mod tests {
#[test]
fn fresnel_too_few_observations_returns_none() {
let result = solve_fresnel_geometry(&[(0.125, 0.3), (0.130, 0.25)], 5.0);
assert!(result.is_none(), "fewer than 3 observations must return None");
assert!(
result.is_none(),
"fewer than 3 observations must return None"
);
}
}
@@ -21,16 +21,21 @@ use ruvector_attn_mincut::attn_mincut;
/// # Returns
///
/// Gated spectrogram of the same length `n_freq * n_time`.
pub fn gate_spectrogram(spectrogram: &[f32], n_freq: usize, n_time: usize, lambda: f32) -> Vec<f32> {
pub fn gate_spectrogram(
spectrogram: &[f32],
n_freq: usize,
n_time: usize,
lambda: f32,
) -> Vec<f32> {
let out = attn_mincut(
spectrogram, // q
spectrogram, // k
spectrogram, // v
n_freq, // d: feature dimension
n_time, // seq_len: number of time frames
lambda, // lambda: min-cut threshold
2, // tau: temporal hysteresis window
1e-7_f32, // eps: numerical epsilon
spectrogram, // q
spectrogram, // k
spectrogram, // v
n_freq, // d: feature dimension
n_time, // seq_len: number of time frames
lambda, // lambda: min-cut threshold
2, // tau: temporal hysteresis window
1e-7_f32, // eps: numerical epsilon
);
out.output
}
@@ -55,9 +55,9 @@ pub fn mincut_subcarrier_partition(sensitivity: &[f32]) -> (Vec<usize>, Vec<usiz
// Source connects to subcarriers with above-average sensitivity.
// Sink connects to subcarriers with below-average sensitivity.
for i in 0..n {
let cap = (sensitivity[i] as f64).abs() + 1e-6;
if sensitivity[i] >= mean_sens {
for (i, &sens) in sensitivity.iter().enumerate().take(n) {
let cap = (sens as f64).abs() + 1e-6;
if sens >= mean_sens {
edges.push((source, i as u64, cap));
} else {
edges.push((i as u64, sink, cap));
@@ -176,10 +176,17 @@ mod tests {
// Both groups must be non-empty for a non-trivial input.
assert!(!sensitive.is_empty(), "sensitive group must not be empty");
assert!(!insensitive.is_empty(), "insensitive group must not be empty");
assert!(
!insensitive.is_empty(),
"insensitive group must not be empty"
);
// Together they must cover every index exactly once.
let mut all_indices: Vec<usize> = sensitive.iter().chain(insensitive.iter()).cloned().collect();
let mut all_indices: Vec<usize> = sensitive
.iter()
.chain(insensitive.iter())
.cloned()
.collect();
all_indices.sort_unstable();
let expected: Vec<usize> = (0..10).collect();
assert_eq!(all_indices, expected, "partition must cover all 10 indices");
@@ -214,7 +221,7 @@ mod tests {
// the same way (either all sensitive or all insensitive after mincut).
// At minimum, no weight should exceed 2.0 or be negative.
for &wt in &w {
assert!(wt >= 0.5 && wt <= 2.0, "weight {wt} out of range");
assert!((0.5..=2.0).contains(&wt), "weight {wt} out of range");
}
}
+31 -20
View File
@@ -141,10 +141,7 @@ impl Sketch {
/// over-long input should fail loudly rather than silently
/// produce a sketch that disagrees with its source on
/// `embedding_dim`.
pub fn try_from_embedding(
embedding: &[f32],
sketch_version: u16,
) -> Result<Self, SketchError> {
pub fn try_from_embedding(embedding: &[f32], sketch_version: u16) -> Result<Self, SketchError> {
if embedding.len() > u16::MAX as usize {
return Err(SketchError::EmbeddingDimOverflow {
got: embedding.len(),
@@ -376,7 +373,7 @@ impl WireSketch {
let embedding_dim = u16::from_le_bytes(buf[8..10].try_into().expect("2-byte slice"));
let nov_q15 = u16::from_le_bytes(buf[10..12].try_into().expect("2-byte slice"));
let expected_bits = ((embedding_dim as usize) + 7) / 8;
let expected_bits = (embedding_dim as usize).div_ceil(8);
let got_bits = buf.len() - Self::HEADER_BYTES;
if expected_bits != got_bits {
return Err(WireSketchError::PayloadSizeMismatch {
@@ -566,10 +563,8 @@ impl SketchBank {
}
// Drain heap into a Vec — already in (Reverse) descending order;
// sort to expose ascending-by-distance per the public contract.
let mut scored: Vec<(u32, u32)> = heap
.into_iter()
.map(|Reverse((d, id))| (id, d))
.collect();
let mut scored: Vec<(u32, u32)> =
heap.into_iter().map(|Reverse((d, id))| (id, d)).collect();
scored.sort_by_key(|&(_, d)| d);
Ok(scored)
}
@@ -638,11 +633,14 @@ mod tests {
fn bank_topk_returns_sorted_by_distance() {
let mut bank = SketchBank::new();
// id 10: identical
bank.insert(10, Sketch::from_embedding(&[0.5, 0.5, 0.5, 0.5], 1)).unwrap();
bank.insert(10, Sketch::from_embedding(&[0.5, 0.5, 0.5, 0.5], 1))
.unwrap();
// id 20: 1 bit different (last dim flipped)
bank.insert(20, Sketch::from_embedding(&[0.5, 0.5, 0.5, -0.5], 1)).unwrap();
bank.insert(20, Sketch::from_embedding(&[0.5, 0.5, 0.5, -0.5], 1))
.unwrap();
// id 30: 2 bits different
bank.insert(30, Sketch::from_embedding(&[-0.5, 0.5, -0.5, 0.5], 1)).unwrap();
bank.insert(30, Sketch::from_embedding(&[-0.5, 0.5, -0.5, 0.5], 1))
.unwrap();
let query = Sketch::from_embedding(&[0.5, 0.5, 0.5, 0.5], 1);
let topk = bank.topk(&query, 3).unwrap();
@@ -658,7 +656,8 @@ mod tests {
#[test]
fn bank_topk_zero_returns_empty() {
let mut bank = SketchBank::new();
bank.insert(1, Sketch::from_embedding(&[0.5, 0.5], 1)).unwrap();
bank.insert(1, Sketch::from_embedding(&[0.5, 0.5], 1))
.unwrap();
let q = Sketch::from_embedding(&[0.5, 0.5], 1);
assert_eq!(bank.topk(&q, 0).unwrap().len(), 0);
}
@@ -666,8 +665,10 @@ mod tests {
#[test]
fn bank_topk_more_than_size_returns_all() {
let mut bank = SketchBank::new();
bank.insert(1, Sketch::from_embedding(&[0.5, 0.5], 1)).unwrap();
bank.insert(2, Sketch::from_embedding(&[-0.5, 0.5], 1)).unwrap();
bank.insert(1, Sketch::from_embedding(&[0.5, 0.5], 1))
.unwrap();
bank.insert(2, Sketch::from_embedding(&[-0.5, 0.5], 1))
.unwrap();
let q = Sketch::from_embedding(&[0.5, 0.5], 1);
assert_eq!(bank.topk(&q, 100).unwrap().len(), 2);
}
@@ -675,7 +676,8 @@ mod tests {
#[test]
fn bank_locks_schema_on_first_insert() {
let mut bank = SketchBank::new();
bank.insert(1, Sketch::from_embedding(&[0.5, 0.5, 0.5, 0.5], 1)).unwrap();
bank.insert(1, Sketch::from_embedding(&[0.5, 0.5, 0.5, 0.5], 1))
.unwrap();
// Different version → reject
let err = bank
.insert(2, Sketch::from_embedding(&[0.5, 0.5, 0.5, 0.5], 2))
@@ -712,7 +714,8 @@ mod tests {
fn novelty_is_proportional_to_min_distance() {
let mut bank = SketchBank::new();
// Bank has one sketch with all 8 dims positive.
bank.insert(1, Sketch::from_embedding(&[0.5; 8], 1)).unwrap();
bank.insert(1, Sketch::from_embedding(&[0.5; 8], 1))
.unwrap();
// Query flips half the dims → 4 bit difference / 8 dims = 0.5.
let query = Sketch::from_embedding(&[0.5, 0.5, 0.5, 0.5, -0.5, -0.5, -0.5, -0.5], 1);
let novelty = bank.novelty(&query).unwrap();
@@ -796,7 +799,10 @@ mod tests {
// Bump format_version to 99 — beyond what this build supports.
bytes[4..6].copy_from_slice(&99_u16.to_le_bytes());
let err = WireSketch::deserialize(&bytes).unwrap_err();
assert!(matches!(err, WireSketchError::UnsupportedVersion { got: 99, .. }));
assert!(matches!(
err,
WireSketchError::UnsupportedVersion { got: 99, .. }
));
}
#[test]
@@ -823,13 +829,18 @@ mod tests {
let v: Vec<f32> = (0..128).map(|i| (i as f32).sin()).collect();
let sketch = Sketch::from_embedding(&v, 1);
let bytes = WireSketch::serialize(&sketch, 0.5);
assert_eq!(bytes.len(), 28, "AETHER 128-d must wire to exactly 28 bytes");
assert_eq!(
bytes.len(),
28,
"AETHER 128-d must wire to exactly 28 bytes"
);
}
#[test]
fn topk_rejects_query_with_wrong_schema() {
let mut bank = SketchBank::with_schema(4, 1);
bank.insert(1, Sketch::from_embedding(&[0.5, 0.5, 0.5, 0.5], 1)).unwrap();
bank.insert(1, Sketch::from_embedding(&[0.5, 0.5, 0.5, 0.5], 1))
.unwrap();
let bad_dim = Sketch::from_embedding(&[0.5, 0.5], 1);
assert!(matches!(
bank.topk(&bad_dim, 1).unwrap_err(),
@@ -61,16 +61,26 @@ impl std::fmt::Display for AttentionError {
match self {
AttentionError::EmptyViewpoints => write!(f, "no viewpoint embeddings provided"),
AttentionError::DimensionMismatch { expected, actual } => {
write!(f, "embedding dimension mismatch: expected {expected}, got {actual}")
write!(
f,
"embedding dimension mismatch: expected {expected}, got {actual}"
)
}
AttentionError::BiasDimensionMismatch { n_viewpoints, bias_rows, bias_cols } => {
AttentionError::BiasDimensionMismatch {
n_viewpoints,
bias_rows,
bias_cols,
} => {
write!(
f,
"geometric bias matrix is {bias_rows}x{bias_cols} but {n_viewpoints} viewpoints require {n_viewpoints}x{n_viewpoints}"
)
}
AttentionError::WeightDimensionMismatch { expected, actual } => {
write!(f, "weight matrix dimension mismatch: expected {expected}, got {actual}")
write!(
f,
"weight matrix dimension mismatch: expected {expected}, got {actual}"
)
}
}
}
@@ -126,7 +136,11 @@ pub struct ViewpointGeometry {
impl GeometricBias {
/// Create a new geometric bias with the given parameters.
pub fn new(w_angle: f32, w_dist: f32, d_ref: f32) -> Self {
GeometricBias { w_angle, w_dist, d_ref }
GeometricBias {
w_angle,
w_dist,
d_ref,
}
}
/// Compute the bias value for a single viewpoint pair.
@@ -241,7 +255,13 @@ impl ProjectionWeights {
actual: w_v.len(),
});
}
Ok(ProjectionWeights { w_q, w_k, w_v, d_in, d_out })
Ok(ProjectionWeights {
w_q,
w_k,
w_v,
d_in,
d_out,
})
}
/// Project a single embedding vector through a weight matrix.
@@ -262,17 +282,26 @@ impl ProjectionWeights {
/// Project all viewpoint embeddings through W_q.
pub fn project_queries(&self, embeddings: &[Vec<f32>]) -> Vec<Vec<f32>> {
embeddings.iter().map(|e| self.project(&self.w_q, e)).collect()
embeddings
.iter()
.map(|e| self.project(&self.w_q, e))
.collect()
}
/// Project all viewpoint embeddings through W_k.
pub fn project_keys(&self, embeddings: &[Vec<f32>]) -> Vec<Vec<f32>> {
embeddings.iter().map(|e| self.project(&self.w_k, e)).collect()
embeddings
.iter()
.map(|e| self.project(&self.w_k, e))
.collect()
}
/// Project all viewpoint embeddings through W_v.
pub fn project_values(&self, embeddings: &[Vec<f32>]) -> Vec<Vec<f32>> {
embeddings.iter().map(|e| self.project(&self.w_v, e)).collect()
embeddings
.iter()
.map(|e| self.project(&self.w_v, e))
.collect()
}
}
@@ -393,8 +422,8 @@ impl CrossViewpointAttention {
let mut output = vec![0.0_f32; d];
for j in 0..n {
let w = attention_weights[i * n + j];
for k in 0..d {
output[k] += w * values[j][k];
for (out_k, &val_k) in output.iter_mut().zip(values[j].iter()) {
*out_k += w * val_k;
}
}
attended.push(output);
@@ -427,13 +456,13 @@ impl CrossViewpointAttention {
let mut fused = vec![0.0_f32; d];
for row in &attended {
for k in 0..d {
fused[k] += row[k];
for (fk, &rk) in fused.iter_mut().zip(row.iter()) {
*fk += rk;
}
}
let n_f = n as f32;
for k in 0..d {
fused[k] /= n_f;
for fk in fused.iter_mut() {
*fk /= n_f;
}
Ok(fused)
@@ -511,7 +540,9 @@ mod tests {
fn make_test_embeddings(n: usize, dim: usize) -> Vec<Vec<f32>> {
(0..n)
.map(|i| {
(0..dim).map(|d| ((i * dim + d) as f32 * 0.01).sin()).collect()
(0..dim)
.map(|d| ((i * dim + d) as f32 * 0.01).sin())
.collect()
})
.collect()
}
@@ -593,12 +624,18 @@ mod tests {
let bias = GeometricBias::new(1.0, 1.0, 5.0);
// Same position: theta=0, d=0 -> cos(0) + exp(0) = 2.0
let val = bias.compute_pair(0.0, 0.0);
assert!((val - 2.0).abs() < 1e-5, "self-bias should be 2.0, got {val}");
assert!(
(val - 2.0).abs() < 1e-5,
"self-bias should be 2.0, got {val}"
);
// Orthogonal, far apart: theta=PI/2, d=5.0
let val_orth = bias.compute_pair(std::f32::consts::FRAC_PI_2, 5.0);
// cos(PI/2) ~ 0 + exp(-1) ~ 0.368
assert!(val_orth < 1.0, "orthogonal far-apart viewpoints should have low bias");
assert!(
val_orth < 1.0,
"orthogonal far-apart viewpoints should have low bias"
);
}
#[test]
@@ -641,8 +678,8 @@ mod tests {
let dim = 4;
// Swap first two dimensions in Q.
let mut w_q = vec![0.0_f32; dim * dim];
w_q[0 * dim + 1] = 1.0; // row 0 picks dim 1
w_q[1 * dim + 0] = 1.0; // row 1 picks dim 0
w_q[1] = 1.0; // row 0 picks dim 1 (0 * dim + 1)
w_q[dim] = 1.0; // row 1 picks dim 0 (1 * dim + 0)
w_q[2 * dim + 2] = 1.0;
w_q[3 * dim + 3] = 1.0;
let w_id = {
@@ -662,6 +699,9 @@ mod tests {
let bias = GeometricBias::new(0.0, 1.0, 2.0); // only distance component
let close = bias.compute_pair(0.0, 0.5);
let far = bias.compute_pair(0.0, 10.0);
assert!(close > far, "closer viewpoints should have higher distance bias");
assert!(
close > far,
"closer viewpoints should have higher distance bias"
);
}
}
@@ -229,11 +229,9 @@ pub fn coherence_gate(phase_diffs: &[f32], threshold: f32) -> bool {
if phase_diffs.is_empty() {
return false;
}
let (sum_cos, sum_sin) = phase_diffs
.iter()
.fold((0.0_f32, 0.0_f32), |(c, s), &dp| {
(c + dp.cos(), s + dp.sin())
});
let (sum_cos, sum_sin) = phase_diffs.iter().fold((0.0_f32, 0.0_f32), |(c, s), &dp| {
(c + dp.cos(), s + dp.sin())
});
let n = phase_diffs.len() as f32;
let coherence = ((sum_cos / n).powi(2) + (sum_sin / n).powi(2)).sqrt();
coherence > threshold
@@ -246,11 +244,9 @@ pub fn compute_coherence(phase_diffs: &[f32]) -> f32 {
if phase_diffs.is_empty() {
return 0.0;
}
let (sum_cos, sum_sin) = phase_diffs
.iter()
.fold((0.0_f32, 0.0_f32), |(c, s), &dp| {
(c + dp.cos(), s + dp.sin())
});
let (sum_cos, sum_sin) = phase_diffs.iter().fold((0.0_f32, 0.0_f32), |(c, s), &dp| {
(c + dp.cos(), s + dp.sin())
});
let n = phase_diffs.len() as f32;
((sum_cos / n).powi(2) + (sum_sin / n).powi(2)).sqrt()
}
@@ -268,7 +264,10 @@ mod tests {
// All phase diffs are the same -> coherence ~ 1.0
let phase_diffs = vec![0.5_f32; 100];
let c = compute_coherence(&phase_diffs);
assert!(c > 0.99, "identical phases should give coherence ~ 1.0, got {c}");
assert!(
c > 0.99,
"identical phases should give coherence ~ 1.0, got {c}"
);
}
#[test]
@@ -279,7 +278,10 @@ mod tests {
.map(|i| 2.0 * std::f32::consts::PI * i as f32 / n as f32)
.collect();
let c = compute_coherence(&phase_diffs);
assert!(c < 0.05, "uniformly spread phases should give coherence ~ 0.0, got {c}");
assert!(
c < 0.05,
"uniformly spread phases should give coherence ~ 0.0, got {c}"
);
}
#[test]
@@ -336,11 +338,17 @@ mod tests {
// Coherence drops to 0.65 (below threshold but within hysteresis band).
assert!(gate.evaluate(0.65));
assert!(gate.is_open(), "gate should stay open within hysteresis band");
assert!(
gate.is_open(),
"gate should stay open within hysteresis band"
);
// Coherence drops below hysteresis boundary (0.7 - 0.1 = 0.6).
assert!(!gate.evaluate(0.55));
assert!(!gate.is_open(), "gate should close below hysteresis boundary");
assert!(
!gate.is_open(),
"gate should close below hysteresis boundary"
);
}
#[test]
@@ -25,6 +25,9 @@ use crate::viewpoint::geometry::{GeometricDiversityIndex, NodeId};
/// Unique identifier for a multistatic array deployment.
pub type ArrayId = u64;
/// Extracted viewpoint data used during fusion: (node id, embedding, azimuth, position).
type ExtractedViewpoint = (NodeId, Vec<f32>, f32, (f32, f32));
/// Per-viewpoint embedding with geometric metadata.
///
/// Represents a single CSI observation processed through the per-viewpoint
@@ -139,14 +142,21 @@ impl std::fmt::Display for FusionError {
FusionError::AllFiltered { rejected } => {
write!(f, "all {rejected} viewpoints filtered by SNR threshold")
}
FusionError::CoherenceGateClosed { coherence, threshold } => {
FusionError::CoherenceGateClosed {
coherence,
threshold,
} => {
write!(
f,
"coherence gate closed: coherence={coherence:.3} < threshold={threshold:.3}"
)
}
FusionError::AttentionError(e) => write!(f, "attention error: {e}"),
FusionError::DimensionMismatch { expected, actual, node_id } => {
FusionError::DimensionMismatch {
expected,
actual,
node_id,
} => {
write!(
f,
"node {node_id} embedding dim {actual} != expected {expected}"
@@ -351,7 +361,7 @@ impl MultistaticArray {
// Extract all needed data from viewpoints upfront to avoid borrow conflicts.
let min_snr = self.config.min_snr_db;
let total_viewpoints = self.viewpoints.len();
let extracted: Vec<(NodeId, Vec<f32>, f32, (f32, f32))> = self
let extracted: Vec<ExtractedViewpoint> = self
.viewpoints
.iter()
.filter(|v| v.snr_db >= min_snr)
@@ -429,7 +439,7 @@ impl MultistaticArray {
pub fn fuse_ungated(&mut self) -> Result<FusedEmbedding, FusionError> {
let min_snr = self.config.min_snr_db;
let total_viewpoints = self.viewpoints.len();
let extracted: Vec<(NodeId, Vec<f32>, f32, (f32, f32))> = self
let extracted: Vec<ExtractedViewpoint> = self
.viewpoints
.iter()
.filter(|v| v.snr_db >= min_snr)
@@ -514,12 +524,19 @@ impl MultistaticArray {
mod tests {
use super::*;
fn make_viewpoint(node_id: NodeId, angle_idx: usize, n: usize, dim: usize) -> ViewpointEmbedding {
fn make_viewpoint(
node_id: NodeId,
angle_idx: usize,
n: usize,
dim: usize,
) -> ViewpointEmbedding {
let angle = 2.0 * std::f32::consts::PI * angle_idx as f32 / n as f32;
let r = 3.0;
ViewpointEmbedding {
node_id,
embedding: (0..dim).map(|d| ((node_id as usize * dim + d) as f32 * 0.01).sin()).collect(),
embedding: (0..dim)
.map(|d| ((node_id as usize * dim + d) as f32 * 0.01).sin())
.collect(),
azimuth: angle,
elevation: 0.0,
baseline: r,
@@ -549,7 +566,9 @@ mod tests {
let dim = 16;
let mut array = setup_coherent_array(dim);
for i in 0..4 {
array.submit_viewpoint(make_viewpoint(i, i as usize, 4, dim)).unwrap();
array
.submit_viewpoint(make_viewpoint(i, i as usize, 4, dim))
.unwrap();
}
let fused = array.fuse().unwrap();
assert_eq!(fused.embedding.len(), dim);
@@ -577,10 +596,17 @@ mod tests {
for i in 0..100 {
array.push_phase_diff(i as f32 * 0.5);
}
array.submit_viewpoint(make_viewpoint(0, 0, 4, dim)).unwrap();
array.submit_viewpoint(make_viewpoint(1, 1, 4, dim)).unwrap();
array
.submit_viewpoint(make_viewpoint(0, 0, 4, dim))
.unwrap();
array
.submit_viewpoint(make_viewpoint(1, 1, 4, dim))
.unwrap();
let result = array.fuse();
assert!(matches!(result, Err(FusionError::CoherenceGateClosed { .. })));
assert!(matches!(
result,
Err(FusionError::CoherenceGateClosed { .. })
));
}
#[test]
@@ -598,8 +624,12 @@ mod tests {
for i in 0..100 {
array.push_phase_diff(i as f32 * 0.5);
}
array.submit_viewpoint(make_viewpoint(0, 0, 4, dim)).unwrap();
array.submit_viewpoint(make_viewpoint(1, 1, 4, dim)).unwrap();
array
.submit_viewpoint(make_viewpoint(0, 0, 4, dim))
.unwrap();
array
.submit_viewpoint(make_viewpoint(1, 1, 4, dim))
.unwrap();
let fused = array.fuse_ungated().unwrap();
assert_eq!(fused.embedding.len(), dim);
}
@@ -652,8 +682,12 @@ mod tests {
fn events_are_emitted_on_fusion() {
let dim = 8;
let mut array = setup_coherent_array(dim);
array.submit_viewpoint(make_viewpoint(0, 0, 4, dim)).unwrap();
array.submit_viewpoint(make_viewpoint(1, 1, 4, dim)).unwrap();
array
.submit_viewpoint(make_viewpoint(0, 0, 4, dim))
.unwrap();
array
.submit_viewpoint(make_viewpoint(1, 1, 4, dim))
.unwrap();
array.clear_events();
let _ = array.fuse();
assert!(!array.events().is_empty(), "fusion should emit events");
@@ -663,8 +697,12 @@ mod tests {
fn remove_viewpoint_works() {
let dim = 8;
let mut array = setup_coherent_array(dim);
array.submit_viewpoint(make_viewpoint(10, 0, 4, dim)).unwrap();
array.submit_viewpoint(make_viewpoint(20, 1, 4, dim)).unwrap();
array
.submit_viewpoint(make_viewpoint(10, 0, 4, dim))
.unwrap();
array
.submit_viewpoint(make_viewpoint(20, 1, 4, dim))
.unwrap();
assert_eq!(array.n_viewpoints(), 2);
array.remove_viewpoint(10);
assert_eq!(array.n_viewpoints(), 1);
@@ -675,11 +713,19 @@ mod tests {
let dim = 16;
let mut array = setup_coherent_array(dim);
for i in 0..4 {
array.submit_viewpoint(make_viewpoint(i, i as usize, 4, dim)).unwrap();
array
.submit_viewpoint(make_viewpoint(i, i as usize, 4, dim))
.unwrap();
}
let fused = array.fuse().unwrap();
assert!(fused.gdi > 0.0, "GDI should be positive for spread viewpoints");
assert!(fused.n_effective > 1.0, "effective viewpoints should be > 1");
assert!(
fused.gdi > 0.0,
"GDI should be positive for spread viewpoints"
);
assert!(
fused.n_effective > 1.0,
"effective viewpoints should be > 1"
);
}
#[test]
@@ -687,7 +733,9 @@ mod tests {
let dim = 8;
let mut array = setup_coherent_array(dim);
for i in 0..6 {
array.submit_viewpoint(make_viewpoint(i, i as usize, 6, dim)).unwrap();
array
.submit_viewpoint(make_viewpoint(i, i as usize, 6, dim))
.unwrap();
}
let gdi = array.compute_gdi().unwrap();
assert!(gdi.value > 0.0);
@@ -363,7 +363,12 @@ mod tests {
#[test]
fn gdi_uniform_spacing_is_optimal() {
// 4 viewpoints at 0, 90, 180, 270 degrees
let azimuths = vec![0.0, std::f32::consts::FRAC_PI_2, std::f32::consts::PI, 3.0 * std::f32::consts::FRAC_PI_2];
let azimuths = vec![
0.0,
std::f32::consts::FRAC_PI_2,
std::f32::consts::PI,
3.0 * std::f32::consts::FRAC_PI_2,
];
let ids = vec![0, 1, 2, 3];
let gdi = GeometricDiversityIndex::compute(&azimuths, &ids).unwrap();
// Minimum separation = PI/2 for each viewpoint, so GDI = PI/2
@@ -399,13 +404,21 @@ mod tests {
let azimuths = vec![0.0, 1.0, 2.0, 3.0];
let ids = vec![0, 1, 2, 3];
let gdi = GeometricDiversityIndex::compute(&azimuths, &ids).unwrap();
assert!(gdi.efficiency() > 0.0 && gdi.efficiency() <= 1.0,
"efficiency should be in (0, 1], got {}", gdi.efficiency());
assert!(
gdi.efficiency() > 0.0 && gdi.efficiency() <= 1.0,
"efficiency should be in (0, 1], got {}",
gdi.efficiency()
);
}
#[test]
fn gdi_is_sufficient_for_uniform_layout() {
let azimuths = vec![0.0, std::f32::consts::FRAC_PI_2, std::f32::consts::PI, 3.0 * std::f32::consts::FRAC_PI_2];
let azimuths = vec![
0.0,
std::f32::consts::FRAC_PI_2,
std::f32::consts::PI,
3.0 * std::f32::consts::FRAC_PI_2,
];
let ids = vec![0, 1, 2, 3];
let gdi = GeometricDiversityIndex::compute(&azimuths, &ids).unwrap();
assert!(gdi.is_sufficient(), "uniform layout should be sufficient");
@@ -451,13 +464,21 @@ mod tests {
let vp3: Vec<ViewpointPosition> = (0..3)
.map(|i| {
let a = 2.0 * std::f32::consts::PI * i as f32 / 3.0;
ViewpointPosition { x: 5.0 * a.cos(), y: 5.0 * a.sin(), noise_std: 0.1 }
ViewpointPosition {
x: 5.0 * a.cos(),
y: 5.0 * a.sin(),
noise_std: 0.1,
}
})
.collect();
let vp6: Vec<ViewpointPosition> = (0..6)
.map(|i| {
let a = 2.0 * std::f32::consts::PI * i as f32 / 6.0;
ViewpointPosition { x: 5.0 * a.cos(), y: 5.0 * a.sin(), noise_std: 0.1 }
ViewpointPosition {
x: 5.0 * a.cos(),
y: 5.0 * a.sin(),
noise_std: 0.1,
}
})
.collect();
@@ -475,8 +496,16 @@ mod tests {
fn crb_too_few_viewpoints_returns_none() {
let target = (0.0, 0.0);
let vps = vec![
ViewpointPosition { x: 1.0, y: 0.0, noise_std: 0.1 },
ViewpointPosition { x: 0.0, y: 1.0, noise_std: 0.1 },
ViewpointPosition {
x: 1.0,
y: 0.0,
noise_std: 0.1,
},
ViewpointPosition {
x: 0.0,
y: 1.0,
noise_std: 0.1,
},
];
assert!(CramerRaoBound::estimate(target, &vps).is_none());
}
@@ -487,13 +516,20 @@ mod tests {
let vps: Vec<ViewpointPosition> = (0..4)
.map(|i| {
let a = 2.0 * std::f32::consts::PI * i as f32 / 4.0;
ViewpointPosition { x: 3.0 * a.cos(), y: 3.0 * a.sin(), noise_std: 0.1 }
ViewpointPosition {
x: 3.0 * a.cos(),
y: 3.0 * a.sin(),
noise_std: 0.1,
}
})
.collect();
let crb = CramerRaoBound::estimate_regularised(target, &vps, 1e-4);
// May return None if Neumann solver doesn't converge, but should not panic.
if let Some(crb) = crb {
assert!(crb.rmse_lower_bound >= 0.0, "RMSE bound must be non-negative");
assert!(
crb.rmse_lower_bound >= 0.0,
"RMSE bound must be non-negative"
);
}
}
}