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}