terminals_core/phase/
kuramoto.rs1use std::f32::consts::PI;
2
3#[derive(Debug, Clone, Copy, PartialEq)]
5pub struct OrderParameter {
6 pub r: f32,
8 pub psi: f32,
10}
11
12pub enum Omega {
14 Uniform(f32),
15 PerNode(Vec<f32>),
16}
17
18#[inline]
20pub fn wrap_to_2pi(theta: f32) -> f32 {
21 let two_pi = 2.0 * PI;
22 let mut t = theta % two_pi;
23 if t < 0.0 {
24 t += two_pi;
25 }
26 t
27}
28
29#[inline]
31pub fn wrap_to_pi(theta: f32) -> f32 {
32 let two_pi = 2.0 * PI;
33 let mut t = (theta + PI) % two_pi;
34 if t < 0.0 {
35 t += two_pi;
36 }
37 t - PI
38}
39
40pub fn order_parameter(phases: &[f32]) -> OrderParameter {
44 let n = phases.len();
45 if n == 0 {
46 return OrderParameter { r: 0.0, psi: 0.0 };
47 }
48 let mut c = 0.0f32;
49 let mut s = 0.0f32;
50 for &theta in phases {
51 c += theta.cos();
52 s += theta.sin();
53 }
54 let inv_n = 1.0 / n as f32;
55 c *= inv_n;
56 s *= inv_n;
57 OrderParameter {
58 r: c.hypot(s),
59 psi: s.atan2(c),
60 }
61}
62
63pub fn kuramoto_step(
74 phases: &[f32],
75 omega: &Omega,
76 k: f32,
77 adjacency: Option<&[&[f32]]>,
78 dt: f32,
79 wrap: bool,
80) -> Vec<f32> {
81 let n = phases.len();
82 let mut next = Vec::with_capacity(n);
83
84 for i in 0..n {
85 let theta_i = phases[i];
86 let w_i = match omega {
87 Omega::Uniform(w) => *w,
88 Omega::PerNode(ref ws) => ws[i],
89 };
90
91 let (coupling_sum, degree) = if let Some(adj) = adjacency {
92 let row = adj[i];
93 let mut cs = 0.0f32;
94 let mut deg = 0.0f32;
95 for j in 0..n {
96 if j == i {
97 continue;
98 }
99 let aij = if j < row.len() { row[j] } else { 0.0 };
100 if aij == 0.0 {
101 continue;
102 }
103 cs += aij * (phases[j] - theta_i).sin();
104 deg += aij.abs();
105 }
106 (cs, deg)
107 } else {
108 let mut cs = 0.0f32;
109 for (j, &phase_j) in phases.iter().enumerate().take(n) {
110 if j == i {
111 continue;
112 }
113 cs += (phase_j - theta_i).sin();
114 }
115 let deg = (n as f32 - 1.0).max(1.0);
116 (cs, deg)
117 };
118
119 let coupling = if degree > 0.0 {
120 (k / degree) * coupling_sum
121 } else {
122 0.0
123 };
124
125 let mut val = theta_i + (w_i + coupling) * dt;
126 if wrap {
127 val = wrap_to_pi(val);
128 }
129 next.push(val);
130 }
131
132 next
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn test_order_parameter_synchronized() {
141 let phases = vec![0.0f32; 10];
142 let op = order_parameter(&phases);
143 assert!((op.r - 1.0).abs() < 1e-6, "R = {}", op.r);
144 }
145
146 #[test]
147 fn test_order_parameter_uniform_spread() {
148 let n = 100usize;
149 let phases: Vec<f32> = (0..n).map(|i| 2.0 * PI * i as f32 / n as f32).collect();
150 let op = order_parameter(&phases);
151 assert!(op.r < 0.1, "R = {} (expected < 0.1)", op.r);
152 }
153
154 #[test]
155 fn test_order_parameter_empty() {
156 let op = order_parameter(&[]);
157 assert_eq!(op.r, 0.0);
158 assert_eq!(op.psi, 0.0);
159 }
160
161 #[test]
162 fn test_wrap_to_pi_zero() {
163 assert!((wrap_to_pi(0.0) - 0.0).abs() < 1e-6);
164 }
165
166 #[test]
167 fn test_wrap_to_pi_4pi() {
168 assert!(
169 (wrap_to_pi(4.0 * PI) - 0.0).abs() < 1e-4,
170 "got {}",
171 wrap_to_pi(4.0 * PI)
172 );
173 }
174
175 #[test]
176 fn test_wrap_to_pi_3pi() {
177 let w = wrap_to_pi(3.0 * PI);
178 assert!(
180 (w - (-PI)).abs() < 1e-4 || (w - PI).abs() < 1e-4,
181 "wrap_to_pi(3π) = {}",
182 w
183 );
184 }
185
186 #[test]
187 fn test_wrap_to_2pi_negative() {
188 let w = wrap_to_2pi(-0.5);
189 assert!(w >= 0.0);
190 assert!(w < 2.0 * PI);
191 }
192
193 #[test]
194 fn test_kuramoto_step_converges() {
195 let phases = vec![0.0f32, 1.0, 2.0, 3.0, 4.0, 5.0];
196 let r_before = order_parameter(&phases).r;
197 let next = kuramoto_step(&phases, &Omega::Uniform(0.0), 5.0, None, 0.01, true);
198 let r_after = order_parameter(&next).r;
199 assert!(
200 r_after >= r_before - 0.01,
201 "r_before={} r_after={}",
202 r_before,
203 r_after
204 );
205 }
206
207 #[test]
208 fn test_kuramoto_step_preserves_length() {
209 let phases = vec![0.1f32, 0.2, 0.3];
210 let next = kuramoto_step(&phases, &Omega::Uniform(1.0), 1.0, None, 0.01, true);
211 assert_eq!(next.len(), 3);
212 }
213
214 #[test]
215 fn test_kuramoto_step_per_node_omega() {
216 let phases = vec![0.0f32, 0.0, 0.0];
217 let omega = Omega::PerNode(vec![1.0, 2.0, 3.0]);
218 let next = kuramoto_step(&phases, &omega, 0.0, None, 0.1, false);
219 assert!((next[0] - 0.1).abs() < 1e-6);
220 assert!((next[1] - 0.2).abs() < 1e-6);
221 assert!((next[2] - 0.3).abs() < 1e-6);
222 }
223
224 #[test]
225 fn test_kuramoto_step_strong_coupling_increases_r() {
226 let mut phases = vec![0.0f32, 1.0, 2.0, 3.0, 4.0, 5.0];
229 for _ in 0..1000 {
230 phases = kuramoto_step(&phases, &Omega::Uniform(0.0), 5.0, None, 0.01, true);
231 }
232 let r = order_parameter(&phases).r;
233 assert!(r > 0.99, "R = {} after convergence", r);
234 }
235
236 #[test]
237 fn test_kuramoto_step_no_wrap() {
238 let phases = vec![3.0f32];
239 let next = kuramoto_step(&phases, &Omega::Uniform(1.0), 0.0, None, 1.0, false);
241 assert!((next[0] - 4.0).abs() < 1e-6);
242 }
243}