use rand::{RngExt, SeedableRng};
use rand_distr::{Distribution, Normal};
use rand_xoshiro::Xoshiro256PlusPlus;
pub fn inject_bitflips(data: &mut [u64], rate: f64, seed: u64) {
if rate <= 0.0 {
return;
}
let mut rng = Xoshiro256PlusPlus::seed_from_u64(seed);
for word in data.iter_mut() {
let mut flip_mask = 0u64;
for bit in 0..64 {
if rng.random::<f64>() < rate {
flip_mask |= 1u64 << bit;
}
}
*word ^= flip_mask;
}
}
pub fn inject_stuck_at(data: &mut [u64], rate: f64, value: bool, seed: u64) {
if rate <= 0.0 {
return;
}
let mut rng = Xoshiro256PlusPlus::seed_from_u64(seed);
for word in data.iter_mut() {
for bit in 0..64 {
if rng.random::<f64>() < rate {
if value {
*word |= 1u64 << bit;
} else {
*word &= !(1u64 << bit);
}
}
}
}
}
pub fn inject_bitflip_u8(bitstream: &mut [u8], ber: f64, seed: u64) -> u64 {
if ber <= 0.0 {
return 0;
}
let mut rng = Xoshiro256PlusPlus::seed_from_u64(seed);
let mut flipped: u64 = 0;
for b in bitstream.iter_mut() {
if rng.random::<f64>() < ber {
*b ^= 1;
flipped += 1;
}
}
flipped
}
pub fn inject_stuck_at_0_u8(bitstream: &mut [u8], ber: f64, seed: u64) -> u64 {
if ber <= 0.0 {
return 0;
}
let mut rng = Xoshiro256PlusPlus::seed_from_u64(seed);
let mut affected: u64 = 0;
for b in bitstream.iter_mut() {
if rng.random::<f64>() < ber {
if *b != 0 {
affected += 1;
}
*b = 0;
}
}
affected
}
pub fn inject_stuck_at_1_u8(bitstream: &mut [u8], ber: f64, seed: u64) -> u64 {
if ber <= 0.0 {
return 0;
}
let mut rng = Xoshiro256PlusPlus::seed_from_u64(seed);
let mut affected: u64 = 0;
for b in bitstream.iter_mut() {
if rng.random::<f64>() < ber {
if *b == 0 {
affected += 1;
}
*b = 1;
}
}
affected
}
pub fn inject_dropout_u8(bitstream: &mut [u8], ber: f64, seed: u64) -> u64 {
inject_stuck_at_0_u8(bitstream, ber, seed)
}
pub fn inject_gaussian_u8(bitstream: &mut [u8], ber: f64, seed: u64) -> u64 {
if ber <= 0.0 {
return 0;
}
let mut rng = Xoshiro256PlusPlus::seed_from_u64(seed);
let normal = Normal::new(0.0_f64, ber).expect("ber > 0");
let mut flipped: u64 = 0;
for b in bitstream.iter_mut() {
let original = *b;
let noisy = (original as f64 + normal.sample(&mut rng)).clamp(0.0, 1.0);
let new_bit: u8 = if noisy > 0.5 { 1 } else { 0 };
if new_bit != original {
flipped += 1;
}
*b = new_bit;
}
flipped
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zero_rate_no_change() {
let mut data = vec![0xDEAD_BEEF_CAFE_BABEu64; 4];
let original = data.clone();
inject_bitflips(&mut data, 0.0, 42);
assert_eq!(data, original);
}
#[test]
fn full_rate_flips_all() {
let mut data = vec![0u64; 2];
inject_bitflips(&mut data, 1.0, 42);
assert_eq!(data, vec![u64::MAX; 2]);
}
#[test]
fn stuck_at_zero() {
let mut data = vec![u64::MAX; 2];
inject_stuck_at(&mut data, 1.0, false, 42);
assert_eq!(data, vec![0u64; 2]);
}
#[test]
fn stuck_at_one() {
let mut data = vec![0u64; 2];
inject_stuck_at(&mut data, 1.0, true, 42);
assert_eq!(data, vec![u64::MAX; 2]);
}
#[test]
fn partial_rate_changes_some() {
let mut data = vec![0u64; 8];
inject_bitflips(&mut data, 0.5, 99);
let total_set: u32 = data.iter().map(|w| w.count_ones()).sum();
assert!(total_set > 100 && total_set < 400);
}
#[test]
fn bitflip_u8_zero_rate_no_change() {
let mut bs = vec![0u8, 1, 0, 1, 1, 0, 1, 0];
let original = bs.clone();
let n = inject_bitflip_u8(&mut bs, 0.0, 7);
assert_eq!(n, 0);
assert_eq!(bs, original);
}
#[test]
fn bitflip_u8_full_rate_inverts_all() {
let mut bs = vec![0u8, 1, 0, 1, 0, 1, 0, 1];
let n = inject_bitflip_u8(&mut bs, 1.0, 7);
assert_eq!(n as usize, bs.len());
assert_eq!(bs, vec![1u8, 0, 1, 0, 1, 0, 1, 0]);
}
#[test]
fn bitflip_u8_statistical_count_within_3sigma() {
let n = 100_000usize;
let ber = 1e-3_f64;
let mut bs = vec![0u8; n];
let flipped = inject_bitflip_u8(&mut bs, ber, 42);
let mean = n as f64 * ber;
let sigma = (n as f64 * ber * (1.0 - ber)).sqrt();
let lo = (mean - 4.0 * sigma) as u64;
let hi = (mean + 4.0 * sigma) as u64;
assert!(
flipped >= lo && flipped <= hi,
"flipped={flipped} not in [{lo},{hi}]"
);
}
#[test]
fn stuck_at_0_only_counts_actual_changes() {
let mut bs = vec![0u8; 64];
let n = inject_stuck_at_0_u8(&mut bs, 1.0, 11);
assert_eq!(n, 0);
assert!(bs.iter().all(|&b| b == 0));
}
#[test]
fn stuck_at_1_only_counts_actual_changes() {
let mut bs = vec![1u8; 64];
let n = inject_stuck_at_1_u8(&mut bs, 1.0, 11);
assert_eq!(n, 0);
assert!(bs.iter().all(|&b| b == 1));
}
#[test]
fn gaussian_noise_zero_sigma_no_change() {
let mut bs = vec![0u8, 1, 0, 1];
let original = bs.clone();
let n = inject_gaussian_u8(&mut bs, 0.0, 5);
assert_eq!(n, 0);
assert_eq!(bs, original);
}
#[test]
fn dropout_equivalent_to_stuck_at_0() {
let mut a = vec![0u8, 1, 1, 0, 1, 0, 1, 1];
let mut b = a.clone();
let na = inject_dropout_u8(&mut a, 0.5, 17);
let nb = inject_stuck_at_0_u8(&mut b, 0.5, 17);
assert_eq!(a, b);
assert_eq!(na, nb);
}
}