use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, PartialEq)]
pub enum RepetitiveError {
InvalidParameter,
ZeroPeriod,
}
impl core::fmt::Display for RepetitiveError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::InvalidParameter => write!(f, "Invalid parameter value"),
Self::ZeroPeriod => write!(f, "Period N must be non-zero"),
}
}
}
pub struct RepetitiveController<S: ControlScalar, const N: usize> {
buffer: [S; N],
head: usize,
q: S,
kr: S,
trial: usize,
rms_error: S,
}
impl<S: ControlScalar, const N: usize> RepetitiveController<S, N> {
pub fn new(q: S, kr: S) -> Result<Self, RepetitiveError> {
if N == 0 {
return Err(RepetitiveError::ZeroPeriod);
}
if q <= S::ZERO || q >= S::ONE {
return Err(RepetitiveError::InvalidParameter);
}
if kr <= S::ZERO {
return Err(RepetitiveError::InvalidParameter);
}
Ok(Self {
buffer: core::array::from_fn(|_| S::ZERO),
head: 0,
q,
kr,
trial: 0,
rms_error: S::ZERO,
})
}
pub fn update(&mut self, error: S) -> Result<S, RepetitiveError> {
let u_prev = self.buffer[self.head];
let u_r = self.q * u_prev + self.kr * error;
self.buffer[self.head] = u_r;
self.head = (self.head + 1) % N;
let alpha = S::from_f64(0.01);
let one_minus_alpha = S::from_f64(0.99);
self.rms_error = one_minus_alpha * self.rms_error + alpha * error * error;
self.trial += 1;
Ok(u_r)
}
pub fn repetitive_signal(&self) -> S {
let last = (self.head + N - 1) % N;
self.buffer[last]
}
pub fn reset(&mut self) {
for v in self.buffer.iter_mut() {
*v = S::ZERO;
}
self.head = 0;
self.trial = 0;
self.rms_error = S::ZERO;
}
pub fn rms_convergence(&self) -> S {
self.rms_error
}
pub fn trial_count(&self) -> usize {
self.trial
}
pub fn q(&self) -> S {
self.q
}
pub fn kr(&self) -> S {
self.kr
}
}
pub struct ModifiedRepetitiveController<S: ControlScalar, const N: usize> {
buffer: [S; N],
q_buf: [S; N],
head: usize,
q0: S,
q1: S,
kr: S,
}
impl<S: ControlScalar, const N: usize> ModifiedRepetitiveController<S, N> {
pub fn new(q0: S, q1: S, kr: S) -> Result<Self, RepetitiveError> {
if N == 0 {
return Err(RepetitiveError::ZeroPeriod);
}
if q0 + S::TWO * q1 >= S::ONE {
return Err(RepetitiveError::InvalidParameter);
}
if kr <= S::ZERO {
return Err(RepetitiveError::InvalidParameter);
}
if q0 < S::ZERO || q1 < S::ZERO {
return Err(RepetitiveError::InvalidParameter);
}
Ok(Self {
buffer: core::array::from_fn(|_| S::ZERO),
q_buf: core::array::from_fn(|_| S::ZERO),
head: 0,
q0,
q1,
kr,
})
}
pub fn update(&mut self, error: S) -> Result<S, RepetitiveError> {
let center_idx = self.head;
let prev_idx = (self.head + N - 1) % N;
let next_idx = (self.head + 1) % N;
let center = self.buffer[center_idx];
let prev = self.buffer[prev_idx];
let next = self.buffer[next_idx];
let filtered = self.q1 * prev + self.q0 * center + self.q1 * next;
let u_r = filtered + self.kr * error;
self.q_buf[self.head] = filtered;
self.buffer[self.head] = u_r;
self.head = (self.head + 1) % N;
Ok(u_r)
}
pub fn repetitive_signal(&self) -> S {
let last = (self.head + N - 1) % N;
self.buffer[last]
}
pub fn filtered_signal(&self) -> S {
let last = (self.head + N - 1) % N;
self.q_buf[last]
}
pub fn reset(&mut self) {
for v in self.buffer.iter_mut() {
*v = S::ZERO;
}
for v in self.q_buf.iter_mut() {
*v = S::ZERO;
}
self.head = 0;
}
pub fn q0(&self) -> S {
self.q0
}
pub fn q1(&self) -> S {
self.q1
}
pub fn kr(&self) -> S {
self.kr
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zero_error_buffer_stays_zero() {
let mut rc = RepetitiveController::<f64, 10>::new(0.95, 0.5).expect("valid parameters");
for _ in 0..20 {
let u_r = rc.update(0.0).expect("update ok");
assert!(
u_r.abs() < 1e-12,
"Expected zero output for zero error, got {u_r}"
);
}
assert!(
rc.repetitive_signal().abs() < 1e-12,
"Buffer should remain zero"
);
}
#[test]
fn sinusoidal_convergence() {
const PERIOD: usize = 20;
let mut rc = RepetitiveController::<f64, PERIOD>::new(0.95, 0.3).expect("valid parameters");
let mut last_rms = f64::MAX;
let mut converging = false;
for k in 0..(10 * PERIOD) {
let t = k as f64 * 2.0 * core::f64::consts::PI / PERIOD as f64;
let scale = (-((k as f64) / (5.0 * PERIOD as f64))).exp();
let error = scale * t.sin();
let _ = rc.update(error).expect("update ok");
let rms = rc.rms_convergence();
if k > PERIOD {
if rms < last_rms {
converging = true;
}
last_rms = rms;
}
}
let final_rms = rc.rms_convergence();
assert!(
final_rms < 0.1,
"Expected convergence, RMS = {final_rms}, converging = {converging}"
);
}
#[test]
fn q_effect_on_convergence_speed() {
const PERIOD: usize = 16;
let mut rc_high_q =
RepetitiveController::<f64, PERIOD>::new(0.99, 0.5).expect("valid high q");
let mut rc_low_q = RepetitiveController::<f64, PERIOD>::new(0.5, 0.5).expect("valid low q");
for _ in 0..(3 * PERIOD) {
let _ = rc_high_q.update(1.0).expect("update ok");
let _ = rc_low_q.update(1.0).expect("update ok");
}
let sig_high = rc_high_q.repetitive_signal().abs();
let sig_low = rc_low_q.repetitive_signal().abs();
assert!(
sig_high > 0.0,
"High q controller should produce nonzero signal"
);
assert!(
sig_low > 0.0,
"Low q controller should produce nonzero signal"
);
assert!(
sig_high > sig_low,
"High q ({sig_high:.4}) should accumulate more than low q ({sig_low:.4})"
);
}
#[test]
fn kr_validation_zero() {
let result = RepetitiveController::<f64, 10>::new(0.95, 0.0);
assert!(
matches!(result, Err(RepetitiveError::InvalidParameter)),
"kr=0.0 should be rejected"
);
}
#[test]
fn kr_validation_negative() {
let result = RepetitiveController::<f64, 10>::new(0.95, -0.1);
assert!(
matches!(result, Err(RepetitiveError::InvalidParameter)),
"kr<0 should be rejected"
);
}
#[test]
fn q_validation_out_of_range() {
let r1 = RepetitiveController::<f64, 10>::new(1.0, 0.5);
assert!(
matches!(r1, Err(RepetitiveError::InvalidParameter)),
"q=1.0 should be rejected"
);
let r2 = RepetitiveController::<f64, 10>::new(0.0, 0.5);
assert!(
matches!(r2, Err(RepetitiveError::InvalidParameter)),
"q=0.0 should be rejected"
);
let r3 = RepetitiveController::<f64, 10>::new(1.1, 0.5);
assert!(
matches!(r3, Err(RepetitiveError::InvalidParameter)),
"q=1.1 should be rejected"
);
let r4 = RepetitiveController::<f64, 10>::new(-0.1, 0.5);
assert!(
matches!(r4, Err(RepetitiveError::InvalidParameter)),
"q=-0.1 should be rejected"
);
}
#[test]
fn modified_fir_applies() {
let mut mrc =
ModifiedRepetitiveController::<f64, 8>::new(0.8, 0.05, 0.3).expect("valid parameters");
for _ in 0..32 {
let _ = mrc.update(1.0).expect("update ok");
}
let sig = mrc.repetitive_signal();
assert!(
sig > 0.0,
"Modified RC should produce positive signal with positive error, got {sig}"
);
}
#[test]
fn modified_fir_stability_validation() {
let result = ModifiedRepetitiveController::<f64, 8>::new(0.8, 0.2, 0.3);
assert!(
matches!(result, Err(RepetitiveError::InvalidParameter)),
"Stability violation should be rejected"
);
}
#[test]
fn reset_clears_buffer() {
let mut rc = RepetitiveController::<f64, 10>::new(0.95, 0.5).expect("valid parameters");
for _ in 0..20 {
let _ = rc.update(1.0).expect("update ok");
}
assert!(
rc.repetitive_signal().abs() > 1e-6,
"Buffer should be nonzero before reset"
);
assert!(
rc.trial_count() > 0,
"Trial count should be nonzero before reset"
);
rc.reset();
assert!(
rc.repetitive_signal().abs() < 1e-12,
"Buffer should be zero after reset, got {}",
rc.repetitive_signal()
);
assert_eq!(
rc.trial_count(),
0,
"Trial count should be zero after reset"
);
assert!(
rc.rms_convergence().abs() < 1e-12,
"RMS error should be zero after reset"
);
}
#[test]
fn rms_decreases_for_decaying_error() {
const PERIOD: usize = 20;
let mut rc = RepetitiveController::<f64, PERIOD>::new(0.95, 0.5).expect("valid parameters");
for k in 0..PERIOD {
let t = k as f64 * 2.0 * core::f64::consts::PI / PERIOD as f64;
let _ = rc.update(t.sin()).expect("update ok");
}
let rms_after_first_period = rc.rms_convergence();
for period in 1..=5 {
for k in 0..PERIOD {
let t = k as f64 * 2.0 * core::f64::consts::PI / PERIOD as f64;
let scale = 1.0 / (period as f64 + 1.0);
let _ = rc.update(scale * t.sin()).expect("update ok");
}
}
let rms_after_decay = rc.rms_convergence();
assert!(
rms_after_decay < rms_after_first_period,
"RMS should decrease as error decays: {rms_after_decay:.6} vs {rms_after_first_period:.6}"
);
}
#[test]
fn modified_reset_clears_buffer() {
let mut mrc =
ModifiedRepetitiveController::<f64, 8>::new(0.8, 0.05, 0.3).expect("valid parameters");
for _ in 0..24 {
let _ = mrc.update(1.0).expect("update ok");
}
assert!(
mrc.repetitive_signal().abs() > 1e-6,
"Should be nonzero before reset"
);
mrc.reset();
assert!(
mrc.repetitive_signal().abs() < 1e-12,
"Should be zero after reset, got {}",
mrc.repetitive_signal()
);
assert!(
mrc.filtered_signal().abs() < 1e-12,
"Filtered signal should be zero after reset"
);
}
#[test]
fn trial_count_increments() {
let mut rc = RepetitiveController::<f64, 10>::new(0.95, 0.5).expect("valid parameters");
assert_eq!(rc.trial_count(), 0);
for i in 1..=15 {
let _ = rc.update(0.0).expect("update ok");
assert_eq!(rc.trial_count(), i);
}
}
}