1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
//! Channel simulation.
//!
//! This module contains the simulation of an AWGN channel.

use rand::Rng;
use rand_distr::{Distribution, Normal};

/// AWGN channel simulation.
///
/// This struct is used to add AWGN to symbols.
#[derive(Debug, Clone)]
pub struct AwgnChannel {
    distr: Normal<f64>,
}

impl AwgnChannel {
    /// Creates a new AWGN channel.
    ///
    /// The channel noise follows a (real) normal distribution with mean zero
    /// and standard deviation sigma.
    ///
    /// # Panics
    ///
    /// This function panics if `noise_sigma` is not a positive finite number.
    pub fn new(noise_sigma: f64) -> AwgnChannel {
        assert!(noise_sigma >= 0.0);
        AwgnChannel {
            distr: Normal::new(0.0, noise_sigma).unwrap(),
        }
    }

    /// Adds noise to a sequence of symbols.
    ///
    /// The noise is added in-place to the slice `symbols`. An [Rng] is used as
    /// source of randomness.
    pub fn add_noise<R: Rng>(&self, rng: &mut R, symbols: &mut [f64]) {
        for x in symbols.iter_mut() {
            *x += self.distr.sample(rng);
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn build_awgn() {
        let _channel = AwgnChannel::new(0.2);
    }

    #[test]
    #[should_panic]
    fn negative_noise_sigma() {
        let _channel = AwgnChannel::new(-3.5);
    }

    #[test]
    fn zero_noise_sigma() {
        let channel = AwgnChannel::new(0.0);
        let mut rng = rand::thread_rng();
        let mut symbols = vec![1.0; 1024];
        let symbols_orig = symbols.clone();
        channel.add_noise(&mut rng, &mut symbols);
        assert_eq!(&symbols, &symbols_orig);
    }
}