use crate::core::scalar::ControlScalar;
#[inline]
fn lcg_next(state: &mut u64) -> f64 {
*state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
(*state >> 11) as f64 / (1u64 << 53) as f64
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DropoutError {
InvalidProbability,
InvalidState,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PacketStatus<S> {
Received(S),
Dropped,
}
pub struct BernoulliDropout<S> {
drop_rate: S,
lcg_state: u64,
last_received: S,
total_sent: usize,
total_dropped: usize,
}
impl<S: ControlScalar> BernoulliDropout<S> {
pub fn new(drop_rate: S, seed: u64) -> Result<Self, DropoutError> {
if drop_rate < S::ZERO || drop_rate > S::ONE {
return Err(DropoutError::InvalidProbability);
}
Ok(Self {
drop_rate,
lcg_state: seed,
last_received: S::ZERO,
total_sent: 0,
total_dropped: 0,
})
}
pub fn transmit(&mut self, value: S) -> PacketStatus<S> {
self.total_sent += 1;
let r = lcg_next(&mut self.lcg_state);
if S::from_f64(r) < self.drop_rate {
self.total_dropped += 1;
PacketStatus::Dropped
} else {
self.last_received = value;
PacketStatus::Received(value)
}
}
pub fn last_held_value(&self) -> S {
self.last_received
}
pub fn drop_rate_actual(&self) -> S {
if self.total_sent == 0 {
return S::ZERO;
}
S::from_f64(self.total_dropped as f64 / self.total_sent as f64)
}
pub fn total_sent(&self) -> usize {
self.total_sent
}
pub fn total_dropped(&self) -> usize {
self.total_dropped
}
}
pub struct MarkovDropout<S> {
p_good: S,
p_bad: S,
q_gb: S,
q_bg: S,
state: bool,
lcg_state: u64,
last_received: S,
consecutive_drops: usize,
}
impl<S: ControlScalar> MarkovDropout<S> {
pub fn new(p_good: S, p_bad: S, q_gb: S, q_bg: S, seed: u64) -> Result<Self, DropoutError> {
for &p in &[p_good, p_bad, q_gb, q_bg] {
if p < S::ZERO || p > S::ONE {
return Err(DropoutError::InvalidProbability);
}
}
Ok(Self {
p_good,
p_bad,
q_gb,
q_bg,
state: true, lcg_state: seed,
last_received: S::ZERO,
consecutive_drops: 0,
})
}
pub fn transmit(&mut self, value: S) -> PacketStatus<S> {
let r1 = lcg_next(&mut self.lcg_state);
if self.state {
if S::from_f64(r1) < self.q_gb {
self.state = false;
}
} else {
if S::from_f64(r1) < self.q_bg {
self.state = true;
}
}
let r2 = lcg_next(&mut self.lcg_state);
let drop_prob = if self.state { self.p_good } else { self.p_bad };
if S::from_f64(r2) < drop_prob {
self.consecutive_drops += 1;
PacketStatus::Dropped
} else {
self.consecutive_drops = 0;
self.last_received = value;
PacketStatus::Received(value)
}
}
pub fn channel_state_good(&self) -> bool {
self.state
}
pub fn consecutive_drops(&self) -> usize {
self.consecutive_drops
}
pub fn last_held_value(&self) -> S {
self.last_received
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bernoulli_zero_drop_rate_all_received() {
let mut ch = BernoulliDropout::new(0.0_f64, 42).unwrap();
for i in 0..100_usize {
let v = i as f64;
match ch.transmit(v) {
PacketStatus::Received(r) => assert!((r - v).abs() < 1e-12),
PacketStatus::Dropped => panic!("Packet dropped with drop_rate=0"),
}
}
assert_eq!(ch.total_dropped(), 0);
}
#[test]
fn bernoulli_full_drop_rate_all_dropped() {
let mut ch = BernoulliDropout::new(1.0_f64, 99).unwrap();
for i in 0..50_usize {
let v = i as f64 + 1.0;
match ch.transmit(v) {
PacketStatus::Dropped => {}
PacketStatus::Received(_) => panic!("Packet received with drop_rate=1"),
}
}
assert!((ch.last_held_value()).abs() < 1e-12);
}
#[test]
fn bernoulli_zoh_holds_last_received_value() {
let mut ch_recv = BernoulliDropout::new(0.0_f64, 7).unwrap();
ch_recv.transmit(42.0_f64);
assert!((ch_recv.last_held_value() - 42.0).abs() < 1e-12);
let mut ch_drop = BernoulliDropout::new(1.0_f64, 7).unwrap();
for _ in 0..10 {
ch_drop.transmit(99.0_f64);
}
assert!((ch_drop.last_held_value()).abs() < 1e-12);
let mut ch = BernoulliDropout::new(0.0_f64, 55).unwrap();
ch.transmit(core::f64::consts::PI);
assert!((ch.last_held_value() - core::f64::consts::PI).abs() < 1e-9);
}
#[test]
fn bernoulli_actual_drop_rate_close_to_configured() {
let configured = 0.3_f64;
let mut ch = BernoulliDropout::new(configured, 12345).unwrap();
for i in 0..10_000_usize {
ch.transmit(i as f64);
}
let actual = ch.drop_rate_actual();
assert!(
(actual - configured).abs() < 0.05,
"Actual drop rate {actual:.3} too far from configured {configured}"
);
}
#[test]
fn bernoulli_invalid_probability_rejected() {
assert!(BernoulliDropout::<f64>::new(-0.1, 0).is_err());
assert!(BernoulliDropout::<f64>::new(1.1, 0).is_err());
}
#[test]
fn bernoulli_no_send_drop_rate_actual_is_zero() {
let ch = BernoulliDropout::<f64>::new(0.5, 1).unwrap();
assert!((ch.drop_rate_actual()).abs() < 1e-12);
}
#[test]
fn markov_zero_drop_probs_all_received() {
let mut ch = MarkovDropout::new(0.0_f64, 0.0, 0.5, 0.5, 7).unwrap();
for i in 0..50_usize {
let v = i as f64;
match ch.transmit(v) {
PacketStatus::Received(r) => assert!((r - v).abs() < 1e-12),
PacketStatus::Dropped => panic!("Dropped with p=0"),
}
}
}
#[test]
fn markov_consecutive_drop_counting() {
let mut ch = MarkovDropout::new(0.0_f64, 1.0, 1.0, 0.0, 42).unwrap();
assert_eq!(ch.consecutive_drops(), 0);
ch.transmit(1.0_f64); assert_eq!(ch.consecutive_drops(), 1);
ch.transmit(2.0_f64); assert_eq!(ch.consecutive_drops(), 2);
}
#[test]
fn markov_consecutive_drops_reset_on_receive() {
let mut ch = MarkovDropout::new(0.0_f64, 1.0, 1.0, 1.0, 99).unwrap();
let s1 = ch.transmit(1.0_f64);
assert!(matches!(s1, PacketStatus::Dropped));
assert_eq!(ch.consecutive_drops(), 1);
let s2 = ch.transmit(2.0_f64);
assert!(matches!(s2, PacketStatus::Received(_)));
assert_eq!(ch.consecutive_drops(), 0);
}
#[test]
fn markov_invalid_probability_rejected() {
assert!(MarkovDropout::<f64>::new(1.5, 0.5, 0.1, 0.1, 0).is_err());
assert!(MarkovDropout::<f64>::new(0.1, -0.1, 0.1, 0.1, 0).is_err());
assert!(MarkovDropout::<f64>::new(0.1, 0.5, 1.5, 0.1, 0).is_err());
}
#[test]
fn markov_zoh_holds_last_received() {
let mut ch = MarkovDropout::new(0.0_f64, 1.0, 0.0, 0.0, 7).unwrap();
let s = ch.transmit(5.5_f64);
assert!(matches!(s, PacketStatus::Received(_)));
assert!((ch.last_held_value() - 5.5).abs() < 1e-9);
ch.transmit(7.0_f64);
assert!((ch.last_held_value() - 7.0).abs() < 1e-9);
}
#[test]
fn markov_channel_state_initial_good() {
let ch = MarkovDropout::<f64>::new(0.1, 0.9, 0.1, 0.5, 0).unwrap();
assert!(ch.channel_state_good());
}
}