Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
//! Thermodynamic observables: magnetisation, entropy, free energy, overlap.
use crate::state::State;
/// Mean magnetisation: m = (1/n) Σᵢ xᵢ ∈ [1, 1].
pub fn magnetisation(s: &State) -> f32 {
if s.x.is_empty() {
return 0.0;
}
s.x.iter().sum::<f32>() / s.x.len() as f32
}
/// Mean-squared activation: ⟨x²⟩.
pub fn mean_sq(s: &State) -> f32 {
if s.x.is_empty() {
return 0.0;
}
s.x.iter().map(|xi| xi * xi).sum::<f32>() / s.x.len() as f32
}
/// Pattern overlap (Hopfield order parameter):
/// m_μ = (1/n) Σᵢ ξᵢ^μ xᵢ
///
/// Returns `None` if lengths differ.
pub fn overlap(s: &State, pattern: &[f32]) -> Option<f32> {
let n = s.x.len();
if pattern.len() != n || n == 0 {
return None;
}
let sum: f32 = s.x.iter().zip(pattern.iter()).map(|(xi, pi)| xi * pi).sum();
Some(sum / n as f32)
}
/// Approximate configurational entropy (binary case) via:
/// S ≈ n [ p ln p + (1p) ln(1p) ]
/// where p = fraction of spins at +1.
///
/// Returns 0 for edge cases (all ±1).
pub fn binary_entropy(s: &State) -> f32 {
let n = s.x.len();
if n == 0 {
return 0.0;
}
let p_up = s.x.iter().filter(|&&xi| xi > 0.0).count() as f32 / n as f32;
let p_dn = 1.0 - p_up;
let h = |p: f32| {
if p <= 0.0 || p >= 1.0 {
0.0
} else {
-p * p.ln() - (1.0 - p) * (1.0 - p).ln()
}
};
n as f32 * h(p_up) * 0.5 + n as f32 * h(p_dn) * 0.5
}
/// Estimate free energy: F ≈ E T·S = E S/β.
///
/// `energy` should be `model.energy(s)`.
pub fn free_energy(energy: f32, entropy: f32, beta: f32) -> f32 {
energy - entropy / beta
}
/// Running statistics accumulator for energy / dissipation traces.
#[derive(Default, Debug, Clone)]
pub struct Trace {
/// Energy samples (one per recorded step).
pub energies: Vec<f32>,
/// Cumulative dissipation at each recorded step.
pub dissipation: Vec<f64>,
}
impl Trace {
pub fn new() -> Self {
Self::default()
}
/// Record one observation.
pub fn push(&mut self, energy: f32, dissipated_j: f64) {
self.energies.push(energy);
self.dissipation.push(dissipated_j);
}
/// Mean energy over all recorded steps.
pub fn mean_energy(&self) -> f32 {
if self.energies.is_empty() {
return 0.0;
}
self.energies.iter().sum::<f32>() / self.energies.len() as f32
}
/// Total heat shed over all steps.
pub fn total_dissipation(&self) -> f64 {
self.dissipation.last().copied().unwrap_or(0.0)
}
}