use std::f64::consts::PI;
use super::ValueGenerator;
pub struct Sine {
amplitude: f64,
period_ticks: f64,
offset: f64,
}
impl Sine {
pub fn new(amplitude: f64, period_secs: f64, offset: f64, rate: f64) -> Self {
let period_ticks = period_secs * rate;
Self {
amplitude,
period_ticks,
offset,
}
}
}
impl ValueGenerator for Sine {
fn value(&self, tick: u64) -> f64 {
self.offset + self.amplitude * (2.0 * PI * tick as f64 / self.period_ticks).sin()
}
}
#[cfg(test)]
mod tests {
use super::*;
const EPSILON: f64 = 1e-10;
fn sine_rate1(amplitude: f64, period_secs: f64, offset: f64) -> Sine {
Sine::new(amplitude, period_secs, offset, 1.0)
}
#[test]
fn sine_at_tick_zero_returns_offset() {
let gen = sine_rate1(5.0, 40.0, 10.0);
assert!(
(gen.value(0) - 10.0).abs() < EPSILON,
"value at tick 0 must equal offset"
);
}
#[test]
fn sine_at_quarter_period_returns_offset_plus_amplitude() {
let amplitude = 5.0;
let period_secs = 40.0;
let offset = 10.0;
let gen = sine_rate1(amplitude, period_secs, offset);
let quarter_tick = (period_secs / 4.0) as u64; let expected = offset + amplitude;
assert!(
(gen.value(quarter_tick) - expected).abs() < EPSILON,
"value at quarter-period tick {} must be ~{} but got {}",
quarter_tick,
expected,
gen.value(quarter_tick)
);
}
#[test]
fn sine_at_half_period_returns_offset() {
let amplitude = 5.0;
let period_secs = 40.0;
let offset = 10.0;
let gen = sine_rate1(amplitude, period_secs, offset);
let half_tick = (period_secs / 2.0) as u64; assert!(
(gen.value(half_tick) - offset).abs() < EPSILON,
"value at half-period tick must equal offset"
);
}
#[test]
fn sine_symmetry_around_offset() {
let amplitude = 5.0;
let period_secs = 40.0;
let offset = 10.0;
let gen = sine_rate1(amplitude, period_secs, offset);
let half_period = (period_secs / 2.0) as u64;
for t in 0..10u64 {
let sum = gen.value(t) + gen.value(t + half_period);
assert!(
(sum - 2.0 * offset).abs() < EPSILON,
"symmetry violated at tick {t}: sum={sum}, expected {}",
2.0 * offset
);
}
}
#[test]
fn sine_at_full_period_returns_offset() {
let amplitude = 5.0;
let period_secs = 40.0;
let offset = 10.0;
let gen = sine_rate1(amplitude, period_secs, offset);
let full_tick = period_secs as u64; assert!(
(gen.value(full_tick) - offset).abs() < EPSILON,
"value at full period must equal offset"
);
}
#[test]
fn sine_period_ticks_pre_computed_from_rate() {
let amplitude = 3.0;
let period_secs = 4.0;
let offset = 0.0;
let rate = 10.0;
let gen = Sine::new(amplitude, period_secs, offset, rate);
assert!(
(gen.value(10) - amplitude).abs() < EPSILON,
"rate-adjusted quarter-period must hit amplitude"
);
}
}