use rand::rngs::SmallRng;
use rand::{Rng, SeedableRng};
use std::f32::consts::PI;
#[derive(Debug)]
pub struct PhaseGenerator {
rng: SmallRng,
coherence: f32,
previous_phases: Vec<f32>,
}
impl PhaseGenerator {
#[must_use]
pub fn new(seed: u64) -> Self {
Self {
rng: SmallRng::seed_from_u64(seed),
coherence: 0.0,
previous_phases: Vec::new(),
}
}
#[must_use]
pub fn with_coherence(mut self, coherence: f32) -> Self {
self.coherence = coherence.clamp(0.0, 1.0);
self
}
pub fn set_coherence(&mut self, coherence: f32) {
self.coherence = coherence.clamp(0.0, 1.0);
}
pub fn generate(&mut self, n_freqs: usize) -> Vec<f32> {
let mut phases = Vec::with_capacity(n_freqs);
for i in 0..n_freqs {
let random_phase = self.rng.random_range(-PI..PI);
let phase = if self.coherence > 0.0 && i < self.previous_phases.len() {
let prev = self.previous_phases[i];
let blended = prev * self.coherence + random_phase * (1.0 - self.coherence);
wrap_phase(blended)
} else {
random_phase
};
phases.push(phase);
}
self.previous_phases = phases.clone();
phases
}
pub fn reset(&mut self, seed: u64) {
self.rng = SmallRng::seed_from_u64(seed);
self.previous_phases.clear();
}
}
#[inline]
fn wrap_phase(phase: f32) -> f32 {
let mut p = phase;
while p > PI {
p -= 2.0 * PI;
}
while p < -PI {
p += 2.0 * PI;
}
p
}
impl Default for PhaseGenerator {
fn default() -> Self {
Self::new(42)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ng8_phase_bounds_basic() {
let mut gen = PhaseGenerator::new(42);
let phases = gen.generate(100);
for &phase in &phases {
assert!(
phase >= -PI && phase <= PI,
"Phase {} out of bounds [-PI, PI]",
phase
);
}
}
#[test]
fn test_ng8_phase_bounds_many_iterations() {
let mut gen = PhaseGenerator::new(12345);
for _ in 0..100 {
let phases = gen.generate(512);
for &phase in &phases {
assert!(phase >= -PI && phase <= PI, "Phase {} out of bounds", phase);
}
}
}
#[test]
fn test_ng8_phase_bounds_with_coherence() {
let mut gen = PhaseGenerator::new(42).with_coherence(0.8);
for _ in 0..50 {
let phases = gen.generate(256);
for &phase in &phases {
assert!(
phase >= -PI && phase <= PI,
"Phase {} out of bounds with coherence",
phase
);
}
}
}
#[test]
fn test_ng8_phase_bounds_small_buffer() {
let mut gen = PhaseGenerator::new(42);
let phases = gen.generate(1);
assert_eq!(phases.len(), 1);
assert!(phases[0] >= -PI && phases[0] <= PI);
}
#[test]
fn test_ng8_phase_bounds_large_buffer() {
let mut gen = PhaseGenerator::new(42);
let phases = gen.generate(4096);
assert_eq!(phases.len(), 4096);
for &phase in &phases {
assert!(phase >= -PI && phase <= PI);
}
}
#[test]
fn test_ng9_deterministic_same_seed() {
let mut gen1 = PhaseGenerator::new(42);
let mut gen2 = PhaseGenerator::new(42);
let phases1 = gen1.generate(100);
let phases2 = gen2.generate(100);
assert_eq!(phases1, phases2, "Same seed should produce same phases");
}
#[test]
fn test_ng9_deterministic_after_reset() {
let mut gen = PhaseGenerator::new(42);
let phases1 = gen.generate(100);
gen.reset(42);
let phases2 = gen.generate(100);
assert_eq!(phases1, phases2, "Reset should restore determinism");
}
#[test]
fn test_ng9_deterministic_multiple_calls() {
let mut gen1 = PhaseGenerator::new(123);
let mut gen2 = PhaseGenerator::new(123);
let p1a = gen1.generate(50);
let p1b = gen1.generate(50);
let p2a = gen2.generate(50);
let p2b = gen2.generate(50);
assert_eq!(p1a, p2a);
assert_eq!(p1b, p2b);
}
#[test]
fn test_ng9_different_seed_different_output() {
let mut gen1 = PhaseGenerator::new(42);
let mut gen2 = PhaseGenerator::new(43);
let phases1 = gen1.generate(100);
let phases2 = gen2.generate(100);
assert_ne!(
phases1, phases2,
"Different seeds should produce different phases"
);
}
#[test]
fn test_ng10_zero_coherence_distribution() {
let mut gen = PhaseGenerator::new(42).with_coherence(0.0);
let mut all_phases = Vec::new();
for _ in 0..100 {
all_phases.extend(gen.generate(100));
}
let n = all_phases.len() as f32;
let q1 = all_phases
.iter()
.filter(|&&p| p >= 0.0 && p < PI / 2.0)
.count();
let q2 = all_phases
.iter()
.filter(|&&p| p >= PI / 2.0 && p <= PI)
.count();
let q3 = all_phases
.iter()
.filter(|&&p| p >= -PI && p < -PI / 2.0)
.count();
let q4 = all_phases
.iter()
.filter(|&&p| p >= -PI / 2.0 && p < 0.0)
.count();
let expected = n / 4.0;
let tolerance = expected * 0.3;
assert!(
(q1 as f32 - expected).abs() < tolerance,
"Q1 distribution off: {} vs expected {}",
q1,
expected
);
assert!(
(q2 as f32 - expected).abs() < tolerance,
"Q2 distribution off: {} vs expected {}",
q2,
expected
);
assert!(
(q3 as f32 - expected).abs() < tolerance,
"Q3 distribution off: {} vs expected {}",
q3,
expected
);
assert!(
(q4 as f32 - expected).abs() < tolerance,
"Q4 distribution off: {} vs expected {}",
q4,
expected
);
}
#[test]
fn test_ng10_zero_coherence_mean_near_zero() {
let mut gen = PhaseGenerator::new(42).with_coherence(0.0);
let mut sum = 0.0;
let mut count = 0;
for _ in 0..100 {
for phase in gen.generate(100) {
sum += phase;
count += 1;
}
}
let mean = sum / count as f32;
assert!(
mean.abs() < 0.2,
"Mean {} should be close to 0 for uniform distribution",
mean
);
}
#[test]
fn test_ng10_full_coherence_stable() {
let mut gen = PhaseGenerator::new(42).with_coherence(1.0);
let phases1 = gen.generate(100);
let phases2 = gen.generate(100);
let mut diff_sum = 0.0;
for (a, b) in phases1.iter().zip(phases2.iter()) {
diff_sum += (a - b).abs();
}
let avg_diff = diff_sum / 100.0;
assert!(
avg_diff < PI / 2.0,
"Full coherence should have similar phases, avg_diff = {}",
avg_diff
);
}
#[test]
fn test_ng10_partial_coherence_intermediate() {
let mut gen = PhaseGenerator::new(42).with_coherence(0.5);
let phases1 = gen.generate(100);
let phases2 = gen.generate(100);
let mut diff_sum = 0.0;
for (a, b) in phases1.iter().zip(phases2.iter()) {
diff_sum += (a - b).abs();
}
for &phase in &phases2 {
assert!(phase >= -PI && phase <= PI);
}
assert!(diff_sum > 0.0);
}
#[test]
fn test_wrap_phase_positive() {
assert!((wrap_phase(PI + 1.0) - (1.0 - PI)).abs() < 0.001);
}
#[test]
fn test_wrap_phase_negative() {
assert!((wrap_phase(-PI - 1.0) - (PI - 1.0)).abs() < 0.001);
}
#[test]
fn test_wrap_phase_in_range() {
assert!((wrap_phase(0.5) - 0.5).abs() < f32::EPSILON);
assert!((wrap_phase(-0.5) - (-0.5)).abs() < f32::EPSILON);
}
#[test]
fn test_phase_generator_default() {
let gen = PhaseGenerator::default();
assert!((gen.coherence - 0.0).abs() < f32::EPSILON);
}
#[test]
fn test_set_coherence() {
let mut gen = PhaseGenerator::new(42);
gen.set_coherence(0.7);
assert!((gen.coherence - 0.7).abs() < 0.001);
}
#[test]
fn test_set_coherence_clamped() {
let mut gen = PhaseGenerator::new(42);
gen.set_coherence(2.0);
assert!((gen.coherence - 1.0).abs() < f32::EPSILON);
gen.set_coherence(-1.0);
assert!((gen.coherence - 0.0).abs() < f32::EPSILON);
}
#[test]
fn test_generate_empty() {
let mut gen = PhaseGenerator::new(42);
let phases = gen.generate(0);
assert!(phases.is_empty());
}
}