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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76template <
typename T,
typename InputType,
typename OutputType>
79 const T&
derived()
const noexcept {
return static_cast<
const T&>(*
this); }
81 T&
derived()
noexcept {
return static_cast<T&>(*
this); }
85
86
87
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]));
96
97
98
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);
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])));
113
114
115
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)));
126
127
128
129
131 if constexpr (std::is_same_v<InputType, OutputType>) {
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))));
136 return std::move(in);
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))));
148
149
150
151
152
153
154 template <FiniteFieldType U>
155 requires(!std::is_same_v<U, InputType>)
177template <
typename T,
typename ElementType>
191
192
193
194
195
196
197
198
199
200
201
202
203
204template <FieldType T>
211
212
213
214
215
216
217
221
222
223
224
228 double get_pe()
const noexcept {
return pe; }
231 double get_px()
const noexcept {
return px; }
234
235
236
237
242
243
244
245
246
247
248
256 return px + 2.0L *
std::
sqrt((1.0L -
pe -
px) *
pe / (
q - 1.0L)) + (
q - 2.0L) *
pe / (
q - 1.0L);
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
273template <FieldType T>
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");
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());
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());
297template <FieldType T>
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();
306 error_failures_before_hit = error_dist(gen());
311#ifdef CECCO_ERASURE_SUPPORT
314 }
else if (px > 0.0) {
315 if (erasure_trials == erasure_failures_before_hit) {
318 erasure_failures_before_hit = erasure_dist(gen());
327template <FieldType T>
332 const double q =
static_cast<
double>(
T::
get_size());
341
342
343
344
345
346
347
348
360
361
362
363
364
365
369
370
371
372
373
374
385
386
387
388
389
390
391
392
393
394
395
401
402
403
404
405
406
407
411
412
413
414
416 if (in == Fp<2>(0))
return in;
421 double get_pe()
const noexcept {
return bsc.get_pe(); }
424
425
426
428 const double pe = bsc.get_pe();
430 if (pe == 0.0)
return 1.0;
431 if (pe == 1.0)
return 0.0;
433 return std::log2(1 + (1 - pe) *
std::pow(pe, pe / (1 - pe)));
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
464
465
466
467
468 constexpr NRZMapper(
double a,
double b)
noexcept : a(a), b(b) {}
471
472
475 constexpr double get_Eb()
const noexcept {
return (a * a) + (b * b) / 4.0; }
478 constexpr double get_a()
const noexcept {
return a; }
481 constexpr double get_b()
const noexcept {
return b; }
486
487
488
489
492 return {a - b / 2.0, 0};
494 return {a + b / 2.0, 0};
503
504
505
506
507
508
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
541
542
543
544
545
546 AWGN(
double EbNodB,
double a,
double b)
548 sigma(
std::sqrt(0.5 * Eb /
std::pow(10.0, EbNodB / 10.0))),
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!");
558
559
568 constexpr double get_pe()
const noexcept {
return pe; }
573
574
575
576
578 if (sigma == 0.0)
return in;
580 const double noise_i = dist(gen());
581 const double noise_q = dist(gen());
582 return {in.real() + noise_i, in.imag() + noise_q};
586
587
588
589
590
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));
599 const double sigma{};
600 std::normal_distribution<
double> dist;
604
605
606
607
608
609
610 static double calculate_pe(
double EbNodB,
double Eb,
double b)
noexcept;
613double AWGN::calculate_pe(
double EbNodB,
double Eb,
double b)
noexcept {
614 if (Eb == 0.0)
return 0.5;
618 const double EbN0_linear =
std::pow(10.0, EbNodB / 10.0);
623 const double constellation_snr = (b * b * EbN0_linear) / (4.0 * Eb);
625 return 0.5 *
std::erfc(
std::sqrt(constellation_snr));
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
650
651
652
653
654
658
659
660
661
662 std::complex<
double>
operator()(
const Fp<2>& in) {
return transmission(encoder(in)); }
671
672
673
674
675
679 constexpr double get_pe()
const noexcept {
return transmission.get_pe(); }
682 double get_sigma()
const noexcept {
return transmission.get_standard_deviation(); }
685
686
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));
701 const double sigma = transmission.get_standard_deviation();
703 if (b == 0.0)
return 0.0;
704 if (sigma == 0.0)
return 1.0;
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);
713 auto g = [&f_Y_star](
double x) ->
double {
714 double fy = f_Y_star(x);
715 if (fy <= 0.0)
return 0.0;
716 return fy *
std::log2(fy);
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;
727 const double h = (ub - lb) / N;
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);
735 const double integral = h * sum / 3.0;
737 return std::clamp(-integral - std::log2(sigma * std::sqrt(2 * std::numbers::pi * std::numbers::e)), 0.0, 1.0);
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
762
763
764
768
769
770
774
775
776
777
790
791
792
793
801
802
803
804
805
806
807
808
809
810template <FiniteFieldType T =
Fp<2>>
814
815
816
817
828
829
830
841
842
843
849
850
851
852
853
856 throw std::
logic_error(
"This LLRCalculator was not constructed for a soft input!");
857 if (
sigmasq == 0.0)
return 0.0;
862
863
864
865
866
874
875
876
877
878
879
880
881
882
883
884
887 throw std::
logic_error(
"This LLRCalculator was not constructed for a soft input!");
892 throw std::
invalid_argument(
"BI-AWGN input must have [T:F2] rows (the binary image of the code symbols)!");
913
914
915
916
917
922 throw std::
logic_error(
"This LLRCalculator was not constructed for hard input!");
927#ifdef CECCO_ERASURE_SUPPORT
942
943
944
945
946
951 throw std::
logic_error(
"This LLRCalculator was not constructed for hard input!");
959#ifdef CECCO_ERASURE_SUPPORT
974 const channel_model_t model;
977 const double sigmasq;
981 double sdmec_reliability()
const noexcept {
983 if (px == 1.0)
return 0.0;
984 return std::numeric_limits<
double>::infinity();
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);
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008template <FiniteFieldType E, FiniteFieldType S>
1015
1016
1017
1018
1022
1023
1024
1025
1029template <FiniteFieldType E, FiniteFieldType S>
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1064
1065
1066
1067
1071
1072
1073
1074
1078template <FiniteFieldType S, FiniteFieldType E>
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099template <
class LHS,
class RHS>
1101 requires(RHS&& r, LHS&& x) { std::forward<RHS>(r)(std::forward<LHS>(x)); }
1103 return std::forward<RHS>(rhs)(std::forward<LHS>(lhs));
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118template <
class LHS,
class RHS>
1123 dst = std::forward<LHS>(lhs);
Additive White Gaussian Noise channel for complex-valued symbols.
double get_standard_deviation() const noexcept
Noise standard deviation σ (per real component).
double get_capacity() const noexcept
Shannon capacity in bits per symbol (real signaling).
constexpr double get_pe() const noexcept
Theoretical hard-decision bit error probability for the configured constellation/SNR.
double get_variance() const noexcept
Noise variance σ² (per real component).
AWGN(double EbNodB, double a, double b)
Construct with SNR and constellation parameters.
std::complex< double > operator()(const std::complex< double > &in)
Add Gaussian noise to a complex symbol.
Binary Asymmetric Channel (Z-channel): 0 is preserved; 1 flips to 0 with probability p.
double get_capacity() const noexcept
Shannon capacity in bits per symbol.
BAC(double p)
Construct with flip probability p.
Fp< 2 > operator()(const Fp< 2 > &in)
Process one bit.
double get_pe() const noexcept
Flip probability p (probability that 1 → 0).
Binary Erasure Channel: symbols are received correctly or marked erased.
BEC(double px)
Construct with symbol erasure probability px.
Binary-Input AWGN: fused NRZMapper + AWGN block.
double get_capacity() const noexcept
Shannon capacity in bits per symbol.
double get_sigma() const noexcept
Noise standard deviation σ.
const AWGN & get_transmission() const noexcept
Internal AWGN block (for noise statistics).
constexpr double get_pe() const noexcept
Theoretical hard-decision bit error probability for this constellation/SNR.
const NRZMapper & get_encoder() const noexcept
Internal NRZ mapper (for constructing matching demappers/LLR calculators).
long double get_Bhattacharyya_param() const noexcept
Bhattacharyya parameter γ = exp(−b²/(8σ²)).
std::complex< double > operator()(const Fp< 2 > &in)
Map a bit and add noise.
BI_AWGN(double EbN0dB, double a=0.0, double b=2.0)
Construct with SNR and constellation parameters.
BPSK hard-decision demapper: NRZDemapper with threshold 0.
constexpr BPSKDemapper() noexcept
Construct BPSK demapper (threshold 0).
Binary Phase Shift Keying mapper: NRZMapper with a = 0, b = 2.
constexpr BPSKMapper() noexcept
Construct BPSK mapper (a = 0, b = 2).
Expand extension-field elements into subfield coordinates.
constexpr DEMUX()=default
constexpr Vector< S > operator()(const E &in)
Expand a single element into its subfield-coefficient vector.
Compute symbol-level log-likelihood ratios.
LLRCalculator(const NRZMapper &nrz, const AWGN &transmission) noexcept
Construct a BI-AWGN calculator from a matching mapper and channel.
Non-Return-to-Zero (NRZ) hard-decision demapper.
NRZDemapper(const BI_AWGN &bi_awgn) noexcept
Construct from a BI-AWGN channel (convenience).
constexpr NRZDemapper(const NRZMapper &nrz) noexcept
Construct from the corresponding mapper.
constexpr Fp< 2 > operator()(const std::complex< double > &in) noexcept
Hard-decide a received symbol.
Non-Return-to-Zero (NRZ) mapper for binary modulation.
constexpr std::complex< double > operator()(const Fp< 2 > &in) noexcept
Map a bit to its constellation point.
constexpr double get_b() const noexcept
Constellation distance parameter b.
constexpr double get_Eb() const noexcept
Energy per bit Eb = a² + b²/4.
constexpr NRZMapper(double a, double b) noexcept
Construct with constellation parameters.
constexpr double get_a() const noexcept
DC offset parameter a.
Symmetric errors-and-erasures channel over a finite field.
double get_pe() const noexcept
Symbol error probability pe (erroneous and not erased).
double get_px() const noexcept
Symbol erasure probability px.
T operator()(const T &in)
Process one symbol.
SDMEC(double pe, double px=0.0)
Construct with error probability pe and erasure probability px.
double get_capacity() const noexcept
Shannon capacity in bits per symbol.
long double get_Bhattacharyya_param() const noexcept
Pairwise Bhattacharyya parameter.
CRTP base providing scalar, vector, and matrix block operations.
Vector< OutputType > operator()(Vector< InputType > &&in)
Apply the block element-wise (rvalue input).
Vector< OutputType > operator()(const Vector< InputType > &in)
Apply the block element-wise to each vector entry.
const T & derived() const noexcept
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 ...
BlockProcessor< T, std::complex< double >, Fp< 2 > > DecoderProcessor
CRTP base for blocks mapping complex symbols back to bits (hard demod).
BlockProcessor< T, ElementType, ElementType > SameTypeProcessor
CRTP base for blocks where input and output element types coincide.
BlockProcessor< T, Fp< 2 >, std::complex< double > > EncoderProcessor
CRTP base for blocks mapping bits to complex symbols (e.g. NRZ/BPSK modulation).
Provides a framework for error correcting codes.
LLRCalculator(const BI_AWGN &) -> LLRCalculator< Fp< 2 > >
decltype(auto) operator>>(LHS &&lhs, RHS &&rhs)
Function-call chaining: x >> f ≡ f(x).
LLRCalculator(const NRZMapper &, const AWGN &) -> LLRCalculator< Fp< 2 > >
RHS & operator>>(LHS &&lhs, RHS &dst)
Assignment chaining: x >> dst ≡ dst = x; return dst.