1use rand::{RngExt, SeedableRng};
12use rand_distr::{Distribution, Normal};
13use rand_xoshiro::Xoshiro256PlusPlus;
14
15pub fn inject_bitflips(data: &mut [u64], rate: f64, seed: u64) {
17 if rate <= 0.0 {
18 return;
19 }
20 let mut rng = Xoshiro256PlusPlus::seed_from_u64(seed);
21 for word in data.iter_mut() {
22 let mut flip_mask = 0u64;
23 for bit in 0..64 {
24 if rng.random::<f64>() < rate {
25 flip_mask |= 1u64 << bit;
26 }
27 }
28 *word ^= flip_mask;
29 }
30}
31
32pub fn inject_stuck_at(data: &mut [u64], rate: f64, value: bool, seed: u64) {
34 if rate <= 0.0 {
35 return;
36 }
37 let mut rng = Xoshiro256PlusPlus::seed_from_u64(seed);
38 for word in data.iter_mut() {
39 for bit in 0..64 {
40 if rng.random::<f64>() < rate {
41 if value {
42 *word |= 1u64 << bit;
43 } else {
44 *word &= !(1u64 << bit);
45 }
46 }
47 }
48 }
49}
50
51pub fn inject_bitflip_u8(bitstream: &mut [u8], ber: f64, seed: u64) -> u64 {
62 if ber <= 0.0 {
63 return 0;
64 }
65 let mut rng = Xoshiro256PlusPlus::seed_from_u64(seed);
66 let mut flipped: u64 = 0;
67 for b in bitstream.iter_mut() {
68 if rng.random::<f64>() < ber {
69 *b ^= 1;
70 flipped += 1;
71 }
72 }
73 flipped
74}
75
76pub fn inject_stuck_at_0_u8(bitstream: &mut [u8], ber: f64, seed: u64) -> u64 {
79 if ber <= 0.0 {
80 return 0;
81 }
82 let mut rng = Xoshiro256PlusPlus::seed_from_u64(seed);
83 let mut affected: u64 = 0;
84 for b in bitstream.iter_mut() {
85 if rng.random::<f64>() < ber {
86 if *b != 0 {
87 affected += 1;
88 }
89 *b = 0;
90 }
91 }
92 affected
93}
94
95pub fn inject_stuck_at_1_u8(bitstream: &mut [u8], ber: f64, seed: u64) -> u64 {
98 if ber <= 0.0 {
99 return 0;
100 }
101 let mut rng = Xoshiro256PlusPlus::seed_from_u64(seed);
102 let mut affected: u64 = 0;
103 for b in bitstream.iter_mut() {
104 if rng.random::<f64>() < ber {
105 if *b == 0 {
106 affected += 1;
107 }
108 *b = 1;
109 }
110 }
111 affected
112}
113
114pub fn inject_dropout_u8(bitstream: &mut [u8], ber: f64, seed: u64) -> u64 {
116 inject_stuck_at_0_u8(bitstream, ber, seed)
117}
118
119pub fn inject_gaussian_u8(bitstream: &mut [u8], ber: f64, seed: u64) -> u64 {
122 if ber <= 0.0 {
123 return 0;
124 }
125 let mut rng = Xoshiro256PlusPlus::seed_from_u64(seed);
126 let normal = Normal::new(0.0_f64, ber).expect("ber > 0");
127 let mut flipped: u64 = 0;
128 for b in bitstream.iter_mut() {
129 let original = *b;
130 let noisy = (original as f64 + normal.sample(&mut rng)).clamp(0.0, 1.0);
131 let new_bit: u8 = if noisy > 0.5 { 1 } else { 0 };
132 if new_bit != original {
133 flipped += 1;
134 }
135 *b = new_bit;
136 }
137 flipped
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn zero_rate_no_change() {
146 let mut data = vec![0xDEAD_BEEF_CAFE_BABEu64; 4];
147 let original = data.clone();
148 inject_bitflips(&mut data, 0.0, 42);
149 assert_eq!(data, original);
150 }
151
152 #[test]
153 fn full_rate_flips_all() {
154 let mut data = vec![0u64; 2];
155 inject_bitflips(&mut data, 1.0, 42);
156 assert_eq!(data, vec![u64::MAX; 2]);
157 }
158
159 #[test]
160 fn stuck_at_zero() {
161 let mut data = vec![u64::MAX; 2];
162 inject_stuck_at(&mut data, 1.0, false, 42);
163 assert_eq!(data, vec![0u64; 2]);
164 }
165
166 #[test]
167 fn stuck_at_one() {
168 let mut data = vec![0u64; 2];
169 inject_stuck_at(&mut data, 1.0, true, 42);
170 assert_eq!(data, vec![u64::MAX; 2]);
171 }
172
173 #[test]
174 fn partial_rate_changes_some() {
175 let mut data = vec![0u64; 8];
176 inject_bitflips(&mut data, 0.5, 99);
177 let total_set: u32 = data.iter().map(|w| w.count_ones()).sum();
178 assert!(total_set > 100 && total_set < 400);
180 }
181
182 #[test]
185 fn bitflip_u8_zero_rate_no_change() {
186 let mut bs = vec![0u8, 1, 0, 1, 1, 0, 1, 0];
187 let original = bs.clone();
188 let n = inject_bitflip_u8(&mut bs, 0.0, 7);
189 assert_eq!(n, 0);
190 assert_eq!(bs, original);
191 }
192
193 #[test]
194 fn bitflip_u8_full_rate_inverts_all() {
195 let mut bs = vec![0u8, 1, 0, 1, 0, 1, 0, 1];
196 let n = inject_bitflip_u8(&mut bs, 1.0, 7);
197 assert_eq!(n as usize, bs.len());
198 assert_eq!(bs, vec![1u8, 0, 1, 0, 1, 0, 1, 0]);
199 }
200
201 #[test]
202 fn bitflip_u8_statistical_count_within_3sigma() {
203 let n = 100_000usize;
204 let ber = 1e-3_f64;
205 let mut bs = vec![0u8; n];
206 let flipped = inject_bitflip_u8(&mut bs, ber, 42);
207 let mean = n as f64 * ber;
209 let sigma = (n as f64 * ber * (1.0 - ber)).sqrt();
210 let lo = (mean - 4.0 * sigma) as u64;
211 let hi = (mean + 4.0 * sigma) as u64;
212 assert!(
213 flipped >= lo && flipped <= hi,
214 "flipped={flipped} not in [{lo},{hi}]"
215 );
216 }
217
218 #[test]
219 fn stuck_at_0_only_counts_actual_changes() {
220 let mut bs = vec![0u8; 64];
222 let n = inject_stuck_at_0_u8(&mut bs, 1.0, 11);
223 assert_eq!(n, 0);
224 assert!(bs.iter().all(|&b| b == 0));
225 }
226
227 #[test]
228 fn stuck_at_1_only_counts_actual_changes() {
229 let mut bs = vec![1u8; 64];
230 let n = inject_stuck_at_1_u8(&mut bs, 1.0, 11);
231 assert_eq!(n, 0);
232 assert!(bs.iter().all(|&b| b == 1));
233 }
234
235 #[test]
236 fn gaussian_noise_zero_sigma_no_change() {
237 let mut bs = vec![0u8, 1, 0, 1];
238 let original = bs.clone();
239 let n = inject_gaussian_u8(&mut bs, 0.0, 5);
240 assert_eq!(n, 0);
241 assert_eq!(bs, original);
242 }
243
244 #[test]
245 fn dropout_equivalent_to_stuck_at_0() {
246 let mut a = vec![0u8, 1, 1, 0, 1, 0, 1, 1];
247 let mut b = a.clone();
248 let na = inject_dropout_u8(&mut a, 0.5, 17);
249 let nb = inject_stuck_at_0_u8(&mut b, 0.5, 17);
250 assert_eq!(a, b);
251 assert_eq!(na, nb);
252 }
253}