use crate::density_matrix::DensityMatrixN;
const PHI_INV: f32 = 0.618033988;
pub fn spectral_annealing_factor(eigenvalues: &[f32]) -> f32 {
if eigenvalues.is_empty() {
return 1.0;
}
let lambda_min = eigenvalues
.iter()
.copied()
.filter(|&l| l > 1e-12)
.fold(f32::MAX, f32::min);
if lambda_min >= f32::MAX {
return 1.0;
}
let factor = -(1.0 + lambda_min.ln());
factor.clamp(PHI_INV.powi(5), PHI_INV.powi(-3)) }
#[derive(Debug, Clone)]
pub struct FreeEnergyMomentum {
history: [f32; 8],
idx: usize,
count: usize,
initial_loss: Option<f32>,
latest_loss: f32,
}
impl FreeEnergyMomentum {
pub fn new() -> Self {
Self {
history: [0.0; 8],
idx: 0,
count: 0,
initial_loss: None,
latest_loss: 0.0,
}
}
pub fn push(&mut self, loss: f32) {
if self.initial_loss.is_none() {
self.initial_loss = Some(loss);
}
self.latest_loss = loss;
self.history[self.idx] = loss;
self.idx = (self.idx + 1) % 8;
self.count += 1;
}
pub fn momentum(&self) -> f32 {
if self.count < 2 {
return 0.0;
}
let mut df = 0.0f32;
let mut weight = 1.0f32;
let n = (self.count - 1).min(7);
for i in 0..n {
let newer = (self.idx + 8 - 1 - i) % 8;
let older = (self.idx + 8 - 2 - i) % 8;
df += weight * (self.history[newer] - self.history[older]);
weight *= PHI_INV;
}
df
}
pub fn should_anneal(&self) -> bool {
if self.count < 8 {
return false;
}
let curr = self.history[(self.idx + 7) % 8];
let prev = self.history[(self.idx + 6) % 8];
let threshold = prev.abs() * PHI_INV * PHI_INV * PHI_INV;
curr > prev + threshold.max(PHI_INV.powi(8)) }
pub fn converged(&self) -> bool {
if self.count < 40 {
return false;
}
if let Some(initial) = self.initial_loss {
let improvement_ratio = 1.0 - (self.latest_loss / initial);
if improvement_ratio < PHI_INV {
return false;
}
} else {
return false;
}
let mom = self.momentum().abs();
let avg = self.history.iter().map(|v| v.abs()).sum::<f32>() / 8.0;
if avg < 1e-6 {
return true;
}
let threshold = PHI_INV.powi(8);
mom / avg < threshold
}
}
pub fn spectral_lr(base_lr: f32, eigenvalues: &[f32], momentum: f32) -> f32 {
let spectral = spectral_annealing_factor(eigenvalues);
let momentum_boost = 1.0 + PHI_INV * momentum.abs().min(2.0);
base_lr * spectral * momentum_boost
}
pub fn eigenvalues(rho: &DensityMatrixN) -> Vec<f32> {
let d = rho.dim;
let mut work = rho.entries.clone();
let mut evals = vec![0.0f32; d];
dreamwell_math::eigen::eigenvalues_hermitian(&mut work, &mut evals, d, 50, 1e-8);
evals
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn spectral_factor_pure_state() {
let evals = vec![1.0, 0.0, 0.0, 0.0, 0.0];
let f = spectral_annealing_factor(&evals);
assert!(f <= 0.2, "pure state should have low factor (converged), got {f}");
}
#[test]
fn spectral_factor_mixed() {
let evals = vec![0.2, 0.2, 0.2, 0.2, 0.2];
let f = spectral_annealing_factor(&evals);
assert!(
(f - PHI_INV).abs() < 0.1,
"mixed state factor should be ~1/φ={PHI_INV}, got {f}"
);
}
#[test]
fn spectral_factor_near_convergence() {
let evals = vec![0.95, 0.01, 0.01, 0.01, 0.02];
let f = spectral_annealing_factor(&evals);
assert!(f > 2.0, "near-convergence should have aggressive factor, got {f}");
}
#[test]
fn spectral_factor_at_golden_equilibrium() {
let evals = vec![0.618, 0.382, 0.0, 0.0, 0.0]; let f = spectral_annealing_factor(&evals);
assert!(f <= 0.2, "golden equilibrium should be near-zero (clamped), got {f}");
}
#[test]
fn momentum_ring_buffer() {
let mut m = FreeEnergyMomentum::new();
m.push(10.0);
m.push(9.0);
m.push(8.0);
let mom = m.momentum();
assert!(mom < 0.0, "decreasing F should give negative momentum, got {mom}");
}
#[test]
fn momentum_phi_decay() {
let mut m = FreeEnergyMomentum::new();
for i in 0..8 {
m.push(100.0 - i as f32);
}
let mom = m.momentum();
assert!(mom < 0.0, "momentum should be negative for decreasing F");
assert!(mom > -10.0, "momentum should be bounded, got {mom}");
}
#[test]
fn convergence_gate_fires() {
let mut m = FreeEnergyMomentum::new();
for i in 0..7 {
m.push(5.0 + i as f32 * 0.1); }
m.push(8.0); assert!(m.should_anneal(), "catastrophic loss spike should trigger annealing");
}
#[test]
fn convergence_gate_stable() {
let mut m = FreeEnergyMomentum::new();
for _ in 0..8 {
m.push(5.0);
}
assert!(!m.should_anneal(), "stable F should not trigger annealing");
}
#[test]
fn spectral_lr_adapts() {
let mixed = vec![0.2, 0.2, 0.2, 0.2, 0.2];
let near_pure = vec![0.95, 0.01, 0.01, 0.01, 0.02];
let lr_mixed = spectral_lr(0.01, &mixed, 0.0);
let lr_near = spectral_lr(0.01, &near_pure, 0.0);
assert!(
lr_near > lr_mixed,
"near-pure lr ({lr_near}) should be > mixed lr ({lr_mixed})"
);
}
#[test]
fn eigenvalues_from_density_matrix() {
let rho = DensityMatrixN::pure_state(0, 5);
let evals = eigenvalues(&rho);
assert_eq!(evals.len(), 5);
assert!(evals[0] > 0.9, "dominant eigenvalue should be ~1.0, got {}", evals[0]);
}
#[test]
fn proof_dephasing_composition_monotonicity() {
let mut rho = DensityMatrixN::equal_superposition(5);
let energies = vec![0.2, 0.3, 0.1, 0.15, 0.25];
let f0 = rho.free_energy(&energies);
rho.dephase(0.1);
let f1 = rho.free_energy(&energies);
rho.dephase(0.1);
let f2 = rho.free_energy(&energies);
assert!(f1 <= f0 + 0.01, "First dephasing: F must not increase ({f1} > {f0})");
assert!(f2 <= f1 + 0.01, "Second dephasing: F must not increase ({f2} > {f1})");
}
#[test]
fn proof_meta_density_matrix_second_law() {
let dim = 10;
let mut meta_rho = DensityMatrixN::equal_superposition(dim);
let energies: Vec<f32> = (0..dim).map(|i| i as f32 * 0.1).collect();
let mut prev_f = meta_rho.free_energy(&energies);
for _ in 0..100 {
meta_rho.dephase(0.05);
let f = meta_rho.free_energy(&energies);
assert!(
f <= prev_f + 0.01,
"Meta-F must not increase under dephasing: {} > {}",
f,
prev_f
);
prev_f = f;
}
}
}