commkit 1.0.0__tar.gz → 1.1.0__tar.gz

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 (168) hide show
  1. {commkit-1.0.0 → commkit-1.1.0}/CLAUDE.md +191 -38
  2. {commkit-1.0.0 → commkit-1.1.0}/PKG-INFO +39 -19
  3. {commkit-1.0.0 → commkit-1.1.0}/README.md +33 -18
  4. {commkit-1.0.0 → commkit-1.1.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0001_commkit_baseline.json +774 -729
  5. {commkit-1.0.0 → commkit-1.1.0}/benchmarks/conftest.py +29 -0
  6. {commkit-1.0.0 → commkit-1.1.0}/commkit/__init__.py +1 -1
  7. {commkit-1.0.0 → commkit-1.1.0}/commkit/analysis/__init__.py +4 -0
  8. {commkit-1.0.0 → commkit-1.1.0}/commkit/analysis/_common.py +177 -17
  9. {commkit-1.0.0 → commkit-1.1.0}/commkit/analysis/allan.py +3 -3
  10. {commkit-1.0.0 → commkit-1.1.0}/commkit/analysis/drift.py +38 -26
  11. {commkit-1.0.0 → commkit-1.1.0}/commkit/analysis/interferometry.py +160 -99
  12. {commkit-1.0.0 → commkit-1.1.0}/commkit/analysis/linewidth.py +78 -86
  13. {commkit-1.0.0 → commkit-1.1.0}/commkit/analysis/trajectory.py +25 -22
  14. {commkit-1.0.0 → commkit-1.1.0}/commkit/backend.py +15 -3
  15. {commkit-1.0.0 → commkit-1.1.0}/commkit/core/frame.py +32 -28
  16. {commkit-1.0.0 → commkit-1.1.0}/commkit/core/generation.py +196 -7
  17. {commkit-1.0.0 → commkit-1.1.0}/commkit/core/signal.py +3 -1
  18. commkit-1.1.0/commkit/equalization/_block/__init__.py +36 -0
  19. commkit-1.1.0/commkit/equalization/_block/_blind.py +371 -0
  20. commkit-1.0.0/commkit/equalization/_block.py → commkit-1.1.0/commkit/equalization/_block/_dd.py +75 -768
  21. commkit-1.1.0/commkit/equalization/_block/_seqmode.py +421 -0
  22. {commkit-1.0.0 → commkit-1.1.0}/commkit/equalization/_common.py +9 -27
  23. {commkit-1.0.0 → commkit-1.1.0}/commkit/equalization/blind.py +107 -8
  24. {commkit-1.0.0 → commkit-1.1.0}/commkit/equalization/linear.py +64 -26
  25. {commkit-1.0.0 → commkit-1.1.0}/commkit/equalization/polarization.py +134 -23
  26. {commkit-1.0.0 → commkit-1.1.0}/commkit/equalization/result.py +7 -5
  27. commkit-1.1.0/commkit/equalization/sequential/__init__.py +22 -0
  28. commkit-1.1.0/commkit/equalization/sequential/_blind.py +1089 -0
  29. commkit-1.0.0/commkit/equalization/sequential.py → commkit-1.1.0/commkit/equalization/sequential/_dd.py +174 -999
  30. {commkit-1.0.0 → commkit-1.1.0}/commkit/filtering.py +386 -254
  31. {commkit-1.0.0 → commkit-1.1.0}/commkit/frequency.py +374 -114
  32. commkit-1.1.0/commkit/helpers.py +1108 -0
  33. {commkit-1.0.0 → commkit-1.1.0}/commkit/impairments/channel/linear.py +132 -54
  34. {commkit-1.0.0 → commkit-1.1.0}/commkit/impairments/frontend.py +74 -57
  35. {commkit-1.0.0 → commkit-1.1.0}/commkit/impairments/noise.py +41 -10
  36. {commkit-1.0.0 → commkit-1.1.0}/commkit/impairments/source.py +54 -14
  37. {commkit-1.0.0 → commkit-1.1.0}/commkit/mapping/__init__.py +13 -1
  38. {commkit-1.0.0 → commkit-1.1.0}/commkit/mapping/bits.py +62 -29
  39. {commkit-1.0.0 → commkit-1.1.0}/commkit/mapping/constellation.py +2 -8
  40. {commkit-1.0.0 → commkit-1.1.0}/commkit/mapping/gray.py +111 -1
  41. {commkit-1.0.0 → commkit-1.1.0}/commkit/mapping/llr.py +71 -22
  42. {commkit-1.0.0 → commkit-1.1.0}/commkit/mapping/shaping.py +39 -0
  43. {commkit-1.0.0 → commkit-1.1.0}/commkit/metrics.py +197 -105
  44. {commkit-1.0.0 → commkit-1.1.0}/commkit/multirate.py +37 -79
  45. {commkit-1.0.0 → commkit-1.1.0}/commkit/plotting/__init__.py +1 -1
  46. {commkit-1.0.0 → commkit-1.1.0}/commkit/plotting/analysis.py +13 -49
  47. {commkit-1.0.0 → commkit-1.1.0}/commkit/plotting/constellation.py +5 -5
  48. {commkit-1.0.0 → commkit-1.1.0}/commkit/plotting/equalizer.py +7 -128
  49. {commkit-1.0.0 → commkit-1.1.0}/commkit/plotting/eye.py +3 -2
  50. commkit-1.1.0/commkit/plotting/filter_response.py +219 -0
  51. {commkit-1.0.0 → commkit-1.1.0}/commkit/plotting/spectral.py +17 -69
  52. {commkit-1.0.0 → commkit-1.1.0}/commkit/plotting/sync.py +13 -46
  53. {commkit-1.0.0 → commkit-1.1.0}/commkit/plotting/theme.py +28 -0
  54. {commkit-1.0.0 → commkit-1.1.0}/commkit/plotting/waveform.py +10 -35
  55. commkit-1.1.0/commkit/recovery/_common.py +119 -0
  56. {commkit-1.0.0 → commkit-1.1.0}/commkit/recovery/bps.py +89 -50
  57. {commkit-1.0.0 → commkit-1.1.0}/commkit/recovery/corrections.py +393 -189
  58. {commkit-1.0.0 → commkit-1.1.0}/commkit/recovery/pilots.py +146 -83
  59. {commkit-1.0.0 → commkit-1.1.0}/commkit/recovery/pll.py +74 -40
  60. {commkit-1.0.0 → commkit-1.1.0}/commkit/recovery/tikhonov.py +84 -74
  61. {commkit-1.0.0 → commkit-1.1.0}/commkit/recovery/viterbi_viterbi.py +77 -89
  62. commkit-1.1.0/commkit/smoothing.py +130 -0
  63. {commkit-1.0.0 → commkit-1.1.0}/commkit/spectral.py +88 -56
  64. {commkit-1.0.0 → commkit-1.1.0}/commkit/timing.py +113 -46
  65. {commkit-1.0.0 → commkit-1.1.0}/pyproject.toml +5 -2
  66. {commkit-1.0.0 → commkit-1.1.0}/tests/analysis/test_interferometry.py +31 -0
  67. commkit-1.1.0/tests/analysis/test_trajectory.py +97 -0
  68. commkit-1.1.0/tests/core/test_generation.py +58 -0
  69. {commkit-1.0.0 → commkit-1.1.0}/tests/core/test_psqam.py +31 -0
  70. {commkit-1.0.0 → commkit-1.1.0}/tests/equalization/test_blind.py +35 -0
  71. {commkit-1.0.0 → commkit-1.1.0}/tests/equalization/test_block.py +24 -2
  72. {commkit-1.0.0 → commkit-1.1.0}/tests/equalization/test_linear.py +53 -0
  73. {commkit-1.0.0 → commkit-1.1.0}/tests/equalization/test_polarization.py +124 -0
  74. {commkit-1.0.0 → commkit-1.1.0}/tests/equalization/test_sequential.py +69 -0
  75. {commkit-1.0.0 → commkit-1.1.0}/tests/equalization/test_sequential_jax.py +11 -7
  76. {commkit-1.0.0 → commkit-1.1.0}/tests/impairments/channel/test_channel_linear.py +48 -0
  77. {commkit-1.0.0 → commkit-1.1.0}/tests/impairments/test_frontend.py +45 -0
  78. {commkit-1.0.0 → commkit-1.1.0}/tests/impairments/test_noise.py +13 -0
  79. {commkit-1.0.0 → commkit-1.1.0}/tests/impairments/test_source.py +13 -0
  80. {commkit-1.0.0 → commkit-1.1.0}/tests/mapping/test_gray.py +79 -0
  81. {commkit-1.0.0 → commkit-1.1.0}/tests/mapping/test_llr.py +34 -0
  82. {commkit-1.0.0 → commkit-1.1.0}/tests/recovery/test_bps.py +30 -0
  83. {commkit-1.0.0 → commkit-1.1.0}/tests/recovery/test_corrections.py +182 -0
  84. {commkit-1.0.0 → commkit-1.1.0}/tests/recovery/test_pilots.py +52 -0
  85. {commkit-1.0.0 → commkit-1.1.0}/tests/recovery/test_pll.py +17 -0
  86. {commkit-1.0.0 → commkit-1.1.0}/tests/recovery/test_tikhonov.py +19 -0
  87. {commkit-1.0.0 → commkit-1.1.0}/tests/recovery/test_viterbi_viterbi.py +17 -0
  88. {commkit-1.0.0 → commkit-1.1.0}/tests/test_filtering.py +164 -51
  89. {commkit-1.0.0 → commkit-1.1.0}/tests/test_frequency.py +77 -0
  90. commkit-1.1.0/tests/test_helpers.py +587 -0
  91. {commkit-1.0.0 → commkit-1.1.0}/tests/test_multirate.py +0 -11
  92. {commkit-1.0.0 → commkit-1.1.0}/tests/test_plotting.py +51 -25
  93. commkit-1.1.0/tests/test_smoothing.py +54 -0
  94. {commkit-1.0.0 → commkit-1.1.0}/tests/test_timing.py +49 -1
  95. {commkit-1.0.0 → commkit-1.1.0}/uv.lock +9 -2
  96. commkit-1.0.0/commkit/helpers.py +0 -489
  97. commkit-1.0.0/tests/analysis/test_trajectory.py +0 -50
  98. commkit-1.0.0/tests/test_helpers.py +0 -225
  99. {commkit-1.0.0 → commkit-1.1.0}/.gitattributes +0 -0
  100. {commkit-1.0.0 → commkit-1.1.0}/.github/workflows/ci.yml +0 -0
  101. {commkit-1.0.0 → commkit-1.1.0}/.github/workflows/publish.yml +0 -0
  102. {commkit-1.0.0 → commkit-1.1.0}/.gitignore +0 -0
  103. {commkit-1.0.0 → commkit-1.1.0}/.python-version +0 -0
  104. {commkit-1.0.0 → commkit-1.1.0}/LICENSE +0 -0
  105. {commkit-1.0.0 → commkit-1.1.0}/benchmarks/bench_block_blind.py +0 -0
  106. {commkit-1.0.0 → commkit-1.1.0}/benchmarks/bench_block_lms.py +0 -0
  107. {commkit-1.0.0 → commkit-1.1.0}/benchmarks/bench_bps.py +0 -0
  108. {commkit-1.0.0 → commkit-1.1.0}/benchmarks/bench_equalizers.py +0 -0
  109. {commkit-1.0.0 → commkit-1.1.0}/benchmarks/bench_sync_misc.py +0 -0
  110. {commkit-1.0.0 → commkit-1.1.0}/benchmarks/benchutils.py +0 -0
  111. {commkit-1.0.0 → commkit-1.1.0}/benchmarks/workloads.py +0 -0
  112. {commkit-1.0.0 → commkit-1.1.0}/commkit/_cuda/__init__.py +0 -0
  113. {commkit-1.0.0 → commkit-1.1.0}/commkit/_cuda/compiler.py +0 -0
  114. {commkit-1.0.0 → commkit-1.1.0}/commkit/_cuda/src/bps_min_d2.cu +0 -0
  115. {commkit-1.0.0 → commkit-1.1.0}/commkit/_cuda/src/cs_block.cu +0 -0
  116. {commkit-1.0.0 → commkit-1.1.0}/commkit/_cuda/src/selftest.cu +0 -0
  117. {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/__init__.py +0 -0
  118. {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/base.py +0 -0
  119. {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/bch.py +0 -0
  120. {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/convolutional.py +0 -0
  121. {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/crc.py +0 -0
  122. {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/galois.py +0 -0
  123. {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/hamming.py +0 -0
  124. {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/interleaving.py +0 -0
  125. {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/ldpc.py +0 -0
  126. {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/polar.py +0 -0
  127. {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/ratematch.py +0 -0
  128. {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/reed_solomon.py +0 -0
  129. {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/turbo.py +0 -0
  130. {commkit-1.0.0 → commkit-1.1.0}/commkit/core/__init__.py +0 -0
  131. {commkit-1.0.0 → commkit-1.1.0}/commkit/equalization/__init__.py +0 -0
  132. {commkit-1.0.0 → commkit-1.1.0}/commkit/equalization/_kernels_jax.py +0 -0
  133. {commkit-1.0.0 → commkit-1.1.0}/commkit/equalization/_kernels_numba.py +0 -0
  134. {commkit-1.0.0 → commkit-1.1.0}/commkit/impairments/__init__.py +0 -0
  135. {commkit-1.0.0 → commkit-1.1.0}/commkit/impairments/channel/__init__.py +0 -0
  136. {commkit-1.0.0 → commkit-1.1.0}/commkit/impairments/channel/nonlinear.py +0 -0
  137. {commkit-1.0.0 → commkit-1.1.0}/commkit/io.py +0 -0
  138. {commkit-1.0.0 → commkit-1.1.0}/commkit/logger.py +0 -0
  139. {commkit-1.0.0 → commkit-1.1.0}/commkit/py.typed +0 -0
  140. {commkit-1.0.0 → commkit-1.1.0}/commkit/recovery/__init__.py +0 -0
  141. {commkit-1.0.0 → commkit-1.1.0}/examples/carrier_phase_analysis.py +0 -0
  142. {commkit-1.0.0 → commkit-1.1.0}/examples/laser_linewidth_dsh.py +0 -0
  143. {commkit-1.0.0 → commkit-1.1.0}/examples/laser_linewidth_homodyne_iq.py +0 -0
  144. {commkit-1.0.0 → commkit-1.1.0}/examples/measurement_laser_linewidth_dsh.py +0 -0
  145. {commkit-1.0.0 → commkit-1.1.0}/examples/measurement_laser_linewidth_homodyne_iq.py +0 -0
  146. {commkit-1.0.0 → commkit-1.1.0}/tests/analysis/test_allan.py +0 -0
  147. {commkit-1.0.0 → commkit-1.1.0}/tests/analysis/test_drift.py +0 -0
  148. {commkit-1.0.0 → commkit-1.1.0}/tests/analysis/test_linewidth.py +0 -0
  149. {commkit-1.0.0 → commkit-1.1.0}/tests/conftest.py +0 -0
  150. {commkit-1.0.0 → commkit-1.1.0}/tests/core/test_frame.py +0 -0
  151. {commkit-1.0.0 → commkit-1.1.0}/tests/core/test_signal.py +0 -0
  152. {commkit-1.0.0 → commkit-1.1.0}/tests/core/test_signal_mimo.py +0 -0
  153. {commkit-1.0.0 → commkit-1.1.0}/tests/equalization/test_block_update.py +0 -0
  154. {commkit-1.0.0 → commkit-1.1.0}/tests/equalization/test_bps_kernel.py +0 -0
  155. {commkit-1.0.0 → commkit-1.1.0}/tests/equalization/test_cpr.py +0 -0
  156. {commkit-1.0.0 → commkit-1.1.0}/tests/equalization/test_cs_kernel.py +0 -0
  157. {commkit-1.0.0 → commkit-1.1.0}/tests/equalization/test_mimo.py +0 -0
  158. {commkit-1.0.0 → commkit-1.1.0}/tests/equalization/test_winit.py +0 -0
  159. {commkit-1.0.0 → commkit-1.1.0}/tests/mapping/test_bits.py +0 -0
  160. {commkit-1.0.0 → commkit-1.1.0}/tests/mapping/test_constellation.py +0 -0
  161. {commkit-1.0.0 → commkit-1.1.0}/tests/recovery/test_joint.py +0 -0
  162. {commkit-1.0.0 → commkit-1.1.0}/tests/test_backend.py +0 -0
  163. {commkit-1.0.0 → commkit-1.1.0}/tests/test_cuda_infra.py +0 -0
  164. {commkit-1.0.0 → commkit-1.1.0}/tests/test_io.py +0 -0
  165. {commkit-1.0.0 → commkit-1.1.0}/tests/test_logger.py +0 -0
  166. {commkit-1.0.0 → commkit-1.1.0}/tests/test_metrics.py +0 -0
  167. {commkit-1.0.0 → commkit-1.1.0}/tests/test_pulse_shaping.py +0 -0
  168. {commkit-1.0.0 → commkit-1.1.0}/tests/test_spectral.py +0 -0
@@ -78,7 +78,7 @@ uv run pytest benchmarks/ --benchmark-only --device=all \
78
78
  --benchmark-compare=0001 --benchmark-storage=file://benchmarks/baselines
79
79
  ```
80
80
 
81
- See **Section 6 - Benchmark Suite** for what each file measures and how to read the results.
81
+ See **Section 5 - Benchmark Suite** for what each file measures and how to read the results.
82
82
 
83
83
  ### Linting & Formatting
84
84
 
@@ -153,37 +153,7 @@ Since `bump-my-version` is defined in the project's development dependencies, al
153
153
 
154
154
  ---
155
155
 
156
- ## 3. Reference Implementation Files
157
-
158
- > `examples/` holds notebook-style scripts (jupytext `# %%` cell format, ready
159
- > to convert to `.ipynb`) that walk through a full analysis chain end-to-end
160
- > with the physics, method limitations, and interpretation of the results
161
- > spelled out in markdown cells:
162
- >
163
- > * `examples/carrier_phase_analysis.py` - data-aided carrier-phase
164
- > characterization of a coherent transmission (trajectory -> drift/PN split ->
165
- > linewidth -> Allan -> dashboard).
166
- > * `examples/laser_linewidth_dsh.py` - delayed self-heterodyne (AOM receiver)
167
- > laser linewidth and FM-noise PSD estimation (all three estimators, both
168
- > coherence regimes, flicker-noise effects).
169
- > * `examples/laser_linewidth_homodyne_iq.py` - the decoherence interferometer
170
- > with a 90°-hybrid IQ receiver (no AOM) as the main path: dark-capture DC
171
- > calibration, GSOP, all three estimators at 0 Hz, discriminator Allan
172
- > deviation.
173
- > * `examples/measurement_laser_linewidth_dsh.py` /
174
- > `examples/measurement_laser_linewidth_homodyne_iq.py` - **measurement
175
- > templates**: edit the system-parameter cell, point `DATA_FILE` at a real
176
- > capture, run. Include τ_d calibration from the notch comb (DSH) and a
177
- > seeded synthetic demo fallback so they run end-to-end without data.
178
- >
179
- > Examples are **runners, not library code**: orchestration chains belong in
180
- > `examples/` (or user projects), never as `commkit` functions. For
181
- > everything not yet covered by an example, `tests/` is the most current usage
182
- > reference for every public function.
183
-
184
- ---
185
-
186
- ## 4. DSP & Coding Guidelines
156
+ ## 3. DSP & Coding Guidelines
187
157
 
188
158
  ### Multi-Backend Dispatching
189
159
 
@@ -264,6 +234,151 @@ Inside library code, **never extract a scalar from a possibly-GPU array inside a
264
234
  * **SISO**: 1-D array: `(N_samples,)`
265
235
  * **MIMO**: 2-D array: `(N_channels, N_samples)` - **time is always on the last axis**.
266
236
 
237
+ Never hand-roll the promote/squeeze idiom. `commkit/helpers.py` owns it, so the
238
+ validation is identical everywhere (0-d and 3-D inputs raise a shape error
239
+ naming the offending argument instead of silently reaching a confusing
240
+ broadcast failure downstream):
241
+
242
+ ```python
243
+ from commkit.helpers import as_2d, broadcast_channels, require_channels, restore_1d
244
+
245
+ def my_dsp_function(samples, ref_symbols):
246
+ x, xp, _ = dispatch(samples)
247
+ x2, was_1d = as_2d(x, name="samples") # (N,) -> (1, N)
248
+ ref = broadcast_channels(xp.asarray(ref_symbols), # (L,) / (1, L) -> (C, L)
249
+ x2.shape[0], xp, name="ref_symbols")
250
+ out = ... # channel-batched body
251
+ return restore_1d(was_1d, out) # squeeze back for SISO
252
+ ```
253
+
254
+ * `as_2d` / `restore_1d` - the promote/squeeze pair. `restore_1d` takes several
255
+ outputs at once: `drift, pn = restore_1d(was_1d, drift, pn)`.
256
+ * `broadcast_channels` - a shared reference across channels, with the
257
+ channel-count check the bare `ref[None, :]` promotion lacks. Returns a
258
+ **read-only** broadcast view; `.copy()` before writing.
259
+ * `require_channels` - for entry points defined only at a fixed channel count
260
+ (dual-pol channel models); pass `description=` to keep domain wording in the
261
+ error.
262
+ * `to_report_scalar` - the reporting-layer counterpart: collapses a `(C,)`
263
+ metric to a Python float for SISO, transferring from device if needed.
264
+ * `linear_trend_slope` / `remove_linear_trend` - the per-channel least-squares
265
+ slope shared by the pilot-tone and DSH detrend stages; keeps `Σ(x-x̄)²` on
266
+ device rather than syncing it back as a float.
267
+
268
+ Note these are plain indexing/broadcast helpers, valid on NumPy, CuPy **and**
269
+ JAX arrays - unlike `dispatch`, which recognizes NumPy/CuPy only and will
270
+ silently pull a JAX array to the host.
271
+
272
+ ### Signal-Awareness
273
+
274
+ Any new DSP function whose primary argument is genuinely **signal-representable
275
+ data** (raw IQ samples, or a field a `Signal` actually carries, like
276
+ `resolved_symbols`) should accept a `Signal` alongside a raw array, transparently:
277
+ array in → array out, `Signal` in → `Signal` out. Use the same idiom
278
+ everywhere, defined once in `commkit/helpers.py`:
279
+
280
+ ```python
281
+ from commkit.helpers import unwrap_signal, rewrap_signal
282
+
283
+ def fir_filter(samples, taps, axis=-1):
284
+ x, sig = unwrap_signal(samples) # x: array; sig: Signal | None
285
+ if sig is not None:
286
+ return rewrap_signal(sig, fir_filter(x, taps, axis=-1))
287
+ ... existing array-only body, unchanged ...
288
+ ```
289
+
290
+ * `unwrap_signal(x, *, field="samples")` - returns `(array, signal_or_None)`.
291
+ Most functions read `.samples`; a few (hard-decision demapping, phase-rotation
292
+ correction on `resolved_symbols`) pass `field=` to read a different attribute.
293
+ * `rewrap_signal(sig, array, **metadata)` - `sig=None` passes `array` through
294
+ unchanged; otherwise returns `sig.copy()` with `.samples = array` and any
295
+ `**metadata` kwargs applied via `setattr` (e.g. `sampling_rate=sig.symbol_rate`
296
+ after decimating to symbol rate).
297
+
298
+ **Metadata priority: the `Signal`'s own value always wins.** When a function
299
+ takes both a `Signal` and a scalar metadata parameter that duplicates one of
300
+ its fields (`sampling_rate`, `sps`, `mod_scheme`, `mod_order`, `ps_pmf`,
301
+ ...), the `Signal`'s field - never the supplied argument - is what the
302
+ Signal-branch call actually uses. A caller passing a conflicting value on a
303
+ `Signal` is a bug to catch, not a request to honor: silently letting the
304
+ supplied value win would let a signal at 2 GSa/s be equalized with a stale
305
+ `sps=2` nobody meant to apply to it.
306
+
307
+ There is no shared helper for this - both cases are short enough to write
308
+ inline in the `if sig is not None:` branch, and a generic helper covering
309
+ both the "always wins" and "falls back with a warning" behaviors in one call
310
+ signature ends up harder to read at the call site than the few extra lines.
311
+
312
+ * **Required fields** (`sampling_rate`, `symbol_rate`, and the derived `sps`)
313
+ are never `None` on a `Signal` - pydantic enforces it - so the Signal
314
+ branch references `sig.sampling_rate`/`sig.sps` directly and drops the
315
+ supplied argument entirely. There is no case where the Signal is missing
316
+ the field, but the caller can still pass a stale/conflicting value by
317
+ mistake, so warn whenever one was supplied at all:
318
+
319
+ ```python
320
+ x, sig = unwrap_signal(samples)
321
+ if sig is not None:
322
+ # sig.sampling_rate is required, so it always wins over a supplied
323
+ # sampling_rate - see CLAUDE.md, "Signal-Awareness".
324
+ if sampling_rate is not None:
325
+ logger.warning(
326
+ "my_dsp_function(): ignoring supplied sampling_rate=%r for "
327
+ "Signal input; using the signal's own sampling_rate=%r instead.",
328
+ sampling_rate,
329
+ sig.sampling_rate,
330
+ )
331
+ return my_dsp_function(x, sig.sampling_rate, ...)
332
+ ```
333
+
334
+ * **Optional fields** (`mod_scheme`, `mod_order`, `ps_pmf`, ...) can
335
+ legitimately be unset on a `Signal` (e.g. an unshaped QAM signal has
336
+ `ps_pmf=None`). Read the Signal's field first; fall back to the supplied
337
+ argument - logging a `logger.warning` - only when the Signal genuinely
338
+ lacks it *and* a fallback was supplied. Passing neither stays silent (the
339
+ common case), so the log isn't spammed on every call to an unshaped signal:
340
+
341
+ ```python
342
+ mod = sig.mod_scheme
343
+ if mod is None:
344
+ mod = modulation
345
+ if mod is not None:
346
+ logger.warning(
347
+ "my_dsp_function(): Signal has no mod_scheme set; falling "
348
+ "back to supplied modulation=%r.", mod,
349
+ )
350
+ ```
351
+
352
+ **Metadata rules for functions that change rate or domain:**
353
+
354
+ | Situation | What to pass `rewrap_signal` |
355
+ | --- | --- |
356
+ | Shape/rate unchanged (most `apply_*`/`correct_*`) | `rewrap_signal(sig, result)` - no metadata |
357
+ | Decimates to one sample/symbol (equalizers) | `sampling_rate=sig.symbol_rate` |
358
+ | Upsamples by an integer factor | `sampling_rate=sig.sampling_rate * factor` |
359
+ | Resamples to an explicit target rate | `sampling_rate=<the target rate param>` |
360
+ | Output field differs from the input field (`resolve_symbols`, `demap_symbols_hard`) | Skip `rewrap_signal`; `sig.copy()` + `setattr` by hand - the one sanctioned exception |
361
+ | Returns a scalar/dict/tuple in a different domain (frequency/tau/PSD bins, phase or frequency *estimates*) | `unwrap_signal` only, on the input - never wrap the output |
362
+
363
+ **Not every array parameter is signal-representable.** A function that
364
+ consumes a *derived* quantity - a phase or frequency trajectory, a
365
+ correlation array, PSD bins, an `EqualizerResult`, filter taps - one or more
366
+ stages downstream of the original capture has no sound field to unwrap from;
367
+ forcing Signal-awareness there is backwards, since there is no `Signal` yet
368
+ (or any more) to preserve metadata from. `smooth_phase_wiener`,
369
+ `estimate_fractional_delay`, `allan_deviation`, and every `plot_*` function
370
+ in `plotting/{analysis,equalizer,sync}.py` are examples: they take arrays a
371
+ `Signal` would never hold as `.samples`, so they stay plain-array functions.
372
+ Likewise, a function whose only job is to build the data a `Signal` gets
373
+ constructed *from* is a synthesis primitive, not a transform on existing
374
+ `Signal` data - same exclusion as `generate_*`. `core/generation.py`'s
375
+ `shape_pulse`/`expand` (TX symbol -> waveform) and
376
+ `equalization.build_pilot_ref` (sparse pilots -> dense equalizer reference)
377
+ are the current examples; note that `shape_pulse`/`expand` live in
378
+ `core/generation.py` rather than `filtering.py`/`multirate.py` for exactly
379
+ this reason - they build the samples a `Signal` gets constructed from, not
380
+ transform an existing one.
381
+
267
382
  ### Naming Conventions
268
383
 
269
384
  * **Verb prefixes for processing functions.** Recovery/correction routines follow a
@@ -296,9 +411,38 @@ Inside library code, **never extract a scalar from a possibly-GPU array inside a
296
411
  modules and `plotting`. Never add a bare-noun plot function that shadows a
297
412
  compute function.
298
413
 
414
+ ### Design-vs-Apply Separation
415
+
416
+ For any DSP building block with distinct "design" and "apply" phases - a
417
+ coefficient/parameter generator, and a step that consumes those coefficients
418
+ against data - keep the two as separate functions rather than one function
419
+ that does both internally. This lets a caller design once and apply many
420
+ times (e.g. reuse the same filter across a batch of signals) and keeps each
421
+ function's signature focused. Established examples in `filtering.py`:
422
+
423
+ * FIR taps generators (`rrc_taps`, `rc_taps`, `gaussian_taps`, `fir_taps`
424
+ (lowpass/highpass/bandpass/bandstop via `btype=`), ...) produce coefficient
425
+ arrays; `fir_filter(samples, taps)` applies them.
426
+ * IIR SOS generators (`butterworth_sos`, `chebyshev1_sos`, `chebyshev2_sos`,
427
+ `elliptic_sos`, `bessel_sos`) produce second-order-section coefficients;
428
+ `iir_filter(samples, sos)` applies them.
429
+
430
+ New DSP building blocks with a design/apply split should follow this same
431
+ two-function pattern rather than folding both steps into one.
432
+
433
+ `filtering.py` vs. `smoothing.py`: `filtering.py` holds real signal-chain
434
+ filters (the design/apply pairs above, matched filtering, Overlap-Save) with
435
+ an actual frequency response and causality claim. `smoothing.py` holds
436
+ diagnostic/plotting-only smoothers (`moving_average`, `savgol_smooth`,
437
+ `smooth_density_2d`) - non-causal, no frequency-response meaning, used only
438
+ to make a plotted curve or a robust estimate less noisy. A routine that
439
+ removes/passes a frequency band as part of the signal chain itself belongs in
440
+ `filtering.py`; a routine that only exists to smooth something for display or
441
+ a peak search belongs in `smoothing.py`.
442
+
299
443
  ---
300
444
 
301
- ## 5. Testing Conventions
445
+ ## 4. Testing Conventions
302
446
 
303
447
  * **Parametrization**: Test cases must utilize `backend_device` and `xp` fixtures from `conftest.py` to automatically validate code correctness on both CPU and GPU backends.
304
448
  * **Assertions**: Standard `numpy.testing` assertions raise `TypeError` when evaluated on GPU arrays. Always use the `xpt` helper assertion module. Use `xp.asarray(expected)` to cast expectation variables to the active backend, and cast reductions to standard Python scalars before comparison:
@@ -318,10 +462,19 @@ Inside library code, **never extract a scalar from a possibly-GPU array inside a
318
462
  `test_winit.py`, `test_linear.py` (zf/MMSE), `test_polarization.py`,
319
463
  `test_blind.py` + `test_block_update.py` (block_cma/block_rde),
320
464
  `test_block.py` (block_lms / FDAF), `test_cpr.py`, and the CUDA-kernel tests
321
- `test_bps_kernel.py` / `test_cs_kernel.py`.
465
+ `test_bps_kernel.py` / `test_cs_kernel.py`. `sequential.py` and `_block.py`
466
+ are themselves subpackages internally (`sequential/_dd.py` + `_blind.py`;
467
+ `_block/_seqmode.py` + `_dd.py` + `_blind.py`) per the module-splitting
468
+ trigger below - the test files above are unaffected since they exercise
469
+ the public `commkit.equalization` surface, not these internal module
470
+ paths (a handful of `patch()`/`monkeypatch` targets in `test_block.py` /
471
+ `test_sequential_jax.py` do reach into the internal paths and were
472
+ updated when the split landed).
322
473
  * `tests/recovery/` <-> `commkit/recovery/` - `test_viterbi_viterbi.py`,
323
474
  `test_bps.py`, `test_pilots.py`, `test_tikhonov.py`, `test_pll.py`,
324
- `test_corrections.py`, plus `test_joint.py` for the cross-algorithm
475
+ `test_corrections.py` (also covers `recovery/_common.py`'s shared
476
+ `_vv_block_phase` block-phase estimator and `_log_phase_summary` CPR
477
+ diagnostic logger), plus `test_joint.py` for the cross-algorithm
325
478
  joint-channel consistency checks.
326
479
  * `tests/core/` <-> `commkit/core/` - `test_signal.py`, `test_signal_mimo.py`,
327
480
  `test_frame.py` (`Preamble`/`SingleCarrierFrame`), `test_psqam.py` (generation).
@@ -336,8 +489,8 @@ Inside library code, **never extract a scalar from a possibly-GPU array inside a
336
489
  * `tests/mapping/` <-> `commkit/mapping/` - `test_gray.py`, `test_bits.py`,
337
490
  `test_llr.py`, `test_constellation.py` (the `Constellation` value object).
338
491
  Probabilistic-shaping tests live in `tests/core/test_psqam.py`.
339
- * Flat modules (`filtering`, `metrics`, `spectral`, `timing`, `frequency`, ...)
340
- keep a single top-level `tests/test_<module>.py`.
492
+ * Flat modules (`filtering`, `metrics`, `spectral`, `timing`, `frequency`,
493
+ `smoothing`, ...) keep a single top-level `tests/test_<module>.py`.
341
494
 
342
495
  When a single module's test file grows unwieldy, split it by *concern* within
343
496
  the same subpackage (e.g. sequential vs. JAX vs. MIMO) rather than letting one
@@ -355,7 +508,7 @@ Inside library code, **never extract a scalar from a possibly-GPU array inside a
355
508
 
356
509
  ---
357
510
 
358
- ## 6. Benchmark Suite
511
+ ## 5. Benchmark Suite
359
512
 
360
513
  The `benchmarks/` directory tracks the performance of the GPU-relevant hot paths. Baselines are committed under `benchmarks/baselines/` so any optimization PR can be gated quantitatively (run -> compare -> quote the delta).
361
514
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: commkit
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: A Python library for high-performance digital communications research.
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -12,6 +12,11 @@ Requires-Dist: numpy>=2.4
12
12
  Requires-Dist: pydantic>=2.13
13
13
  Requires-Dist: pyyaml>=6.0.2
14
14
  Requires-Dist: scipy>=1.17
15
+ Provides-Extra: full
16
+ Requires-Dist: cupy-cuda13x>=14.1; extra == 'full'
17
+ Requires-Dist: jax[cuda13]>=0.10; extra == 'full'
18
+ Requires-Dist: jupyter>=1.1.1; extra == 'full'
19
+ Requires-Dist: nvidia-curand>=10.4.1.34; extra == 'full'
15
20
  Provides-Extra: gpu
16
21
  Requires-Dist: cupy-cuda13x>=14.1; extra == 'gpu'
17
22
  Requires-Dist: jax[cuda13]>=0.10; extra == 'gpu'
@@ -39,7 +44,7 @@ CommKit is a Python library for digital communications research that treats hard
39
44
 
40
45
  - **One object, complete context:** Sampling rate, symbol rate, modulation format, and pulse shape travel with the signal through the processing pipeline.
41
46
  - **Backend-transparent DSP:** `dispatch()` resolves NumPy, CuPy, or SciPy modules at runtime. The same code executes seamlessly on CPU or GPU.
42
- - **Method chaining:** High-level fluent API (`sig.to("gpu").fir_filter(taps).resample(sps_out=2)...`) for rapid prototyping.
47
+ - **Functional pipelines:** DSP functions accept and return a `Signal` directly (`sig = fir_filter(sig, taps)`; `sig = resample(sig, sps_out=2)`), so pipelines compose without a monolithic `Signal` wrapper API - `sig.to("gpu")` moves data across backends, the rest is plain function composition.
43
48
  - **JAX escape hatch:** Zero-copy DLPack export on GPU allows direct application of JAX transforms (gradients, `vmap`, `scan`) without leaving the research loop.
44
49
 
45
50
  ---
@@ -48,23 +53,24 @@ CommKit is a Python library for digital communications research that treats hard
48
53
 
49
54
  | Module | Key Capabilities & Features |
50
55
  | --- | --- |
51
- | [`commkit.core`](commkit/core) | `Signal` container (IQ samples + metadata), `SingleCarrierFrame`, `Preamble`, and symbol/frame factories (PAM, PSK, QAM). |
56
+ | [`commkit.core`](commkit/core) | `Signal` container (IQ samples + metadata), `SingleCarrierFrame`, `Preamble`, and symbol/frame factories (PAM, PSK, QAM, PS-QAM). |
52
57
  | [`commkit.backend`](commkit/backend.py) | Hardware abstraction layer (`dispatch`, `to_device`, `to_jax`, `from_jax`), placement management, and backend execution (NumPy, CuPy, JAX). |
53
- | [`commkit.mapping`](commkit/mapping) | Gray-coded constellations, symbol mapping, hard demapping, soft LLR computation (max-log and exact log-sum-exp via JAX JIT), and GMI. |
54
- | [`commkit.filtering`](commkit/filtering.py) | Pulse shaping (RRC, RC, Gaussian, Smooth-Rectangle), tap generators, matched filtering, Overlap-Save, and polyphase multirate filtering. |
58
+ | [`commkit.mapping`](commkit/mapping) | Gray-coded constellations, symbol mapping, hard demapping, soft LLR computation (max-log and exact log-sum-exp via JAX JIT), and probabilistic shaping (Maxwell-Boltzmann). |
59
+ | [`commkit.filtering`](commkit/filtering.py) | Pulse shaping (RRC, RC, Gaussian, Smooth-Rectangle), FIR tap generators, IIR SOS filter design (Butterworth, Chebyshev I/II, elliptic, Bessel) and application, matched filtering, and Overlap-Save. |
55
60
  | [`commkit.multirate`](commkit/multirate.py) | Fractional and integer sample rate conversion (`resample`, `decimate`, `upsample`, `decimate_to_symbol_rate`). |
56
61
  | [`commkit.timing`](commkit/timing.py) | Preamble generation (Barker, Zadoff-Chu), cross-correlation timing delay estimation, and frame alignment. |
57
- | [`commkit.frequency`](commkit/frequency.py) | Carrier frequency offset estimation (FOE via M-th power, Mengali-Morelli, Jacobsen) and phase-locked FOE correction. |
58
- | [`commkit.recovery`](commkit/recovery) | Carrier phase recovery (CPR via Viterbi-Viterbi, BPS, DD-PLL) and cycle-slip detection/correction. |
59
- | [`commkit.equalization`](commkit/equalization) | Adaptive equalizers (`lms`, `rls`, `cma`, `rde`, `zf`), butterfly MIMO topology support, with Numba JIT and JAX execution backends. |
60
- | [`commkit.impairments`](commkit/impairments) | Channel impairments simulation: AWGN (with SPS correction), PMD (differential group delay, Jones matrix), phase noise, IQ imbalance, and chromatic dispersion. |
61
- | [`commkit.coding`](commkit/coding) | Channel coding and FEC primitives (BCH, Convolutional, CRC, Galois field arithmetic, Hamming, Interleaving, LDPC, Polar, Rate matching, Reed-Solomon, Turbo codes). |
62
- | [`commkit.metrics`](commkit/metrics.py) | System performance evaluation: EVM (%, dB), data-aided SNR, and BER estimation. |
62
+ | [`commkit.frequency`](commkit/frequency.py) | Carrier frequency offset estimation (FOE via M-th power, Mengali-Morelli, pilot-symbol, bias-tone) and static/blockwise time-varying FOE correction. |
63
+ | [`commkit.recovery`](commkit/recovery) | Carrier phase recovery (CPR via Viterbi-Viterbi, BPS, DD-PLL, MAP Tikhonov-RTS, pilot-symbol/pilot-tone), cycle-slip detection/correction, and phase/channel-permutation ambiguity resolution. |
64
+ | [`commkit.equalization`](commkit/equalization) | Sequential (`lms`, `rls`, `cma`, `rde`) and frequency-domain block (`block_lms`, `block_cma`, `block_rde`) adaptive equalizers, `zf_equalizer`, butterfly MIMO topology support, and polarization-tone demultiplexing, with Numba JIT and JAX execution backends. |
65
+ | [`commkit.impairments`](commkit/impairments) | Channel impairments simulation: AWGN (with SPS correction), PMD (differential group delay, Jones matrix), phase noise, IQ imbalance (application + Löwdin/Gram-Schmidt compensation), and chromatic dispersion. |
66
+ | [`commkit.coding`](commkit/coding) | **Planned, not yet implemented** - scaffold-only placeholders reserving the layout for channel coding / FEC primitives (BCH, Convolutional, CRC, Galois field arithmetic, Hamming, Interleaving, LDPC, Polar, Rate matching, Reed-Solomon, Turbo codes). |
67
+ | [`commkit.metrics`](commkit/metrics.py) | System performance evaluation: EVM, SNR, BER, SER, and capacity metrics (GMI, MI) with PS-QAM support. |
63
68
  | [`commkit.analysis`](commkit/analysis) | Laser phase and linewidth characterization: DSH, homodyne IQ, zero-phase drift detrending, AWGN-free lag-slope linewidth fit, Di Domenico $\beta$-separation line FWHM, and Allan deviation. |
64
- | [`commkit.spectral`](commkit/spectral.py) | Welch PSD estimation and frequency shifting with bin-quantized mixing. |
69
+ | [`commkit.spectral`](commkit/spectral.py) | Welch PSD estimation, spectrograms, and frequency shifting with bin-quantized mixing. |
70
+ | [`commkit.smoothing`](commkit/smoothing.py) | Diagnostic/plotting-only smoothers (moving average, Savitzky-Golay, 2-D density smoothing) - not signal-chain filters; see `commkit.filtering` for those. |
65
71
  | [`commkit.io`](commkit/io.py) | Signal persistence and disk serialization (`load_npz`, `save_npz`). |
66
- | [`commkit.plotting`](commkit/plotting) | Visualization tools for constellations, eye diagrams, PSDs, time-domain signals, filter responses, and equalizer convergence. |
67
- | [`commkit.helpers`](commkit/helpers.py) | General DSP helpers: random bit/symbol generators, array normalization, RMS calculation, and SI prefix formatting. |
72
+ | [`commkit.plotting`](commkit/plotting) | Visualization tools for constellations, eye diagrams, PSDs/spectrograms, time-domain signals, filter responses, equalizer convergence, and sync/CPR diagnostics (timing correlation, FOE spectra, carrier-phase trajectories). |
73
+ | [`commkit.helpers`](commkit/helpers.py) | General DSP helpers: random bit/symbol generators, array normalization, RMS calculation, dB<->linear conversion, and SI prefix formatting. |
68
74
 
69
75
  ---
70
76
 
@@ -76,10 +82,10 @@ CommKit is a Python library for digital communications research that treats hard
76
82
 
77
83
  ```bash
78
84
  # Using uv (Recommended)
79
- uv pip install git+https://github.com/lokgar/commkit.git
85
+ uv pip install commkit
80
86
 
81
87
  # Or with standard pip
82
- pip install git+https://github.com/lokgar/commkit.git
88
+ pip install commkit
83
89
  ```
84
90
 
85
91
  ### GPU Support
@@ -88,10 +94,10 @@ To install with CUDA acceleration (includes JAX CUDA 13 and CuPy stacks):
88
94
 
89
95
  ```bash
90
96
  # Using uv
91
- uv pip install "commkit[gpu] @ git+https://github.com/lokgar/commkit.git"
97
+ uv pip install "commkit[gpu]"
92
98
 
93
99
  # Or with standard pip
94
- pip install "commkit[gpu] @ git+https://github.com/lokgar/commkit.git"
100
+ pip install "commkit[gpu]"
95
101
  ```
96
102
 
97
103
  > [!NOTE]
@@ -104,6 +110,20 @@ pip install "commkit[gpu] @ git+https://github.com/lokgar/commkit.git"
104
110
  >
105
111
  > *(Assumes `commkit` is cloned in `$HOME/commkit`. If located elsewhere, replace `$HOME/commkit` with `<path-to-repo>`. `python3.*/` matches any Python version automatically).*
106
112
 
113
+ ### Notebook Support
114
+
115
+ To run the example notebooks and enable the rich HTML `Signal.print_info()` table (falls back to plain text without it):
116
+
117
+ ```bash
118
+ # Using uv
119
+ uv pip install "commkit[notebook]"
120
+
121
+ # Or with standard pip
122
+ pip install "commkit[notebook]"
123
+ ```
124
+
125
+ Extras can be combined, e.g. `commkit[gpu,notebook]`, or install everything at once with `commkit[full]`.
126
+
107
127
  ### Development Installation
108
128
 
109
129
  ```bash
@@ -113,7 +133,7 @@ cd commkit
113
133
  # Sync core environment
114
134
  uv sync
115
135
 
116
- # Sync environment with all extras (including GPU packages)
136
+ # Sync environment with all extras (including GPU and notebook packages)
117
137
  uv sync --all-extras
118
138
  ```
119
139
 
@@ -17,7 +17,7 @@ CommKit is a Python library for digital communications research that treats hard
17
17
 
18
18
  - **One object, complete context:** Sampling rate, symbol rate, modulation format, and pulse shape travel with the signal through the processing pipeline.
19
19
  - **Backend-transparent DSP:** `dispatch()` resolves NumPy, CuPy, or SciPy modules at runtime. The same code executes seamlessly on CPU or GPU.
20
- - **Method chaining:** High-level fluent API (`sig.to("gpu").fir_filter(taps).resample(sps_out=2)...`) for rapid prototyping.
20
+ - **Functional pipelines:** DSP functions accept and return a `Signal` directly (`sig = fir_filter(sig, taps)`; `sig = resample(sig, sps_out=2)`), so pipelines compose without a monolithic `Signal` wrapper API - `sig.to("gpu")` moves data across backends, the rest is plain function composition.
21
21
  - **JAX escape hatch:** Zero-copy DLPack export on GPU allows direct application of JAX transforms (gradients, `vmap`, `scan`) without leaving the research loop.
22
22
 
23
23
  ---
@@ -26,23 +26,24 @@ CommKit is a Python library for digital communications research that treats hard
26
26
 
27
27
  | Module | Key Capabilities & Features |
28
28
  | --- | --- |
29
- | [`commkit.core`](commkit/core) | `Signal` container (IQ samples + metadata), `SingleCarrierFrame`, `Preamble`, and symbol/frame factories (PAM, PSK, QAM). |
29
+ | [`commkit.core`](commkit/core) | `Signal` container (IQ samples + metadata), `SingleCarrierFrame`, `Preamble`, and symbol/frame factories (PAM, PSK, QAM, PS-QAM). |
30
30
  | [`commkit.backend`](commkit/backend.py) | Hardware abstraction layer (`dispatch`, `to_device`, `to_jax`, `from_jax`), placement management, and backend execution (NumPy, CuPy, JAX). |
31
- | [`commkit.mapping`](commkit/mapping) | Gray-coded constellations, symbol mapping, hard demapping, soft LLR computation (max-log and exact log-sum-exp via JAX JIT), and GMI. |
32
- | [`commkit.filtering`](commkit/filtering.py) | Pulse shaping (RRC, RC, Gaussian, Smooth-Rectangle), tap generators, matched filtering, Overlap-Save, and polyphase multirate filtering. |
31
+ | [`commkit.mapping`](commkit/mapping) | Gray-coded constellations, symbol mapping, hard demapping, soft LLR computation (max-log and exact log-sum-exp via JAX JIT), and probabilistic shaping (Maxwell-Boltzmann). |
32
+ | [`commkit.filtering`](commkit/filtering.py) | Pulse shaping (RRC, RC, Gaussian, Smooth-Rectangle), FIR tap generators, IIR SOS filter design (Butterworth, Chebyshev I/II, elliptic, Bessel) and application, matched filtering, and Overlap-Save. |
33
33
  | [`commkit.multirate`](commkit/multirate.py) | Fractional and integer sample rate conversion (`resample`, `decimate`, `upsample`, `decimate_to_symbol_rate`). |
34
34
  | [`commkit.timing`](commkit/timing.py) | Preamble generation (Barker, Zadoff-Chu), cross-correlation timing delay estimation, and frame alignment. |
35
- | [`commkit.frequency`](commkit/frequency.py) | Carrier frequency offset estimation (FOE via M-th power, Mengali-Morelli, Jacobsen) and phase-locked FOE correction. |
36
- | [`commkit.recovery`](commkit/recovery) | Carrier phase recovery (CPR via Viterbi-Viterbi, BPS, DD-PLL) and cycle-slip detection/correction. |
37
- | [`commkit.equalization`](commkit/equalization) | Adaptive equalizers (`lms`, `rls`, `cma`, `rde`, `zf`), butterfly MIMO topology support, with Numba JIT and JAX execution backends. |
38
- | [`commkit.impairments`](commkit/impairments) | Channel impairments simulation: AWGN (with SPS correction), PMD (differential group delay, Jones matrix), phase noise, IQ imbalance, and chromatic dispersion. |
39
- | [`commkit.coding`](commkit/coding) | Channel coding and FEC primitives (BCH, Convolutional, CRC, Galois field arithmetic, Hamming, Interleaving, LDPC, Polar, Rate matching, Reed-Solomon, Turbo codes). |
40
- | [`commkit.metrics`](commkit/metrics.py) | System performance evaluation: EVM (%, dB), data-aided SNR, and BER estimation. |
35
+ | [`commkit.frequency`](commkit/frequency.py) | Carrier frequency offset estimation (FOE via M-th power, Mengali-Morelli, pilot-symbol, bias-tone) and static/blockwise time-varying FOE correction. |
36
+ | [`commkit.recovery`](commkit/recovery) | Carrier phase recovery (CPR via Viterbi-Viterbi, BPS, DD-PLL, MAP Tikhonov-RTS, pilot-symbol/pilot-tone), cycle-slip detection/correction, and phase/channel-permutation ambiguity resolution. |
37
+ | [`commkit.equalization`](commkit/equalization) | Sequential (`lms`, `rls`, `cma`, `rde`) and frequency-domain block (`block_lms`, `block_cma`, `block_rde`) adaptive equalizers, `zf_equalizer`, butterfly MIMO topology support, and polarization-tone demultiplexing, with Numba JIT and JAX execution backends. |
38
+ | [`commkit.impairments`](commkit/impairments) | Channel impairments simulation: AWGN (with SPS correction), PMD (differential group delay, Jones matrix), phase noise, IQ imbalance (application + Löwdin/Gram-Schmidt compensation), and chromatic dispersion. |
39
+ | [`commkit.coding`](commkit/coding) | **Planned, not yet implemented** - scaffold-only placeholders reserving the layout for channel coding / FEC primitives (BCH, Convolutional, CRC, Galois field arithmetic, Hamming, Interleaving, LDPC, Polar, Rate matching, Reed-Solomon, Turbo codes). |
40
+ | [`commkit.metrics`](commkit/metrics.py) | System performance evaluation: EVM, SNR, BER, SER, and capacity metrics (GMI, MI) with PS-QAM support. |
41
41
  | [`commkit.analysis`](commkit/analysis) | Laser phase and linewidth characterization: DSH, homodyne IQ, zero-phase drift detrending, AWGN-free lag-slope linewidth fit, Di Domenico $\beta$-separation line FWHM, and Allan deviation. |
42
- | [`commkit.spectral`](commkit/spectral.py) | Welch PSD estimation and frequency shifting with bin-quantized mixing. |
42
+ | [`commkit.spectral`](commkit/spectral.py) | Welch PSD estimation, spectrograms, and frequency shifting with bin-quantized mixing. |
43
+ | [`commkit.smoothing`](commkit/smoothing.py) | Diagnostic/plotting-only smoothers (moving average, Savitzky-Golay, 2-D density smoothing) - not signal-chain filters; see `commkit.filtering` for those. |
43
44
  | [`commkit.io`](commkit/io.py) | Signal persistence and disk serialization (`load_npz`, `save_npz`). |
44
- | [`commkit.plotting`](commkit/plotting) | Visualization tools for constellations, eye diagrams, PSDs, time-domain signals, filter responses, and equalizer convergence. |
45
- | [`commkit.helpers`](commkit/helpers.py) | General DSP helpers: random bit/symbol generators, array normalization, RMS calculation, and SI prefix formatting. |
45
+ | [`commkit.plotting`](commkit/plotting) | Visualization tools for constellations, eye diagrams, PSDs/spectrograms, time-domain signals, filter responses, equalizer convergence, and sync/CPR diagnostics (timing correlation, FOE spectra, carrier-phase trajectories). |
46
+ | [`commkit.helpers`](commkit/helpers.py) | General DSP helpers: random bit/symbol generators, array normalization, RMS calculation, dB<->linear conversion, and SI prefix formatting. |
46
47
 
47
48
  ---
48
49
 
@@ -54,10 +55,10 @@ CommKit is a Python library for digital communications research that treats hard
54
55
 
55
56
  ```bash
56
57
  # Using uv (Recommended)
57
- uv pip install git+https://github.com/lokgar/commkit.git
58
+ uv pip install commkit
58
59
 
59
60
  # Or with standard pip
60
- pip install git+https://github.com/lokgar/commkit.git
61
+ pip install commkit
61
62
  ```
62
63
 
63
64
  ### GPU Support
@@ -66,10 +67,10 @@ To install with CUDA acceleration (includes JAX CUDA 13 and CuPy stacks):
66
67
 
67
68
  ```bash
68
69
  # Using uv
69
- uv pip install "commkit[gpu] @ git+https://github.com/lokgar/commkit.git"
70
+ uv pip install "commkit[gpu]"
70
71
 
71
72
  # Or with standard pip
72
- pip install "commkit[gpu] @ git+https://github.com/lokgar/commkit.git"
73
+ pip install "commkit[gpu]"
73
74
  ```
74
75
 
75
76
  > [!NOTE]
@@ -82,6 +83,20 @@ pip install "commkit[gpu] @ git+https://github.com/lokgar/commkit.git"
82
83
  >
83
84
  > *(Assumes `commkit` is cloned in `$HOME/commkit`. If located elsewhere, replace `$HOME/commkit` with `<path-to-repo>`. `python3.*/` matches any Python version automatically).*
84
85
 
86
+ ### Notebook Support
87
+
88
+ To run the example notebooks and enable the rich HTML `Signal.print_info()` table (falls back to plain text without it):
89
+
90
+ ```bash
91
+ # Using uv
92
+ uv pip install "commkit[notebook]"
93
+
94
+ # Or with standard pip
95
+ pip install "commkit[notebook]"
96
+ ```
97
+
98
+ Extras can be combined, e.g. `commkit[gpu,notebook]`, or install everything at once with `commkit[full]`.
99
+
85
100
  ### Development Installation
86
101
 
87
102
  ```bash
@@ -91,7 +106,7 @@ cd commkit
91
106
  # Sync core environment
92
107
  uv sync
93
108
 
94
- # Sync environment with all extras (including GPU packages)
109
+ # Sync environment with all extras (including GPU and notebook packages)
95
110
  uv sync --all-extras
96
111
  ```
97
112