Skip to main content

terminals_core/phase/
plv.rs

1/// Phase Locking Value: PLV = |⟨exp(i(φ_a − φ_b))⟩|
2///
3/// Measures phase synchronization between two oscillator sequences.
4/// Returns a value in [0, 1] where 1 = perfect locking.
5pub 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        // Constant phase difference → PLV = 1.0
37        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        // Random-ish phases with no consistent relationship → PLV near 0
52        // Use evenly spread differences: d_i = 2π·i/n
53        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]; // shorter
74        let plv = phase_locking_value(&a, &b);
75        // All differences = 0 → PLV = 1.0
76        assert!((plv - 1.0).abs() < 1e-6, "PLV = {}", plv);
77    }
78}