use crate::core::scalar::ControlScalar;
const SAMPLE_COUNT: usize = 16;
pub struct DriftCompensator<S: ControlScalar> {
kp: S,
ki: S,
integral: S,
samples: [S; SAMPLE_COUNT],
head: usize,
count: usize,
correction: S,
drift_ppb: S,
prev_avg: S,
total_correction: S,
}
impl<S: ControlScalar> DriftCompensator<S> {
pub fn new(kp: S, ki: S) -> Self {
Self {
kp,
ki,
integral: S::zero(),
samples: [S::zero(); SAMPLE_COUNT],
head: 0,
count: 0,
correction: S::zero(),
drift_ppb: S::zero(),
prev_avg: S::zero(),
total_correction: S::zero(),
}
}
pub fn measure_offset(&mut self, offset_ns: S) {
self.samples[self.head] = offset_ns;
self.head = (self.head + 1) % SAMPLE_COUNT;
if self.count < SAMPLE_COUNT {
self.count += 1;
}
}
pub fn average_offset(&self) -> S {
if self.count == 0 {
return S::zero();
}
let mut sum = S::zero();
for i in 0..self.count {
sum += self.samples[i];
}
let n = S::from_f64(self.count as f64);
sum / n
}
pub fn apply_correction(&mut self) -> S {
let avg = self.average_offset();
self.integral += avg * self.ki;
let limit = S::from_f64(1_000_000.0); if self.integral > limit {
self.integral = limit;
} else if self.integral < -limit {
self.integral = -limit;
}
let new_correction = avg * self.kp + self.integral;
let delta_avg = avg - self.prev_avg;
self.drift_ppb = delta_avg * S::from_f64(1_000.0);
self.prev_avg = avg;
self.total_correction += new_correction - self.correction;
self.correction = new_correction;
new_correction
}
pub fn drift_rate_ppb(&self) -> S {
self.drift_ppb
}
pub fn correction_ns(&self) -> S {
self.correction
}
pub fn sample_count(&self) -> usize {
self.count
}
pub fn reset(&mut self) {
self.integral = S::zero();
self.samples = [S::zero(); SAMPLE_COUNT];
self.head = 0;
self.count = 0;
self.correction = S::zero();
self.drift_ppb = S::zero();
self.prev_avg = S::zero();
self.total_correction = S::zero();
}
pub fn feed_samples(&mut self, offsets: &[S]) {
for &o in offsets {
self.measure_offset(o);
}
}
pub fn drift_exceeds(&self, threshold_ns: S) -> bool {
let avg = self.average_offset();
let abs_avg = if avg < S::zero() {
S::zero() - avg
} else {
avg
};
abs_avg > threshold_ns
}
pub fn total_correction(&self) -> S {
self.total_correction
}
}
#[derive(Debug, Clone, Copy)]
pub struct ClockAdjustment<S: ControlScalar> {
pub offset_ns: S,
pub correction_ns: S,
pub drift_ppb: S,
}
impl<S: ControlScalar> ClockAdjustment<S> {
pub fn new(offset_ns: S, correction_ns: S, drift_ppb: S) -> Self {
Self {
offset_ns,
correction_ns,
drift_ppb,
}
}
}
pub struct DriftTable<S: ControlScalar, const N: usize> {
compensators: [DriftCompensator<S>; N],
active: [bool; N],
}
impl<S: ControlScalar, const N: usize> DriftTable<S, N> {
pub fn new(kp: S, ki: S) -> Self {
Self {
compensators: core::array::from_fn(|_| DriftCompensator::new(kp, ki)),
active: [false; N],
}
}
pub fn activate(&mut self, slot: usize) {
if slot < N {
self.active[slot] = true;
}
}
pub fn record_offset(&mut self, slot: usize, offset_ns: S) {
if slot < N && self.active[slot] {
self.compensators[slot].measure_offset(offset_ns);
}
}
pub fn apply_correction(&mut self, slot: usize) -> S {
if slot < N && self.active[slot] {
self.compensators[slot].apply_correction()
} else {
S::zero()
}
}
pub fn drift_ppb(&self, slot: usize) -> S {
if slot < N && self.active[slot] {
self.compensators[slot].drift_rate_ppb()
} else {
S::zero()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_measure_and_average() {
let mut dc = DriftCompensator::<f64>::new(0.1, 0.01);
dc.measure_offset(100.0);
dc.measure_offset(200.0);
dc.measure_offset(300.0);
assert_eq!(dc.sample_count(), 3);
let avg = dc.average_offset();
assert!((avg - 200.0).abs() < 1e-9);
}
#[test]
fn test_apply_correction_reduces_offset() {
let mut dc = DriftCompensator::<f64>::new(0.5, 0.1);
for _ in 0..16 {
dc.measure_offset(1000.0);
}
let corr = dc.apply_correction();
assert!(corr > 0.0);
}
#[test]
fn test_reset_clears_state() {
let mut dc = DriftCompensator::<f32>::new(0.1, 0.01);
dc.measure_offset(500.0);
dc.apply_correction();
dc.reset();
assert_eq!(dc.sample_count(), 0);
assert_eq!(dc.correction_ns(), 0.0f32);
assert_eq!(dc.average_offset(), 0.0f32);
}
#[test]
fn test_drift_exceeds_threshold() {
let mut dc = DriftCompensator::<f64>::new(0.1, 0.01);
dc.feed_samples(&[500.0, 600.0, 700.0]);
assert!(dc.drift_exceeds(100.0));
assert!(!dc.drift_exceeds(10000.0));
}
#[test]
fn test_circular_buffer_overwrites() {
let mut dc = DriftCompensator::<f64>::new(0.1, 0.01);
for i in 0..20 {
dc.measure_offset(i as f64 * 10.0);
}
assert_eq!(dc.sample_count(), SAMPLE_COUNT);
let avg = dc.average_offset();
assert!(avg > 0.0);
}
#[test]
fn test_drift_table() {
let mut table = DriftTable::<f64, 4>::new(0.1, 0.01);
table.activate(0);
table.record_offset(0, 200.0);
table.record_offset(0, 200.0);
let corr = table.apply_correction(0);
assert!(corr > 0.0);
assert_eq!(table.apply_correction(1), 0.0);
}
}