Skip to main content

terminals_core/substrate/
kuramoto.rs

1//! KuramotoProjection — Phase oscillator state.
2//!
3//! Each atom carries a phase θ, natural frequency ω, and coupling strength K.
4//! These feed directly into the Kuramoto model via the existing
5//! `phase::kuramoto::kuramoto_step()`.
6
7use super::projection::{Projection, ProjectionId};
8
9/// 3 floats: theta, omega, coupling = 12 bytes.
10const KURAMOTO_BYTES: usize = 3 * 4;
11
12#[derive(Debug, Clone, Copy)]
13pub struct KuramotoProjection {
14    /// Phase angle θ in radians.
15    pub theta: f32,
16    /// Natural frequency ω (rad/s).
17    pub omega: f32,
18    /// Per-atom coupling strength K.
19    pub coupling: f32,
20}
21
22impl Default for KuramotoProjection {
23    fn default() -> Self {
24        Self {
25            theta: 0.0,
26            omega: 0.0,
27            coupling: 1.0,
28        }
29    }
30}
31
32impl Projection for KuramotoProjection {
33    fn byte_size() -> usize {
34        KURAMOTO_BYTES
35    }
36
37    fn id() -> ProjectionId {
38        ProjectionId::Kuramoto
39    }
40
41    fn read(buf: &[u8]) -> Self {
42        assert!(buf.len() >= KURAMOTO_BYTES);
43        Self {
44            theta: f32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]),
45            omega: f32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]),
46            coupling: f32::from_le_bytes([buf[8], buf[9], buf[10], buf[11]]),
47        }
48    }
49
50    fn write(&self, buf: &mut [u8]) {
51        assert!(buf.len() >= KURAMOTO_BYTES);
52        buf[0..4].copy_from_slice(&self.theta.to_le_bytes());
53        buf[4..8].copy_from_slice(&self.omega.to_le_bytes());
54        buf[8..12].copy_from_slice(&self.coupling.to_le_bytes());
55    }
56
57    fn shape_hash_contribution(&self) -> u32 {
58        let mut hash = 0x811c_9dc5u32;
59        for byte in self.theta.to_bits().to_le_bytes() {
60            hash ^= byte as u32;
61            hash = hash.wrapping_mul(0x0100_0193);
62        }
63        hash
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    use std::f32::consts::PI;
71
72    #[test]
73    fn test_kuramoto_byte_size() {
74        assert_eq!(KuramotoProjection::byte_size(), 12);
75    }
76
77    #[test]
78    fn test_kuramoto_roundtrip() {
79        let proj = KuramotoProjection {
80            theta: PI / 4.0,
81            omega: 1.5,
82            coupling: 2.0,
83        };
84        let mut buf = vec![0u8; KuramotoProjection::byte_size()];
85        proj.write(&mut buf);
86        let restored = KuramotoProjection::read(&buf);
87        assert!((restored.theta - PI / 4.0).abs() < 1e-6);
88        assert!((restored.omega - 1.5).abs() < 1e-6);
89        assert!((restored.coupling - 2.0).abs() < 1e-6);
90    }
91}