#[derive(Debug, Clone, PartialEq)]
pub enum Topology {
Ring,
Star,
SmallWorld { k: usize, p_rewire: f64 },
Random { p_edge: f64 },
AllToAll,
}
impl Default for Topology {
fn default() -> Self {
Self::Ring
}
}
#[derive(Debug, Clone)]
pub struct NetworkConfig {
pub n: usize,
pub coupling: f64,
pub topology: Topology,
pub frequency_spread: f64,
pub base_frequency: f64,
pub dt: f64,
}
impl Default for NetworkConfig {
fn default() -> Self {
Self {
n: 8,
coupling: 1.5,
topology: Topology::Ring,
frequency_spread: 0.3,
base_frequency: 220.0,
dt: 0.01,
}
}
}
pub struct CoupledOscillatorNetwork {
pub config: NetworkConfig,
phases: Vec<f64>,
omega: Vec<f64>,
neighbours: Vec<Vec<usize>>,
t: f64,
rng: u64,
order_param: f64,
}
impl CoupledOscillatorNetwork {
pub fn new(config: NetworkConfig) -> Self {
let mut net = Self {
phases: vec![0.0; config.n],
omega: vec![0.0; config.n],
neighbours: vec![vec![]; config.n],
t: 0.0,
rng: 987654321098765,
order_param: 0.0,
config,
};
net.init();
net
}
fn init(&mut self) {
let n = self.config.n;
self.omega = (0..n)
.map(|i| {
let u = (i as f64 + 0.5) / n as f64;
let u_safe = u.clamp(1e-6, 1.0 - 1e-6);
self.config.frequency_spread
* (std::f64::consts::PI * (u_safe - 0.5)).tan()
})
.collect();
self.phases = (0..n)
.map(|i| 2.0 * std::f64::consts::PI * i as f64 / n as f64)
.collect();
self.neighbours = self.build_adjacency();
}
fn build_adjacency(&mut self) -> Vec<Vec<usize>> {
let n = self.config.n;
match &self.config.topology.clone() {
Topology::Ring => {
(0..n)
.map(|i| vec![(i + n - 1) % n, (i + 1) % n])
.collect()
}
Topology::Star => {
let mut adj = vec![vec![]; n];
for i in 1..n {
adj[0].push(i);
adj[i].push(0);
}
adj
}
Topology::SmallWorld { k, p_rewire } => {
let k = *k;
let p = *p_rewire;
let mut adj: Vec<Vec<usize>> = (0..n)
.map(|i| {
(1..=k / 2)
.flat_map(|d| {
let left = (i + n - d) % n;
let right = (i + d) % n;
vec![left, right]
})
.collect()
})
.collect();
for i in 0..n {
let neighbours = adj[i].clone();
for old_j in neighbours {
if self.lcg_float() < p {
adj[i].retain(|&x| x != old_j);
adj[old_j].retain(|&x| x != i);
let mut new_j = (self.lcg_float() * n as f64) as usize % n;
let mut attempts = 0;
while (new_j == i || adj[i].contains(&new_j)) && attempts < n {
new_j = (self.lcg_float() * n as f64) as usize % n;
attempts += 1;
}
if new_j != i && !adj[i].contains(&new_j) {
adj[i].push(new_j);
adj[new_j].push(i);
}
}
}
}
adj
}
Topology::Random { p_edge } => {
let p = *p_edge;
let mut adj = vec![vec![]; n];
for i in 0..n {
for j in (i + 1)..n {
if self.lcg_float() < p {
adj[i].push(j);
adj[j].push(i);
}
}
}
adj
}
Topology::AllToAll => {
(0..n)
.map(|i| (0..n).filter(|&j| j != i).collect())
.collect()
}
}
}
pub fn step(&mut self) -> Vec<f64> {
let n = self.config.n;
let dt = self.config.dt;
let k = self.config.coupling;
let deriv = |phases: &[f64]| -> Vec<f64> {
(0..n)
.map(|i| {
let nb = &self.neighbours[i];
let deg = nb.len().max(1) as f64;
let coupling_sum: f64 =
nb.iter().map(|&j| (phases[j] - phases[i]).sin()).sum();
self.omega[i] + k / deg * coupling_sum
})
.collect()
};
let k1 = deriv(&self.phases);
let y2: Vec<f64> = self.phases.iter().zip(&k1).map(|(p, d)| p + 0.5 * dt * d).collect();
let k2 = deriv(&y2);
let y3: Vec<f64> = self.phases.iter().zip(&k2).map(|(p, d)| p + 0.5 * dt * d).collect();
let k3 = deriv(&y3);
let y4: Vec<f64> = self.phases.iter().zip(&k3).map(|(p, d)| p + dt * d).collect();
let k4 = deriv(&y4);
for i in 0..n {
self.phases[i] +=
dt / 6.0 * (k1[i] + 2.0 * k2[i] + 2.0 * k3[i] + k4[i]);
self.phases[i] = self.phases[i].rem_euclid(2.0 * std::f64::consts::PI);
}
self.t += dt;
let re: f64 = self.phases.iter().map(|&p| p.cos()).sum::<f64>() / n as f64;
let im: f64 = self.phases.iter().map(|&p| p.sin()).sum::<f64>() / n as f64;
self.order_param = (re * re + im * im).sqrt();
let k1_ref = deriv(&self.phases);
(0..n)
.map(|i| {
let phase_vel = k1_ref[i].abs().min(4.0) / 4.0;
phase_vel
})
.collect()
}
pub fn order_parameter(&self) -> f64 {
self.order_param
}
pub fn frequencies(&self) -> Vec<f64> {
let base = self.config.base_frequency;
self.phases
.iter()
.enumerate()
.map(|(i, &theta)| {
let harmonic = (i + 1) as f64;
base * harmonic * (1.0 + 0.05 * theta.cos())
})
.collect()
}
pub fn phases(&self) -> &[f64] {
&self.phases
}
pub fn adjacency(&self) -> &[Vec<usize>] {
&self.neighbours
}
fn lcg_float(&mut self) -> f64 {
self.rng = self
.rng
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
(self.rng >> 11) as f64 / (1u64 << 53) as f64
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ring_adjacency_size() {
let cfg = NetworkConfig {
n: 6,
topology: Topology::Ring,
..Default::default()
};
let net = CoupledOscillatorNetwork::new(cfg);
for adj in net.adjacency() {
assert_eq!(adj.len(), 2, "ring: each node should have exactly 2 neighbours");
}
}
#[test]
fn star_hub_degree() {
let cfg = NetworkConfig {
n: 5,
topology: Topology::Star,
..Default::default()
};
let net = CoupledOscillatorNetwork::new(cfg);
assert_eq!(net.adjacency()[0].len(), 4, "hub should connect to all spokes");
for i in 1..5 {
assert_eq!(net.adjacency()[i].len(), 1, "spoke should connect only to hub");
}
}
#[test]
fn step_output_length() {
let mut net = CoupledOscillatorNetwork::new(NetworkConfig::default());
let audio = net.step();
assert_eq!(audio.len(), net.config.n);
}
#[test]
fn step_audio_in_range() {
let mut net = CoupledOscillatorNetwork::new(NetworkConfig::default());
for _ in 0..50 {
let audio = net.step();
for a in audio {
assert!((0.0..=1.0).contains(&a), "audio amplitude out of range: {a}");
}
}
}
#[test]
fn order_parameter_in_range() {
let mut net = CoupledOscillatorNetwork::new(NetworkConfig {
n: 8,
coupling: 5.0, topology: Topology::AllToAll,
..Default::default()
});
for _ in 0..500 {
net.step();
}
let r = net.order_parameter();
assert!((0.0..=1.0 + 1e-9).contains(&r), "order parameter out of range: {r}");
}
#[test]
fn phases_wrapped() {
let mut net = CoupledOscillatorNetwork::new(NetworkConfig::default());
for _ in 0..200 {
net.step();
}
for &p in net.phases() {
assert!(p >= 0.0 && p < 2.0 * std::f64::consts::PI + 1e-10,
"phase out of [0, 2π): {p}");
}
}
#[test]
fn small_world_has_some_long_range() {
let cfg = NetworkConfig {
n: 10,
topology: Topology::SmallWorld { k: 2, p_rewire: 0.5 },
..Default::default()
};
let net = CoupledOscillatorNetwork::new(cfg);
assert_eq!(net.phases().len(), 10);
}
}