Skip to main content

comnum_core/
traits.rs

1//! One verb trait per role in the chain.
2//!
3//! Methods taking `&self` are pure; `&mut self` signals internal state (memory,
4//! adaptation). Every trait has an `_into` method writing into a caller-owned buffer,
5//! which it resizes and fills in place (never replaces), so that a loop passing the same
6//! buffer allocates no output once its shape is stable; scratch buffers may still
7//! allocate (`docs/adr/0011-into-reuses-the-output.md`). All but [`Precode`] and
8//! [`Detect`] also have an allocating method that calls it. Randomness is always
9//! explicit: a random block receives `&mut SimRng`.
10//!
11//! The waveform traits are generic over the symbols they carry, [`Symbols`] by default:
12//! a waveform of complex symbols (OFDM) implements `Modulate`, one that carries the
13//! index of a symbol (chirp spread spectrum, continuous-phase modulation) implements
14//! `Modulate<Labels>` (`docs/adr/0012-index-symbol-waveforms.md`). [`Demap`] takes the
15//! a priori log-probabilities of the points and the [`Jacobian`] logarithm through
16//! [`Demapping`], uniform and exact by default.
17//!
18//! | Trait | Role | Input → output |
19//! |---|---|---|
20//! | [`Encode`] / [`Decode`] | channel coding | [`Bits`] → [`Bits`], [`Llr`] → [`Bits`] |
21//! | [`Map`] / [`Demap`] | constellation mapping | [`Bits`] → [`Symbols`] → [`Llr`] |
22//! | [`Modulate`] / [`Demodulate`] | waveform | [`Symbols`] or [`Labels`](crate::signal::Labels) → [`Signal`] → [`Symbols`] or [`Labels`](crate::signal::Labels) |
23//! | [`Precode`] / [`Detect`] | MIMO | [`Symbols`] → [`Symbols`] |
24//! | [`Propagate`] | propagation medium | [`Signal`] → [`Signal`] |
25//! | [`Impair`] | component non-ideality | [`Signal`] → [`Signal`] |
26//!
27//! # Examples
28//! ```
29//! use comnum_core::prelude::*;
30//!
31//! /// A noiseless gain, the smallest `Impair`.
32//! struct Gain(f64);
33//!
34//! impl Impair for Gain {
35//!     fn impair_into(&mut self, x: &Signal, _: &mut SimRng, out: &mut Signal) -> Result<()> {
36//!         out.resize(x.n_streams(), x.len(), x.sample_rate());
37//!         out.as_array_mut().assign(&(x.as_array() * self.0));
38//!         Ok(())
39//!     }
40//! }
41//!
42//! let x = Signal::from_stream(vec![C64::new(1.0, 0.0); 4], Hertz(1.0))?;
43//! let y = Gain(2.0).impair(&x, &mut batch_rng(1, 0))?;
44//! assert_eq!(y.stream(0)[3], C64::new(2.0, 0.0));
45//! # Ok::<(), comnum_core::Error>(())
46//! ```
47
48use crate::error::Result;
49use crate::rng::SimRng;
50use crate::signal::{Bits, Llr, Signal, Symbols};
51use crate::units::Hertz;
52
53/// Channel encoding of information bits into codewords.
54pub trait Encode {
55    /// Code rate `k/n`.
56    fn rate(&self) -> f64;
57
58    /// Encode `info` into `out`.
59    ///
60    /// # Errors
61    /// When the length of `info` is not a multiple of the information length.
62    fn encode_into(&self, info: &Bits, out: &mut Bits) -> Result<()>;
63
64    /// Encode `info` (allocates, calls [`Encode::encode_into`]).
65    ///
66    /// # Errors
67    /// As [`Encode::encode_into`].
68    fn encode(&self, info: &Bits) -> Result<Bits> {
69        let mut out = Bits::default();
70        self.encode_into(info, &mut out)?;
71        Ok(out)
72    }
73}
74
75/// Channel decoding of log-likelihood ratios into information bits.
76pub trait Decode {
77    /// Decode `llr` into `out`.
78    ///
79    /// # Errors
80    /// When the length of `llr` is not a multiple of the codeword length.
81    fn decode_into(&mut self, llr: &Llr, out: &mut Bits) -> Result<()>;
82
83    /// Decode `llr` (allocates, calls [`Decode::decode_into`]).
84    ///
85    /// # Errors
86    /// As [`Decode::decode_into`].
87    fn decode(&mut self, llr: &Llr) -> Result<Bits> {
88        let mut out = Bits::default();
89        self.decode_into(llr, &mut out)?;
90        Ok(out)
91    }
92}
93
94/// Mapping of bits onto constellation symbols.
95pub trait Map {
96    /// Map `bits` into `out`.
97    ///
98    /// # Errors
99    /// When the length of a stream is not a multiple of the bits per symbol.
100    fn map_into(&self, bits: &Bits, out: &mut Symbols) -> Result<()>;
101
102    /// Map `bits` (allocates, calls [`Map::map_into`]).
103    ///
104    /// # Errors
105    /// As [`Map::map_into`].
106    fn map(&self, bits: &Bits) -> Result<Symbols> {
107        let mut out = Symbols::default();
108        self.map_into(bits, &mut out)?;
109        Ok(out)
110    }
111}
112
113/// The Jacobian logarithm of the log-MAP algorithms and of the soft demappers, exact or
114/// approximated by its maximum:
115///
116/// $$
117/// \ln\left(e^{a} + e^{b}\right) = \max(a, b) + \ln\left(1 + e^{-\lvert a - b \rvert}\right)
118/// \approx \max(a, b).
119/// $$
120///
121/// One choice for [`Demap`] ([`Demapping::jacobian`]), `max_star` of `comnum-math` (which
122/// re-exports it as `comnum_math::Jacobian`), the BCJR and turbo decoders of `comnum-fec` and
123/// the demappers of `comnum-mapping`; a specification keeps the boolean key `max_log`
124/// ([`Jacobian::from_max_log`]).
125#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
126pub enum Jacobian {
127    /// The exact logarithm (log-MAP, log-sum-exp).
128    #[default]
129    Exact,
130    /// The max-log approximation, $\max(a, b)$.
131    MaxLog,
132}
133
134impl Jacobian {
135    /// [`Jacobian::MaxLog`] when `max_log` is true, [`Jacobian::Exact`] otherwise (the
136    /// boolean key `max_log` of the specifications and of the Python API).
137    ///
138    /// # Examples
139    /// ```
140    /// use comnum_core::traits::Jacobian;
141    /// assert_eq!(Jacobian::from_max_log(true), Jacobian::MaxLog);
142    /// assert_eq!(Jacobian::from_max_log(false), Jacobian::default());
143    /// ```
144    pub fn from_max_log(max_log: bool) -> Jacobian {
145        if max_log { Jacobian::MaxLog } else { Jacobian::Exact }
146    }
147
148    /// Whether the logarithm is exact.
149    pub fn is_exact(self) -> bool {
150        self == Jacobian::Exact
151    }
152}
153
154/// The a priori information and the approximation of a soft demapper
155/// ([`Demap::demap_with_into`]). With the log-priors $\lambda_{x} = \ln P(x)$ of the
156/// points $x$ of the alphabet and the variance $\sigma^{2}$ of the complex noise, the
157/// LLR of a bit of the received symbol $y$ is
158///
159/// $$
160/// L(b) = \ln \sum_{x:\\, b = 0} e^{\lambda_{x} - |y - x|^{2} / \sigma^{2}}
161///      - \ln \sum_{x:\\, b = 1} e^{\lambda_{x} - |y - x|^{2} / \sigma^{2}},
162/// $$
163///
164/// each sum exact or max-log by [`Demapping::jacobian`]. A constant added to every
165/// $\lambda_{x}$ cancels, so the log-priors need not be normalised. The default, uniform
166/// priors and the exact logarithm, is that of [`Demap::demap_into`].
167///
168/// # Examples
169/// ```
170/// use comnum_core::traits::{Demapping, Jacobian};
171/// let lp = [-0.5f64, -1.5];
172/// let d = Demapping { log_priors: Some(&lp), jacobian: Jacobian::MaxLog };
173/// assert_eq!(Demapping::default(), Demapping { log_priors: None, jacobian: Jacobian::Exact });
174/// assert_ne!(d, Demapping::default());
175/// ```
176#[derive(Clone, Copy, Debug, Default, PartialEq)]
177pub struct Demapping<'a> {
178    /// The a priori log-probabilities $\ln P(x)$, one per point in the order of the
179    /// labels (probabilistic shaping); `None` for uniform priors, which need no table.
180    pub log_priors: Option<&'a [f64]>,
181    /// The Jacobian logarithm of the sums over the labels: exact or max-log.
182    pub jacobian: Jacobian,
183}
184
185/// Soft demapping of received symbols into log-likelihood ratios.
186pub trait Demap {
187    /// Demap `y`, received with a complex noise variance `noise_var`, into `out`, with
188    /// the priors and the logarithm of `demapping` (see [`Demapping`]).
189    ///
190    /// # Errors
191    /// [`crate::Error::Config`] for a noise variance that is not positive and finite, or
192    /// log-priors with a NaN or $+\infty$, or none finite; [`crate::Error::Shape`] for
193    /// log-priors of another length than the alphabet.
194    fn demap_with_into(&self, y: &Symbols, noise_var: f64, demapping: Demapping<'_>, out: &mut Llr) -> Result<()>;
195
196    /// Demap `y`, received with a complex noise variance `noise_var`, into `out`: the
197    /// exact LLRs under uniform priors ([`Demap::demap_with_into`] with the default
198    /// [`Demapping`]).
199    ///
200    /// # Errors
201    /// As [`Demap::demap_with_into`].
202    fn demap_into(&self, y: &Symbols, noise_var: f64, out: &mut Llr) -> Result<()> {
203        self.demap_with_into(y, noise_var, Demapping::default(), out)
204    }
205
206    /// Demap `y` with `demapping` (allocates, calls [`Demap::demap_with_into`]).
207    ///
208    /// # Errors
209    /// As [`Demap::demap_with_into`].
210    fn demap_with(&self, y: &Symbols, noise_var: f64, demapping: Demapping<'_>) -> Result<Llr> {
211        let mut out = Llr::default();
212        self.demap_with_into(y, noise_var, demapping, &mut out)?;
213        Ok(out)
214    }
215
216    /// Demap `y` (allocates, calls [`Demap::demap_into`]).
217    ///
218    /// # Errors
219    /// As [`Demap::demap_into`].
220    fn demap(&self, y: &Symbols, noise_var: f64) -> Result<Llr> {
221        let mut out = Llr::default();
222        self.demap_into(y, noise_var, &mut out)?;
223        Ok(out)
224    }
225}
226
227/// Modulation of symbols onto a waveform: complex [`Symbols`] by default, or the
228/// [`Labels`](crate::signal::Labels) of a waveform that carries the index of a symbol (`Modulate<Labels>`).
229pub trait Modulate<In = Symbols> {
230    /// Modulate `x` into `out`.
231    ///
232    /// # Errors
233    /// When the number of symbols does not fit the waveform structure.
234    fn modulate_into(&self, x: &In, out: &mut Signal) -> Result<()>;
235
236    /// Modulate `x` (allocates, calls [`Modulate::modulate_into`]).
237    ///
238    /// # Errors
239    /// As [`Modulate::modulate_into`].
240    fn modulate(&self, x: &In) -> Result<Signal> {
241        let mut out = Signal::zeros(0, 0, Hertz(1.0));
242        self.modulate_into(x, &mut out)?;
243        Ok(out)
244    }
245}
246
247/// Demodulation of a waveform back to symbols: complex [`Symbols`] by default, or the
248/// decided [`Labels`](crate::signal::Labels) of a waveform that carries the index of a symbol
249/// (`Demodulate<Labels>`).
250pub trait Demodulate<Out = Symbols> {
251    /// Demodulate `y` into `out`.
252    ///
253    /// # Errors
254    /// When the number of samples does not fit the waveform structure.
255    fn demodulate_into(&mut self, y: &Signal, out: &mut Out) -> Result<()>;
256
257    /// Demodulate `y` (allocates, calls [`Demodulate::demodulate_into`]).
258    ///
259    /// # Errors
260    /// As [`Demodulate::demodulate_into`].
261    fn demodulate(&mut self, y: &Signal) -> Result<Out>
262    where
263        Out: Default,
264    {
265        let mut out = Out::default();
266        self.demodulate_into(y, &mut out)?;
267        Ok(out)
268    }
269}
270
271/// MIMO precoding of streams of symbols.
272pub trait Precode {
273    /// Precode `x` into `out`.
274    ///
275    /// # Errors
276    /// When the number of streams does not match the precoder.
277    fn precode_into(&self, x: &Symbols, out: &mut Symbols) -> Result<()>;
278}
279
280/// Detection of transmitted symbols from received streams.
281pub trait Detect {
282    /// Detect the streams of `y`, received with a noise variance `noise_var`, into `out`.
283    ///
284    /// # Errors
285    /// When the number of streams does not match the channel.
286    fn detect_into(&mut self, y: &Symbols, noise_var: f64, out: &mut Symbols) -> Result<()>;
287}
288
289/// Propagation through a medium. Randomness is always explicit.
290pub trait Propagate {
291    /// Propagate `x` into `out`, drawing the random numbers from `rng`.
292    ///
293    /// # Errors
294    /// When the input does not fit the medium (empty signal, wrong number of streams).
295    fn propagate_into(&mut self, x: &Signal, rng: &mut SimRng, out: &mut Signal) -> Result<()>;
296
297    /// Propagate `x` (allocates, calls [`Propagate::propagate_into`]).
298    ///
299    /// # Errors
300    /// As [`Propagate::propagate_into`].
301    fn propagate(&mut self, x: &Signal, rng: &mut SimRng) -> Result<Signal> {
302        let mut out = Signal::zeros(0, 0, x.sample_rate());
303        self.propagate_into(x, rng, &mut out)?;
304        Ok(out)
305    }
306}
307
308/// A component non-ideality (amplifier, oscillator, converter…).
309pub trait Impair {
310    /// Apply the impairment to `x` into `out`, drawing the random numbers from `rng`.
311    ///
312    /// # Errors
313    /// When the input does not fit the component.
314    fn impair_into(&mut self, x: &Signal, rng: &mut SimRng, out: &mut Signal) -> Result<()>;
315
316    /// Apply the impairment to `x` (allocates, calls [`Impair::impair_into`]).
317    ///
318    /// # Errors
319    /// As [`Impair::impair_into`].
320    fn impair(&mut self, x: &Signal, rng: &mut SimRng) -> Result<Signal> {
321        let mut out = Signal::zeros(0, 0, x.sample_rate());
322        self.impair_into(x, rng, &mut out)?;
323        Ok(out)
324    }
325}