ldpc_toolbox/simulation/
modulation.rs

1//! Modulation and demodulation.
2//!
3//! This module implements routines for modulation of bits to symbols and
4//! demodulation of symbols to LLRs.
5
6use super::channel::ChannelType;
7use crate::gf2::GF2;
8use ndarray::{ArrayBase, Data, Ix1};
9use num_complex::Complex;
10use num_traits::{One, Zero};
11
12/// Modulation.
13///
14/// This trait is used to define the modulations that can be handled by the
15/// simulation. It ties together a modulator and demodulator that work over the
16/// same channel type (either real or complex), and declares the number of bits
17/// per symbol of the modulation.
18pub trait Modulation: 'static {
19    /// Channel type.
20    ///
21    /// This is the scalar type for the symbols of the channel.
22    type T: ChannelType;
23    /// Modulator type.
24    type Modulator: Modulator<T = Self::T>;
25    /// Demodulator type.
26    type Demodulator: Demodulator<T = Self::T>;
27    /// Number of bits per symbol.
28    const BITS_PER_SYMBOL: f64;
29}
30
31/// Modulator.
32///
33/// This trait defines modulators, which can convert a sequence of bits into
34/// symbols.
35pub trait Modulator: Default + Clone + Send {
36    /// Scalar type for the symbols.
37    type T;
38
39    /// Modulates a sequence of bits into symbols.
40    fn modulate<S>(&self, codeword: &ArrayBase<S, Ix1>) -> Vec<Self::T>
41    where
42        S: Data<Elem = GF2>;
43}
44
45/// Demodulator.
46///
47/// This trait defines demodulators, which can compute the bit LLRs for a
48/// sequence of symbols.
49pub trait Demodulator: Send {
50    /// Scalar type for the symbols.
51    type T;
52
53    /// Creates a new demodulator.
54    ///
55    /// The parameter `noise_sigma` indicates the channel noise standard
56    /// deviation in its real and imaginary part (or the channel noise standard
57    /// deviation if the channel is real).
58    fn from_noise_sigma(noise_sigma: f64) -> Self;
59
60    /// Returns the LLRs corresponding to a sequence of symbols.
61    fn demodulate(&self, symbols: &[Self::T]) -> Vec<f64>;
62}
63
64/// BPSK modulation.
65#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default)]
66pub struct Bpsk {}
67
68impl Modulation for Bpsk {
69    type T = f64;
70    type Modulator = BpskModulator;
71    type Demodulator = BpskDemodulator;
72    const BITS_PER_SYMBOL: f64 = 1.0;
73}
74
75/// BPSK modulator.
76///
77/// Maps the bit 0 to the symbol -1.0 and the bit 1 to the symbol +1.0.
78#[derive(Debug, Clone, Default)]
79pub struct BpskModulator {}
80
81impl BpskModulator {
82    /// Creates a new BPSK modulator.
83    pub fn new() -> BpskModulator {
84        BpskModulator::default()
85    }
86
87    fn modulate_bit(bit: GF2) -> f64 {
88        if bit.is_zero() {
89            -1.0
90        } else if bit.is_one() {
91            1.0
92        } else {
93            panic!("invalid GF2 value")
94        }
95    }
96}
97
98impl Modulator for BpskModulator {
99    type T = f64;
100
101    fn modulate<S>(&self, codeword: &ArrayBase<S, Ix1>) -> Vec<f64>
102    where
103        S: Data<Elem = GF2>,
104    {
105        codeword.iter().cloned().map(Self::modulate_bit).collect()
106    }
107}
108
109/// BPSK demodulator.
110///
111/// Assumes the same mapping as the [BpskModulator].
112#[derive(Debug, Clone, Default)]
113pub struct BpskDemodulator {
114    scale: f64,
115}
116
117impl BpskDemodulator {
118    /// Creates a new BPSK demodulator.
119    ///
120    /// The `noise_sigma` indicates the channel noise standard deviation. The
121    /// channel noise is assumed to be a real Gaussian with mean zero and
122    /// standard deviation `noise_sigma`.
123    pub fn new(noise_sigma: f64) -> BpskDemodulator {
124        BpskDemodulator {
125            // Negative scale because we use the convention that +1 means a 1
126            // bit.
127            scale: -2.0 / (noise_sigma * noise_sigma),
128        }
129    }
130}
131
132impl Demodulator for BpskDemodulator {
133    type T = f64;
134
135    fn from_noise_sigma(noise_sigma: f64) -> BpskDemodulator {
136        BpskDemodulator::new(noise_sigma)
137    }
138
139    fn demodulate(&self, symbols: &[f64]) -> Vec<f64> {
140        symbols.iter().map(|&x| self.scale * x).collect()
141    }
142}
143
144/// 8PSK modulation.
145#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default)]
146pub struct Psk8 {}
147
148impl Modulation for Psk8 {
149    type T = Complex<f64>;
150    type Modulator = Psk8Modulator;
151    type Demodulator = Psk8Demodulator;
152    const BITS_PER_SYMBOL: f64 = 3.0;
153}
154
155/// 8PSK modulator.
156///
157/// 8PSK modulator using the DVB-S2 Gray-coded constellation. The modulator can
158/// only work with codewords whose length is a multiple of 3 bits.
159#[derive(Debug, Clone, Default)]
160pub struct Psk8Modulator {}
161
162impl Psk8Modulator {
163    /// Creates a new 8PSK modulator.
164    pub fn new() -> Psk8Modulator {
165        Psk8Modulator::default()
166    }
167
168    fn modulate_bits(b0: GF2, b1: GF2, b2: GF2) -> Complex<f64> {
169        let a = (0.5f64).sqrt();
170        match (b0.is_one(), b1.is_one(), b2.is_one()) {
171            (false, false, false) => Complex::new(a, a),
172            (true, false, false) => Complex::new(0.0, 1.0),
173            (true, true, false) => Complex::new(-a, a),
174            (false, true, false) => Complex::new(-1.0, 0.0),
175            (false, true, true) => Complex::new(-a, -a),
176            (true, true, true) => Complex::new(0.0, -1.0),
177            (true, false, true) => Complex::new(a, -a),
178            (false, false, true) => Complex::new(1.0, 0.0),
179        }
180    }
181}
182
183impl Modulator for Psk8Modulator {
184    type T = Complex<f64>;
185
186    /// Modulates a sequence of bits into symbols.
187    ///
188    /// # Panics
189    ///
190    /// Panics if the length of the codeword is not a multiple of 3 bits.
191    fn modulate<S>(&self, codeword: &ArrayBase<S, Ix1>) -> Vec<Complex<f64>>
192    where
193        S: Data<Elem = GF2>,
194    {
195        assert_eq!(codeword.len() % 3, 0);
196        codeword
197            .iter()
198            .step_by(3)
199            .zip(codeword.iter().skip(1).step_by(3))
200            .zip(codeword.iter().skip(2).step_by(3))
201            .map(|((&b0, &b1), &b2)| Self::modulate_bits(b0, b1, b2))
202            .collect()
203    }
204}
205
206/// 8PSK demodulator.
207///
208/// Assumes the same mapping as the [Psk8Modulator]. Demodulates symbols into
209/// LLRs using the exact formula implemented with the max-* function.
210#[derive(Debug, Clone, Default)]
211pub struct Psk8Demodulator {
212    scale: f64,
213}
214
215impl Psk8Demodulator {
216    /// Creates a new 8PSK demodulator.
217    ///
218    /// The `noise_sigma` indicates the channel noise standard deviation. The
219    /// channel noise is assumed to be a circularly symmetric Gaussian with mean
220    /// zero and standard deviation `noise_sigma` in its real part and imaginary
221    /// part (the total variance is `2 * noise_sigma * noise_sigma`.
222    pub fn new(noise_sigma: f64) -> Psk8Demodulator {
223        Psk8Demodulator {
224            scale: 1.0 / (noise_sigma * noise_sigma),
225        }
226    }
227
228    fn demodulate_symbol(&self, symbol: Complex<f64>) -> [f64; 3] {
229        let a = (0.5f64).sqrt();
230        let symbol = symbol * self.scale;
231        let d000 = dot(symbol, Complex::new(a, a));
232        let d100 = dot(symbol, Complex::new(0.0, 1.0));
233        let d110 = dot(symbol, Complex::new(-a, a));
234        let d010 = dot(symbol, Complex::new(-1.0, 0.0));
235        let d011 = dot(symbol, Complex::new(-a, -a));
236        let d111 = dot(symbol, Complex::new(0.0, -1.0));
237        let d101 = dot(symbol, Complex::new(a, -a));
238        let d001 = dot(symbol, Complex::new(1.0, 0.0));
239        let b0 = [d000, d001, d010, d011]
240            .into_iter()
241            .reduce(maxstar)
242            .unwrap()
243            - [d100, d101, d110, d111]
244                .into_iter()
245                .reduce(maxstar)
246                .unwrap();
247        let b1 = [d000, d001, d100, d101]
248            .into_iter()
249            .reduce(maxstar)
250            .unwrap()
251            - [d010, d011, d110, d111]
252                .into_iter()
253                .reduce(maxstar)
254                .unwrap();
255        let b2 = [d000, d010, d100, d110]
256            .into_iter()
257            .reduce(maxstar)
258            .unwrap()
259            - [d001, d011, d101, d111]
260                .into_iter()
261                .reduce(maxstar)
262                .unwrap();
263        [b0, b1, b2]
264    }
265}
266
267impl Demodulator for Psk8Demodulator {
268    type T = Complex<f64>;
269
270    fn from_noise_sigma(noise_sigma: f64) -> Psk8Demodulator {
271        Psk8Demodulator::new(noise_sigma)
272    }
273
274    fn demodulate(&self, symbols: &[Complex<f64>]) -> Vec<f64> {
275        symbols
276            .iter()
277            .flat_map(|&x| self.demodulate_symbol(x))
278            .collect()
279    }
280}
281
282fn dot(a: Complex<f64>, b: Complex<f64>) -> f64 {
283    a.re * b.re + a.im * b.im
284}
285
286fn maxstar(a: f64, b: f64) -> f64 {
287    a.max(b) + (-((a - b).abs())).exp().ln_1p()
288}
289
290#[cfg(test)]
291mod test {
292    use super::*;
293
294    #[test]
295    fn bpsk_modulator() {
296        let modulator = BpskModulator::new();
297        let x = modulator.modulate(&ndarray::arr1(&[GF2::one(), GF2::zero()]));
298        assert_eq!(&x, &[1.0, -1.0]);
299    }
300
301    #[test]
302    fn bpsk_demodulator() {
303        let demodulator = BpskDemodulator::new(2.0_f64.sqrt());
304        let x = demodulator.demodulate(&[1.0, -1.0]);
305        assert_eq!(x.len(), 2);
306        let tol = 1e-4;
307        assert!((x[0] + 1.0).abs() < tol);
308        assert!((x[1] - 1.0).abs() < tol);
309    }
310
311    #[test]
312    fn psk8_modulator() {
313        let o = GF2::one();
314        let z = GF2::zero();
315        let modulator = Psk8Modulator::new();
316        let x = modulator.modulate(&ndarray::arr1(&[o, o, z, z, z, z, o, z, o]));
317        let a = (0.5f64).sqrt();
318        assert_eq!(
319            &x,
320            &[Complex::new(-a, a), Complex::new(a, a), Complex::new(a, -a)]
321        );
322    }
323
324    #[test]
325    fn psk8_demodulator_signs() {
326        let noise_sigma = 1.0;
327        let demodulator = Psk8Demodulator::new(noise_sigma);
328        let a = (0.5f64).sqrt();
329        let llr = demodulator.demodulate(&[
330            Complex::new(1.0, 0.0),
331            Complex::new(a, a),
332            Complex::new(0.0, 1.0),
333        ]);
334        // 001
335        assert!(llr[0] > 0.0);
336        assert!(llr[1] > 0.0);
337        assert!(llr[2] < 0.0);
338        // 000
339        assert!(llr[3] > 0.0);
340        assert!(llr[4] > 0.0);
341        assert!(llr[5] > 0.0);
342        // 100
343        assert!(llr[6] < 0.0);
344        assert!(llr[7] > 0.0);
345        assert!(llr[8] > 0.0);
346    }
347}