commkit 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- commkit/__init__.py +74 -0
- commkit/_cuda/__init__.py +321 -0
- commkit/_cuda/compiler.py +88 -0
- commkit/_cuda/src/bps_min_d2.cu +104 -0
- commkit/_cuda/src/cs_block.cu +119 -0
- commkit/_cuda/src/selftest.cu +14 -0
- commkit/analysis/__init__.py +55 -0
- commkit/analysis/_common.py +236 -0
- commkit/analysis/allan.py +108 -0
- commkit/analysis/drift.py +213 -0
- commkit/analysis/interferometry.py +887 -0
- commkit/analysis/linewidth.py +480 -0
- commkit/analysis/trajectory.py +91 -0
- commkit/backend.py +507 -0
- commkit/coding/__init__.py +23 -0
- commkit/coding/base.py +17 -0
- commkit/coding/bch.py +6 -0
- commkit/coding/convolutional.py +7 -0
- commkit/coding/crc.py +7 -0
- commkit/coding/galois.py +8 -0
- commkit/coding/hamming.py +6 -0
- commkit/coding/interleaving.py +7 -0
- commkit/coding/ldpc.py +8 -0
- commkit/coding/polar.py +8 -0
- commkit/coding/ratematch.py +6 -0
- commkit/coding/reed_solomon.py +6 -0
- commkit/coding/turbo.py +8 -0
- commkit/core/__init__.py +32 -0
- commkit/core/frame.py +992 -0
- commkit/core/generation.py +581 -0
- commkit/core/signal.py +725 -0
- commkit/equalization/__init__.py +49 -0
- commkit/equalization/_block.py +1855 -0
- commkit/equalization/_common.py +606 -0
- commkit/equalization/_kernels_jax.py +1720 -0
- commkit/equalization/_kernels_numba.py +1704 -0
- commkit/equalization/blind.py +223 -0
- commkit/equalization/linear.py +365 -0
- commkit/equalization/polarization.py +790 -0
- commkit/equalization/result.py +191 -0
- commkit/equalization/sequential.py +2805 -0
- commkit/filtering.py +1120 -0
- commkit/frequency.py +1191 -0
- commkit/helpers.py +489 -0
- commkit/impairments/__init__.py +43 -0
- commkit/impairments/channel/__init__.py +20 -0
- commkit/impairments/channel/linear.py +310 -0
- commkit/impairments/channel/nonlinear.py +11 -0
- commkit/impairments/frontend.py +229 -0
- commkit/impairments/noise.py +105 -0
- commkit/impairments/source.py +219 -0
- commkit/io.py +308 -0
- commkit/logger.py +103 -0
- commkit/mapping/__init__.py +46 -0
- commkit/mapping/bits.py +240 -0
- commkit/mapping/constellation.py +153 -0
- commkit/mapping/gray.py +429 -0
- commkit/mapping/llr.py +253 -0
- commkit/mapping/shaping.py +218 -0
- commkit/metrics.py +949 -0
- commkit/multirate.py +476 -0
- commkit/plotting/__init__.py +78 -0
- commkit/plotting/analysis.py +627 -0
- commkit/plotting/constellation.py +483 -0
- commkit/plotting/equalizer.py +390 -0
- commkit/plotting/eye.py +388 -0
- commkit/plotting/spectral.py +575 -0
- commkit/plotting/sync.py +953 -0
- commkit/plotting/theme.py +203 -0
- commkit/plotting/waveform.py +200 -0
- commkit/py.typed +0 -0
- commkit/recovery/__init__.py +51 -0
- commkit/recovery/bps.py +337 -0
- commkit/recovery/corrections.py +751 -0
- commkit/recovery/pilots.py +803 -0
- commkit/recovery/pll.py +482 -0
- commkit/recovery/tikhonov.py +424 -0
- commkit/recovery/viterbi_viterbi.py +227 -0
- commkit/spectral.py +560 -0
- commkit/timing.py +841 -0
- commkit-1.0.0.dist-info/METADATA +145 -0
- commkit-1.0.0.dist-info/RECORD +84 -0
- commkit-1.0.0.dist-info/WHEEL +4 -0
- commkit-1.0.0.dist-info/licenses/LICENSE +21 -0
commkit/mapping/bits.py
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hard bit mapping and demapping.
|
|
3
|
+
|
|
4
|
+
Maps a flat bit sequence to Gray-coded symbols (``map_bits``) and recovers the
|
|
5
|
+
most-likely bits from received symbols via minimum-distance decoding
|
|
6
|
+
(``demap_symbols_hard``).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from ..backend import ArrayType, dispatch
|
|
12
|
+
from ..core.signal import Signal
|
|
13
|
+
from ..logger import logger
|
|
14
|
+
from .gray import gray_code, gray_constellation
|
|
15
|
+
from .shaping import constellation_power
|
|
16
|
+
|
|
17
|
+
__all__ = ["demap_symbols_hard", "map_bits"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def map_bits(
|
|
21
|
+
bits: ArrayType,
|
|
22
|
+
modulation: str,
|
|
23
|
+
order: int,
|
|
24
|
+
unipolar: bool = False,
|
|
25
|
+
) -> ArrayType:
|
|
26
|
+
"""
|
|
27
|
+
Maps a bit sequence to complex or real symbols.
|
|
28
|
+
|
|
29
|
+
This function follows a bit-first architecture: it takes a flat sequence
|
|
30
|
+
of bits and packs them into symbols according to the modulation scheme
|
|
31
|
+
and Gray mapping.
|
|
32
|
+
|
|
33
|
+
Output dtype is ``complex64`` for PSK/QAM and ``float32`` for ASK/PAM.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
bits : array_like
|
|
38
|
+
Input binary sequence (0s and 1s).
|
|
39
|
+
modulation : {"psk", "qam", "ask", "pam"}
|
|
40
|
+
Modulation scheme.
|
|
41
|
+
order : int
|
|
42
|
+
Modulation order (number of symbols).
|
|
43
|
+
unipolar : bool, default False
|
|
44
|
+
Trigger unipolar mapping for ASK/PAM.
|
|
45
|
+
|
|
46
|
+
Returns
|
|
47
|
+
-------
|
|
48
|
+
array_like
|
|
49
|
+
Array of symbols. Shape: (N_bits / log2(order),).
|
|
50
|
+
Backend (NumPy/CuPy) matches the input `bits`.
|
|
51
|
+
"""
|
|
52
|
+
logger.debug("Mapping bits to %s %s-level symbols.", modulation.upper(), order)
|
|
53
|
+
bits, xp, _ = dispatch(bits)
|
|
54
|
+
|
|
55
|
+
if order < 2:
|
|
56
|
+
raise ValueError("Order must be at least 2 for modulation")
|
|
57
|
+
|
|
58
|
+
k = int(np.log2(order))
|
|
59
|
+
if 2**k != order:
|
|
60
|
+
raise ValueError(f"Order must be a power of 2, got {order}")
|
|
61
|
+
|
|
62
|
+
if len(bits) % k != 0:
|
|
63
|
+
raise ValueError(
|
|
64
|
+
f"Number of bits ({len(bits)}) must be divisible by bits per symbol ({k})"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Reshape bits into symbols (N/k, k)
|
|
68
|
+
num_symbols = len(bits) // k
|
|
69
|
+
|
|
70
|
+
# Pack bits into integer indices
|
|
71
|
+
bits_reshaped = bits.reshape((num_symbols, k))
|
|
72
|
+
powers = 2 ** xp.arange(k - 1, -1, -1, dtype=int)
|
|
73
|
+
indices = xp.sum(bits_reshaped * powers, axis=1)
|
|
74
|
+
|
|
75
|
+
# Get constellation (returns NumPy)
|
|
76
|
+
constellation = gray_constellation(modulation, order, unipolar=unipolar)
|
|
77
|
+
|
|
78
|
+
# Ensure constellation is on the same backend and dtype
|
|
79
|
+
constellation = xp.asarray(constellation)
|
|
80
|
+
|
|
81
|
+
# ASK/PAM constellations are real-valued; PSK/QAM are complex.
|
|
82
|
+
mod_lower = modulation.lower()
|
|
83
|
+
|
|
84
|
+
if "ask" in mod_lower or "pam" in mod_lower:
|
|
85
|
+
constellation = constellation.astype(xp.float32)
|
|
86
|
+
else:
|
|
87
|
+
constellation = constellation.astype(xp.complex64)
|
|
88
|
+
|
|
89
|
+
# Map indices to points
|
|
90
|
+
return constellation[indices]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def demap_symbols_hard(
|
|
94
|
+
symbols: ArrayType | Signal,
|
|
95
|
+
modulation: str | None = None,
|
|
96
|
+
order: int | None = None,
|
|
97
|
+
unipolar: bool = False,
|
|
98
|
+
pmf: np.ndarray | None = None,
|
|
99
|
+
) -> ArrayType | Signal:
|
|
100
|
+
"""
|
|
101
|
+
Maps complex symbols back to a sequence of bits (hard decisions).
|
|
102
|
+
|
|
103
|
+
This function performs minimum Euclidean distance decoding to find the
|
|
104
|
+
most likely bit pattern for each received symbol.
|
|
105
|
+
|
|
106
|
+
Parameters
|
|
107
|
+
----------
|
|
108
|
+
symbols : array_like
|
|
109
|
+
Input array of received symbols. Shape: (..., N_symbols).
|
|
110
|
+
modulation : {"psk", "qam", "ask"}
|
|
111
|
+
Modulation type.
|
|
112
|
+
order : int
|
|
113
|
+
Modulation order.
|
|
114
|
+
unipolar : bool, default False
|
|
115
|
+
Trigger unipolar demapping for ASK/PAM.
|
|
116
|
+
pmf : np.ndarray, optional
|
|
117
|
+
Symbol PMF of shape ``(order,)`` for PS-QAM. When provided, the
|
|
118
|
+
input symbols are scaled by ``sqrt(E_PS)`` (where
|
|
119
|
+
``E_PS = Σ P(s_m) |s_m|²`` on the normalised grid) before the
|
|
120
|
+
nearest-neighbour search, mapping unit-avg-power resolved symbols
|
|
121
|
+
back to the ``{s_m}`` grid used by ``gray_constellation``.
|
|
122
|
+
Use this when ``symbols`` comes from
|
|
123
|
+
``resolved_symbols`` of a PS-QAM signal.
|
|
124
|
+
Has no effect for uniform modulations.
|
|
125
|
+
|
|
126
|
+
Returns
|
|
127
|
+
-------
|
|
128
|
+
array_like
|
|
129
|
+
Sequence of bits (0s and 1s). Shape: (..., N_symbols * log2(order)).
|
|
130
|
+
The backend (NumPy/CuPy) matches the input `symbols`.
|
|
131
|
+
"""
|
|
132
|
+
if isinstance(symbols, Signal):
|
|
133
|
+
sig = symbols
|
|
134
|
+
if sig.signal_type is not None:
|
|
135
|
+
logger.warning(
|
|
136
|
+
"demap_symbols_hard() called on a frame-generated signal - skipping. "
|
|
137
|
+
"Extract the payload segment via frame.get_structure_map() and build "
|
|
138
|
+
"a plain Signal before demapping."
|
|
139
|
+
)
|
|
140
|
+
return sig.copy()
|
|
141
|
+
if sig.mod_scheme is None or sig.mod_order is None:
|
|
142
|
+
raise ValueError("Modulation scheme and order required for demapping.")
|
|
143
|
+
if sig.resolved_symbols is None:
|
|
144
|
+
raise ValueError(
|
|
145
|
+
"No resolved symbols available. Call resolve_symbols(sig) first."
|
|
146
|
+
)
|
|
147
|
+
new = sig.copy()
|
|
148
|
+
new.resolved_bits = demap_symbols_hard(
|
|
149
|
+
sig.resolved_symbols,
|
|
150
|
+
sig.mod_scheme,
|
|
151
|
+
sig.mod_order,
|
|
152
|
+
unipolar=sig.mod_unipolar or False,
|
|
153
|
+
pmf=sig.ps_pmf if pmf is None else pmf,
|
|
154
|
+
)
|
|
155
|
+
return new
|
|
156
|
+
|
|
157
|
+
if modulation is None or order is None:
|
|
158
|
+
raise ValueError("demap_symbols_hard() requires modulation and order.")
|
|
159
|
+
|
|
160
|
+
logger.debug("Demapping %s %s-level symbols to bits.", modulation.upper(), order)
|
|
161
|
+
symbols, xp, _ = dispatch(symbols)
|
|
162
|
+
|
|
163
|
+
# Capture original shape to restore structure later
|
|
164
|
+
# If input is (N,), output (N*k,)
|
|
165
|
+
# If input is (C, N), output (C, N*k)
|
|
166
|
+
original_shape = symbols.shape
|
|
167
|
+
|
|
168
|
+
# Ensure 1D for vectorized distance broadcasting
|
|
169
|
+
symbols_flat = symbols.flatten()
|
|
170
|
+
|
|
171
|
+
# Get constellation
|
|
172
|
+
constellation = gray_constellation(modulation, order, unipolar=unipolar)
|
|
173
|
+
|
|
174
|
+
# PS-QAM: receive-path symbols at unit average power live on the
|
|
175
|
+
# ``{s_m/sqrt(E_PS)}`` grid. Rescale by ``sqrt(E_PS)`` to bring them back
|
|
176
|
+
# to the ``{s_m}`` grid that ``gray_constellation`` returns, so the
|
|
177
|
+
# nearest-neighbour search is exact. No-op for uniform modulations.
|
|
178
|
+
if pmf is not None:
|
|
179
|
+
e_ps = constellation_power(constellation, pmf)
|
|
180
|
+
if e_ps < 1.0 - 1e-6:
|
|
181
|
+
symbols_flat = symbols_flat * xp.asarray(
|
|
182
|
+
np.sqrt(e_ps), dtype=symbols_flat.real.dtype
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
constellation = xp.asarray(constellation)
|
|
186
|
+
|
|
187
|
+
# 1. Find nearest constellation point (Hard Decision)
|
|
188
|
+
k = int(np.log2(order))
|
|
189
|
+
is_sq_qam = (modulation == "qam") and (order != 8) and (k % 2 == 0)
|
|
190
|
+
|
|
191
|
+
if is_sq_qam:
|
|
192
|
+
# O(1) component-wise rounding - no (N, M) distance matrix.
|
|
193
|
+
# Levels are evenly spaced; round each axis independently.
|
|
194
|
+
n_ax = k // 2 # bits per axis
|
|
195
|
+
side = 2**n_ax # points per axis
|
|
196
|
+
levels = xp.sort(xp.unique(constellation.real)) # (side,)
|
|
197
|
+
lev_min = float(levels[0])
|
|
198
|
+
d_grid = float(levels[1] - levels[0])
|
|
199
|
+
# Gray LUT: sorted-level index (geometric) -> natural-binary symbol index
|
|
200
|
+
gray_lut = xp.asarray(gray_code(n_ax)) # (side,)
|
|
201
|
+
g_i = xp.clip(
|
|
202
|
+
xp.round((symbols_flat.real - lev_min) / d_grid).astype(xp.int64),
|
|
203
|
+
0,
|
|
204
|
+
side - 1,
|
|
205
|
+
)
|
|
206
|
+
g_q = xp.clip(
|
|
207
|
+
xp.round((symbols_flat.imag - lev_min) / d_grid).astype(xp.int64),
|
|
208
|
+
0,
|
|
209
|
+
side - 1,
|
|
210
|
+
)
|
|
211
|
+
indices = (gray_lut[g_i] << n_ax) | gray_lut[g_q] # (N_flat,)
|
|
212
|
+
else:
|
|
213
|
+
# General path: chunk N to bound peak memory at (CHUNK_N, M_const).
|
|
214
|
+
CHUNK_N = 4096
|
|
215
|
+
indices = xp.empty(len(symbols_flat), dtype=xp.int64)
|
|
216
|
+
for n0 in range(0, len(symbols_flat), CHUNK_N):
|
|
217
|
+
n1 = min(n0 + CHUNK_N, len(symbols_flat))
|
|
218
|
+
d = xp.abs(symbols_flat[n0:n1, xp.newaxis] - constellation[xp.newaxis, :])
|
|
219
|
+
indices[n0:n1] = xp.argmin(d, axis=1)
|
|
220
|
+
|
|
221
|
+
# 2. Convert indices to bits
|
|
222
|
+
# Extract bits from indices: (N, k)
|
|
223
|
+
# We use bit shifting: (index >> shift) & 1
|
|
224
|
+
shifts = xp.arange(k - 1, -1, -1, dtype="int32")
|
|
225
|
+
bits = ((indices[:, xp.newaxis] >> shifts) & 1).astype(xp.int8)
|
|
226
|
+
|
|
227
|
+
# 3. Reshape to restore original structure
|
|
228
|
+
# bits is currently (Total_Symbols, k)
|
|
229
|
+
# We flatten it to (Total_Symbols * k)
|
|
230
|
+
flat_bits = bits.flatten()
|
|
231
|
+
|
|
232
|
+
# Calculate new shape: replace last dimension D with D*k
|
|
233
|
+
# If original shape was (D1, D2, ..., Dn), new shape is (D1, D2, ..., Dn * k)
|
|
234
|
+
if len(original_shape) > 0:
|
|
235
|
+
new_shape = list(original_shape)
|
|
236
|
+
new_shape[-1] = new_shape[-1] * k
|
|
237
|
+
return flat_bits.reshape(new_shape)
|
|
238
|
+
else:
|
|
239
|
+
# Scalar input case (if supported), returns 1D array of bits
|
|
240
|
+
return flat_bits
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The :class:`Constellation` value object.
|
|
3
|
+
|
|
4
|
+
A small immutable container bundling the three things that are otherwise
|
|
5
|
+
passed around as loose tuples/arrays at every CMA/RDE radius and GMI/MI call
|
|
6
|
+
site: the constellation ``points``, their natural-binary ``bit_labels``, and an
|
|
7
|
+
optional probabilistic-shaping ``pmf``. ``Constellation.gray(...)`` is the
|
|
8
|
+
canonical constructor (delegating to :func:`gray_constellation`), and
|
|
9
|
+
``power()`` / ``map()`` / ``demap()`` / ``llr()`` are thin wrappers over the
|
|
10
|
+
existing free functions so a single object carries geometry + labels + prior
|
|
11
|
+
together.
|
|
12
|
+
|
|
13
|
+
The loose-array forms (``gray_constellation``, ``map_bits``,
|
|
14
|
+
``demap_symbols_hard``, ``compute_llr``, ``constellation_power``) remain the
|
|
15
|
+
public surface; this object is an additive convenience layered on top.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass, replace
|
|
19
|
+
from functools import lru_cache
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
|
|
23
|
+
from ..backend import ArrayType
|
|
24
|
+
from .bits import demap_symbols_hard, map_bits
|
|
25
|
+
from .gray import gray_constellation
|
|
26
|
+
from .llr import compute_llr
|
|
27
|
+
from .shaping import constellation_power
|
|
28
|
+
|
|
29
|
+
__all__ = ["Constellation"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True, eq=False)
|
|
33
|
+
class Constellation:
|
|
34
|
+
"""Immutable constellation: points, Gray bit-labels, and optional PS pmf.
|
|
35
|
+
|
|
36
|
+
Attributes
|
|
37
|
+
----------
|
|
38
|
+
points : np.ndarray
|
|
39
|
+
Constellation points, shape ``(M,)``, indexed by natural-binary symbol
|
|
40
|
+
value (``points[i]`` is the symbol for integer ``i``). Complex for
|
|
41
|
+
PSK/QAM, real for ASK/PAM.
|
|
42
|
+
bit_labels : np.ndarray
|
|
43
|
+
Natural-binary bit pattern per symbol index, shape ``(M, k)`` where
|
|
44
|
+
``k = log2(M)``, dtype ``int8`` (MSB first).
|
|
45
|
+
modulation : str
|
|
46
|
+
Modulation scheme (``"psk"``, ``"qam"``, ``"ask"``, ``"pam"``).
|
|
47
|
+
order : int
|
|
48
|
+
Modulation order ``M``.
|
|
49
|
+
unipolar : bool
|
|
50
|
+
Whether the grid is the unipolar ASK/PAM variant.
|
|
51
|
+
pmf : np.ndarray or None
|
|
52
|
+
Optional Maxwell-Boltzmann shaping prior, shape ``(M,)``, aligned with
|
|
53
|
+
``points``. ``None`` means uniform.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
points: np.ndarray
|
|
57
|
+
bit_labels: np.ndarray
|
|
58
|
+
modulation: str
|
|
59
|
+
order: int
|
|
60
|
+
unipolar: bool = False
|
|
61
|
+
pmf: np.ndarray | None = None
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def bits_per_symbol(self) -> int:
|
|
65
|
+
"""Number of bits per symbol, ``log2(order)``."""
|
|
66
|
+
return int(self.bit_labels.shape[1])
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def gray(
|
|
70
|
+
cls,
|
|
71
|
+
modulation: str,
|
|
72
|
+
order: int,
|
|
73
|
+
*,
|
|
74
|
+
normalize: bool = True,
|
|
75
|
+
unipolar: bool = False,
|
|
76
|
+
pmf: np.ndarray | None = None,
|
|
77
|
+
) -> "Constellation":
|
|
78
|
+
"""Build a Gray-mapped constellation (cached for the ``pmf=None`` base).
|
|
79
|
+
|
|
80
|
+
Parameters mirror :func:`gray_constellation`. ``pmf`` attaches a
|
|
81
|
+
probabilistic-shaping prior without rebuilding the geometry.
|
|
82
|
+
"""
|
|
83
|
+
base = _gray_base(modulation, order, normalize, unipolar)
|
|
84
|
+
if pmf is None:
|
|
85
|
+
return base
|
|
86
|
+
return replace(base, pmf=np.asarray(pmf, dtype=np.float64))
|
|
87
|
+
|
|
88
|
+
def power(self) -> float:
|
|
89
|
+
"""pmf-weighted average symbol power ``E[|s|^2]`` (uniform if no pmf)."""
|
|
90
|
+
return constellation_power(self.points, self.pmf)
|
|
91
|
+
|
|
92
|
+
def map(self, bits: ArrayType) -> ArrayType:
|
|
93
|
+
"""Map a flat bit sequence to symbols (see :func:`map_bits`)."""
|
|
94
|
+
return map_bits(bits, self.modulation, self.order, unipolar=self.unipolar)
|
|
95
|
+
|
|
96
|
+
def demap(self, symbols: ArrayType) -> ArrayType:
|
|
97
|
+
"""Hard-decision demap symbols to bits (see :func:`demap_symbols_hard`).
|
|
98
|
+
|
|
99
|
+
Carries this constellation's ``pmf`` through for PS-QAM rescaling.
|
|
100
|
+
"""
|
|
101
|
+
return demap_symbols_hard(
|
|
102
|
+
symbols,
|
|
103
|
+
self.modulation,
|
|
104
|
+
self.order,
|
|
105
|
+
unipolar=self.unipolar,
|
|
106
|
+
pmf=self.pmf,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def llr(
|
|
110
|
+
self,
|
|
111
|
+
symbols: ArrayType,
|
|
112
|
+
noise_var: float,
|
|
113
|
+
*,
|
|
114
|
+
method: str = "maxlog",
|
|
115
|
+
output: str = "jax",
|
|
116
|
+
) -> ArrayType:
|
|
117
|
+
"""Soft-decision LLRs (see :func:`compute_llr`), carrying this ``pmf``."""
|
|
118
|
+
return compute_llr(
|
|
119
|
+
symbols,
|
|
120
|
+
self.modulation,
|
|
121
|
+
self.order,
|
|
122
|
+
noise_var,
|
|
123
|
+
method=method,
|
|
124
|
+
unipolar=self.unipolar,
|
|
125
|
+
output=output,
|
|
126
|
+
pmf=self.pmf,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@lru_cache(maxsize=128)
|
|
131
|
+
def _gray_base(
|
|
132
|
+
modulation: str, order: int, normalize: bool, unipolar: bool
|
|
133
|
+
) -> Constellation:
|
|
134
|
+
"""Cached Gray constellation (geometry + labels), without a shaping pmf."""
|
|
135
|
+
points = gray_constellation(
|
|
136
|
+
modulation, order, normalize=normalize, unipolar=unipolar
|
|
137
|
+
)
|
|
138
|
+
k = int(np.log2(order))
|
|
139
|
+
bit_labels = (
|
|
140
|
+
(
|
|
141
|
+
np.arange(order, dtype="int32")[:, None]
|
|
142
|
+
>> np.arange(k - 1, -1, -1, dtype="int32")
|
|
143
|
+
)
|
|
144
|
+
& 1
|
|
145
|
+
).astype(np.int8)
|
|
146
|
+
return Constellation(
|
|
147
|
+
points=points,
|
|
148
|
+
bit_labels=bit_labels,
|
|
149
|
+
modulation=modulation,
|
|
150
|
+
order=order,
|
|
151
|
+
unipolar=unipolar,
|
|
152
|
+
pmf=None,
|
|
153
|
+
)
|