terminals_core/phase/
plv.rs1pub fn phase_locking_value(phi_a: &[f32], phi_b: &[f32]) -> f32 {
6 let n = phi_a.len().min(phi_b.len());
7 if n == 0 {
8 return 0.0;
9 }
10 let mut c = 0.0f32;
11 let mut s = 0.0f32;
12 for i in 0..n {
13 let d = phi_a[i] - phi_b[i];
14 c += d.cos();
15 s += d.sin();
16 }
17 let inv_n = 1.0 / n as f32;
18 c *= inv_n;
19 s *= inv_n;
20 c.hypot(s)
21}
22
23#[cfg(test)]
24mod tests {
25 use super::*;
26
27 #[test]
28 fn test_plv_identical() {
29 let a = vec![0.0f32, 1.0, 2.0, 3.0];
30 let plv = phase_locking_value(&a, &a);
31 assert!((plv - 1.0).abs() < 1e-6, "PLV = {}", plv);
32 }
33
34 #[test]
35 fn test_plv_constant_offset() {
36 let a: Vec<f32> = (0..100).map(|i| i as f32 * 0.1).collect();
38 let b: Vec<f32> = a.iter().map(|&x| x + 0.5).collect();
39 let plv = phase_locking_value(&a, &b);
40 assert!((plv - 1.0).abs() < 1e-4, "PLV = {}", plv);
41 }
42
43 #[test]
44 fn test_plv_empty() {
45 let plv = phase_locking_value(&[], &[]);
46 assert_eq!(plv, 0.0);
47 }
48
49 #[test]
50 fn test_plv_orthogonal() {
51 let n = 100usize;
54 let a: Vec<f32> = (0..n).map(|i| i as f32 * 0.1).collect();
55 let b: Vec<f32> = (0..n)
56 .map(|i| a[i] + 2.0 * std::f32::consts::PI * i as f32 / n as f32)
57 .collect();
58 let plv = phase_locking_value(&a, &b);
59 assert!(plv < 0.1, "PLV = {} (expected < 0.1)", plv);
60 }
61
62 #[test]
63 fn test_plv_range() {
64 let a = vec![0.0f32, 1.0, 2.0];
65 let b = vec![3.0f32, 0.5, 1.5];
66 let plv = phase_locking_value(&a, &b);
67 assert!((0.0..=(1.0 + 1e-6)).contains(&plv), "PLV = {}", plv);
68 }
69
70 #[test]
71 fn test_plv_mismatched_lengths_uses_min() {
72 let a = vec![0.0f32, 0.0, 0.0, 0.0];
73 let b = vec![0.0f32, 0.0]; let plv = phase_locking_value(&a, &b);
75 assert!((plv - 1.0).abs() < 1e-6, "PLV = {}", plv);
77 }
78}