1use super::{
6 channel::{AwgnChannel, Channel},
7 factory::Ber,
8 interleaving::Interleaver,
9 modulation::{Demodulator, Modulation, Modulator},
10 puncturing::Puncturer,
11};
12use crate::{
13 decoder::{
14 LdpcDecoder,
15 factory::{DecoderFactory, DecoderImplementation},
16 },
17 encoder::{Encoder, Error},
18 gf2::GF2,
19 sparse::SparseMatrix,
20};
21use ndarray::Array1;
22use num_traits::{One, Zero};
23use rand::{Rng, distr::StandardUniform};
24use std::{
25 sync::mpsc::{self, Receiver, Sender, SyncSender, TryRecvError},
26 time::{Duration, Instant},
27};
28
29#[derive(Debug)]
33pub struct BerTest<Mod: Modulation, Dec = DecoderImplementation> {
34 decoder_implementation: Dec,
35 h: SparseMatrix,
36 num_workers: usize,
37 k: usize,
38 n: usize,
39 n_cw: usize,
40 rate: f64,
41 encoder: Encoder,
42 puncturer: Option<Puncturer>,
43 interleaver: Option<Interleaver>,
44 modulator: Mod::Modulator,
45 ebn0s_db: Vec<f32>,
46 statistics: Vec<Statistics>,
47 bch_max_errors: u64,
48 max_iterations: usize,
49 max_frame_errors: u64,
50 reporter: Option<Reporter>,
51 last_reported: Instant,
52}
53
54#[derive(Debug)]
55struct Worker<Mod: Modulation> {
56 terminate_rx: Receiver<()>,
57 results_tx: Sender<WorkerResult>,
58 k: usize,
59 encoder: Encoder,
60 puncturer: Option<Puncturer>,
61 interleaver: Option<Interleaver>,
62 modulator: Mod::Modulator,
63 channel: AwgnChannel,
64 demodulator: Mod::Demodulator,
65 decoder: Box<dyn LdpcDecoder>,
66 max_iterations: usize,
67}
68
69#[derive(Debug, Clone)]
70struct WorkerResultOk {
71 bit_errors: u64,
72 frame_error: bool,
73 false_decode: bool,
74 iterations: u64,
75}
76
77type WorkerResult = Result<WorkerResultOk, ()>;
78
79#[derive(Debug, Clone, PartialEq)]
80struct CurrentStatistics {
81 num_frames: u64,
82 false_decodes: u64,
83 total_iterations: u64,
84 start: Instant,
85 ldpc: CurrentCodeStatistics,
86 bch: Option<CurrentCodeStatistics>,
87}
88
89#[derive(Debug, Clone, PartialEq)]
90struct CurrentCodeStatistics {
91 bit_errors: u64,
92 frame_errors: u64,
93 correct_iterations: u64,
94}
95
96#[derive(Debug, Clone, PartialEq)]
101pub struct Statistics {
102 pub ebn0_db: f32,
104 pub num_frames: u64,
106 pub total_iterations: u64,
108 pub false_decodes: u64,
113 pub average_iterations: f64,
115 pub elapsed: Duration,
117 pub throughput_mbps: f64,
119 pub ldpc: CodeStatistics,
121 pub bch: Option<CodeStatistics>,
123}
124
125#[derive(Debug, Clone, PartialEq)]
132pub struct CodeStatistics {
133 pub bit_errors: u64,
135 pub frame_errors: u64,
137 pub correct_iterations: u64,
139 pub ber: f64,
141 pub fer: f64,
143 pub average_iterations_correct: f64,
145}
146
147#[derive(Debug, Clone)]
152pub struct Reporter {
153 pub tx: Sender<Report>,
155 pub interval: Duration,
157}
158
159#[derive(Debug, Clone, PartialEq)]
164pub enum Report {
165 Statistics(Statistics),
169 Finished,
173}
174
175macro_rules! report {
176 ($self:expr, $current_statistics:expr, $ebn0_db:expr, $final:expr) => {
177 if let Some(reporter) = $self.reporter.as_ref() {
178 let now = Instant::now();
179 if $final || $self.last_reported + reporter.interval < now {
180 reporter
181 .tx
182 .send(Report::Statistics(Statistics::from_current(
183 &$current_statistics,
184 $ebn0_db,
185 $self.k,
186 )))
187 .unwrap();
188 $self.last_reported = now;
189 }
190 }
191 };
192}
193
194impl<Mod: Modulation, Dec: DecoderFactory> BerTest<Mod, Dec> {
195 #[allow(clippy::too_many_arguments)]
207 pub fn new(
208 h: SparseMatrix,
209 decoder_implementation: Dec,
210 puncturing_pattern: Option<&[bool]>,
211 interleaving_columns: Option<isize>,
212 max_frame_errors: u64,
213 max_iterations: usize,
214 ebn0s_db: &[f32],
215 reporter: Option<Reporter>,
216 bch_max_errors: u64,
217 num_workers: usize,
218 ) -> Result<BerTest<Mod, Dec>, Error> {
219 let k = h.num_cols() - h.num_rows();
220 let n_cw = h.num_cols();
221 let puncturer = puncturing_pattern.map(Puncturer::new);
222 let interleaver = interleaving_columns.map(|n| Interleaver::new(n.unsigned_abs(), n < 0));
223 let puncturer_rate = if let Some(p) = puncturer.as_ref() {
224 p.rate()
225 } else {
226 1.0
227 };
228 let n = (n_cw as f64 / puncturer_rate).round() as usize;
229 let rate = k as f64 / n as f64;
230 Ok(BerTest {
231 decoder_implementation,
232 num_workers,
233 k,
234 n,
235 n_cw,
236 rate,
237 encoder: Encoder::from_h(&h)?,
238 h,
239 puncturer,
240 interleaver,
241 modulator: Mod::Modulator::default(),
242 ebn0s_db: ebn0s_db.to_owned(),
243 statistics: Vec::with_capacity(ebn0s_db.len()),
244 bch_max_errors,
245 max_iterations,
246 max_frame_errors,
247 reporter,
248 last_reported: Instant::now(),
249 })
250 }
251
252 pub fn run(mut self) -> Result<Vec<Statistics>, Box<dyn std::error::Error>> {
257 let ret = self.do_run();
258 if let Some(reporter) = self.reporter.as_ref() {
259 reporter.tx.send(Report::Finished).unwrap();
260 }
261 ret?;
262 Ok(self.statistics)
263 }
264
265 fn do_run(&mut self) -> Result<(), Box<dyn std::error::Error>> {
266 self.last_reported = Instant::now();
267 for &ebn0_db in &self.ebn0s_db {
268 let ebn0 = 10.0_f64.powf(0.1 * f64::from(ebn0_db));
269 let esn0 = self.rate * Mod::BITS_PER_SYMBOL * ebn0;
270 let noise_sigma = (0.5 / esn0).sqrt();
271 let (results_tx, results_rx) = mpsc::channel();
272 let workers = std::iter::repeat_with(|| {
273 let (mut worker, terminate_tx) = self.make_worker(noise_sigma, results_tx.clone());
274 let handle = std::thread::spawn(move || worker.work());
275 (handle, terminate_tx)
276 })
277 .take(self.num_workers)
278 .collect::<Vec<_>>();
279
280 let mut current_statistics = CurrentStatistics::new(self.bch_max_errors > 0);
281 while current_statistics.errors_for_termination() < self.max_frame_errors {
282 match results_rx.recv().unwrap() {
283 Ok(result) => {
284 current_statistics.ldpc.bit_errors += result.bit_errors;
285 current_statistics.ldpc.frame_errors += u64::from(result.frame_error);
286 current_statistics.false_decodes += u64::from(result.false_decode);
287 current_statistics.total_iterations += result.iterations;
288 if !result.frame_error {
289 current_statistics.ldpc.correct_iterations += result.iterations;
290 }
291 current_statistics.num_frames += 1;
292 if let Some(bch) = &mut current_statistics.bch {
293 if result.bit_errors > self.bch_max_errors {
294 bch.bit_errors += result.bit_errors;
296 bch.frame_errors += 1;
297 } else {
298 bch.correct_iterations += result.iterations;
300 }
301 }
302 }
303 Err(()) => break,
304 }
305 report!(self, current_statistics, ebn0_db, false);
306 }
307 report!(self, current_statistics, ebn0_db, true);
308
309 for (_, terminate_tx) in workers.iter() {
310 let _ = terminate_tx.send(());
313 }
314
315 let mut join_error = None;
316 for (handle, _) in workers.into_iter() {
317 if let Err(e) = handle.join().unwrap() {
318 join_error = Some(e);
319 }
320 }
321 if let Some(e) = join_error {
322 return Err(e);
323 }
324
325 self.statistics.push(Statistics::from_current(
326 ¤t_statistics,
327 ebn0_db,
328 self.k,
329 ));
330 }
331 Ok(())
332 }
333
334 fn make_worker(
335 &self,
336 noise_sigma: f64,
337 results_tx: Sender<WorkerResult>,
338 ) -> (Worker<Mod>, SyncSender<()>) {
339 let (terminate_tx, terminate_rx) = mpsc::sync_channel(1);
340 (
341 Worker {
342 terminate_rx,
343 results_tx,
344 k: self.k,
345 encoder: self.encoder.clone(),
346 puncturer: self.puncturer.clone(),
347 interleaver: self.interleaver.clone(),
348 modulator: self.modulator.clone(),
349 channel: AwgnChannel::new(noise_sigma),
350 demodulator: Mod::Demodulator::from_noise_sigma(noise_sigma),
351 decoder: self.decoder_implementation.build_decoder(self.h.clone()),
352 max_iterations: self.max_iterations,
353 },
354 terminate_tx,
355 )
356 }
357}
358
359impl<Mod: Modulation, Dec: DecoderFactory> Ber for BerTest<Mod, Dec> {
360 fn run(self: Box<Self>) -> Result<Vec<Statistics>, Box<dyn std::error::Error>> {
361 BerTest::run(*self)
362 }
363
364 fn n(&self) -> usize {
365 self.n
366 }
367
368 fn n_cw(&self) -> usize {
369 self.n_cw
370 }
371
372 fn k(&self) -> usize {
373 self.k
374 }
375
376 fn rate(&self) -> f64 {
377 self.rate
378 }
379}
380
381impl<Mod: Modulation> Worker<Mod> {
382 fn work(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
383 let mut rng = rand::rng();
384 loop {
385 match self.terminate_rx.try_recv() {
386 Ok(()) => return Ok(()),
387 Err(TryRecvError::Disconnected) => panic!(),
388 Err(TryRecvError::Empty) => (),
389 };
390 let result = self.simulate(&mut rng);
391 let to_send = match result.as_ref() {
392 Ok(r) => Ok(r.clone()),
393 Err(_) => Err(()),
394 };
395 self.results_tx.send(to_send).unwrap();
396 result?;
397 }
398 }
399
400 fn simulate<R: Rng>(
401 &mut self,
402 rng: &mut R,
403 ) -> Result<WorkerResultOk, Box<dyn std::error::Error + Send + Sync + 'static>> {
404 let message = Self::random_message(rng, self.k);
405 let codeword = self.encoder.encode(&Self::gf2_array(&message));
406 let transmitted = match self.puncturer.as_ref() {
407 Some(p) => p.puncture(&codeword)?,
408 None => codeword,
409 };
410 let transmitted = match self.interleaver.as_ref() {
411 Some(i) => i.interleave(&transmitted),
412 None => transmitted,
413 };
414 let mut symbols = self.modulator.modulate(&transmitted);
415 self.channel.add_noise(rng, &mut symbols);
416 let llrs_demod = self.demodulator.demodulate(&symbols);
417 let llrs_decoder = match self.interleaver.as_ref() {
418 Some(i) => i.deinterleave(&llrs_demod),
419 None => llrs_demod,
420 };
421 let llrs_decoder = match self.puncturer.as_ref() {
422 Some(p) => p.depuncture(&llrs_decoder)?,
423 None => llrs_decoder,
424 };
425
426 let (decoded, iterations, success) =
427 match self.decoder.decode(&llrs_decoder, self.max_iterations) {
428 Ok(output) => (output.codeword, output.iterations, true),
429 Err(output) => (output.codeword, output.iterations, false),
430 };
431 let bit_errors = message
433 .iter()
434 .zip(decoded.iter())
435 .filter(|(a, b)| a != b)
436 .count() as u64;
437 let frame_error = bit_errors > 0;
438 let false_decode = frame_error && success;
439 Ok(WorkerResultOk {
440 bit_errors,
441 frame_error,
442 false_decode,
443 iterations: iterations as u64,
444 })
445 }
446
447 fn random_message<R: Rng>(rng: &mut R, size: usize) -> Vec<u8> {
448 rng.sample_iter(StandardUniform)
449 .map(<u8 as From<bool>>::from)
450 .take(size)
451 .collect()
452 }
453
454 fn gf2_array(bits: &[u8]) -> Array1<GF2> {
455 Array1::from_iter(
456 bits.iter()
457 .map(|&b| if b == 1 { GF2::one() } else { GF2::zero() }),
458 )
459 }
460}
461
462impl CurrentStatistics {
463 fn new(has_bch: bool) -> CurrentStatistics {
464 CurrentStatistics {
465 num_frames: 0,
466 false_decodes: 0,
467 total_iterations: 0,
468 start: Instant::now(),
469 ldpc: CurrentCodeStatistics::new(),
470 bch: if has_bch {
471 Some(CurrentCodeStatistics::new())
472 } else {
473 None
474 },
475 }
476 }
477
478 fn errors_for_termination(&self) -> u64 {
479 if let Some(bch) = &self.bch {
480 bch.frame_errors
481 } else {
482 self.ldpc.frame_errors
483 }
484 }
485}
486
487impl CurrentCodeStatistics {
488 fn new() -> CurrentCodeStatistics {
489 CurrentCodeStatistics {
490 bit_errors: 0,
491 frame_errors: 0,
492 correct_iterations: 0,
493 }
494 }
495}
496
497impl Default for CurrentCodeStatistics {
498 fn default() -> CurrentCodeStatistics {
499 CurrentCodeStatistics::new()
500 }
501}
502
503impl CodeStatistics {
504 fn from_current(stats: &CurrentCodeStatistics, num_frames: u64, k: usize) -> CodeStatistics {
505 CodeStatistics {
506 bit_errors: stats.bit_errors,
507 frame_errors: stats.frame_errors,
508 correct_iterations: stats.correct_iterations,
509 ber: stats.bit_errors as f64 / (k as f64 * num_frames as f64),
510 fer: stats.frame_errors as f64 / num_frames as f64,
511 average_iterations_correct: stats.correct_iterations as f64
512 / (num_frames - stats.frame_errors) as f64,
513 }
514 }
515}
516
517impl Statistics {
518 fn from_current(stats: &CurrentStatistics, ebn0_db: f32, k: usize) -> Statistics {
519 let elapsed = Instant::now() - stats.start;
520 Statistics {
521 ebn0_db,
522 num_frames: stats.num_frames,
523 false_decodes: stats.false_decodes,
524 total_iterations: stats.total_iterations,
525 average_iterations: stats.total_iterations as f64 / stats.num_frames as f64,
526 elapsed,
527 throughput_mbps: 1e-6 * (k as f64 * stats.num_frames as f64) / elapsed.as_secs_f64(),
528 ldpc: CodeStatistics::from_current(&stats.ldpc, stats.num_frames, k),
529 bch: stats
530 .bch
531 .as_ref()
532 .map(|b| CodeStatistics::from_current(b, stats.num_frames, k)),
533 }
534 }
535}