use serde::{Deserialize, Serialize};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use super::hardware::{HardwareError, OpticalHardware};
use super::now_timestamp;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ProbePattern {
pub seed: u64,
pub pattern_hash: u64,
}
impl ProbePattern {
pub fn generate(seed: u64, dimensions: (usize, usize)) -> Self {
let mut hasher = DefaultHasher::new();
seed.hash(&mut hasher);
dimensions.hash(&mut hasher);
Self {
seed,
pattern_hash: hasher.finish(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ProbeResponse {
pub probe: ProbePattern,
pub amplitudes: Vec<f32>,
pub total_intensity: f32,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TMatrixFingerprint {
pub responses: Vec<ProbeResponse>,
pub hardware_id: String,
pub temperature_celsius: f32,
pub captured_at: u64,
pub n_modes: usize,
}
#[derive(Clone, Debug)]
pub enum FingerprintValidation {
Valid,
Drifted {
correlation: f32,
estimated_drift: f32,
},
DifferentHardware {
expected_id: String,
actual_id: String,
},
NoFingerprint,
}
impl FingerprintValidation {
pub fn is_usable(&self) -> bool {
matches!(self, Self::Valid | Self::Drifted { .. })
}
pub fn needs_full_calibration(&self) -> bool {
matches!(self, Self::DifferentHardware { .. } | Self::NoFingerprint)
}
}
impl TMatrixFingerprint {
pub const DEFAULT_N_PROBES: usize = 5;
pub const VALID_THRESHOLD: f32 = 0.95;
pub const DIFFERENT_THRESHOLD: f32 = 0.70;
pub fn capture<H: OpticalHardware>(
hardware: &mut H,
n_probes: usize,
) -> Result<Self, HardwareError> {
let mut responses = Vec::with_capacity(n_probes);
let dimensions = hardware.dimensions();
for i in 0..n_probes {
let seed = i as u64 * 12345 + 67890;
let probe = ProbePattern::generate(seed, dimensions);
let hologram = Self::generate_probe_hologram(seed, dimensions);
hardware.display(&hologram)?;
let measurement = hardware.measure()?;
responses.push(ProbeResponse {
probe,
amplitudes: measurement.mode_amplitudes,
total_intensity: measurement.total_intensity,
});
}
Ok(Self {
responses,
hardware_id: hardware.id().to_string(),
temperature_celsius: hardware.temperature()?,
captured_at: now_timestamp(),
n_modes: hardware.n_modes(),
})
}
fn generate_probe_hologram(
seed: u64,
dimensions: (usize, usize),
) -> amari_holographic::optical::BinaryHologram {
use amari_holographic::optical::{
GeometricLeeEncoder, LeeEncoderConfig, OpticalRotorField,
};
let field = OpticalRotorField::random(dimensions, seed);
let config = LeeEncoderConfig {
carrier_frequency: 0.25,
carrier_angle: 0.0,
dimensions,
};
let encoder = GeometricLeeEncoder::new(config);
encoder.encode(&field)
}
pub fn validate<H: OpticalHardware>(
&self,
hardware: &mut H,
) -> Result<FingerprintValidation, HardwareError> {
if hardware.id() != self.hardware_id {
return Ok(FingerprintValidation::DifferentHardware {
expected_id: self.hardware_id.clone(),
actual_id: hardware.id().to_string(),
});
}
let mut correlations = Vec::new();
for stored in &self.responses {
let hologram = Self::generate_probe_hologram(stored.probe.seed, hardware.dimensions());
hardware.display(&hologram)?;
let measurement = hardware.measure()?;
let corr = Self::correlation(&stored.amplitudes, &measurement.mode_amplitudes);
correlations.push(corr);
}
let mean_correlation = if correlations.is_empty() {
0.0
} else {
correlations.iter().sum::<f32>() / correlations.len() as f32
};
if mean_correlation > Self::VALID_THRESHOLD {
Ok(FingerprintValidation::Valid)
} else if mean_correlation > Self::DIFFERENT_THRESHOLD {
Ok(FingerprintValidation::Drifted {
correlation: mean_correlation,
estimated_drift: 1.0 - mean_correlation,
})
} else {
Ok(FingerprintValidation::DifferentHardware {
expected_id: self.hardware_id.clone(),
actual_id: format!("{} (drifted beyond recognition)", hardware.id()),
})
}
}
fn correlation(a: &[f32], b: &[f32]) -> f32 {
if a.len() != b.len() || a.is_empty() {
return 0.0;
}
let n = a.len() as f32;
let mean_a: f32 = a.iter().sum::<f32>() / n;
let mean_b: f32 = b.iter().sum::<f32>() / n;
let mut cov = 0.0f32;
let mut var_a = 0.0f32;
let mut var_b = 0.0f32;
for (ai, bi) in a.iter().zip(b.iter()) {
let da = ai - mean_a;
let db = bi - mean_b;
cov += da * db;
var_a += da * da;
var_b += db * db;
}
let denom = (var_a * var_b).sqrt();
if denom > 1e-10 {
cov / denom
} else {
0.0
}
}
pub fn age_seconds(&self) -> u64 {
let now = now_timestamp();
if now > self.captured_at {
(now - self.captured_at) / 1000
} else {
0
}
}
pub fn is_stale(&self, max_age_seconds: u64) -> bool {
self.age_seconds() > max_age_seconds
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_probe_pattern_generate() {
let p1 = ProbePattern::generate(42, (64, 64));
let p2 = ProbePattern::generate(42, (64, 64));
let p3 = ProbePattern::generate(43, (64, 64));
assert_eq!(p1.seed, p2.seed);
assert_eq!(p1.pattern_hash, p2.pattern_hash);
assert_ne!(p1.pattern_hash, p3.pattern_hash);
}
#[test]
fn test_correlation_identical() {
let a = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let b = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let corr = TMatrixFingerprint::correlation(&a, &b);
assert!((corr - 1.0).abs() < 0.001);
}
#[test]
fn test_correlation_opposite() {
let a = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let b = vec![5.0, 4.0, 3.0, 2.0, 1.0];
let corr = TMatrixFingerprint::correlation(&a, &b);
assert!((corr - (-1.0)).abs() < 0.001);
}
#[test]
fn test_correlation_empty() {
let a: Vec<f32> = vec![];
let b: Vec<f32> = vec![];
let corr = TMatrixFingerprint::correlation(&a, &b);
assert_eq!(corr, 0.0);
}
#[test]
fn test_fingerprint_validation_usable() {
assert!(FingerprintValidation::Valid.is_usable());
assert!(FingerprintValidation::Drifted {
correlation: 0.8,
estimated_drift: 0.2
}
.is_usable());
assert!(!FingerprintValidation::DifferentHardware {
expected_id: "a".to_string(),
actual_id: "b".to_string()
}
.is_usable());
}
}