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 ) -> Result<BerTest<Mod, Dec>, Error> {
218 let k = h.num_cols() - h.num_rows();
219 let n_cw = h.num_cols();
220 let puncturer = puncturing_pattern.map(Puncturer::new);
221 let interleaver = interleaving_columns.map(|n| Interleaver::new(n.unsigned_abs(), n < 0));
222 let puncturer_rate = if let Some(p) = puncturer.as_ref() {
223 p.rate()
224 } else {
225 1.0
226 };
227 let n = (n_cw as f64 / puncturer_rate).round() as usize;
228 let rate = k as f64 / n as f64;
229 Ok(BerTest {
230 decoder_implementation,
231 num_workers: num_cpus::get(),
232 k,
233 n,
234 n_cw,
235 rate,
236 encoder: Encoder::from_h(&h)?,
237 h,
238 puncturer,
239 interleaver,
240 modulator: Mod::Modulator::default(),
241 ebn0s_db: ebn0s_db.to_owned(),
242 statistics: Vec::with_capacity(ebn0s_db.len()),
243 bch_max_errors,
244 max_iterations,
245 max_frame_errors,
246 reporter,
247 last_reported: Instant::now(),
248 })
249 }
250
251 pub fn run(mut self) -> Result<Vec<Statistics>, Box<dyn std::error::Error>> {
256 let ret = self.do_run();
257 if let Some(reporter) = self.reporter.as_ref() {
258 reporter.tx.send(Report::Finished).unwrap();
259 }
260 ret?;
261 Ok(self.statistics)
262 }
263
264 fn do_run(&mut self) -> Result<(), Box<dyn std::error::Error>> {
265 self.last_reported = Instant::now();
266 for &ebn0_db in &self.ebn0s_db {
267 let ebn0 = 10.0_f64.powf(0.1 * f64::from(ebn0_db));
268 let esn0 = self.rate * Mod::BITS_PER_SYMBOL * ebn0;
269 let noise_sigma = (0.5 / esn0).sqrt();
270 let (results_tx, results_rx) = mpsc::channel();
271 let workers = std::iter::repeat_with(|| {
272 let (mut worker, terminate_tx) = self.make_worker(noise_sigma, results_tx.clone());
273 let handle = std::thread::spawn(move || worker.work());
274 (handle, terminate_tx)
275 })
276 .take(self.num_workers)
277 .collect::<Vec<_>>();
278
279 let mut current_statistics = CurrentStatistics::new(self.bch_max_errors > 0);
280 while current_statistics.errors_for_termination() < self.max_frame_errors {
281 match results_rx.recv().unwrap() {
282 Ok(result) => {
283 current_statistics.ldpc.bit_errors += result.bit_errors;
284 current_statistics.ldpc.frame_errors += u64::from(result.frame_error);
285 current_statistics.false_decodes += u64::from(result.false_decode);
286 current_statistics.total_iterations += result.iterations;
287 if !result.frame_error {
288 current_statistics.ldpc.correct_iterations += result.iterations;
289 }
290 current_statistics.num_frames += 1;
291 if let Some(bch) = &mut current_statistics.bch {
292 if result.bit_errors > self.bch_max_errors {
293 bch.bit_errors += result.bit_errors;
295 bch.frame_errors += 1;
296 } else {
297 bch.correct_iterations += result.iterations;
299 }
300 }
301 }
302 Err(()) => break,
303 }
304 report!(self, current_statistics, ebn0_db, false);
305 }
306 report!(self, current_statistics, ebn0_db, true);
307
308 for (_, terminate_tx) in workers.iter() {
309 let _ = terminate_tx.send(());
312 }
313
314 let mut join_error = None;
315 for (handle, _) in workers.into_iter() {
316 if let Err(e) = handle.join().unwrap() {
317 join_error = Some(e);
318 }
319 }
320 if let Some(e) = join_error {
321 return Err(e);
322 }
323
324 self.statistics.push(Statistics::from_current(
325 ¤t_statistics,
326 ebn0_db,
327 self.k,
328 ));
329 }
330 Ok(())
331 }
332
333 fn make_worker(
334 &self,
335 noise_sigma: f64,
336 results_tx: Sender<WorkerResult>,
337 ) -> (Worker<Mod>, SyncSender<()>) {
338 let (terminate_tx, terminate_rx) = mpsc::sync_channel(1);
339 (
340 Worker {
341 terminate_rx,
342 results_tx,
343 k: self.k,
344 encoder: self.encoder.clone(),
345 puncturer: self.puncturer.clone(),
346 interleaver: self.interleaver.clone(),
347 modulator: self.modulator.clone(),
348 channel: AwgnChannel::new(noise_sigma),
349 demodulator: Mod::Demodulator::from_noise_sigma(noise_sigma),
350 decoder: self.decoder_implementation.build_decoder(self.h.clone()),
351 max_iterations: self.max_iterations,
352 },
353 terminate_tx,
354 )
355 }
356}
357
358impl<Mod: Modulation, Dec: DecoderFactory> Ber for BerTest<Mod, Dec> {
359 fn run(self: Box<Self>) -> Result<Vec<Statistics>, Box<dyn std::error::Error>> {
360 BerTest::run(*self)
361 }
362
363 fn n(&self) -> usize {
364 self.n
365 }
366
367 fn n_cw(&self) -> usize {
368 self.n_cw
369 }
370
371 fn k(&self) -> usize {
372 self.k
373 }
374
375 fn rate(&self) -> f64 {
376 self.rate
377 }
378}
379
380impl<Mod: Modulation> Worker<Mod> {
381 fn work(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
382 let mut rng = rand::rng();
383 loop {
384 match self.terminate_rx.try_recv() {
385 Ok(()) => return Ok(()),
386 Err(TryRecvError::Disconnected) => panic!(),
387 Err(TryRecvError::Empty) => (),
388 };
389 let result = self.simulate(&mut rng);
390 let to_send = match result.as_ref() {
391 Ok(r) => Ok(r.clone()),
392 Err(_) => Err(()),
393 };
394 self.results_tx.send(to_send).unwrap();
395 result?;
396 }
397 }
398
399 fn simulate<R: Rng>(
400 &mut self,
401 rng: &mut R,
402 ) -> Result<WorkerResultOk, Box<dyn std::error::Error + Send + Sync + 'static>> {
403 let message = Self::random_message(rng, self.k);
404 let codeword = self.encoder.encode(&Self::gf2_array(&message));
405 let transmitted = match self.puncturer.as_ref() {
406 Some(p) => p.puncture(&codeword)?,
407 None => codeword,
408 };
409 let transmitted = match self.interleaver.as_ref() {
410 Some(i) => i.interleave(&transmitted),
411 None => transmitted,
412 };
413 let mut symbols = self.modulator.modulate(&transmitted);
414 self.channel.add_noise(rng, &mut symbols);
415 let llrs_demod = self.demodulator.demodulate(&symbols);
416 let llrs_decoder = match self.interleaver.as_ref() {
417 Some(i) => i.deinterleave(&llrs_demod),
418 None => llrs_demod,
419 };
420 let llrs_decoder = match self.puncturer.as_ref() {
421 Some(p) => p.depuncture(&llrs_decoder)?,
422 None => llrs_decoder,
423 };
424
425 let (decoded, iterations, success) =
426 match self.decoder.decode(&llrs_decoder, self.max_iterations) {
427 Ok(output) => (output.codeword, output.iterations, true),
428 Err(output) => (output.codeword, output.iterations, false),
429 };
430 let bit_errors = message
432 .iter()
433 .zip(decoded.iter())
434 .filter(|(a, b)| a != b)
435 .count() as u64;
436 let frame_error = bit_errors > 0;
437 let false_decode = frame_error && success;
438 Ok(WorkerResultOk {
439 bit_errors,
440 frame_error,
441 false_decode,
442 iterations: iterations as u64,
443 })
444 }
445
446 fn random_message<R: Rng>(rng: &mut R, size: usize) -> Vec<u8> {
447 rng.sample_iter(StandardUniform)
448 .map(<u8 as From<bool>>::from)
449 .take(size)
450 .collect()
451 }
452
453 fn gf2_array(bits: &[u8]) -> Array1<GF2> {
454 Array1::from_iter(
455 bits.iter()
456 .map(|&b| if b == 1 { GF2::one() } else { GF2::zero() }),
457 )
458 }
459}
460
461impl CurrentStatistics {
462 fn new(has_bch: bool) -> CurrentStatistics {
463 CurrentStatistics {
464 num_frames: 0,
465 false_decodes: 0,
466 total_iterations: 0,
467 start: Instant::now(),
468 ldpc: CurrentCodeStatistics::new(),
469 bch: if has_bch {
470 Some(CurrentCodeStatistics::new())
471 } else {
472 None
473 },
474 }
475 }
476
477 fn errors_for_termination(&self) -> u64 {
478 if let Some(bch) = &self.bch {
479 bch.frame_errors
480 } else {
481 self.ldpc.frame_errors
482 }
483 }
484}
485
486impl CurrentCodeStatistics {
487 fn new() -> CurrentCodeStatistics {
488 CurrentCodeStatistics {
489 bit_errors: 0,
490 frame_errors: 0,
491 correct_iterations: 0,
492 }
493 }
494}
495
496impl Default for CurrentCodeStatistics {
497 fn default() -> CurrentCodeStatistics {
498 CurrentCodeStatistics::new()
499 }
500}
501
502impl CodeStatistics {
503 fn from_current(stats: &CurrentCodeStatistics, num_frames: u64, k: usize) -> CodeStatistics {
504 CodeStatistics {
505 bit_errors: stats.bit_errors,
506 frame_errors: stats.frame_errors,
507 correct_iterations: stats.correct_iterations,
508 ber: stats.bit_errors as f64 / (k as f64 * num_frames as f64),
509 fer: stats.frame_errors as f64 / num_frames as f64,
510 average_iterations_correct: stats.correct_iterations as f64
511 / (num_frames - stats.frame_errors) as f64,
512 }
513 }
514}
515
516impl Statistics {
517 fn from_current(stats: &CurrentStatistics, ebn0_db: f32, k: usize) -> Statistics {
518 let elapsed = Instant::now() - stats.start;
519 Statistics {
520 ebn0_db,
521 num_frames: stats.num_frames,
522 false_decodes: stats.false_decodes,
523 total_iterations: stats.total_iterations,
524 average_iterations: stats.total_iterations as f64 / stats.num_frames as f64,
525 elapsed,
526 throughput_mbps: 1e-6 * (k as f64 * stats.num_frames as f64) / elapsed.as_secs_f64(),
527 ldpc: CodeStatistics::from_current(&stats.ldpc, stats.num_frames, k),
528 bch: stats
529 .bch
530 .as_ref()
531 .map(|b| CodeStatistics::from_current(b, stats.num_frames, k)),
532 }
533 }
534}