#[derive(Debug, Clone, PartialEq)]
pub struct VanDerPolConfig {
pub mu: f64,
}
impl Default for VanDerPolConfig {
fn default() -> Self {
Self { mu: 1.0 }
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct VanDerPolState {
pub x: f64,
pub y: f64,
}
impl Default for VanDerPolState {
fn default() -> Self {
Self { x: 2.0, y: 0.0 }
}
}
impl VanDerPolState {
pub fn as_array(&self) -> [f64; 2] {
[self.x, self.y]
}
pub fn from_slice(s: &[f64]) -> Self {
Self {
x: s.get(0).copied().unwrap_or(2.0),
y: s.get(1).copied().unwrap_or(0.0),
}
}
}
fn rk4_step(state: [f64; 2], dt: f64, deriv: impl Fn([f64; 2]) -> [f64; 2]) -> [f64; 2] {
let k1 = deriv(state);
let s2 = [state[0] + dt/2.0 * k1[0], state[1] + dt/2.0 * k1[1]];
let k2 = deriv(s2);
let s3 = [state[0] + dt/2.0 * k2[0], state[1] + dt/2.0 * k2[1]];
let k3 = deriv(s3);
let s4 = [state[0] + dt * k3[0], state[1] + dt * k3[1]];
let k4 = deriv(s4);
[
state[0] + dt/6.0 * (k1[0] + 2.0*k2[0] + 2.0*k3[0] + k4[0]),
state[1] + dt/6.0 * (k1[1] + 2.0*k2[1] + 2.0*k3[1] + k4[1]),
]
}
pub struct VanDerPolOscillator {
config: VanDerPolConfig,
state: VanDerPolState,
speed: f64,
}
impl VanDerPolOscillator {
pub fn new(config: VanDerPolConfig) -> Self {
Self {
config,
state: VanDerPolState::default(),
speed: 0.0,
}
}
pub fn state(&self) -> &VanDerPolState {
&self.state
}
pub fn config(&self) -> &VanDerPolConfig {
&self.config
}
pub fn speed(&self) -> f64 {
self.speed
}
pub fn set_state(&mut self, s: VanDerPolState) {
if s.x.is_finite() { self.state.x = s.x; }
if s.y.is_finite() { self.state.y = s.y; }
}
pub fn derivatives(&self, s: &VanDerPolState) -> VanDerPolState {
let mu = self.config.mu;
VanDerPolState {
x: s.y,
y: mu * (1.0 - s.x * s.x) * s.y - s.x,
}
}
pub fn step(&mut self, dt: f64) {
let mu = self.config.mu;
let prev = self.state.as_array();
let next = rk4_step(prev, dt, |s| {
[s[1], mu * (1.0 - s[0] * s[0]) * s[1] - s[0]]
});
let ds = ((next[0] - prev[0]).powi(2) + (next[1] - prev[1]).powi(2)).sqrt();
self.speed = if dt != 0.0 { ds / dt.abs() } else { 0.0 };
self.state = VanDerPolState::from_slice(&next);
}
pub fn state_vec(&self) -> Vec<f64> {
vec![self.state.x, self.state.y]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_default() {
let cfg = VanDerPolConfig::default();
assert!((cfg.mu - 1.0).abs() < 1e-12);
}
#[test]
fn test_state_default() {
let s = VanDerPolState::default();
assert!((s.x - 2.0).abs() < 1e-12);
assert!((s.y - 0.0).abs() < 1e-12);
}
#[test]
fn test_step_changes_state() {
let mut osc = VanDerPolOscillator::new(VanDerPolConfig::default());
let before = osc.state().clone();
osc.step(0.01);
let after = osc.state();
assert!(
(after.x - before.x).abs() > 1e-15 || (after.y - before.y).abs() > 1e-15,
"State must change after a step"
);
}
#[test]
fn test_finite_output_after_many_steps() {
let mut osc = VanDerPolOscillator::new(VanDerPolConfig::default());
for _ in 0..1000 {
osc.step(0.01);
}
let s = osc.state();
assert!(s.x.is_finite(), "x not finite: {}", s.x);
assert!(s.y.is_finite(), "y not finite: {}", s.y);
}
#[test]
fn test_bounded_limit_cycle() {
let mut osc = VanDerPolOscillator::new(VanDerPolConfig::default());
for _ in 0..2000 {
osc.step(0.01);
}
let s = osc.state();
assert!(s.x.abs() < 5.0, "x out of expected limit cycle: {}", s.x);
assert!(s.y.abs() < 10.0, "y out of expected limit cycle: {}", s.y);
}
#[test]
fn test_derivatives_at_initial_state() {
let osc = VanDerPolOscillator::new(VanDerPolConfig::default());
let d = osc.derivatives(&VanDerPolState::default());
assert!((d.x - 0.0).abs() < 1e-12, "dx at (2,0): {}", d.x);
assert!((d.y - (-2.0)).abs() < 1e-12, "dy at (2,0): {}", d.y);
}
#[test]
fn test_speed_positive_after_step() {
let mut osc = VanDerPolOscillator::new(VanDerPolConfig::default());
osc.step(0.01);
assert!(osc.speed() > 0.0, "speed should be positive: {}", osc.speed());
}
#[test]
fn test_set_state_ignores_nan() {
let mut osc = VanDerPolOscillator::new(VanDerPolConfig::default());
osc.set_state(VanDerPolState { x: f64::NAN, y: 3.0 });
assert!((osc.state().x - 2.0).abs() < 1e-12);
assert!((osc.state().y - 3.0).abs() < 1e-12);
}
#[test]
fn test_deterministic() {
let mut osc1 = VanDerPolOscillator::new(VanDerPolConfig::default());
let mut osc2 = VanDerPolOscillator::new(VanDerPolConfig::default());
for _ in 0..500 {
osc1.step(0.01);
osc2.step(0.01);
}
let s1 = osc1.state();
let s2 = osc2.state();
assert!((s1.x - s2.x).abs() < 1e-15);
assert!((s1.y - s2.y).abs() < 1e-15);
}
#[test]
fn test_dt_zero_no_change() {
let mut osc = VanDerPolOscillator::new(VanDerPolConfig::default());
let before = osc.state().clone();
osc.step(0.0);
let after = osc.state();
assert!((after.x - before.x).abs() < 1e-15);
assert!((after.y - before.y).abs() < 1e-15);
}
#[test]
fn test_state_vec_length() {
let osc = VanDerPolOscillator::new(VanDerPolConfig::default());
assert_eq!(osc.state_vec().len(), 2);
}
#[test]
fn test_state_from_slice() {
let s = VanDerPolState::from_slice(&[1.5, -0.5]);
assert!((s.x - 1.5).abs() < 1e-12);
assert!((s.y - (-0.5)).abs() < 1e-12);
}
}