C(++)ECCO
C++ Error Control COding: a header-only library for ECC simulations and experiments, modeling complete coding systems across arbitrary finite fields and complex inter-field relationships - Christian Senger <senger@inue.uni-stuttgart.de>
Loading...
Searching...
No Matches
blocks.hpp
Go to the documentation of this file.
1/**
2 * @file blocks.hpp
3 * @brief Communication system blocks library
4 * @author Christian Senger <senger@inue.uni-stuttgart.de>
5 * @version 2.9.1
6 * @date 2026
7 *
8 * @copyright
9 * Copyright (c) 2026, Christian Senger <senger@inue.uni-stuttgart.de>
10 *
11 * Licensed for noncommercial use only, including academic teaching, research, and personal non-profit purposes.
12 * Commercial use is prohibited without a separate commercial license. See the [LICENSE](../../LICENSE) file in the
13 * repository root for full terms and how to request a commercial license.
14 *
15 * @section Description
16 *
17 * Channels, modulation and demodulation, and extension-/subfield multiplexing blocks
18 * for coding experiments. Blocks compose left-to-right with `operator>>` and generally
19 * accept scalars, vectors, and matrices.
20 *
21 * @code{.cpp}
22 * using F2 = Fp<2>;
23 * using F4 = Ext<F2, {1, 1, 1}>;
24 * Vector<F4> c = {F4(0), F4(1), F4(2), F4(3)};
25 * BI_AWGN channel(6.0);
26 * LLRCalculator<F4> llr(channel);
27 * Matrix<double> L = c >> DEMUX<F4, F2>{} >> channel >> llr;
28 * @endcode
29 */
30
31#ifndef BLOCKS_HPP
32#define BLOCKS_HPP
33
34#include <numbers>
35
36#include "fields.hpp"
37#include "vectors.hpp"
38
39/*
40// transitive
41#include <algorithm>
42#include <array>
43#include <cmath>
44#include <complex>
45#include <ios>
46#include <limits>
47#include <random>
48#include <stdexcept>
49#include <string>
50#include <type_traits>
51#include <utility>
52
53#include "field_concepts_traits.hpp"
54#include "matrices.hpp"
55*/
56
57namespace CECCO {
58
59namespace details {
60
61/**
62 * @brief CRTP base providing scalar, vector, and matrix block operations
63 * @tparam T Derived block type
64 * @tparam InputType Scalar input type
65 * @tparam OutputType Scalar output type
66 *
67 * Derived classes implement the scalar call operator; this base supplies vector and matrix
68 * overloads, reusing rvalue storage when input and output types match.
69 *
70 * Calls with another finite-field type are accepted syntactically and throw at runtime,
71 * allowing otherwise discarded non-template branches to remain well-formed.
72 *
73 * @note Blocks are movable but non-copyable. Channel copies would duplicate distribution
74 * caches and stochastic countdown state, producing statistically coupled block instances.
75 */
76template <typename T, typename InputType, typename OutputType>
77class BlockProcessor : private NonCopyable {
78 protected:
79 const T& derived() const noexcept { return static_cast<const T&>(*this); }
80
81 T& derived() noexcept { return static_cast<T&>(*this); }
82
83 public:
84 /**
85 * @brief Apply the block element-wise to each vector entry
86 * @param in Input vector
87 * @return Vector of processed outputs
88 */
90 Vector<OutputType> res(in.get_n());
91 for (size_t i = 0; i < in.get_n(); ++i) res.set_component(i, derived()(in[i]));
92 return res;
93 }
94
95 /**
96 * @brief Apply the block element-wise (rvalue input)
97 * @param in Input vector (moved)
98 * @return Vector of processed outputs (reuses input storage when InputType == OutputType)
99 */
101 if constexpr (std::is_same_v<InputType, OutputType>) {
102 for (size_t i = 0; i < in.get_n(); ++i) in.set_component(i, derived()(std::move(in[i])));
103 return std::move(in);
104 } else {
105 const size_t n = in.get_n();
106 Vector<OutputType> res(n);
107 for (size_t i = 0; i < n; ++i) res.set_component(i, derived()(std::move(in[i])));
108 return res;
109 }
110 }
111
112 /**
113 * @brief Apply the block element-wise to each matrix entry
114 * @param in Input matrix
115 * @return Matrix of processed outputs
116 */
118 Matrix<OutputType> res(in.get_m(), in.get_n());
119 for (size_t i = 0; i < res.get_m(); ++i) {
120 for (size_t j = 0; j < res.get_n(); ++j) res.set_component(i, j, derived()(in(i, j)));
121 }
122 return res;
123 }
124
125 /**
126 * @brief Apply the block element-wise (rvalue input)
127 * @param in Input matrix (moved)
128 * @return Matrix of processed outputs (reuses input storage when InputType == OutputType)
129 */
131 if constexpr (std::is_same_v<InputType, OutputType>) {
132 // Same type: process in-place and reuse storage
133 for (size_t i = 0; i < in.get_m(); ++i) {
134 for (size_t j = 0; j < in.get_n(); ++j) in.set_component(i, j, derived()(std::move(in(i, j))));
135 }
136 return std::move(in);
137 } else {
138 // Different types: create new matrix
139 Matrix<OutputType> res(in.get_m(), in.get_n());
140 for (size_t i = 0; i < res.get_m(); ++i) {
141 for (size_t j = 0; j < res.get_n(); ++j) res.set_component(i, j, derived()(std::move(in(i, j))));
142 }
143 return res;
144 }
145 }
146
147 /**
148 * @brief Catch-all for inputs whose element type differs from `InputType`
149 * @tparam U Any @ref CECCO::FiniteFieldType other than `InputType`
150 *
151 * Always throws `std::logic_error` at runtime. See the class doc for the `if constexpr`
152 * use case that motivates these overloads.
153 */
154 template <FiniteFieldType U>
155 requires(!std::is_same_v<U, InputType>)
157 throw std::logic_error("Instance of BlockProcessor can only accept inputs from " + InputType::get_info() +
158 ", got " + U::get_info());
159 }
160
161 template <FiniteFieldType U>
164 throw std::logic_error("Instance of BlockProcessor can only accept inputs from " + InputType::get_info() +
165 ", got " + U::get_info());
166 }
167
168 template <FiniteFieldType U>
171 throw std::logic_error("Instance of BlockProcessor can only accept inputs from " + InputType::get_info() +
172 ", got " + U::get_info());
173 }
174};
175
176/** @brief CRTP base for blocks where input and output element types coincide. */
177template <typename T, typename ElementType>
178using SameTypeProcessor = BlockProcessor<T, ElementType, ElementType>;
179
180/** @brief CRTP base for blocks mapping bits to complex symbols (e.g. NRZ/BPSK modulation). */
181template <typename T>
182using EncoderProcessor = BlockProcessor<T, Fp<2>, std::complex<double>>;
183
184/** @brief CRTP base for blocks mapping complex symbols back to bits (hard demod). */
185template <typename T>
186using DecoderProcessor = BlockProcessor<T, std::complex<double>, Fp<2>>;
187
188} // namespace details
189
190/**
191 * @brief Symmetric errors-and-erasures channel over a finite field
192 * @tparam T Finite field of the transmitted symbols
193 *
194 * Independently replaces a symbol by a uniformly selected different symbol with probability
195 * `pe`, then marks it erased with probability `px`; erasure takes precedence over error.
196 *
197 * For `px == 0` this is an @ref CECCO::SDMC; over 𝔽₂, `pe == 0` gives an
198 * @ref CECCO::BEC. Erasures require @ref CECCO_ERASURE_SUPPORT.
199 *
200 * The capacity is
201 * `C = (1 − px)[log₂(q) + (1 − p̃)log₂(1 − p̃) + p̃log₂(p̃) − p̃log₂(q − 1)]`,
202 * where `p̃ = pe / (1 − px)`.
203 */
204template <FieldType T>
206 public:
207 // Bring base class operator() overloads into scope
209
210 /**
211 * @brief Construct with error probability pe and erasure probability px
212 * @param pe Symbol error probability ∈ [0, 1]
213 * @param px Symbol erasure probability ∈ [0, 1 − pe]; default 0
214 *
215 * @throws std::out_of_range if pe ∉ [0, 1] or px ∉ [0, 1 − pe]
216 * @throws std::invalid_argument if px ≠ 0 and @ref CECCO_ERASURE_SUPPORT is not defined
217 */
218 SDMEC(double pe, double px = 0.0);
219
220 /**
221 * @brief Process one symbol
222 * @param in Input symbol
223 * @return Output symbol (possibly erased when @ref CECCO_ERASURE_SUPPORT is defined)
224 */
225 T operator()(const T& in);
226
227 /** @brief Symbol error probability pe (erroneous and not erased). */
228 double get_pe() const noexcept { return pe; }
229
230 /** @brief Symbol erasure probability px. */
231 double get_px() const noexcept { return px; }
232
233 /**
234 * @brief Shannon capacity in bits per symbol
235 * @return C = (1 − px) · [log₂(q) + (1 − p̃)·log₂(1 − p̃) + p̃·log₂(p̃) − p̃·log₂(q − 1)],
236 * where p̃ = pe / (1 − px). Edge cases pe ∈ {0, 1} use lim_{x→0} x·log₂(x) = 0.
237 */
238 double get_capacity() const noexcept
240
241 /**
242 * @brief Pairwise Bhattacharyya parameter
243 *
244 * Uses γ = px + 2·√((1 − pe − px)·pe/(q − 1)) + (q − 2)·pe/(q − 1),
245 * with q = |T| and pe the probability of an erroneous, non-erased output.
246 *
247 * @return Common pairwise Bhattacharyya parameter γ for distinct input symbols
248 */
249 long double get_Bhattacharyya_param() const noexcept
251 {
252 const long double q = T::get_size();
253 const long double pe = get_pe();
254 const long double px = get_px();
255
256 return px + 2.0L * std::sqrt((1.0L - pe - px) * pe / (q - 1.0L)) + (q - 2.0L) * pe / (q - 1.0L);
257 }
258
259 private:
260 double pe;
261 double px;
262 double p_error_given_not_erased{0.0};
263 std::geometric_distribution<unsigned long long> error_dist;
264 unsigned long long error_trials{0};
265 unsigned long long error_failures_before_hit{0};
266#ifdef CECCO_ERASURE_SUPPORT
267 std::geometric_distribution<unsigned long long> erasure_dist;
268 unsigned long long erasure_trials{0};
269 unsigned long long erasure_failures_before_hit{0};
270#endif
271};
272
273template <FieldType T>
274SDMEC<T>::SDMEC(double pe, double px) : pe(pe), px(px) {
275 if (!std::isfinite(pe) || pe < 0.0 || pe > 1.0)
276 throw std::out_of_range("SDMEC error probability must be in [0,1], got: " + std::to_string(pe));
277 if (!std::isfinite(px) || px < 0.0 || px > 1.0 - pe)
278 throw std::out_of_range("SDMEC erasure probability must be in [0," + std::to_string(1.0 - pe) +
279 "], got: " + std::to_string(px));
280#ifndef CECCO_ERASURE_SUPPORT
281 if (px != 0.0) throw std::invalid_argument("px != 0 requires CECCO_ERASURE_SUPPORT");
282#endif
283
284 p_error_given_not_erased = (px >= 1.0) ? 0.0 : pe / (1.0 - px);
285 if (p_error_given_not_erased > 0.0 && p_error_given_not_erased < 1.0) {
286 error_dist = std::geometric_distribution<unsigned long long>(p_error_given_not_erased);
287 error_failures_before_hit = error_dist(gen());
288 }
289#ifdef CECCO_ERASURE_SUPPORT
290 if (px > 0.0 && px < 1.0) {
291 erasure_dist = std::geometric_distribution<unsigned long long>(px);
292 erasure_failures_before_hit = erasure_dist(gen());
293 }
294#endif
295}
296
297template <FieldType T>
298T SDMEC<T>::operator()(const T& in) {
299 T res(in);
300 if (p_error_given_not_erased >= 1.0) {
301 res.randomize_force_change();
302 } else if (p_error_given_not_erased > 0.0) {
303 if (error_trials == error_failures_before_hit) {
304 res.randomize_force_change();
305 error_trials = 0;
306 error_failures_before_hit = error_dist(gen());
307 } else {
308 ++error_trials;
309 }
310 }
311#ifdef CECCO_ERASURE_SUPPORT
312 if (px >= 1.0) {
313 res.erase();
314 } else if (px > 0.0) {
315 if (erasure_trials == erasure_failures_before_hit) {
316 res.erase();
317 erasure_trials = 0;
318 erasure_failures_before_hit = erasure_dist(gen());
319 } else {
320 ++erasure_trials;
321 }
322 }
323#endif
324 return res;
325}
326
327template <FieldType T>
328double SDMEC<T>::get_capacity() const noexcept
329 requires(FiniteFieldType<T>)
330{
331 const double ptilde = p_error_given_not_erased;
332 const double q = static_cast<double>(T::get_size());
333
334 const double term1 = (ptilde > 0.0 && ptilde < 1.0) ? ptilde * std::log2(ptilde) : 0.0;
335 const double term2 = (ptilde > 0.0 && ptilde < 1.0) ? (1.0 - ptilde) * std::log2(1.0 - ptilde) : 0.0;
336
337 return (std::log2(q) + term2 + term1 - ptilde * std::log2(q - 1.0)) * (1.0 - px);
338}
339
340/**
341 * @brief Symmetric Discrete Memoryless Channel: errors only, no erasures
342 * @tparam T Finite field type for channel input/output symbols
343 *
344 * Convenience wrapper for `SDMEC<T>(pe, 0.0)` with a single-parameter constructor.
345 *
346 * @see @ref CECCO::SDMEC for the full errors-and-erasures channel
347 * @see @ref CECCO::BSC for the binary case
348 */
349template <FieldType T>
350class SDMC : public SDMEC<T> {
351 public:
352 /**
353 * @brief Construct with symbol error probability pe
354 * @param pe Symbol error probability ∈ [0, 1]
355 */
356 SDMC(double pe) : SDMEC<T>(pe, 0.0) {}
357};
358
359/**
360 * @brief Binary Symmetric Channel: type alias for `SDMC<Fp<2>>`
361 *
362 * Bits are flipped with probability pe.
363 *
364 * @see @ref CECCO::SDMC, @ref CECCO::BEC
365 */
366using BSC = SDMC<Fp<2>>;
367
368/**
369 * @brief Binary Erasure Channel: symbols are received correctly or marked erased
370 *
371 * Convenience wrapper for `SDMEC<Fp<2>>(0.0, px)`. Requires @ref CECCO_ERASURE_SUPPORT.
372 *
373 * @see @ref CECCO::SDMEC, @ref CECCO::BSC
374 */
375class BEC : public SDMEC<Fp<2>> {
376 public:
377 /**
378 * @brief Construct with symbol erasure probability px
379 * @param px Symbol erasure probability ∈ [0, 1]
380 */
381 BEC(double px) : SDMEC<Fp<2>>(0.0, px) {}
382};
383
384/**
385 * @brief Binary Asymmetric Channel (Z-channel): 0 is preserved; 1 flips to 0 with probability p
386 *
387 * @code{.cpp}
388 * BAC bac(0.1); // 10% probability that 1 flips to 0
389 * Vector<Fp<2>> c = {0, 1, 1, 0};
390 * Vector<Fp<2>> r = c >> bac;
391 * double C = bac.get_capacity();
392 * @endcode
393 *
394 * @see @ref CECCO::BSC, @ref CECCO::BEC
395 */
396class BAC : public details::SameTypeProcessor<BAC, Fp<2>> {
397 public:
398 using details::SameTypeProcessor<BAC, Fp<2>>::operator();
399
400 /**
401 * @brief Construct with flip probability p
402 * @param p Probability that 1 → 0, p ∈ [0, 1]
403 *
404 * @throws std::out_of_range if p ∉ [0, 1]
405 *
406 * @note p = 0 is the perfect channel (capacity 1); p = 1 makes only 0 transmissible (capacity 0).
407 */
408 BAC(double p) : bsc(p) {}
409
410 /**
411 * @brief Process one bit
412 * @param in Input bit
413 * @return Output: 0 is preserved; 1 flips to 0 with probability p
414 */
415 Fp<2> operator()(const Fp<2>& in) {
416 if (in == Fp<2>(0)) return in;
417 return bsc(in);
418 }
419
420 /** @brief Flip probability p (probability that 1 → 0). */
421 double get_pe() const noexcept { return bsc.get_pe(); }
422
423 /**
424 * @brief Shannon capacity in bits per symbol
425 * @return C = log₂(1 + (1 − p)·p^{p/(1−p)}); edge cases p ∈ {0, 1} handled explicitly.
426 */
427 double get_capacity() const noexcept {
428 const double pe = bsc.get_pe();
429
430 if (pe == 0.0) return 1.0;
431 if (pe == 1.0) return 0.0;
432
433 return std::log2(1 + (1 - pe) * std::pow(pe, pe / (1 - pe)));
434 }
435
436 private:
437 BSC bsc;
438};
439
440/**
441 * @brief Non-Return-to-Zero (NRZ) mapper for binary modulation
442 *
443 * Maps 𝔽_2 symbols to real constellation points (a − b/2, 0) and (a + b/2, 0), where `a` is
444 * the DC offset and `b` is the constellation distance. Energy per bit is Eb = a² + b²/4
445 * (unit symbol duration assumed).
446 *
447 * Typical configurations: BPSK (a = 0, b = 2, constellation {−1, +1}, Eb = 1) and
448 * OOK (a = 1, b = 2, constellation {0, +2}, Eb = 2).
449 *
450 * @code{.cpp}
451 * NRZMapper ook(1.0, 2.0);
452 * Vector<Fp<2>> bits = {0, 1, 0, 1};
453 * Vector<std::complex<double>> signal = bits >> ook; // (0, 2, 0, 2)
454 * @endcode
455 *
456 * @see @ref CECCO::BPSKMapper, @ref CECCO::AWGN, @ref CECCO::NRZDemapper
457 */
459 public:
460 // Bring base class operator() overloads into scope
462
463 /**
464 * @brief Construct with constellation parameters
465 * @param a DC offset (real-axis shift)
466 * @param b Constellation distance (separation between symbols)
467 */
468 constexpr NRZMapper(double a, double b) noexcept : a(a), b(b) {}
469
470 /** @name Constellation Parameters
471 * @{
472 */
473
474 /** @brief Energy per bit Eb = a² + b²/4. */
475 constexpr double get_Eb() const noexcept { return (a * a) + (b * b) / 4.0; }
476
477 /** @brief DC offset parameter `a`. */
478 constexpr double get_a() const noexcept { return a; }
479
480 /** @brief Constellation distance parameter `b`. */
481 constexpr double get_b() const noexcept { return b; }
482
483 /** @} */
484
485 /**
486 * @brief Map a bit to its constellation point
487 * @param in Binary input
488 * @return (a − b/2, 0) for input 0; (a + b/2, 0) for input 1
489 */
490 constexpr std::complex<double> operator()(const Fp<2>& in) noexcept {
491 if (in == Fp<2>(0))
492 return {a - b / 2.0, 0};
493 else
494 return {a + b / 2.0, 0};
495 }
496
497 private:
498 const double a{};
499 const double b{};
500};
501
502/**
503 * @brief Binary Phase Shift Keying mapper: `NRZMapper` with a = 0, b = 2
504 *
505 * Antipodal constellation {−1, +1}, Eb = 1.
506 *
507 * @see @ref CECCO::NRZMapper, @ref CECCO::BPSKDemapper
508 */
509class BPSKMapper : public NRZMapper {
510 public:
511 /** @brief Construct BPSK mapper (a = 0, b = 2). */
512 constexpr BPSKMapper() noexcept : NRZMapper(0.0, 2.0) {}
513};
514
515/**
516 * @brief Additive White Gaussian Noise channel for complex-valued symbols
517 *
518 * Adds independent 𝒩(0, σ²) noise to the real and imaginary parts of each input symbol.
519 * The noise variance is derived from the SNR Eb/N₀ and the constellation parameters
520 * supplied at construction: σ² = Eb / (2 · 10^(EbN0dB/10)).
521 *
522 * For NRZ/BPSK input the bit error probability after a hard decision is
523 * Pe = ½ · erfc(√( b²·Eb/N₀ / (4·Eb) )),
524 * available via @ref get_pe.
525 *
526 * @code{.cpp}
527 * BPSKMapper map;
528 * AWGN awgn(6.0, map.get_a(), map.get_b()); // 6 dB Eb/N₀
529 * Vector<Fp<2>> message = {0, 1, 1, 0};
530 * Vector<std::complex<double>> y = message >> map >> awgn;
531 * @endcode
532 *
533 * @see @ref CECCO::NRZMapper, @ref CECCO::BPSKMapper, @ref CECCO::BI_AWGN, @ref CECCO::LLRCalculator
534 */
535class AWGN : public details::SameTypeProcessor<AWGN, std::complex<double>> {
536 public:
537 // Bring base class operator() overloads into scope
538 using details::SameTypeProcessor<AWGN, std::complex<double>>::operator();
539
540 /**
541 * @brief Construct with SNR and constellation parameters
542 * @param EbNodB SNR Eb/N₀ in dB
543 * @param a DC offset of the source constellation
544 * @param b Constellation distance of the source constellation
545 */
546 AWGN(double EbNodB, double a, double b)
547 : Eb(NRZMapper(a, b).get_Eb()),
548 sigma(std::sqrt(0.5 * Eb / std::pow(10.0, EbNodB / 10.0))),
549 dist(0, sigma > 0.0 ? sigma : 1.0), // sigma = 0 violates normal_distribution's precondition
550 pe(calculate_pe(EbNodB, Eb, b)) {
551 if (!std::isfinite(EbNodB) || !std::isfinite(a) || !std::isfinite(b))
552 throw std::invalid_argument("AWGN parameters must be finite!");
553 if (!std::isfinite(sigma))
554 throw std::invalid_argument("AWGN noise standard deviation must be finite, adjust Eb/N0!");
555 }
556
557 /** @name Noise Parameters
558 * @{
559 */
560
561 /** @brief Noise variance σ² (per real component). */
562 double get_variance() const noexcept { return sigma * sigma; }
563
564 /** @brief Noise standard deviation σ (per real component). */
565 double get_standard_deviation() const noexcept { return sigma; }
566
567 /** @brief Theoretical hard-decision bit error probability for the configured constellation/SNR. */
568 constexpr double get_pe() const noexcept { return pe; }
569
570 /** @} */
571
572 /**
573 * @brief Add Gaussian noise to a complex symbol
574 * @param in Input symbol
575 * @return Symbol with independent Gaussian noise added to each real component
576 */
577 std::complex<double> operator()(const std::complex<double>& in) {
578 if (sigma == 0.0) return in;
579 // sequenced draws: I before Q, so seeded runs reproduce across compilers
580 const double noise_i = dist(gen());
581 const double noise_q = dist(gen());
582 return {in.real() + noise_i, in.imag() + noise_q};
583 }
584
585 /**
586 * @brief Shannon capacity in bits per symbol (real signaling)
587 * @return C = ½·log₂(1 + Eb/σ²) for one real dimension (NRZ/BPSK).
588 *
589 * For complex signaling with noise on both I and Q the formula is C = log₂(1 + Eb/(2σ²)).
590 */
591 double get_capacity() const noexcept {
592 // sigma = 0 is either the noiseless channel (Eb > 0) or the zero-energy constellation (a = b = 0)
593 if (sigma == 0.0) return Eb > 0.0 ? std::numeric_limits<double>::infinity() : 0.0;
594 return 0.5 * std::log2(1 + Eb / (sigma * sigma));
595 }
596
597 private:
598 const double Eb{};
599 const double sigma{};
600 std::normal_distribution<double> dist;
601 const double pe{};
602
603 /**
604 * @brief Closed-form NRZ/BPSK hard-decision Pe
605 * @param EbNodB SNR Eb/N₀ in dB
606 * @param Eb Energy per bit of the source constellation
607 * @param b Constellation distance
608 * @return Pe = ½·erfc(√( b²·10^(EbNodB/10) / (4·Eb) ))
609 */
610 static double calculate_pe(double EbNodB, double Eb, double b) noexcept;
611};
612
613double AWGN::calculate_pe(double EbNodB, double Eb, double b) noexcept {
614 if (Eb == 0.0) return 0.5; // degenerate zero-energy constellation (a = b = 0), avoid 0/0
615
616 // For NRZ/BPSK: Pe = 0.5 * erfc(b/(2*sigma))
617 // where b is constellation distance and sigma is noise std dev
618 const double EbN0_linear = std::pow(10.0, EbNodB / 10.0);
619
620 // Signal-to-noise ratio in terms of constellation distance
621 // SNR = (b/2)² / σ² = (b/2)² / (No/2) = (b/2)² * 2/No = (b²/2) / No
622 // where No = Eb/EbN0_linear
623 const double constellation_snr = (b * b * EbN0_linear) / (4.0 * Eb);
624
625 return 0.5 * std::erfc(std::sqrt(constellation_snr));
626}
627
628/**
629 * @brief Binary-Input AWGN: fused @ref NRZMapper + @ref AWGN block
630 *
631 * Maps binary inputs through an internal NRZMapper and then through AWGN, yielding noisy
632 * complex symbols ready for hard decision (@ref NRZDemapper) or soft decision
633 * (@ref LLRCalculator). Default parameters give BPSK (a = 0, b = 2). For codes over 𝔽_{2^m}
634 * each code symbol occupies m channel uses via its binary image (cf. @ref CECCO::DEMUX);
635 * energy accounting remains per transmitted bit.
636 *
637 * @code{.cpp}
638 * BI_AWGN channel(6.0); // BPSK at 6 dB
639 * Vector<Fp<2>> c = {1, 0, 1, 0};
640 * Vector<Fp<2>> r = c >> channel >> NRZDemapper(channel); // hard decisions
641 * @endcode
642 *
643 * @see @ref CECCO::AWGN, @ref CECCO::NRZDemapper, @ref CECCO::LLRCalculator
644 */
645class BI_AWGN : public details::BlockProcessor<BI_AWGN, Fp<2>, std::complex<double>> {
646 public:
647 using details::BlockProcessor<BI_AWGN, Fp<2>, std::complex<double>>::operator();
648
649 /**
650 * @brief Construct with SNR and constellation parameters
651 * @param EbN0dB SNR Eb/N₀ in dB
652 * @param a NRZ DC offset (default 0 for BPSK)
653 * @param b NRZ constellation distance (default 2 for BPSK)
654 */
655 BI_AWGN(double EbN0dB, double a = 0.0, double b = 2.0) : encoder(a, b), transmission(EbN0dB, a, b) {}
656
657 /**
658 * @brief Map a bit and add noise
659 * @param in Input bit
660 * @return NRZ symbol corrupted by AWGN
661 */
662 std::complex<double> operator()(const Fp<2>& in) { return transmission(encoder(in)); }
663
664 /** @brief Internal NRZ mapper (for constructing matching demappers/LLR calculators). */
665 const NRZMapper& get_encoder() const noexcept { return encoder; }
666
667 /** @brief Internal AWGN block (for noise statistics). */
668 const AWGN& get_transmission() const noexcept { return transmission; }
669
670 /**
671 * @brief Shannon capacity in bits per symbol
672 * @return C ∈ [0, 1], computed by composite Simpson's rule (no closed form for BI-AWGN).
673 *
674 * Returns 0 if the constellation distance is 0 and 1 if the noise vanishes.
675 */
676 double get_capacity() const noexcept;
677
678 /** @brief Theoretical hard-decision bit error probability for this constellation/SNR. */
679 constexpr double get_pe() const noexcept { return transmission.get_pe(); }
680
681 /** @brief Noise standard deviation σ. */
682 double get_sigma() const noexcept { return transmission.get_standard_deviation(); }
683
684 /**
685 * @brief Bhattacharyya parameter γ = exp(−b²/(8σ²))
686 */
687 long double get_Bhattacharyya_param() const noexcept {
688 const long double b = encoder.get_b();
689 const long double sigma = transmission.get_standard_deviation();
690 return std::exp(-(b * b) / (8.0L * sigma * sigma));
691 }
692
693 private:
694 NRZMapper encoder;
695 AWGN transmission;
696};
697
698double BI_AWGN::get_capacity() const noexcept {
699 const double a = encoder.get_a();
700 const double b = encoder.get_b();
701 const double sigma = transmission.get_standard_deviation();
702
703 if (b == 0.0) return 0.0; // no separation -> capacity zero
704 if (sigma == 0.0) return 1.0; // noiseless -> capacity one
705
706 auto f_Y_star = [a, b, sigma](double x) -> double {
707 double m = 1.0 / (2.0 * sigma * std::sqrt(2.0 * std::numbers::pi));
708 double s0 = std::exp(-0.5 * std::pow((x - (a - b / 2.0)) / sigma, 2.0));
709 double s1 = std::exp(-0.5 * std::pow((x - (a + b / 2.0)) / sigma, 2.0));
710 return m * (s0 + s1);
711 };
712
713 auto g = [&f_Y_star](double x) -> double {
714 double fy = f_Y_star(x);
715 if (fy <= 0.0) return 0.0; // avoid log2(0)
716 return fy * std::log2(fy);
717 };
718
719 const double K = 8.0;
720 const double mu0 = a - b / 2.0;
721 const double mu1 = a + b / 2.0;
722 const double lb = std::min(mu0, mu1) - K * sigma;
723 const double ub = std::max(mu0, mu1) + K * sigma;
724
725 // ---- composite Simpson's rule on [lb, ub] ----
726 const int N = 16000; // must be even, tune for accuracy/speed
727 const double h = (ub - lb) / N;
728
729 double sum = g(lb) + g(ub);
730 for (int i = 1; i < N; ++i) {
731 double x = lb + i * h;
732 sum += (i % 2 == 0 ? 2.0 : 4.0) * g(x);
733 }
734
735 const double integral = h * sum / 3.0; // ≈ ∫ g(x) dx
736
737 return std::clamp(-integral - std::log2(sigma * std::sqrt(2 * std::numbers::pi * std::numbers::e)), 0.0, 1.0);
738}
739
740/**
741 * @brief Non-Return-to-Zero (NRZ) hard-decision demapper
742 *
743 * Maximum-likelihood threshold detector for NRZ-modulated symbols received over AWGN:
744 * outputs 1 if Re(r) ≥ a, otherwise 0, where `a` is the DC offset of the corresponding
745 * @ref CECCO::NRZMapper. The imaginary part is ignored.
746 *
747 * @code{.cpp}
748 * NRZMapper map(0.5, 1.0); // Constellation: {0, 1}
749 * NRZDemapper demap(map); // Threshold at a = 0.5
750 * Vector<std::complex<double>> y = {{0.3, 0.1}, {0.8, −0.2}};
751 * Vector<Fp<2>> c_est = y >> demap; // {0, 1}
752 * @endcode
753 *
754 * @see @ref CECCO::NRZMapper, @ref CECCO::BPSKDemapper, @ref CECCO::LLRCalculator
755 */
757 public:
758 // Bring base class operator() overloads into scope
760
761 /**
762 * @brief Construct from the corresponding mapper
763 * @param nrz Source mapper whose DC offset becomes the decision threshold
764 */
765 constexpr NRZDemapper(const NRZMapper& nrz) noexcept : a(nrz.get_a()) {}
766
767 /**
768 * @brief Construct from a BI-AWGN channel (convenience)
769 * @param bi_awgn Channel whose internal mapper provides the decision threshold
770 */
771 NRZDemapper(const BI_AWGN& bi_awgn) noexcept : a(bi_awgn.get_encoder().get_a()) {}
772
773 /**
774 * @brief Hard-decide a received symbol
775 * @param in Received complex value (imaginary part ignored)
776 * @return 1 if Re(in) ≥ a, else 0
777 */
778 constexpr Fp<2> operator()(const std::complex<double>& in) noexcept {
779 if (in.real() >= a)
780 return {1};
781 else
782 return {0};
783 }
784
785 private:
786 const double a{}; ///< Decision threshold (DC offset from mapper)
787};
788
789/**
790 * @brief BPSK hard-decision demapper: `NRZDemapper` with threshold 0
791 *
792 * @see @ref CECCO::BPSKMapper, @ref CECCO::NRZDemapper
793 */
794class BPSKDemapper : public NRZDemapper {
795 public:
796 /** @brief Construct BPSK demapper (threshold 0). */
797 constexpr BPSKDemapper() noexcept : NRZDemapper(BPSKMapper()) {}
798};
799
800/**
801 * @brief Compute symbol-level log-likelihood ratios
802 * @tparam T Code-symbol field
803 *
804 * Produces a `(q − 1) × n` matrix whose entry `(a − 1, j)` is
805 * `ln(P(r_j | 0) / P(r_j | a))`. Positive values favor zero; magnitude is reliability.
806 *
807 * Supports prime-field SDMEC observations and binary-image BI-AWGN observations. BI-AWGN
808 * requires characteristic two and combines the independent bit LLRs of each extension-field symbol.
809 */
810template <FiniteFieldType T = Fp<2>>
812 public:
813 /**
814 * @brief Construct a BI-AWGN calculator from a matching mapper and channel
815 * @param nrz Source mapper providing (a, b)
816 * @param transmission Channel providing variance
817 */
818 LLRCalculator(const NRZMapper& nrz, const AWGN& transmission) noexcept
821 a(nrz.get_a()),
822 b(nrz.get_b()),
824 pe(0.0),
825 px(0.0) {}
826
827 /**
828 * @brief Construct a BI-AWGN calculator from a BI-AWGN channel (convenience)
829 * @param bi_awgn Channel providing (a, b) and variance
830 */
839
840 /**
841 * @brief Construct an SDMEC calculator from a discrete channel
842 * @param channel Symmetric discrete memoryless erasure channel providing pe and px
843 */
844 LLRCalculator(const SDMEC<T>& channel) noexcept
846 : model(channel_model_t::sdmec), a(0.0), b(0.0), sigmasq(0.0), pe(channel.get_pe()), px(channel.get_px()) {}
847
848 /**
849 * @brief Compute the LLR of a received complex symbol (BI-AWGN model)
850 * @param in Received complex symbol
851 * @return LLR = b·(a − Re(in)) / σ² in nats; positive ⇒ bit 0, negative ⇒ bit 1
852 * @throws std::logic_error if this calculator was constructed from an SDMEC
853 */
854 double operator()(const std::complex<double>& in) {
856 throw std::logic_error("This LLRCalculator was not constructed for a soft input!");
857 if (sigmasq == 0.0) return 0.0; // degenerate zero-energy constellation (a = b = 0)
858 return b * (a - in.real()) / sigmasq;
859 }
860
861 /**
862 * @brief Compute LLRs for a received complex word (BI-AWGN model)
863 * @param in Received complex word
864 * @return Vector of binary LLRs
865 * @throws std::logic_error if this calculator was constructed from an SDMEC
866 */
867 Vector<double> operator()(const Vector<std::complex<double>>& in) {
868 Vector<double> llrs(in.get_n());
869 for (size_t i = 0; i < in.get_n(); ++i) llrs.set_component(i, (*this)(in[i]));
870 return llrs;
871 }
872
873 /**
874 * @brief Compute the symbol-level LLR matrix of a received binary image (BI-AWGN model)
875 * @param in Received complex matrix; the [T:𝔽_2]-row binary image of the code word, column j
876 * holding the bits of symbol j in `as_vector()`/@ref CECCO::DEMUX order
877 * @return (q−1) × n matrix; entry (a−1, j) = ln(P(r_j|0)/P(r_j|a)) for symbol a = 1, …, q−1
878 * @throws std::logic_error if this calculator was constructed from an SDMEC
879 * @throws std::invalid_argument if the input does not have [T:𝔽_2] rows
880 *
881 * The bits of a symbol are transmitted over independent channel uses, so a symbol LLR is the
882 * sum of the bit LLRs selected by its nonzero coordinates. For a binary code (T = 𝔽_2) this is
883 * one LLR per symbol.
884 */
885 Matrix<double> operator()(const Matrix<std::complex<double>>& in) {
887 throw std::logic_error("This LLRCalculator was not constructed for a soft input!");
888
889 constexpr size_t q = T::get_size();
890 constexpr size_t m = details::degree_over_prime_v<T>;
891 if (in.get_m() != m)
892 throw std::invalid_argument("BI-AWGN input must have [T:F2] rows (the binary image of the code symbols)!");
893
894 const size_t n = in.get_n();
895 Matrix<double> llrs(q - 1, n);
896 std::array<double, m> bit;
897 for (size_t j = 0; j < n; ++j) {
898 for (size_t i = 0; i < m; ++i) bit[i] = (*this)(in(i, j));
899 for (size_t lab = 1; lab < q; ++lab) {
900 double L = 0.0;
901 size_t bits = lab;
902 for (size_t i = 0; i < m; ++i) {
903 if (bits & 1u) L += bit[m - 1 - i];
904 bits >>= 1u;
905 }
906 llrs.set_component(lab - 1, j, L);
907 }
908 }
909 return llrs;
910 }
911
912 /**
913 * @brief Compute symbol LLRs for one received field symbol (SDMEC model)
914 * @param r Received 𝔽_p symbol, possibly erased
915 * @return Vector with entry a−1 equal to ln(P(r|0)/P(r|a))
916 * @throws std::logic_error if this calculator was constructed from a BI-AWGN channel
917 */
918 Vector<double> operator()(const T& r)
920 {
922 throw std::logic_error("This LLRCalculator was not constructed for hard input!");
923
924 constexpr size_t q = T::get_size();
925 Vector<double> llrs(q - 1);
926
927#ifdef CECCO_ERASURE_SUPPORT
928 if (r.is_erased()) return llrs;
929#endif
930
931 const double L = sdmec_reliability();
932 const size_t y = r.get_label();
933 if (y == 0)
934 for (size_t a = 1; a < q; ++a) llrs.set_component(a - 1, L);
935 else
936 llrs.set_component(y - 1, -L);
937
938 return llrs;
939 }
940
941 /**
942 * @brief Compute the symbol-LLR matrix of a received word (SDMEC model)
943 * @param r Received word of 𝔽_p symbols, possibly erased
944 * @return (p−1) × n matrix; entry (a−1, j) = ln(P(r_j|0)/P(r_j|a))
945 * @throws std::logic_error if this calculator was constructed from a BI-AWGN channel
946 */
947 Matrix<double> operator()(const Vector<T>& r)
949 {
951 throw std::logic_error("This LLRCalculator was not constructed for hard input!");
952
953 constexpr size_t q = T::get_size();
954 const size_t n = r.get_n();
955 const double L = sdmec_reliability();
956
957 Matrix<double> llrs(q - 1, n);
958 for (size_t j = 0; j < n; ++j) {
959#ifdef CECCO_ERASURE_SUPPORT
960 if (r[j].is_erased()) continue;
961#endif
962 const size_t y = r[j].get_label();
963 if (y == 0)
964 for (size_t a = 1; a < q; ++a) llrs.set_component(a - 1, j, L);
965 else
966 llrs.set_component(y - 1, j, -L);
967 }
968 return llrs;
969 }
970
971 private:
972 enum class channel_model_t { bi_awgn, sdmec };
973
974 const channel_model_t model;
975 const double a; ///< DC offset from mapper (BI-AWGN)
976 const double b; ///< Constellation distance from mapper (BI-AWGN)
977 const double sigmasq; ///< Noise variance from channel (BI-AWGN)
978 const double pe; ///< Symbol error probability (SDMEC)
979 const double px; ///< Symbol erasure probability (SDMEC)
980
981 double sdmec_reliability() const noexcept {
982 if (pe == 0.0) {
983 if (px == 1.0) return 0.0;
984 return std::numeric_limits<double>::infinity();
985 }
986 if (1.0 - pe - px == 0.0) return -std::numeric_limits<double>::infinity();
987 return std::log((1.0 - pe - px) * (static_cast<double>(T::get_size()) - 1.0) / pe);
988 }
989};
990
993template <FiniteFieldType T>
996
997/**
998 * @brief Expand extension-field elements into subfield coordinates
999 * @tparam E Extension field
1000 * @tparam S Subfield of E (S ⊆ E)
1001 *
1002 * A vector of `n` elements of E becomes an `[E:S] × n` matrix over S. Column `j`
1003 * contains the coefficients of `input[j]` in `as_vector()` construction-tower order.
1004 * For `S = 𝔽₂`, this is the binary image used by binary-input channels.
1005 *
1006 * @see @ref CECCO::MUX for the inverse
1007 */
1008template <FiniteFieldType E, FiniteFieldType S>
1009 requires(SubfieldOf<E, S>)
1010class DEMUX : private details::NonCopyable {
1011 public:
1012 constexpr DEMUX() = default;
1013
1014 /**
1015 * @brief Expand a single element into its subfield-coefficient vector
1016 * @param in Extension field element
1017 * @return Length-[E:S] vector of S coefficients
1018 */
1019 constexpr Vector<S> operator()(const E& in) { return in.template as_vector<S>(); }
1020
1021 /**
1022 * @brief Expand a vector of elements into a subfield-coefficient matrix
1023 * @param in Vector of n extension field elements
1024 * @return [E:S] × n matrix; column j holds the coefficients of `in[j]`
1025 */
1026 Matrix<S> operator()(const Vector<E>& in);
1027};
1028
1029template <FiniteFieldType E, FiniteFieldType S>
1030 requires(SubfieldOf<E, S>)
1031Matrix<S> DEMUX<E, S>::operator()(const Vector<E>& in) {
1033 if (in.get_n() == 0) return Matrix<S>(m, 0);
1034
1035 Matrix<S> M(m, in.get_n());
1036
1037 for (size_t j = 0; j < in.get_n(); ++j) {
1038 const auto column = in[j].template as_vector<S>();
1039 for (size_t i = 0; i < m; ++i) M.set_component(i, j, column[i]);
1040 }
1041
1042 return M;
1043}
1044
1045/**
1046 * @brief Field multiplexer: reconstruct 𝔽_E elements from 𝔽_S coefficients
1047 * @tparam S Subfield
1048 * @tparam E Extension of S
1049 *
1050 * Inverse of @ref DEMUX. The input matrix must have [E:S] rows; each column is interpreted
1051 * as the coefficient vector of one E element, and the output `Vector<E>` has one entry per
1052 * input column. `original >> DEMUX{} >> MUX{} == original` for compatible field pairs.
1053 *
1054 * @see @ref CECCO::DEMUX for the inverse direction
1055 * @see @ref CECCO::Ext, @ref CECCO::ExtensionOf
1056 */
1059class MUX : private details::NonCopyable {
1060 public:
1061 constexpr MUX() = default;
1062
1063 /**
1064 * @brief Reconstruct an element from its subfield-coefficient vector
1065 * @param in Length-[E:S] coefficient vector
1066 * @return Reconstructed extension field element
1067 */
1068 constexpr E operator()(const Vector<S>& in) { return E(in); }
1069
1070 /**
1071 * @brief Reconstruct a vector of elements from a subfield-coefficient matrix
1072 * @param in [E:S] × n matrix of S coefficients
1073 * @return Length-n vector of E elements (column j of `in` becomes element j)
1074 */
1075 Vector<E> operator()(const Matrix<S>& in);
1076};
1077
1078template <FiniteFieldType S, FiniteFieldType E>
1079 requires(ExtensionOf<S, E>)
1080Vector<E> MUX<S, E>::operator()(const Matrix<S>& in) {
1081 Vector<E> v(in.get_n());
1082 for (size_t i = 0; i < in.get_n(); ++i) v.set_component(i, E(in.get_col(i)));
1083
1084 return v;
1085}
1086
1087/**
1088 * @brief Function-call chaining: `x >> f` ≡ `f(x)`
1089 * @tparam LHS Input type
1090 * @tparam RHS Callable on `LHS`
1091 * @param lhs Input value (perfect-forwarded)
1092 * @param rhs Callable (perfect-forwarded)
1093 * @return `rhs(lhs)`
1094 *
1095 * Selected when `RHS` is invocable with `LHS`. Disabled for `std::ios_base`-derived types
1096 * to avoid clashing with stream operators. The companion overload below handles the case
1097 * where the right-hand side is an assignment target.
1098 */
1099template <class LHS, class RHS>
1101 requires(RHS&& r, LHS&& x) { std::forward<RHS>(r)(std::forward<LHS>(x)); }
1102decltype(auto) operator>>(LHS&& lhs, RHS&& rhs) {
1103 return std::forward<RHS>(rhs)(std::forward<LHS>(lhs));
1104}
1105
1106/**
1107 * @brief Assignment chaining: `x >> dst` ≡ `dst = x; return dst`
1108 * @tparam LHS Input type
1109 * @tparam RHS Assignment target type (assignable from `LHS`)
1110 * @param lhs Input value
1111 * @param dst Destination variable
1112 * @return Reference to `dst` for continued chaining
1113 *
1114 * Selected only when the callable overload above is not viable, so block calls always win
1115 * over capture-into-variable when both are possible. Useful for capturing intermediate
1116 * results in a chain (`c >> map >> x >> awgn >> y >> demap >> r;`).
1117 */
1118template <class LHS, class RHS>
1120 (!requires(RHS&& r, LHS&& x) { std::forward<RHS>(r)(std::forward<LHS>(x)); }) &&
1121 requires(RHS& dst, LHS&& x) { dst = std::forward<LHS>(x); }
1123 dst = std::forward<LHS>(lhs);
1124 return dst;
1125}
1126
1127} // namespace CECCO
1128
1129#endif
Additive White Gaussian Noise channel for complex-valued symbols.
Definition blocks.hpp:535
double get_standard_deviation() const noexcept
Noise standard deviation σ (per real component).
Definition blocks.hpp:565
double get_capacity() const noexcept
Shannon capacity in bits per symbol (real signaling).
Definition blocks.hpp:591
constexpr double get_pe() const noexcept
Theoretical hard-decision bit error probability for the configured constellation/SNR.
Definition blocks.hpp:568
double get_variance() const noexcept
Noise variance σ² (per real component).
Definition blocks.hpp:562
AWGN(double EbNodB, double a, double b)
Construct with SNR and constellation parameters.
Definition blocks.hpp:546
std::complex< double > operator()(const std::complex< double > &in)
Add Gaussian noise to a complex symbol.
Definition blocks.hpp:577
Binary Asymmetric Channel (Z-channel): 0 is preserved; 1 flips to 0 with probability p.
Definition blocks.hpp:396
double get_capacity() const noexcept
Shannon capacity in bits per symbol.
Definition blocks.hpp:427
BAC(double p)
Construct with flip probability p.
Definition blocks.hpp:408
Fp< 2 > operator()(const Fp< 2 > &in)
Process one bit.
Definition blocks.hpp:415
double get_pe() const noexcept
Flip probability p (probability that 1 → 0).
Definition blocks.hpp:421
Binary Erasure Channel: symbols are received correctly or marked erased.
Definition blocks.hpp:375
BEC(double px)
Construct with symbol erasure probability px.
Definition blocks.hpp:381
Binary-Input AWGN: fused NRZMapper + AWGN block.
Definition blocks.hpp:645
double get_capacity() const noexcept
Shannon capacity in bits per symbol.
Definition blocks.hpp:698
double get_sigma() const noexcept
Noise standard deviation σ.
Definition blocks.hpp:682
const AWGN & get_transmission() const noexcept
Internal AWGN block (for noise statistics).
Definition blocks.hpp:668
constexpr double get_pe() const noexcept
Theoretical hard-decision bit error probability for this constellation/SNR.
Definition blocks.hpp:679
const NRZMapper & get_encoder() const noexcept
Internal NRZ mapper (for constructing matching demappers/LLR calculators).
Definition blocks.hpp:665
long double get_Bhattacharyya_param() const noexcept
Bhattacharyya parameter γ = exp(−b²/(8σ²)).
Definition blocks.hpp:687
std::complex< double > operator()(const Fp< 2 > &in)
Map a bit and add noise.
Definition blocks.hpp:662
BI_AWGN(double EbN0dB, double a=0.0, double b=2.0)
Construct with SNR and constellation parameters.
Definition blocks.hpp:655
BPSK hard-decision demapper: NRZDemapper with threshold 0.
Definition blocks.hpp:794
constexpr BPSKDemapper() noexcept
Construct BPSK demapper (threshold 0).
Definition blocks.hpp:797
Binary Phase Shift Keying mapper: NRZMapper with a = 0, b = 2.
Definition blocks.hpp:509
constexpr BPSKMapper() noexcept
Construct BPSK mapper (a = 0, b = 2).
Definition blocks.hpp:512
Expand extension-field elements into subfield coordinates.
Definition blocks.hpp:1010
constexpr DEMUX()=default
constexpr Vector< S > operator()(const E &in)
Expand a single element into its subfield-coefficient vector.
Definition blocks.hpp:1019
Prime field 𝔽_p ≅ ℤ/pℤ
Definition fields.hpp:1663
Compute symbol-level log-likelihood ratios.
Definition blocks.hpp:811
LLRCalculator(const NRZMapper &nrz, const AWGN &transmission) noexcept
Construct a BI-AWGN calculator from a matching mapper and channel.
Definition blocks.hpp:818
Non-Return-to-Zero (NRZ) hard-decision demapper.
Definition blocks.hpp:756
NRZDemapper(const BI_AWGN &bi_awgn) noexcept
Construct from a BI-AWGN channel (convenience).
Definition blocks.hpp:771
constexpr NRZDemapper(const NRZMapper &nrz) noexcept
Construct from the corresponding mapper.
Definition blocks.hpp:765
constexpr Fp< 2 > operator()(const std::complex< double > &in) noexcept
Hard-decide a received symbol.
Definition blocks.hpp:778
Non-Return-to-Zero (NRZ) mapper for binary modulation.
Definition blocks.hpp:458
constexpr std::complex< double > operator()(const Fp< 2 > &in) noexcept
Map a bit to its constellation point.
Definition blocks.hpp:490
constexpr double get_b() const noexcept
Constellation distance parameter b.
Definition blocks.hpp:481
constexpr double get_Eb() const noexcept
Energy per bit Eb = a² + b²/4.
Definition blocks.hpp:475
constexpr NRZMapper(double a, double b) noexcept
Construct with constellation parameters.
Definition blocks.hpp:468
constexpr double get_a() const noexcept
DC offset parameter a.
Definition blocks.hpp:478
Symmetric errors-and-erasures channel over a finite field.
Definition blocks.hpp:205
double get_pe() const noexcept
Symbol error probability pe (erroneous and not erased).
Definition blocks.hpp:228
double get_px() const noexcept
Symbol erasure probability px.
Definition blocks.hpp:231
T operator()(const T &in)
Process one symbol.
Definition blocks.hpp:298
SDMEC(double pe, double px=0.0)
Construct with error probability pe and erasure probability px.
Definition blocks.hpp:274
double get_capacity() const noexcept
Shannon capacity in bits per symbol.
Definition blocks.hpp:328
long double get_Bhattacharyya_param() const noexcept
Pairwise Bhattacharyya parameter.
Definition blocks.hpp:249
CRTP base providing scalar, vector, and matrix block operations.
Definition blocks.hpp:77
Vector< OutputType > operator()(Vector< InputType > &&in)
Apply the block element-wise (rvalue input).
Definition blocks.hpp:100
Vector< OutputType > operator()(const Vector< InputType > &in)
Apply the block element-wise to each vector entry.
Definition blocks.hpp:89
const T & derived() const noexcept
Definition blocks.hpp:79
Mixin that deletes the copy operations and defaults the move operations.
Contains implementation details not to be exposed to the user. Functions and classes here may change ...
Definition blocks.hpp:59
BlockProcessor< T, std::complex< double >, Fp< 2 > > DecoderProcessor
CRTP base for blocks mapping complex symbols back to bits (hard demod).
Definition blocks.hpp:186
BlockProcessor< T, ElementType, ElementType > SameTypeProcessor
CRTP base for blocks where input and output element types coincide.
Definition blocks.hpp:178
BlockProcessor< T, Fp< 2 >, std::complex< double > > EncoderProcessor
CRTP base for blocks mapping bits to complex symbols (e.g. NRZ/BPSK modulation).
Definition blocks.hpp:182
Provides a framework for error correcting codes.
Definition blocks.hpp:57
LLRCalculator(const BI_AWGN &) -> LLRCalculator< Fp< 2 > >
decltype(auto) operator>>(LHS &&lhs, RHS &&rhs)
Function-call chaining: x >> f ≡ f(x).
Definition blocks.hpp:1102
LLRCalculator(const NRZMapper &, const AWGN &) -> LLRCalculator< Fp< 2 > >
RHS & operator>>(LHS &&lhs, RHS &dst)
Assignment chaining: x >> dst ≡ dst = x; return dst.
Definition blocks.hpp:1122