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.
Files changed (84) hide show
  1. commkit/__init__.py +74 -0
  2. commkit/_cuda/__init__.py +321 -0
  3. commkit/_cuda/compiler.py +88 -0
  4. commkit/_cuda/src/bps_min_d2.cu +104 -0
  5. commkit/_cuda/src/cs_block.cu +119 -0
  6. commkit/_cuda/src/selftest.cu +14 -0
  7. commkit/analysis/__init__.py +55 -0
  8. commkit/analysis/_common.py +236 -0
  9. commkit/analysis/allan.py +108 -0
  10. commkit/analysis/drift.py +213 -0
  11. commkit/analysis/interferometry.py +887 -0
  12. commkit/analysis/linewidth.py +480 -0
  13. commkit/analysis/trajectory.py +91 -0
  14. commkit/backend.py +507 -0
  15. commkit/coding/__init__.py +23 -0
  16. commkit/coding/base.py +17 -0
  17. commkit/coding/bch.py +6 -0
  18. commkit/coding/convolutional.py +7 -0
  19. commkit/coding/crc.py +7 -0
  20. commkit/coding/galois.py +8 -0
  21. commkit/coding/hamming.py +6 -0
  22. commkit/coding/interleaving.py +7 -0
  23. commkit/coding/ldpc.py +8 -0
  24. commkit/coding/polar.py +8 -0
  25. commkit/coding/ratematch.py +6 -0
  26. commkit/coding/reed_solomon.py +6 -0
  27. commkit/coding/turbo.py +8 -0
  28. commkit/core/__init__.py +32 -0
  29. commkit/core/frame.py +992 -0
  30. commkit/core/generation.py +581 -0
  31. commkit/core/signal.py +725 -0
  32. commkit/equalization/__init__.py +49 -0
  33. commkit/equalization/_block.py +1855 -0
  34. commkit/equalization/_common.py +606 -0
  35. commkit/equalization/_kernels_jax.py +1720 -0
  36. commkit/equalization/_kernels_numba.py +1704 -0
  37. commkit/equalization/blind.py +223 -0
  38. commkit/equalization/linear.py +365 -0
  39. commkit/equalization/polarization.py +790 -0
  40. commkit/equalization/result.py +191 -0
  41. commkit/equalization/sequential.py +2805 -0
  42. commkit/filtering.py +1120 -0
  43. commkit/frequency.py +1191 -0
  44. commkit/helpers.py +489 -0
  45. commkit/impairments/__init__.py +43 -0
  46. commkit/impairments/channel/__init__.py +20 -0
  47. commkit/impairments/channel/linear.py +310 -0
  48. commkit/impairments/channel/nonlinear.py +11 -0
  49. commkit/impairments/frontend.py +229 -0
  50. commkit/impairments/noise.py +105 -0
  51. commkit/impairments/source.py +219 -0
  52. commkit/io.py +308 -0
  53. commkit/logger.py +103 -0
  54. commkit/mapping/__init__.py +46 -0
  55. commkit/mapping/bits.py +240 -0
  56. commkit/mapping/constellation.py +153 -0
  57. commkit/mapping/gray.py +429 -0
  58. commkit/mapping/llr.py +253 -0
  59. commkit/mapping/shaping.py +218 -0
  60. commkit/metrics.py +949 -0
  61. commkit/multirate.py +476 -0
  62. commkit/plotting/__init__.py +78 -0
  63. commkit/plotting/analysis.py +627 -0
  64. commkit/plotting/constellation.py +483 -0
  65. commkit/plotting/equalizer.py +390 -0
  66. commkit/plotting/eye.py +388 -0
  67. commkit/plotting/spectral.py +575 -0
  68. commkit/plotting/sync.py +953 -0
  69. commkit/plotting/theme.py +203 -0
  70. commkit/plotting/waveform.py +200 -0
  71. commkit/py.typed +0 -0
  72. commkit/recovery/__init__.py +51 -0
  73. commkit/recovery/bps.py +337 -0
  74. commkit/recovery/corrections.py +751 -0
  75. commkit/recovery/pilots.py +803 -0
  76. commkit/recovery/pll.py +482 -0
  77. commkit/recovery/tikhonov.py +424 -0
  78. commkit/recovery/viterbi_viterbi.py +227 -0
  79. commkit/spectral.py +560 -0
  80. commkit/timing.py +841 -0
  81. commkit-1.0.0.dist-info/METADATA +145 -0
  82. commkit-1.0.0.dist-info/RECORD +84 -0
  83. commkit-1.0.0.dist-info/WHEEL +4 -0
  84. commkit-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,218 @@
1
+ """
2
+ Probabilistic shaping (PS-QAM).
3
+
4
+ Maxwell-Boltzmann symbol distributions over a QAM constellation, the entropy /
5
+ shaping-parameter inversion, MB sampling, and the pmf-weighted constellation
6
+ power that ties the PS scale conventions together.
7
+ """
8
+
9
+ from functools import lru_cache
10
+
11
+ import numpy as np
12
+
13
+ from ..backend import ArrayType, to_device
14
+ from .gray import gray_constellation
15
+
16
+ __all__ = [
17
+ "constellation_power",
18
+ "maxwell_boltzmann",
19
+ "optimal_nu",
20
+ "ps_entropy",
21
+ "sample_ps_symbols",
22
+ ]
23
+
24
+
25
+ def constellation_power(
26
+ constellation: ArrayType, pmf: ArrayType | None = None
27
+ ) -> float:
28
+ r"""Average symbol power ``E[|s|^2]`` of a constellation.
29
+
30
+ For a uniform constellation (``pmf=None``) this is the unweighted mean
31
+ power ``mean(|s|^2)``. For probabilistic shaping it is the pmf-weighted
32
+ power ``Σ_m P(s_m) |s_m|^2`` - the quantity written ``E_PS`` when the
33
+ constellation is on the normalised grid (where it is ``< 1``).
34
+
35
+ The value is *grid-agnostic*: it reports the average power of the
36
+ constellation exactly as passed - ``≈1`` for a normalised uniform grid,
37
+ ``E_PS < 1`` for a normalised shaped grid, or the raw integer-grid energy
38
+ (e.g. ``10`` for an unnormalised 16-QAM). Callers apply their own
39
+ rescaling (``√E_PS`` on the received symbols, or ``1/√E_PS`` on the
40
+ constellation) using the returned value.
41
+
42
+ This is the single source of truth for PS-QAM power across the library;
43
+ prefer it over the inline ``Σ pmf·|s|^2`` idiom.
44
+
45
+ Parameters
46
+ ----------
47
+ constellation : array_like
48
+ Constellation points, shape ``(M,)``. NumPy, CuPy, or list.
49
+ pmf : array_like, optional
50
+ Per-point probabilities, shape ``(M,)``, aligned with
51
+ ``constellation`` and summing to 1 (e.g. from
52
+ :func:`maxwell_boltzmann`). ``None`` (uniform) returns the
53
+ unweighted mean power.
54
+
55
+ Returns
56
+ -------
57
+ float
58
+ Host scalar ``E[|s|^2]``.
59
+
60
+ Raises
61
+ ------
62
+ ValueError
63
+ If ``pmf`` is supplied and its length does not match the
64
+ constellation.
65
+ """
66
+ const = np.asarray(to_device(constellation, "cpu"))
67
+ energies = np.abs(const).astype(np.float64) ** 2
68
+ if pmf is None:
69
+ return float(np.mean(energies))
70
+ pmf_arr = np.asarray(to_device(pmf, "cpu"), dtype=np.float64).ravel()
71
+ if pmf_arr.shape[0] != energies.shape[0]:
72
+ raise ValueError(
73
+ f"pmf length {pmf_arr.shape[0]} does not match constellation "
74
+ f"length {energies.shape[0]}."
75
+ )
76
+ return float(np.dot(pmf_arr, energies))
77
+
78
+
79
+ @lru_cache(maxsize=256)
80
+ def maxwell_boltzmann(order: int, nu: float) -> np.ndarray:
81
+ """
82
+ Computes the Maxwell-Boltzmann PMF over a QAM constellation.
83
+
84
+ P(s_m) = exp(-nu * |s_m|^2) / Z(nu), where |s_m|^2 is on the unnormalized
85
+ integer grid (literature-compatible nu scale). Indexed consistently with
86
+ ``gray_constellation``.
87
+
88
+ Parameters
89
+ ----------
90
+ order : int
91
+ QAM modulation order (must be a power of 2, e.g. 16, 64, 256).
92
+ nu : float
93
+ Shaping parameter nu >= 0. nu = 0 returns the uniform distribution.
94
+ Larger nu concentrates probability on lower-energy points.
95
+
96
+ Returns
97
+ -------
98
+ np.ndarray
99
+ PMF array of shape ``(order,)``, dtype float64, summing to 1.
100
+ Cached by ``(order, nu)``.
101
+ """
102
+ # Energies computed on the unnormalized integer grid for literature-compatible ν.
103
+ # The PMF index ordering matches gray_constellation (normalized), since both
104
+ # use the same Gray-code index assignment.
105
+ unnorm_constellation = gray_constellation("qam", order, normalize=False)
106
+ if nu == 0.0:
107
+ return np.full(order, 1.0 / order, dtype=np.float64)
108
+ energies = np.abs(unnorm_constellation) ** 2 # (M,) unnormalized energies
109
+ log_p = -nu * energies
110
+ log_p -= log_p.max() # shift for numerical stability before exp
111
+ p = np.exp(log_p)
112
+ return p / p.sum()
113
+
114
+
115
+ def ps_entropy(order: int, nu: float) -> float:
116
+ r"""
117
+ Computes the per-symbol Shannon entropy under a Maxwell-Boltzmann distribution.
118
+
119
+ H(X) = -sum_m P(s_m) * log2(P(s_m)) [bits/symbol]
120
+
121
+ Parameters
122
+ ----------
123
+ order : int
124
+ QAM modulation order.
125
+ nu : float
126
+ MB shaping parameter nu >= 0. nu = 0 returns log2(order).
127
+
128
+ Returns
129
+ -------
130
+ float
131
+ Entropy in bits per symbol. In the range (0, log2(order)].
132
+ """
133
+ pmf = maxwell_boltzmann(order, nu)
134
+ nonzero = pmf > 0
135
+ return float(-np.sum(pmf[nonzero] * np.log2(pmf[nonzero])))
136
+
137
+
138
+ def optimal_nu(order: int, entropy_bits: float) -> tuple:
139
+ r"""
140
+ Finds the MB shaping parameter ``ν`` that achieves a target per-symbol entropy.
141
+
142
+ Uses ``scipy.optimize.brentq`` to bisect on
143
+ ``ps_entropy(order, ν) - entropy_bits = 0``.
144
+
145
+ Parameters
146
+ ----------
147
+ order : int
148
+ QAM modulation order.
149
+ entropy_bits : float
150
+ Target per-symbol entropy in bits. Must be in ``(0, log₂(order)]``.
151
+
152
+ Returns
153
+ -------
154
+ nu : float
155
+ Shaping parameter achieving the target entropy.
156
+ achieved_entropy : float
157
+ Actual entropy at the returned ``nu`` (may differ by ``< 1e-6`` bits).
158
+
159
+ Raises
160
+ ------
161
+ ValueError
162
+ If ``entropy_bits`` is outside ``(0, log₂(order)]``.
163
+ """
164
+ from scipy.optimize import brentq
165
+
166
+ max_h = np.log2(order)
167
+ if not (0 < entropy_bits <= max_h):
168
+ raise ValueError(
169
+ f"entropy_bits must be in (0, {max_h:.3f}] for {order}-QAM, "
170
+ f"got {entropy_bits:.4f}"
171
+ )
172
+ if np.isclose(entropy_bits, max_h, atol=1e-8):
173
+ return 0.0, float(max_h)
174
+
175
+ def _obj(nu):
176
+ return ps_entropy(order, nu) - entropy_bits
177
+
178
+ # Grow upper bound until entropy drops below target
179
+ nu_hi = 0.01
180
+ while ps_entropy(order, nu_hi) > entropy_bits:
181
+ nu_hi *= 10.0
182
+
183
+ nu_opt = float(brentq(_obj, 0.0, nu_hi, xtol=1e-9, rtol=1e-9))
184
+ return nu_opt, ps_entropy(order, nu_opt)
185
+
186
+
187
+ def sample_ps_symbols(
188
+ num_symbols: int,
189
+ order: int,
190
+ pmf: np.ndarray,
191
+ seed: int | None = None,
192
+ ) -> np.ndarray:
193
+ """
194
+ Draws QAM symbols from a Maxwell-Boltzmann distribution.
195
+
196
+ Parameters
197
+ ----------
198
+ num_symbols : int
199
+ Number of symbols to generate.
200
+ order : int
201
+ QAM modulation order.
202
+ pmf : np.ndarray
203
+ Symbol PMF of shape ``(order,)``. Typically from
204
+ ``maxwell_boltzmann``.
205
+ seed : int, optional
206
+ Random seed for reproducibility.
207
+
208
+ Returns
209
+ -------
210
+ np.ndarray
211
+ Complex-valued symbols drawn from the MB distribution.
212
+ Shape: ``(num_symbols,)``, dtype ``complex64``.
213
+ All values lie exactly on the normalized QAM constellation grid.
214
+ """
215
+ rng = np.random.default_rng(seed)
216
+ constellation = gray_constellation("qam", order).astype(np.complex64)
217
+ indices = rng.choice(order, size=num_symbols, p=np.asarray(pmf, dtype=np.float64))
218
+ return constellation[indices]