ldpc_toolbox/simulation/
channel.rs

1//! Channel simulation.
2//!
3//! This module contains the simulation of an AWGN channel.
4
5use num_complex::Complex;
6use rand::Rng;
7use rand_distr::{Distribution, Normal};
8
9/// Channel type.
10///
11/// Represents a real or complex (IQ) channel.
12///
13/// This trait is implemented for `f64` and `Complex<f64>` as a way of handling
14/// both real and complex channels internally.
15pub trait ChannelType: sealed::Sealed + std::ops::AddAssign + Sized {
16    #[doc(hidden)]
17    fn noise<R: Rng>(awgn_channel: &AwgnChannel, rng: &mut R) -> Self;
18}
19
20/// Channel model.
21///
22/// A channel model is able to add noise to a sequence of symbols, which can be
23/// either real or complex.
24pub trait Channel {
25    /// Adds noise to a sequence of symbols.
26    ///
27    /// The noise is added in-place to the slice `symbols`. An [Rng] is used as
28    /// source of randomness.
29    fn add_noise<R: Rng, T: ChannelType>(&self, rng: &mut R, symbols: &mut [T]);
30}
31
32/// AWGN channel simulation.
33///
34/// This struct is used to add AWGN to symbols.
35#[derive(Debug, Clone)]
36pub struct AwgnChannel {
37    distr: Normal<f64>,
38}
39
40impl AwgnChannel {
41    /// Creates a new AWGN channel (either real or complex).
42    ///
43    /// When the channel is real, the channel noise follows a (real) normal
44    /// distribution with mean zero and standard deviation `noise_sigma`. When
45    /// the channel is complex, the channel noise follows a circularly symmetric
46    /// normal distribution with mean zero and standard deviation of its real
47    /// and imaginary part `noise_sigma`.
48    ///
49    /// # Panics
50    ///
51    /// This function panics if `noise_sigma` is not a positive finite number.
52    pub fn new(noise_sigma: f64) -> AwgnChannel {
53        assert!(noise_sigma >= 0.0);
54        AwgnChannel {
55            distr: Normal::new(0.0, noise_sigma).unwrap(),
56        }
57    }
58}
59
60impl Channel for AwgnChannel {
61    fn add_noise<R: Rng, T: ChannelType>(&self, rng: &mut R, symbols: &mut [T]) {
62        for x in symbols.iter_mut() {
63            *x += T::noise(self, rng);
64        }
65    }
66}
67
68impl ChannelType for f64 {
69    fn noise<R: Rng>(awgn_channel: &AwgnChannel, rng: &mut R) -> f64 {
70        awgn_channel.distr.sample(rng)
71    }
72}
73
74impl ChannelType for Complex<f64> {
75    fn noise<R: Rng>(awgn_channel: &AwgnChannel, rng: &mut R) -> Complex<f64> {
76        Complex::new(
77            awgn_channel.distr.sample(rng),
78            awgn_channel.distr.sample(rng),
79        )
80    }
81}
82
83mod sealed {
84    use num_complex::Complex;
85    pub trait Sealed {}
86    impl Sealed for f64 {}
87    impl Sealed for Complex<f64> {}
88}
89
90#[cfg(test)]
91mod test {
92    use super::*;
93
94    #[test]
95    fn build_awgn() {
96        let _channel = AwgnChannel::new(0.2);
97    }
98
99    #[test]
100    #[should_panic]
101    fn negative_noise_sigma() {
102        let _channel = AwgnChannel::new(-3.5);
103    }
104
105    #[test]
106    fn zero_noise_sigma() {
107        let channel = AwgnChannel::new(0.0);
108        let mut rng = rand::rng();
109        let mut symbols = vec![1.0; 1024];
110        let symbols_orig = symbols.clone();
111        channel.add_noise(&mut rng, &mut symbols);
112        assert_eq!(&symbols, &symbols_orig);
113    }
114}