commkit 2.3.0__tar.gz → 3.0.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 (291) hide show
  1. {commkit-2.3.0 → commkit-3.0.0}/.github/workflows/ci.yml +12 -2
  2. {commkit-2.3.0 → commkit-3.0.0}/AGENTS.md +53 -25
  3. commkit-3.0.0/CHANGELOG.md +161 -0
  4. {commkit-2.3.0 → commkit-3.0.0}/PKG-INFO +11 -11
  5. {commkit-2.3.0 → commkit-3.0.0}/README.md +10 -10
  6. commkit-3.0.0/benchmarks/baselines/Linux-CPython-3.14-64bit/0007_v3_0.json +5394 -0
  7. commkit-3.0.0/benchmarks/bench_coding.py +64 -0
  8. {commkit-2.3.0 → commkit-3.0.0}/benchmarks/conftest.py +25 -0
  9. {commkit-2.3.0 → commkit-3.0.0}/benchmarks/workloads.py +16 -0
  10. {commkit-2.3.0 → commkit-3.0.0}/commkit/__init__.py +5 -4
  11. {commkit-2.3.0 → commkit-3.0.0}/commkit/_cuda/__init__.py +73 -2
  12. commkit-3.0.0/commkit/_cuda/src/ldpc.cu +156 -0
  13. {commkit-2.3.0 → commkit-3.0.0}/commkit/_cuda/src/llr.cu +1 -1
  14. {commkit-2.3.0 → commkit-3.0.0}/commkit/_fiber.py +18 -0
  15. {commkit-2.3.0 → commkit-3.0.0}/commkit/analysis/fm_noise.py +1 -0
  16. {commkit-2.3.0 → commkit-3.0.0}/commkit/analysis/linewidth/dsh.py +1 -0
  17. {commkit-2.3.0 → commkit-3.0.0}/commkit/backend.py +36 -0
  18. commkit-3.0.0/commkit/coding/__init__.py +76 -0
  19. commkit-3.0.0/commkit/coding/_galois.py +139 -0
  20. commkit-3.0.0/commkit/coding/_gf2.py +170 -0
  21. commkit-3.0.0/commkit/coding/_ldpc_tables.py +1590 -0
  22. commkit-3.0.0/commkit/coding/_numba_loops.py +639 -0
  23. commkit-3.0.0/commkit/coding/algebraic.py +515 -0
  24. commkit-3.0.0/commkit/coding/base.py +313 -0
  25. commkit-3.0.0/commkit/coding/convolutional.py +287 -0
  26. commkit-3.0.0/commkit/coding/crc.py +244 -0
  27. commkit-3.0.0/commkit/coding/interleaving.py +178 -0
  28. commkit-3.0.0/commkit/coding/ldpc.py +568 -0
  29. commkit-3.0.0/commkit/coding/linear.py +490 -0
  30. commkit-3.0.0/commkit/coding/polar.py +325 -0
  31. commkit-3.0.0/commkit/coding/product.py +544 -0
  32. commkit-3.0.0/commkit/coding/ratematch.py +212 -0
  33. commkit-3.0.0/commkit/coding/shaping.py +442 -0
  34. commkit-3.0.0/commkit/coding/turbo.py +220 -0
  35. {commkit-2.3.0 → commkit-3.0.0}/commkit/core/frame.py +46 -5
  36. {commkit-2.3.0 → commkit-3.0.0}/commkit/core/generation.py +89 -15
  37. {commkit-2.3.0 → commkit-3.0.0}/commkit/core/signal.py +104 -22
  38. {commkit-2.3.0 → commkit-3.0.0}/commkit/equalization/_block/_blind.py +2 -0
  39. {commkit-2.3.0 → commkit-3.0.0}/commkit/equalization/_block/_dd.py +8 -0
  40. {commkit-2.3.0 → commkit-3.0.0}/commkit/equalization/_block/_engine.py +15 -6
  41. {commkit-2.3.0 → commkit-3.0.0}/commkit/equalization/_numba_loops.py +15 -9
  42. {commkit-2.3.0 → commkit-3.0.0}/commkit/equalization/polarization.py +3 -2
  43. {commkit-2.3.0 → commkit-3.0.0}/commkit/equalization/sequential/_blind.py +1 -0
  44. {commkit-2.3.0 → commkit-3.0.0}/commkit/equalization/sequential/_dd.py +8 -0
  45. {commkit-2.3.0 → commkit-3.0.0}/commkit/equalization/sequential/_setup.py +6 -0
  46. {commkit-2.3.0 → commkit-3.0.0}/commkit/filtering/pulses.py +14 -4
  47. {commkit-2.3.0 → commkit-3.0.0}/commkit/impairments/frontend.py +1 -0
  48. {commkit-2.3.0 → commkit-3.0.0}/commkit/io.py +47 -0
  49. commkit-3.0.0/commkit/mapping/_numba_llr.py +222 -0
  50. {commkit-2.3.0 → commkit-3.0.0}/commkit/mapping/constellation.py +1 -0
  51. {commkit-2.3.0 → commkit-3.0.0}/commkit/mapping/gray.py +9 -4
  52. {commkit-2.3.0 → commkit-3.0.0}/commkit/mapping/llr.py +13 -248
  53. {commkit-2.3.0 → commkit-3.0.0}/commkit/mapping/shaping.py +5 -0
  54. {commkit-2.3.0 → commkit-3.0.0}/commkit/metrics.py +196 -42
  55. {commkit-2.3.0 → commkit-3.0.0}/commkit/plotting/analysis.py +1 -1
  56. {commkit-2.3.0 → commkit-3.0.0}/commkit/plotting/filter_response.py +1 -0
  57. {commkit-2.3.0 → commkit-3.0.0}/commkit/recovery/_common.py +2 -0
  58. {commkit-2.3.0 → commkit-3.0.0}/commkit/recovery/bps.py +10 -0
  59. {commkit-2.3.0 → commkit-3.0.0}/commkit/recovery/carrier_phase.py +1 -0
  60. {commkit-2.3.0 → commkit-3.0.0}/commkit/spectral.py +4 -0
  61. {commkit-2.3.0 → commkit-3.0.0}/examples/01-constellations-and-bit-mapping.ipynb +8 -5
  62. {commkit-2.3.0 → commkit-3.0.0}/examples/02-spectral-analysis.ipynb +2 -2
  63. {commkit-2.3.0 → commkit-3.0.0}/examples/04-pulse-shaping-and-matched-filtering.ipynb +1 -1
  64. {commkit-2.3.0 → commkit-3.0.0}/examples/06-awgn-and-performance-metrics.ipynb +1 -1
  65. commkit-3.0.0/examples/07-channel-coding.ipynb +412 -0
  66. commkit-3.0.0/examples/08-probabilistic-shaping-and-coded-modulation.ipynb +278 -0
  67. commkit-2.3.0/examples/07-laser-phase-noise.ipynb → commkit-3.0.0/examples/09-laser-phase-noise.ipynb +4 -4
  68. commkit-2.3.0/examples/08-front-end-impairments.ipynb → commkit-3.0.0/examples/10-front-end-impairments.ipynb +6 -6
  69. commkit-2.3.0/examples/09-frame-synchronization-and-timing.ipynb → commkit-3.0.0/examples/11-frame-synchronization-and-timing.ipynb +3 -3
  70. commkit-2.3.0/examples/10-frequency-offset-estimation.ipynb → commkit-3.0.0/examples/12-frequency-offset-estimation.ipynb +4 -4
  71. commkit-2.3.0/examples/11-blind-carrier-phase-recovery.ipynb → commkit-3.0.0/examples/13-blind-carrier-phase-recovery.ipynb +5 -5
  72. commkit-2.3.0/examples/12-pilot-aided-carrier-recovery.ipynb → commkit-3.0.0/examples/14-pilot-aided-carrier-recovery.ipynb +5 -5
  73. commkit-2.3.0/examples/13-linear-channel-equalization.ipynb → commkit-3.0.0/examples/15-linear-channel-equalization.ipynb +3 -3
  74. commkit-2.3.0/examples/14-adaptive-equalization.ipynb → commkit-3.0.0/examples/16-adaptive-equalization.ipynb +8 -8
  75. commkit-2.3.0/examples/15-blind-equalization.ipynb → commkit-3.0.0/examples/17-blind-equalization.ipynb +3 -3
  76. commkit-2.3.0/examples/16-polarization-multiplexing.ipynb → commkit-3.0.0/examples/18-polarization-multiplexing.ipynb +5 -5
  77. commkit-2.3.0/examples/17-chromatic-dispersion.ipynb → commkit-3.0.0/examples/19-chromatic-dispersion.ipynb +3 -3
  78. commkit-2.3.0/examples/18-nonlinear-fiber-and-backpropagation.ipynb → commkit-3.0.0/examples/20-nonlinear-fiber-and-backpropagation.ipynb +3 -3
  79. commkit-3.0.0/examples/21-fec-in-optical-standards.ipynb +317 -0
  80. commkit-2.3.0/examples/19-coherent-transceiver-end-to-end.ipynb → commkit-3.0.0/examples/22-coherent-transceiver-end-to-end.ipynb +61 -13
  81. commkit-2.3.0/examples/20-carrier-phase-characterization.ipynb → commkit-3.0.0/examples/23-carrier-phase-characterization.ipynb +4 -4
  82. commkit-2.3.0/examples/21-laser-linewidth-self-heterodyne.ipynb → commkit-3.0.0/examples/24-laser-linewidth-self-heterodyne.ipynb +11 -11
  83. commkit-2.3.0/examples/22-laser-linewidth-short-delay-and-flicker.ipynb → commkit-3.0.0/examples/25-laser-linewidth-short-delay-and-flicker.ipynb +5 -5
  84. commkit-2.3.0/examples/23-laser-linewidth-self-homodyne-iq.ipynb → commkit-3.0.0/examples/26-laser-linewidth-self-homodyne-iq.ipynb +5 -5
  85. commkit-2.3.0/examples/24-measurement-template-self-heterodyne.ipynb → commkit-3.0.0/examples/27-measurement-template-self-heterodyne.ipynb +2 -2
  86. commkit-2.3.0/examples/25-measurement-template-self-homodyne-iq.ipynb → commkit-3.0.0/examples/28-measurement-template-self-homodyne-iq.ipynb +2 -2
  87. {commkit-2.3.0 → commkit-3.0.0}/examples/README.md +22 -19
  88. {commkit-2.3.0 → commkit-3.0.0}/pyproject.toml +2 -9
  89. commkit-3.0.0/tests/coding/test_algebraic.py +267 -0
  90. commkit-3.0.0/tests/coding/test_coding_base.py +116 -0
  91. commkit-3.0.0/tests/coding/test_convolutional.py +113 -0
  92. commkit-3.0.0/tests/coding/test_crc.py +76 -0
  93. commkit-3.0.0/tests/coding/test_gf2.py +116 -0
  94. commkit-3.0.0/tests/coding/test_interleaving.py +61 -0
  95. commkit-3.0.0/tests/coding/test_ldpc.py +279 -0
  96. commkit-3.0.0/tests/coding/test_ldpc_kernel.py +75 -0
  97. commkit-3.0.0/tests/coding/test_ldpc_tables.py +64 -0
  98. commkit-3.0.0/tests/coding/test_linear_codes.py +226 -0
  99. commkit-3.0.0/tests/coding/test_polar.py +141 -0
  100. commkit-3.0.0/tests/coding/test_product.py +238 -0
  101. commkit-3.0.0/tests/coding/test_ratematch.py +111 -0
  102. commkit-3.0.0/tests/coding/test_shaping.py +231 -0
  103. commkit-3.0.0/tests/coding/test_turbo.py +115 -0
  104. commkit-3.0.0/tests/common/coding.py +45 -0
  105. {commkit-2.3.0 → commkit-3.0.0}/tests/common/reference_impl.py +61 -0
  106. {commkit-2.3.0 → commkit-3.0.0}/tests/core/test_frame.py +43 -0
  107. {commkit-2.3.0 → commkit-3.0.0}/tests/core/test_generation.py +76 -6
  108. {commkit-2.3.0 → commkit-3.0.0}/tests/core/test_signal.py +59 -0
  109. {commkit-2.3.0 → commkit-3.0.0}/tests/mapping/test_llr.py +4 -4
  110. {commkit-2.3.0 → commkit-3.0.0}/tests/plugins/transfer_trace.py +13 -0
  111. {commkit-2.3.0 → commkit-3.0.0}/tests/test_api_contracts.py +104 -7
  112. {commkit-2.3.0 → commkit-3.0.0}/tests/test_import_hygiene.py +7 -0
  113. {commkit-2.3.0 → commkit-3.0.0}/tests/test_io.py +51 -0
  114. {commkit-2.3.0 → commkit-3.0.0}/tests/test_metrics.py +126 -0
  115. {commkit-2.3.0 → commkit-3.0.0}/uv.lock +1 -1
  116. commkit-2.3.0/CHANGELOG.md +0 -116
  117. commkit-2.3.0/commkit/coding/__init__.py +0 -21
  118. commkit-2.3.0/commkit/coding/base.py +0 -16
  119. commkit-2.3.0/commkit/coding/bch.py +0 -5
  120. commkit-2.3.0/commkit/coding/convolutional.py +0 -6
  121. commkit-2.3.0/commkit/coding/crc.py +0 -6
  122. commkit-2.3.0/commkit/coding/galois.py +0 -7
  123. commkit-2.3.0/commkit/coding/hamming.py +0 -5
  124. commkit-2.3.0/commkit/coding/interleaving.py +0 -6
  125. commkit-2.3.0/commkit/coding/ldpc.py +0 -7
  126. commkit-2.3.0/commkit/coding/polar.py +0 -7
  127. commkit-2.3.0/commkit/coding/ratematch.py +0 -5
  128. commkit-2.3.0/commkit/coding/reed_solomon.py +0 -5
  129. commkit-2.3.0/commkit/coding/turbo.py +0 -7
  130. {commkit-2.3.0 → commkit-3.0.0}/.gitattributes +0 -0
  131. {commkit-2.3.0 → commkit-3.0.0}/.github/workflows/publish.yml +0 -0
  132. {commkit-2.3.0 → commkit-3.0.0}/.gitignore +0 -0
  133. {commkit-2.3.0 → commkit-3.0.0}/.python-version +0 -0
  134. {commkit-2.3.0 → commkit-3.0.0}/LICENSE +0 -0
  135. {commkit-2.3.0 → commkit-3.0.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0002_pre_v2.json +0 -0
  136. {commkit-2.3.0 → commkit-3.0.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0003_v2_0.json +0 -0
  137. {commkit-2.3.0 → commkit-3.0.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0004_fiber_dbp.json +0 -0
  138. {commkit-2.3.0 → commkit-3.0.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0005_gpu_decoder_path.json +0 -0
  139. {commkit-2.3.0 → commkit-3.0.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0006_perf_cleanup.json +0 -0
  140. {commkit-2.3.0 → commkit-3.0.0}/benchmarks/bench_block_blind.py +0 -0
  141. {commkit-2.3.0 → commkit-3.0.0}/benchmarks/bench_block_lms.py +0 -0
  142. {commkit-2.3.0 → commkit-3.0.0}/benchmarks/bench_bps.py +0 -0
  143. {commkit-2.3.0 → commkit-3.0.0}/benchmarks/bench_equalizers.py +0 -0
  144. {commkit-2.3.0 → commkit-3.0.0}/benchmarks/bench_fiber.py +0 -0
  145. {commkit-2.3.0 → commkit-3.0.0}/benchmarks/bench_filters.py +0 -0
  146. {commkit-2.3.0 → commkit-3.0.0}/benchmarks/bench_import.py +0 -0
  147. {commkit-2.3.0 → commkit-3.0.0}/benchmarks/bench_llr.py +0 -0
  148. {commkit-2.3.0 → commkit-3.0.0}/benchmarks/bench_mapping.py +0 -0
  149. {commkit-2.3.0 → commkit-3.0.0}/benchmarks/bench_signal_pipeline.py +0 -0
  150. {commkit-2.3.0 → commkit-3.0.0}/benchmarks/bench_sync_misc.py +0 -0
  151. {commkit-2.3.0 → commkit-3.0.0}/benchmarks/benchutils.py +0 -0
  152. {commkit-2.3.0 → commkit-3.0.0}/benchmarks/record_baseline.py +0 -0
  153. {commkit-2.3.0 → commkit-3.0.0}/commkit/_array.py +0 -0
  154. {commkit-2.3.0 → commkit-3.0.0}/commkit/_circular.py +0 -0
  155. {commkit-2.3.0 → commkit-3.0.0}/commkit/_cuda/compiler.py +0 -0
  156. {commkit-2.3.0 → commkit-3.0.0}/commkit/_cuda/graphs.py +0 -0
  157. {commkit-2.3.0 → commkit-3.0.0}/commkit/_cuda/src/bps_anchor.cu +0 -0
  158. {commkit-2.3.0 → commkit-3.0.0}/commkit/_cuda/src/bps_min_d2.cu +0 -0
  159. {commkit-2.3.0 → commkit-3.0.0}/commkit/_cuda/src/cs_block.cu +0 -0
  160. {commkit-2.3.0 → commkit-3.0.0}/commkit/_cuda/src/iir.cu +0 -0
  161. {commkit-2.3.0 → commkit-3.0.0}/commkit/_cuda/src/selftest.cu +0 -0
  162. {commkit-2.3.0 → commkit-3.0.0}/commkit/_dispersion.py +0 -0
  163. {commkit-2.3.0 → commkit-3.0.0}/commkit/_fft.py +0 -0
  164. {commkit-2.3.0 → commkit-3.0.0}/commkit/_overlap_save.py +0 -0
  165. {commkit-2.3.0 → commkit-3.0.0}/commkit/_random.py +0 -0
  166. {commkit-2.3.0 → commkit-3.0.0}/commkit/_sequences.py +0 -0
  167. {commkit-2.3.0 → commkit-3.0.0}/commkit/analysis/__init__.py +0 -0
  168. {commkit-2.3.0 → commkit-3.0.0}/commkit/analysis/_common.py +0 -0
  169. {commkit-2.3.0 → commkit-3.0.0}/commkit/analysis/allan.py +0 -0
  170. {commkit-2.3.0 → commkit-3.0.0}/commkit/analysis/drift.py +0 -0
  171. {commkit-2.3.0 → commkit-3.0.0}/commkit/analysis/interferometry.py +0 -0
  172. {commkit-2.3.0 → commkit-3.0.0}/commkit/analysis/linewidth/__init__.py +0 -0
  173. {commkit-2.3.0 → commkit-3.0.0}/commkit/analysis/linewidth/phase.py +0 -0
  174. {commkit-2.3.0 → commkit-3.0.0}/commkit/analysis/linewidth/result.py +0 -0
  175. {commkit-2.3.0 → commkit-3.0.0}/commkit/analysis/trajectory.py +0 -0
  176. {commkit-2.3.0 → commkit-3.0.0}/commkit/core/__init__.py +0 -0
  177. {commkit-2.3.0 → commkit-3.0.0}/commkit/core/_signal_adapter.py +0 -0
  178. {commkit-2.3.0 → commkit-3.0.0}/commkit/equalization/__init__.py +0 -0
  179. {commkit-2.3.0 → commkit-3.0.0}/commkit/equalization/_block/__init__.py +0 -0
  180. {commkit-2.3.0 → commkit-3.0.0}/commkit/equalization/_common.py +0 -0
  181. {commkit-2.3.0 → commkit-3.0.0}/commkit/equalization/_kernels_numba.py +0 -0
  182. {commkit-2.3.0 → commkit-3.0.0}/commkit/equalization/blind.py +0 -0
  183. {commkit-2.3.0 → commkit-3.0.0}/commkit/equalization/linear.py +0 -0
  184. {commkit-2.3.0 → commkit-3.0.0}/commkit/equalization/result.py +0 -0
  185. {commkit-2.3.0 → commkit-3.0.0}/commkit/equalization/sequential/__init__.py +0 -0
  186. {commkit-2.3.0 → commkit-3.0.0}/commkit/filtering/__init__.py +0 -0
  187. {commkit-2.3.0 → commkit-3.0.0}/commkit/filtering/fiber.py +0 -0
  188. {commkit-2.3.0 → commkit-3.0.0}/commkit/filtering/linear.py +0 -0
  189. {commkit-2.3.0 → commkit-3.0.0}/commkit/frequency.py +0 -0
  190. {commkit-2.3.0 → commkit-3.0.0}/commkit/impairments/__init__.py +0 -0
  191. {commkit-2.3.0 → commkit-3.0.0}/commkit/impairments/channel/__init__.py +0 -0
  192. {commkit-2.3.0 → commkit-3.0.0}/commkit/impairments/channel/linear.py +0 -0
  193. {commkit-2.3.0 → commkit-3.0.0}/commkit/impairments/channel/nonlinear.py +0 -0
  194. {commkit-2.3.0 → commkit-3.0.0}/commkit/impairments/noise.py +0 -0
  195. {commkit-2.3.0 → commkit-3.0.0}/commkit/impairments/source.py +0 -0
  196. {commkit-2.3.0 → commkit-3.0.0}/commkit/logger.py +0 -0
  197. {commkit-2.3.0 → commkit-3.0.0}/commkit/mapping/__init__.py +0 -0
  198. {commkit-2.3.0 → commkit-3.0.0}/commkit/mapping/bits.py +0 -0
  199. {commkit-2.3.0 → commkit-3.0.0}/commkit/math.py +0 -0
  200. {commkit-2.3.0 → commkit-3.0.0}/commkit/multirate.py +0 -0
  201. {commkit-2.3.0 → commkit-3.0.0}/commkit/plotting/__init__.py +0 -0
  202. {commkit-2.3.0 → commkit-3.0.0}/commkit/plotting/constellation.py +0 -0
  203. {commkit-2.3.0 → commkit-3.0.0}/commkit/plotting/equalizer.py +0 -0
  204. {commkit-2.3.0 → commkit-3.0.0}/commkit/plotting/eye.py +0 -0
  205. {commkit-2.3.0 → commkit-3.0.0}/commkit/plotting/spectral.py +0 -0
  206. {commkit-2.3.0 → commkit-3.0.0}/commkit/plotting/sync.py +0 -0
  207. {commkit-2.3.0 → commkit-3.0.0}/commkit/plotting/theme.py +0 -0
  208. {commkit-2.3.0 → commkit-3.0.0}/commkit/plotting/waveform.py +0 -0
  209. {commkit-2.3.0 → commkit-3.0.0}/commkit/py.typed +0 -0
  210. {commkit-2.3.0 → commkit-3.0.0}/commkit/recovery/__init__.py +0 -0
  211. {commkit-2.3.0 → commkit-3.0.0}/commkit/recovery/corrections.py +0 -0
  212. {commkit-2.3.0 → commkit-3.0.0}/commkit/recovery/pilots.py +0 -0
  213. {commkit-2.3.0 → commkit-3.0.0}/commkit/recovery/pll.py +0 -0
  214. {commkit-2.3.0 → commkit-3.0.0}/commkit/recovery/tikhonov.py +0 -0
  215. {commkit-2.3.0 → commkit-3.0.0}/commkit/recovery/viterbi_viterbi.py +0 -0
  216. {commkit-2.3.0 → commkit-3.0.0}/commkit/smoothing.py +0 -0
  217. {commkit-2.3.0 → commkit-3.0.0}/commkit/timing.py +0 -0
  218. {commkit-2.3.0 → commkit-3.0.0}/examples/00-getting-started.ipynb +0 -0
  219. {commkit-2.3.0 → commkit-3.0.0}/examples/03-digital-filters.ipynb +0 -0
  220. {commkit-2.3.0 → commkit-3.0.0}/examples/05-multirate-and-resampling.ipynb +0 -0
  221. {commkit-2.3.0 → commkit-3.0.0}/tests/analysis/test_allan.py +0 -0
  222. {commkit-2.3.0 → commkit-3.0.0}/tests/analysis/test_drift.py +0 -0
  223. {commkit-2.3.0 → commkit-3.0.0}/tests/analysis/test_fm_noise.py +0 -0
  224. {commkit-2.3.0 → commkit-3.0.0}/tests/analysis/test_interferometry.py +0 -0
  225. {commkit-2.3.0 → commkit-3.0.0}/tests/analysis/test_linewidth.py +0 -0
  226. {commkit-2.3.0 → commkit-3.0.0}/tests/analysis/test_trajectory.py +0 -0
  227. {commkit-2.3.0 → commkit-3.0.0}/tests/common/__init__.py +0 -0
  228. {commkit-2.3.0 → commkit-3.0.0}/tests/common/conversions.py +0 -0
  229. {commkit-2.3.0 → commkit-3.0.0}/tests/common/kernel_utils.py +0 -0
  230. {commkit-2.3.0 → commkit-3.0.0}/tests/common/metrics.py +0 -0
  231. {commkit-2.3.0 → commkit-3.0.0}/tests/common/periodic.py +0 -0
  232. {commkit-2.3.0 → commkit-3.0.0}/tests/common/signals.py +0 -0
  233. {commkit-2.3.0 → commkit-3.0.0}/tests/conftest.py +0 -0
  234. {commkit-2.3.0 → commkit-3.0.0}/tests/core/test_psqam.py +0 -0
  235. {commkit-2.3.0 → commkit-3.0.0}/tests/core/test_signal_adapter.py +0 -0
  236. {commkit-2.3.0 → commkit-3.0.0}/tests/core/test_signal_mimo.py +0 -0
  237. {commkit-2.3.0 → commkit-3.0.0}/tests/equalization/test_blind.py +0 -0
  238. {commkit-2.3.0 → commkit-3.0.0}/tests/equalization/test_block.py +0 -0
  239. {commkit-2.3.0 → commkit-3.0.0}/tests/equalization/test_bps_kernel.py +0 -0
  240. {commkit-2.3.0 → commkit-3.0.0}/tests/equalization/test_cpr.py +0 -0
  241. {commkit-2.3.0 → commkit-3.0.0}/tests/equalization/test_cs_kernel.py +0 -0
  242. {commkit-2.3.0 → commkit-3.0.0}/tests/equalization/test_equalizer_oracles.py +0 -0
  243. {commkit-2.3.0 → commkit-3.0.0}/tests/equalization/test_linear.py +0 -0
  244. {commkit-2.3.0 → commkit-3.0.0}/tests/equalization/test_mimo.py +0 -0
  245. {commkit-2.3.0 → commkit-3.0.0}/tests/equalization/test_polarization.py +0 -0
  246. {commkit-2.3.0 → commkit-3.0.0}/tests/equalization/test_sequential.py +0 -0
  247. {commkit-2.3.0 → commkit-3.0.0}/tests/equalization/test_state.py +0 -0
  248. {commkit-2.3.0 → commkit-3.0.0}/tests/equalization/test_winit.py +0 -0
  249. {commkit-2.3.0 → commkit-3.0.0}/tests/impairments/channel/test_channel_linear.py +0 -0
  250. {commkit-2.3.0 → commkit-3.0.0}/tests/impairments/channel/test_channel_nonlinear.py +0 -0
  251. {commkit-2.3.0 → commkit-3.0.0}/tests/impairments/test_frontend.py +0 -0
  252. {commkit-2.3.0 → commkit-3.0.0}/tests/impairments/test_noise.py +0 -0
  253. {commkit-2.3.0 → commkit-3.0.0}/tests/impairments/test_source.py +0 -0
  254. {commkit-2.3.0 → commkit-3.0.0}/tests/mapping/test_bits.py +0 -0
  255. {commkit-2.3.0 → commkit-3.0.0}/tests/mapping/test_constellation.py +0 -0
  256. {commkit-2.3.0 → commkit-3.0.0}/tests/mapping/test_gray.py +0 -0
  257. {commkit-2.3.0 → commkit-3.0.0}/tests/plotting/test_plot_analysis.py +0 -0
  258. {commkit-2.3.0 → commkit-3.0.0}/tests/plotting/test_plot_constellation.py +0 -0
  259. {commkit-2.3.0 → commkit-3.0.0}/tests/plotting/test_plot_equalizer.py +0 -0
  260. {commkit-2.3.0 → commkit-3.0.0}/tests/plotting/test_plot_eye.py +0 -0
  261. {commkit-2.3.0 → commkit-3.0.0}/tests/plotting/test_plot_filter_response.py +0 -0
  262. {commkit-2.3.0 → commkit-3.0.0}/tests/plotting/test_plot_spectral.py +0 -0
  263. {commkit-2.3.0 → commkit-3.0.0}/tests/plotting/test_plot_sync.py +0 -0
  264. {commkit-2.3.0 → commkit-3.0.0}/tests/plotting/test_plot_theme.py +0 -0
  265. {commkit-2.3.0 → commkit-3.0.0}/tests/plotting/test_plot_waveform.py +0 -0
  266. {commkit-2.3.0 → commkit-3.0.0}/tests/plugins/__init__.py +0 -0
  267. {commkit-2.3.0 → commkit-3.0.0}/tests/plugins/precision_trace.py +0 -0
  268. {commkit-2.3.0 → commkit-3.0.0}/tests/recovery/test_bps.py +0 -0
  269. {commkit-2.3.0 → commkit-3.0.0}/tests/recovery/test_corrections.py +0 -0
  270. {commkit-2.3.0 → commkit-3.0.0}/tests/recovery/test_joint.py +0 -0
  271. {commkit-2.3.0 → commkit-3.0.0}/tests/recovery/test_pilots.py +0 -0
  272. {commkit-2.3.0 → commkit-3.0.0}/tests/recovery/test_pll.py +0 -0
  273. {commkit-2.3.0 → commkit-3.0.0}/tests/recovery/test_recovery_oracles.py +0 -0
  274. {commkit-2.3.0 → commkit-3.0.0}/tests/recovery/test_tikhonov.py +0 -0
  275. {commkit-2.3.0 → commkit-3.0.0}/tests/recovery/test_viterbi_viterbi.py +0 -0
  276. {commkit-2.3.0 → commkit-3.0.0}/tests/test_array.py +0 -0
  277. {commkit-2.3.0 → commkit-3.0.0}/tests/test_backend.py +0 -0
  278. {commkit-2.3.0 → commkit-3.0.0}/tests/test_cuda_infra.py +0 -0
  279. {commkit-2.3.0 → commkit-3.0.0}/tests/test_dispersion.py +0 -0
  280. {commkit-2.3.0 → commkit-3.0.0}/tests/test_examples.py +0 -0
  281. {commkit-2.3.0 → commkit-3.0.0}/tests/test_fiber.py +0 -0
  282. {commkit-2.3.0 → commkit-3.0.0}/tests/test_filtering.py +0 -0
  283. {commkit-2.3.0 → commkit-3.0.0}/tests/test_frequency.py +0 -0
  284. {commkit-2.3.0 → commkit-3.0.0}/tests/test_logger.py +0 -0
  285. {commkit-2.3.0 → commkit-3.0.0}/tests/test_math.py +0 -0
  286. {commkit-2.3.0 → commkit-3.0.0}/tests/test_multirate.py +0 -0
  287. {commkit-2.3.0 → commkit-3.0.0}/tests/test_pulse_shaping.py +0 -0
  288. {commkit-2.3.0 → commkit-3.0.0}/tests/test_signal_pipeline.py +0 -0
  289. {commkit-2.3.0 → commkit-3.0.0}/tests/test_smoothing.py +0 -0
  290. {commkit-2.3.0 → commkit-3.0.0}/tests/test_spectral.py +0 -0
  291. {commkit-2.3.0 → commkit-3.0.0}/tests/test_timing.py +0 -0
@@ -110,12 +110,22 @@ jobs:
110
110
  - name: Run kernel tests with JIT disabled
111
111
  env:
112
112
  NUMBA_DISABLE_JIT: "1"
113
- run: uv run pytest tests/equalization tests/recovery tests/test_frequency.py --device=cpu --cov=commkit --cov-report=
113
+ run: >-
114
+ uv run pytest tests/equalization tests/recovery tests/test_frequency.py tests/mapping/test_llr.py
115
+ tests/coding/test_ldpc.py tests/coding/test_shaping.py::TestCCDM
116
+ tests/coding/test_polar.py::TestEncoding
117
+ tests/coding/test_polar.py::TestDecoding::test_sc_matches_the_recursive_reference
118
+ tests/coding/test_polar.py::TestDecoding::test_full_list_is_maximum_likelihood
119
+ tests/coding/test_polar.py::TestDecoding::test_crc_failure_keeps_the_best_path
120
+ tests/coding/test_convolutional.py tests/coding/test_turbo.py::TestEncoding
121
+ tests/coding/test_turbo.py::TestDecoding::test_noiseless
122
+ -k "not waterfall and not threshold"
123
+ --device=cpu --cov=commkit --cov-report=
114
124
 
115
125
  - name: Kernel coverage report
116
126
  run: >-
117
127
  uv run coverage report --show-missing --fail-under=85
118
- --include="commkit/equalization/_kernels_numba.py,commkit/equalization/_numba_loops.py,commkit/equalization/sequential/*,commkit/frequency.py,commkit/recovery/*"
128
+ --include="commkit/equalization/_kernels_numba.py,commkit/equalization/_numba_loops.py,commkit/equalization/sequential/*,commkit/frequency.py,commkit/recovery/*,commkit/coding/ldpc.py,commkit/coding/shaping.py,commkit/coding/polar.py,commkit/coding/convolutional.py,commkit/coding/turbo.py"
119
129
 
120
130
  # GPU legs (--device=gpu / --device=all) require CuPy + CUDA and are run
121
131
  # manually or on a self-hosted runner; they are intentionally not part of
@@ -36,27 +36,53 @@ uv run pytest --device=cpu # what CI runs
36
36
  uv run pytest tests/equalization/ -k lms # subset
37
37
  uv run pytest --cov=commkit # coverage
38
38
  NUMBA_DISABLE_JIT=1 uv run pytest tests/equalization tests/recovery \
39
- tests/test_frequency.py --device=cpu --cov=commkit # real kernel coverage
39
+ tests/test_frequency.py tests/mapping/test_llr.py tests/coding/test_ldpc.py tests/coding/test_shaping.py::TestCCDM \
40
+ tests/coding/test_polar.py::TestEncoding "tests/coding/test_polar.py::TestDecoding::test_sc_matches_the_recursive_reference" \
41
+ "tests/coding/test_polar.py::TestDecoding::test_full_list_is_maximum_likelihood" \
42
+ "tests/coding/test_polar.py::TestDecoding::test_crc_failure_keeps_the_best_path" \
43
+ tests/coding/test_convolutional.py tests/coding/test_turbo.py::TestEncoding \
44
+ tests/coding/test_turbo.py::TestDecoding::test_noiseless \
45
+ -k "not waterfall and not threshold" \
46
+ --device=cpu --cov=commkit # real kernel coverage
40
47
  uv run pytest --device=cpu -p tests.plugins.precision_trace \
41
48
  --ignore=tests/test_examples.py # precision audit (see Numerics); the suite passes under it
42
49
  uv run pytest --device=gpu -p tests.plugins.transfer_trace \
43
50
  --ignore=tests/test_examples.py # transfer audit (see Performance); fails on an undocumented transfer
44
51
  ```
45
52
 
46
- **CUDA sanitizer** (see Kernel policy): NVIDIA's `compute-sanitizer` checks the kernels for out-of-bounds and misaligned accesses (`memcheck`), shared-memory races (`racecheck`), barrier misuse (`synccheck`) and reads of uninitialized device memory (`initcheck`). It is not a dependency: install its wheel, matching the CUDA major version of `cupy-cuda13x`, into a scratch directory and run the GPU tests of the kernels under each tool (10-100x slower):
53
+ **CUDA sanitizer** (see Kernel policy): NVIDIA's `compute-sanitizer` checks the kernels for out-of-bounds and misaligned accesses (`memcheck`), shared-memory races (`racecheck`), barrier misuse (`synccheck`) and reads of uninitialized device memory (`initcheck`). It is not a dependency: install its wheel, matching the CUDA major version of `cupy-cuda13x`, into a scratch directory and run the GPU tests of the kernels under each tool (10-100x slower; the whole pass takes about 50 minutes on the reference machine):
47
54
 
48
55
  ```bash
49
56
  uv pip install --target /tmp/sanitizer "nvidia-cuda-sanitizer-api==13.*"
57
+ S=/tmp/sanitizer/nvidia/cu13/bin/compute-sanitizer
58
+ OWN=(--kernel-name kns=commkit_ --kernel-name kns=bps_ --kernel-name kns=cs_block \
59
+ --kernel-name kns=llr --kernel-name kns=iir_ --kernel-name kns=selftest --kernel-name kns=ldpc)
60
+ FILES="tests/test_cuda_infra.py tests/equalization/test_cs_kernel.py tests/equalization/test_bps_kernel.py
61
+ tests/equalization/test_blind.py tests/mapping/test_llr.py tests/mapping/test_gray.py tests/recovery/test_bps.py
62
+ tests/test_filtering.py tests/test_fiber.py tests/impairments/channel/test_channel_nonlinear.py
63
+ tests/coding/test_ldpc_kernel.py"
64
+ BLOCK=$(.venv/bin/python -m pytest --device=gpu -q --collect-only tests/equalization/test_block.py | grep '::')
50
65
  for tool in memcheck racecheck synccheck initcheck; do
51
- /tmp/sanitizer/nvidia/cu13/bin/compute-sanitizer --tool $tool --leak-check full \
52
- .venv/bin/python -m pytest --device=gpu -p no:cacheprovider \
53
- tests/equalization/test_cs_kernel.py tests/equalization/test_bps_kernel.py \
54
- tests/equalization/test_block.py tests/equalization/test_blind.py tests/mapping/test_llr.py \
55
- tests/test_filtering.py tests/test_fiber.py tests/impairments/channel/test_channel_nonlinear.py
66
+ filter=(); [[ $tool == racecheck || $tool == synccheck ]] && filter=("${OWN[@]}")
67
+ for target in $FILES $BLOCK; do
68
+ "$S" --tool $tool "${filter[@]}" .venv/bin/python -m pytest --device=gpu -p no:cacheprovider -q "$target" \
69
+ 2>&1 | grep -E "SUMMARY|passed|failed"
70
+ done
56
71
  done
57
72
  ```
58
73
 
59
- Under WSL2 the sanitizer needs the Windows GPU debugger interface; without it every run stops at "Failed to initialize WDDM debugger interface". Enable it once by running `EnableDebuggerInterface.bat` as a Windows administrator; the script comes with the Windows CUDA Toolkit, not with the Linux wheel.
74
+ - **One process per file, and one per test for `test_block.py`.** Under the sanitizer each CUDA-graph test of the block equalizers gets slower the more tests ran before it in the process (one took 10 minutes after 26 others, 61 s alone).
75
+ - **`racecheck` and `synccheck` check commkit's kernels only** (`OWN`: the `.cu` kernels and the `commkit_*` ElementwiseKernels). Unfiltered, racecheck tracks every CuPy and cuFFT launch of the 100k-symbol graph tests and grows by about 0.1 GB of host memory per second until the machine runs out. Those two tools concern shared memory and barriers, which only the `.cu` kernels use; `memcheck` and `initcheck` run unfiltered, as a CuPy kernel reading a buffer commkit never filled is a commkit bug.
76
+ - **No `--leak-check full`.** CuPy's pool holds device memory until exit, so the check finds nothing in commkit, and with it every CuPy process exits with SIGSEGV after the summary (`import cupy as cp; cp.arange(3).sum()` alone does).
77
+ - **The wheel must be complete.** The 13.2 and 13.3 wheels lack the launcher helpers (`TreeLauncherSubreaper`, `TreeLauncherTargetLdPreloadHelper`) and stop every target, even `/bin/echo`, with "Target application terminated before first instrumented API call". The 13.4 wheel has them and runs on a 13.3 driver. If a driver needs an older version, take NVIDIA's redistributable archive (`developer.download.nvidia.com/compute/cuda/redist/cuda_sanitizer_api/`), which is complete.
78
+
79
+ Under WSL2 the sanitizer needs the Windows GPU debugger interface; without it every run stops at "Error: Device not supported" (older versions: "Failed to initialize WDDM debugger interface"). Enable it once from an elevated Windows prompt, then restart WSL (`wsl --shutdown`):
80
+
81
+ ```bat
82
+ reg add "HKLM\SOFTWARE\NVIDIA Corporation\GPUDebugger" /v EnableInterface /t REG_DWORD /d 1 /f
83
+ ```
84
+
85
+ Windows also kills a kernel that runs longer than `TdrDelay` seconds (`HKLM\System\CurrentControlSet\Control\GraphicsDrivers`, 8 on the reference machine, which the whole pass stays under); raise it if a sanitized launch times out.
60
86
 
61
87
  **Commit gate.** CI fails on any of these, so run all of them before every commit, with `ruff format` last:
62
88
 
@@ -78,7 +104,7 @@ Timed runs:
78
104
  ```bash
79
105
  uv run pytest benchmarks/bench_bps.py --benchmark-only --device=all
80
106
  uv run pytest benchmarks/ --benchmark-only --device=all \
81
- --benchmark-compare=0006 --benchmark-storage=file://benchmarks/baselines
107
+ --benchmark-compare=0007 --benchmark-storage=file://benchmarks/baselines
82
108
  uv run python benchmarks/record_baseline.py NAME # record a new baseline
83
109
  ```
84
110
 
@@ -100,7 +126,7 @@ Dependencies point downward only:
100
126
  | --- | --- | --- |
101
127
  | Infrastructure | `backend`, `logger`, `_cuda` | NumPy, CuPy |
102
128
  | Array and math helpers | `_array` (shape and validation), `math` (power, normalization, dB, linear trend) | infrastructure |
103
- | Value objects | `mapping.Constellation`, pulse classes, `recovery` CPR configs | layers above |
129
+ | Value objects | `mapping.Constellation`, pulse classes, `recovery` CPR configs, `coding` (code objects with their `encode` / `decode`; array-only, never imports `core`) | layers above |
104
130
  | Core | `core` (`Signal`, frames, generation), `io` | layers above |
105
131
  | DSP | `mapping`, `filtering`, `multirate`, `spectral`, `smoothing`, `impairments`, `timing`, `frequency`, `recovery`, `equalization`, `metrics`, `analysis` | layers above, and lower DSP modules |
106
132
  | Presentation | `plotting` | everything; **nothing imports plotting** |
@@ -111,7 +137,6 @@ Dependencies point downward only:
111
137
  - `analysis` holds laser and phase characterization.
112
138
  - `metrics` is the reporting layer.
113
139
  - **Splitting modules:** promote a flat module to a package only when it exceeds about 1000 lines *and* has two or more separable concerns. The package `__init__` re-exports the public names. Never create a `utils/` package or one-function files.
114
- - **Placeholder modules are kept on purpose.** `commkit.coding` is a reminder of planned work. Never delete it or treat it as dead code. It is excluded from coverage and from the contract registry, and the README labels it "planned, not implemented".
115
140
  - **Shared physics lives in one private module**, used by the channel and by its compensation: `_dispersion` (β₂, β₃, the dispersion response) and `_fiber` (`Fiber` and the split-step solver behind `apply_fiber_propagation` and `correct_fiber_propagation`).
116
141
 
117
142
  ---
@@ -120,7 +145,7 @@ Dependencies point downward only:
120
145
 
121
146
  ### Signal
122
147
 
123
- - `Signal` holds: samples (NumPy or CuPy, `(N,)` or `(C, N)`, time on the last axis), the **facts** `sampling_rate`, `symbol_rate` and `center_frequency`, the **description** `constellation` and `pulse`, and the ground truth `reference` (transmitted symbols and bits) plus the `frame` layout.
148
+ - `Signal` holds: samples (NumPy or CuPy, `(N,)` or `(C, N)`, time on the last axis), the **facts** `sampling_rate`, `symbol_rate` and `center_frequency`, the **description** `constellation` and `pulse`, and the ground truth `reference` (transmitted symbols, the bits on the air `bits` and the source bits `info_bits`, the same array when nothing is coded or matched) plus the `frame` layout.
124
149
  - It never holds pipeline results.
125
150
  - Construction does no hidden work: no mapping, no normalization, no device move, no shape guessing. Factories (`generate`, frames) do that work.
126
151
  - `sig.to("gpu")` and `sig.replace(...)` return new Signals. Arrays are shared, not copied; use `clone()` for an independent copy.
@@ -157,7 +182,7 @@ Dependencies point downward only:
157
182
  - **Transforms:** array in gives array out on the same device; Signal in gives a new Signal out (update `sampling_rate` when the rate changes).
158
183
  - **Estimators** reduce over the time axis by input rank: `(N,)` gives 0-d and `(C, N)` gives `(C,)`. Results stay on the input device.
159
184
  - **Analysis summaries** (`estimate_linewidth`, `allan_deviation`, the `frequency_drift` statistics) are reporting-layer fits on plot-sized reductions. Like the metrics, they return host floats or `(C,)` arrays. Sample-rate outputs (trajectories, PSDs, `dsh_phase`) stay on the input device.
160
- - **Metrics** (`evm`, `snr`, `ber`, `ser`, `gmi`, `mi`) return host values: a `float` for 1-D input and `np.ndarray (C,)` for 2-D input. They **raise** on empty selections; they never return `None` or `0.0` for "nothing measured". Metrics never silently align, rotate or permute against the reference.
185
+ - **Metrics** (`evm`, `snr`, `ber`, `fer`, `ser`, `gmi`, `ngmi`, `mi`) return host values: a `float` for 1-D input and `np.ndarray (C,)` for 2-D input. They **raise** on empty selections; they never return `None` or `0.0` for "nothing measured". Metrics never silently align, rotate or permute against the reference.
161
186
  - **More than one value** is returned as a frozen dataclass with named fields. No dicts, and no tuples longer than two.
162
187
  - **Equalizers** return `EqualizerResult`. `y_hat` is always an array, `signal` holds the 1-SPS Signal for Signal input, `state` is the continuation state, and diagnostics are returned as data.
163
188
 
@@ -170,6 +195,7 @@ Dependencies point downward only:
170
195
  | `correct_<quantity>(x, how)` | remove the impairment; returns only the corrected data. `how` is an estimate or an algorithm object (which estimates first) |
171
196
  | `resolve_*` | pick among discrete candidates (π/2 rotation, channel permutation); nothing else |
172
197
  | `generate` | synthesis from parameters, takes `rng` |
198
+ | `encode` / `decode` | information bits to a code's transmitted bits, and LLRs (or hard decisions) back to a `DecodeResult`; dispatched on the code object, the decoder chosen by a `method` object |
173
199
  | `plot_*` | every public plotting function (only `apply_default_theme` is exempt) |
174
200
 
175
201
  - The algorithm is chosen by an object, never by the function name or a string: `estimate_carrier_phase(y, BPS())`, not `recover_carrier_phase_bps(y)`. The verb function dispatches on the object's type through a private table in its module. There is no `recover_*` or `compensate_*`.
@@ -226,11 +252,15 @@ Docstrings describe the code as it is; a change in behavior updates them in the
226
252
  | --- | --- | --- |
227
253
  | Vectorized: filters, resampling, spectra, impairments, metrics, frequency-domain equalizers | NumPy/CuPy via `x, xp, sp = dispatch(samples)` | follows the input |
228
254
  | Per-symbol searches over a small table: LLRs (over all points, or per axis for separable QAM; max-log without a prior in closed form, from the nearest levels of each bit's two values), hard decisions of constellations without a square lattice | CPU: Numba `parallel=True` over blocks of symbols (LLRs; the closed form from 16 levels per axis, below which the level metrics are as fast), chunked NumPy (decisions). GPU: CUDA kernels, one thread per symbol (`llr`, `llr_separable`, `llr_separable_maxlog` at every order, `bps_min_d2`'s nearest-point mode); decisions on double input or more than 1024 points run the chunked search on both devices, LLRs above 16 bits per symbol raise on the GPU | follows the input |
229
- | Sequential recursions: LMS/RLS/CMA/RDE, PLL, Tikhonov, cycle slips, the Mengali-Morelli bootstrap over the lags | Numba `@njit(cache=True, fastmath=True, nogil=True)`; independent rows in parallel (`prange`: the bootstrap's blocks). The equalizers' kernel with inline carrier recovery compiles strict (its unwrap and running sums) and calls its tap and distance loops as separate fast-math functions, which keep their flags when inlined (an `inline="always"` helper takes its caller's) | CPU; GPU input does one round trip to the host and back |
255
+ | Sequential recursions: LMS/RLS/CMA/RDE, PLL, Tikhonov, cycle slips, the Mengali-Morelli bootstrap over the lags, per-codeword decoders on trellises and trees (CCDM matching, polar SC/SCL, Viterbi, BCJR, turbo decoding, recursive systematic encoding; independent blocks in parallel) | Numba `@njit(cache=True, fastmath=True, nogil=True)`; independent rows in parallel (`prange`: the bootstrap's blocks). The equalizers' kernel with inline carrier recovery compiles strict (its unwrap and running sums) and calls its tap and distance loops as separate fast-math functions, which keep their flags when inlined (an `inline="always"` helper takes its caller's) | CPU; GPU input does one round trip to the host and back |
230
256
  | Linear recursions: IIR filtering | CPU: Numba kernel without `fastmath`, so double input gives SciPy's result bit for bit. GPU: CUDA kernel (`iir`), blocks filtered in parallel and the state carried across them in two levels, in double precision | follows the input |
231
257
  | Hot spots limited by intermediates, launches or round trips | CUDA C++ via CuPy `RawModule` (`commkit/_cuda`) | GPU |
258
+ | Binary linear algebra of codes: encoding, syndromes, the correlations of maximum-likelihood decoding, the evaluations of algebraic decoding (Chien search, Forney), Chase's test patterns through a hard decoder | one float32 GEMM per chunk (`coding._gf2.matmul_mod2`: BLAS, cuBLAS), exact below an inner dimension of 2^24; small tables (generators, coset leaders) cached per code object and device (`backend._device_tables`) | follows the input |
259
+ | Iterative decoding of many codewords: layered LDPC belief propagation (min-sum, sum-product) | CPU: Numba `parallel=True` over codewords, all layers and iterations inside, without fast-math. GPU: CUDA kernel (`ldpc_layered`), one thread block per codeword, the rows of a layer in parallel, early stop per codeword inside the kernel; the same rows in the same order on both, so min-sum agrees bit for bit (a CuPy version, about 12 launches per layer, was 7-23x slower) | follows the input |
232
260
  | Elementwise chains between multi-core FFTs (the fiber solver's Kerr and PMD stages) | NumPy: a Numba `parallel=True` pass there competes with `scipy.fft`'s threads (the solver's Numba passes measured 1.1-1.8x slower end to end, 2^14-2^20); adopt one only on a paired end-to-end A/B | CPU |
233
261
 
262
+ **Numba kernels live at module level**, in a private module that a getter imports on first use (`equalization/_numba_loops.py`, `coding/_numba_loops.py`, `mapping/_numba_llr.py`), so `import commkit` does not load Numba and the disk cache (`cache=True`) keeps them across processes. A kernel defined inside a function that calls another kernel defined there captures that dispatcher in its closure, which Numba's cache key does not reproduce in a new process: it compiled again in every process (3 s for `compute_llr`, 6 s for polar list decoding). A getter may build a kernel in a closure that captures only constants (`_get_numba_sos`).
263
+
234
264
  **Host-sync hygiene:**
235
265
 
236
266
  - Never extract scalars from a possibly-GPU array inside a loop (`float(x[c])`, `.item()`). Compute the vector on the device, transfer it once with `to_device(v, "cpu")`, then loop on the host.
@@ -278,20 +308,20 @@ Docstrings describe the code as it is; a change in behavior updates them in the
278
308
 
279
309
  ## 8. Benchmarks
280
310
 
281
- `benchmarks/` tracks the GPU-relevant hot paths. Baselines are committed under `benchmarks/baselines/`. The current reference is `0006` (`perf_cleanup`) on the reference machine: RTX 4070 Ti, Ryzen 7 7800X3D, WSL2. It is the first baseline with `bench_filters.py`, `bench_pll`, `bench_mengali_morelli_blockwise` and `bench_correct_iq_imbalance`, recorded after the performance pass of the IIR kernel and the block equalizers' in-graph block copies, best of five passes; `0005` (`gpu_decoder_path`) is the first with `bench_mapping.py` and `bench_compute_llr_nonseparable`, recorded after the GPU LLR, mapping and decision kernels; `0004` (`fiber_dbp`) adds the fiber and DBP benchmarks to `0003` (`v2_0`, the 2.0 state); `0002` (`pre_v2`) is the 1.x state. `0002` ran the CPR equalizer and Viterbi-Viterbi benchmarks on workloads that did not converge, so compare those only from `0003` on.
311
+ `benchmarks/` tracks the GPU-relevant hot paths. Baselines are committed under `benchmarks/baselines/`. The current reference is `0007` (`v3_0`) on the reference machine: RTX 4070 Ti, Ryzen 7 7800X3D, WSL2. It is the first baseline with `bench_coding.py` (LDPC encoding and decoding, KP4 decoding), recorded at the 3.0 state with the session warmup of Numba's thread pool, best of five passes; `0006` (`perf_cleanup`) is the first with `bench_filters.py`, `bench_pll`, `bench_mengali_morelli_blockwise` and `bench_correct_iq_imbalance`, recorded after the performance pass of the IIR kernel and the block equalizers' in-graph block copies, best of five passes; `0005` (`gpu_decoder_path`) is the first with `bench_mapping.py` and `bench_compute_llr_nonseparable`, recorded after the GPU LLR, mapping and decision kernels; `0004` (`fiber_dbp`) adds the fiber and DBP benchmarks to `0003` (`v2_0`, the 2.0 state); `0002` (`pre_v2`) is the 1.x state. `0002` ran the CPR equalizer and Viterbi-Viterbi benchmarks on workloads that did not converge, so compare those only from `0003` on.
282
312
 
283
313
  - **Workloads must converge.** A workload the algorithm cannot handle times a failure mode (slip storms, divergence), not the operating point. Every equalizer benchmark asserts its symbol error rate with `benchutils.assert_converged`.
284
- - **Baselines** are recorded with `benchmarks/record_baseline.py`: each file in its own process, best of three passes (`--passes`; `0006` used five, as three still kept outliers of 2-4x on a few small benchmarks). In one full-suite process, small GPU benchmarks after the large equalizer workloads ran 2-12x slower than alone, so never record a baseline from a single `pytest benchmarks/` run.
314
+ - **Baselines** are recorded with `benchmarks/record_baseline.py`: each file in its own process, best of three passes (`--passes`; `0006` and `0007` used five, as three still kept outliers of 2-4x on a few small benchmarks). In one full-suite process, small GPU benchmarks after the large equalizer workloads ran 2-12x slower than alone, so never record a baseline from a single `pytest benchmarks/` run.
285
315
 
286
316
  - **IDs** are `[cpu]` or `[gpu]`, meaning the input device.
287
317
  - **Timing:**
288
318
  - Timed bodies end with the `sync` fixture; otherwise GPU timings measure launches, not execution.
289
- - Each benchmark has one warmup round, so compilation and pool growth are excluded.
319
+ - Each benchmark has one warmup round, so compilation and pool growth are excluded, and a session fixture keeps Numba's thread pool busy for half a second before the first: the first parallel calls of a process ran 4-8x slower while the OpenMP workers came up, which a kernel loaded from Numba's cache reaches in the timed rounds.
290
320
  - **Workloads** come from `benchmarks/workloads.py` with fixed seeds; never generate data inline.
291
321
  - **Tools:** `benchmarks/benchutils.py` provides `CudaEventTimer` and `nvtx_range` for `nsys` profiling.
292
322
  - **Logging** is set to WARNING in `benchmarks/conftest.py`.
293
323
  - **Trust deltas, not single runs.** A ±20-40% swing has been seen under load. Re-run a suspected regression in isolation, or do a controlled A/B with at least 7 repetitions, alternating old and new per repetition and comparing the paired ratios. When even unchanged benchmarks move, time the changed part itself with `CudaEventTimer` and quote it against the step it sits in.
294
- - A commit touching a hot path quotes its delta against `0006`. Tolerance is 5% on CPU and 10% on GPU.
324
+ - A commit touching a hot path quotes its delta against `0007`. Tolerance is 5% on CPU and 10% on GPU.
295
325
 
296
326
  ---
297
327
 
@@ -314,7 +344,7 @@ Steps, once CuPy 14.3 (with cp315 wheels for `cupy-cuda13x`) and a fixed `jupyte
314
344
  - `pyproject.toml`: `requires-python = ">=3.15"`, classifiers 3.15 only, ruff `target-version = "py315"` (if ruff supports it), raise the dependency minimums to the first releases with 3.15 wheels (`cupy-cuda13x[ctk]>=14.3`, `numba>=0.68`, `numpy>=2.5`, `scipy>=1.18`; recheck each).
315
345
  - `ci.yml`: test matrix `python-version: ["3.15"]` (keep both `locked` and `lowest-direct`); the static-analysis, kernel-coverage and notebook jobs on 3.15. `publish.yml`: build on 3.15.
316
346
  - `uv lock`; README install requirements; §2 here ("Python 3.12+").
317
- - Benchmarks: record a new baseline on 3.15 (it lands in `benchmarks/baselines/Linux-CPython-3.15-64bit/`), compare it with `0006` by a paired A/B of 3.14 against 3.15 on unchanged code, and make it the reference.
347
+ - Benchmarks: record a new baseline on 3.15 (it lands in `benchmarks/baselines/Linux-CPython-3.15-64bit/`), compare it with `0007` by a paired A/B of 3.14 against 3.15 on unchanged code, and make it the reference.
318
348
  3. **Lazy imports (PEP 810)**. Confirm the rules on the interpreter first. As specified, `lazy` is allowed only for module-level imports: not inside functions, class bodies or `try` blocks, and not with `*`.
319
349
  - About 60 function-local imports exist to keep `import commkit` light or optional: `import cupy as cp` (11 sites), `import numba` (9), `from . import compiler` in `_cuda` (5), and local imports that break cycles (`Constellation`, `Signal`, `normalize`). Move each to a module-level `lazy import` / `lazy from`. Concentrated in `_cuda/__init__.py`, `backend.py`, `io.py`, `recovery/`, `core/`, `plotting/`.
320
350
  - CuPy stays optional: a lazy import of a missing module fails only when used, so every use must stay behind the existing availability checks (`is_cupy_available`, `dispatch`). `backend.py`'s probing needs care: decide per site whether the probe imports eagerly inside `try`.
@@ -327,13 +357,11 @@ Steps, once CuPy 14.3 (with cp315 wheels for `cupy-cuda13x`) and a fixed `jupyte
327
357
  - `strptime` with a day and no year now raises (the `jupyter_client` failure above); grep commkit for date parsing.
328
358
  - The 3.15 sampling profiler (PEP 799) is a candidate tool for the CPU hot paths next to `nsys`; mention it in §8 if it proves useful.
329
359
 
330
- ### CUDA sanitizer pass (blocked on the WDDM debugger interface)
331
-
332
- The kernels have not run under `compute-sanitizer` (§2): on the reference machine (WSL2) it needs the Windows GPU debugger interface, which is not enabled yet. The review that stands in for it (every `.cu` kernel and wrapper read for bounds, barriers, races and overflow; empty, one-sample and NaN/Inf inputs; 4 KB guard zones around every array the library allocated during the GPU kernel tests, 565 tests and 57,723 arrays, none touched) catches out-of-bounds writes but not out-of-bounds reads or reads of uninitialized memory. Once the interface is enabled, run the four tools of §2, fix what they report, and remove this item.
333
-
334
- ### Planned modules
360
+ ### Channel coding: open items
335
361
 
336
- 1. **Channel coding** (`coding`, a placeholder package). Soft demapping is GPU-validated on both devices. Start with one soft decoder (LDPC) that consumes `compute_llr` output (positive LLR = bit 0) and reports post-FEC BER next to `gmi`, then add encoders. The package stays out of `commkit/__init__.py` until a real encode/decode entry point exists. Decoders are iterative message passing over many codewords: batch codewords on the GPU from the start.
362
+ 1. **Storing frames with a payload code.** `save_npz` raises for a frame with a `payload_code`, because code objects are not serialized; store `extract_payload(signal)` instead.
363
+ 2. **oFEC** (OpenROADM, ITU-T G.709.3) is not implemented: its block structure is specified in the OpenROADM MSA documents, which were not available in a form to implement from. It would build on `BCHCode` (extended BCH(256, 239)), `Chase` with soft output and the staircase decoder's iteration over a window.
364
+ 3. **NR rate matching for polar codes** (sub-block interleaving, the circular buffer, puncturing and shortening with their frozen-set rules, parity-check bits) and **LTE rate matching for turbo codes** are not included: `PolarCode` and `TurboCode` are the mother codes. `Punctured` and `Shortened` cover generic rate matching.
337
365
 
338
366
  ### Nonlinear fiber channel: future features
339
367
 
@@ -0,0 +1,161 @@
1
+ # Changelog
2
+
3
+ Notable changes to CommKit, newest first. Performance figures are from the reference machine (RTX 4070 Ti, Ryzen 7 7800X3D, WSL2) unless stated otherwise, as a ratio of new to old time (below 1 is faster).
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [3.0.0] - 2026-10-28
8
+
9
+ The channel-coding release. `commkit.coding` grows from an empty scaffold into a full FEC toolbox: one `encode` / `decode` pair dispatched on code objects covers linear block, LDPC, BCH and Reed-Solomon, polar, convolutional and turbo codes, the FEC structures of the optical standards (KP4, the 802.3dj and 400ZR concatenated codes, the G.709.2 staircase code, PON LDPC), CRCs, interleavers, rate matching and probabilistic amplitude shaping. `generate` and frames produce coded payloads, the reference keeps the source bits next to the bits on the air, and three new notebooks teach it. Shaped `generate` now goes through a distribution matcher, and the example notebooks are renumbered; see the first section.
10
+
11
+ ### Changes that may need code updates
12
+
13
+ - **Shaped `generate` goes through a distribution matcher.** A shaped constellation used to draw i.i.d. symbols from its pmf and take their labels as the "bits", so the bits described a stream that was never sent. Now random bits pass through `CCDM(constellation, block_length=num_symbols)` per channel: every symbol comes from bits, each record holds every point exactly as often as the pmf (quantized to `1/num_symbols`) says, and `reference.info_bits` holds the matcher's input. For the same seed the symbols differ from 2.x; GMI and MI agree within Monte Carlo error. A shaped record must be long enough to carry a bit (a few symbols). Frames inherit this, and `save_npz` stores a shaped frame's source bits.
14
+ - **`Reference` has two more fields**, `info_bits` and `code_block`. `bits` are the bits on the air; `info_bits` are the source bits before coding. Without coding `info_bits` is `bits` itself and `code_block` is `(1, 1)`, so existing code that reads `reference.bits` is unaffected. Code that builds a `Reference` positionally keeps working.
15
+ - **The placeholder modules of `commkit.coding` are gone.** 2.x shipped empty scaffold modules (`hamming`, `bch`, `reed_solomon`, `galois`) that held only docstrings. The codes now live in `linear` (Hamming and other linear block codes), `algebraic` (BCH and Reed-Solomon) and the private `_galois`; import the public names from `commkit.coding` itself (`from commkit.coding import BCHCode, ReedSolomonCode`), which is the supported path. `commkit.coding` is also reachable as `ck.coding` after `import commkit as ck`.
16
+ - **The example notebooks are renumbered** to fit the coding chapters: 07 Channel coding and 08 Probabilistic shaping and coded modulation join Part II, 21 FEC in optical standards joins Part IV. Old 07-18 are now 09-20, old 19 (the end-to-end transceiver) is 22, and old 20-25 (characterization and templates) are 23-28. Update bookmarks and links to the notebooks.
17
+
18
+ ### Added
19
+
20
+ - **`commkit.coding`**, the channel-coding module: `encode(bits, code=...)` and `decode(llrs, code=..., method=...)`, dispatched on a code object, returning a `DecodeResult` (decoded bits, per-block success check and iterations).
21
+ - `LinearBlockCode` from a generator or parity-check matrix, and `LinearBlockCode.hamming(r, extended=, length=)` for Hamming codes, including the inner codes of 400ZR, Hamming(128,119), and of IEEE 802.3dj, Hamming(128,120).
22
+ - Decoders `Syndrome(max_weight=)` (coset leaders; `max_weight=1` makes an extended Hamming code correct one error and detect two) and `MaximumLikelihood()` (exhaustive, for small codes).
23
+ - **LDPC codes:** `LDPCCode.from_parity_check(H)`, `LDPCCode.quasi_cyclic(base, lifting)`, `LDPCCode.dvbs2(rate, frame)` (all DVB-S2 rates, normal and short frames) and `LDPCCode.pon()` (LDPC(17280, 14592) of IEEE 802.3ca and ITU-T G.9804.2). Decoders `MinSum(scale=, offset=)` and `SumProduct()`, layered, with early stopping per codeword, on the CPU (Numba) and the GPU (a CUDA kernel, about 10x the CPU on 64 DVB-S2 codewords). Encoding is systematic: a sparse product and a cumulative XOR for DVB-S2, a sparse product and a GEMM with the inverted parity part for PON.
24
+ - **Algebraic codes:** `ReedSolomonCode(symbols, info_symbols, m=)` with the presets `g709()` (RS(255,239)), `kr4()` (RS(528,514)), `kp4()` (RS(544,514)), `epon10g()` (RS(255,223)) and `xgspon()` (RS(248,216)), and binary `BCHCode(m, t, length=)`, decoded by `BerlekampMassey()` (Chien search, Forney) on both devices; a codeword with more than `t` errors is reported `valid=False` or decoded to a codeword within distance `t`. Encoding, syndromes and the locator evaluations are GEMMs over GF(2): KP4 decodes 2048 codewords in 0.45 s on the CPU and 89 ms on the GPU.
25
+ - **Soft decoding of block codes and optical FEC structures:** `Chase(test_positions=, hard=, beta=)`, Chase-II decoding with Pyndiah's soft output for linear block and BCH codes; `LinearBlockCode.oif400zr()`, the double-extended Hamming(128,119) of OIF 400ZR exactly as its clause 10.5 defines it; `StaircaseCode.g709_2(num_blocks)`, the 512 x 510 staircase code of ITU-T G.709.2 (its double-extended BCH(1022,990) component and column permutation as specified), decoded by `HardIterations()`; and `Concatenated(outer, inner, interleaver)` with the presets `cfec()` (400ZR C-FEC) and `ieee_802_3dj()` (KP4 + Hamming(128,120)), decoded by `Stages(inner=, outer=)`. Measured thresholds match the standards: the staircase code decodes at an input BER of 4.5e-3, the C-FEC at 1.2e-2.
26
+ - **Polar codes:** `PolarCode.nr(k, n, crc=)` with the information set of the 5G NR reliability sequence (TS 38.212 Table 5.3.1.2-1) and an optional NR CRC, or `PolarCode.from_frozen(mask)`; decoders `SC()` and `SCL(list_size)` (CRC-aided when the code has a CRC). Mother code only (no NR rate matching).
27
+ - **Convolutional and turbo codes:** `ConvolutionalCode(generators, constraint_length, block_length, termination=)` and `ConvolutionalCode.k7()` (133, 171), terminated or tail-biting, decoded by `Viterbi()` (maximum likelihood) or `BCJR(exact=)` (a-posteriori LLRs); `TurboCode.lte(K)` for the 188 LTE block sizes (QPP interleaver of TS 36.212 Table 5.1.3-3, trellis termination as clause 5.1.3.2.2), decoded by `TurboIterations(iterations, exact=)`.
28
+ - **Bit-level building blocks:** `CRC` as a code (`CRC.nr` for the 5G NR and LTE polynomials, `CRC.ethernet` for the IEEE 802.3 CRC-32, or any Rocksoft-model CRC), whose `decode` flags blocks that fail the check; `interleave` / `deinterleave` with an `Interleaver` (explicit, `Interleaver.block`, `Interleaver.random`); and the rate-matching codes `Punctured(code, positions)` and `Shortened(code, num_bits)`, decoded with the inner code's decoders.
29
+ - **Distribution matching:** `CCDM(constellation, block_length=)`, a constant-composition matcher (an exact 64-bit range coder, invertible for every input, blocks up to 2^30 points, 0.35 s for 10^6 points of 64-QAM on one core), and `PAS(code, constellation)`, probabilistic amplitude shaping with a systematic FEC code supplying the signs. Both are codes: `generate(..., code=PAS(LDPCCode.dvbs2("3/4"), Constellation.qam(64).shaped(entropy=5)))` builds a coded PS payload, and `decode` runs the FEC decoder and the dematcher.
30
+ - **Coded payloads:** `generate(..., code=)` and `SingleCarrierFrame(payload_code=)` encode random information bits before mapping. The reference keeps both bit streams.
31
+ - **`metrics.fer`**, the block error rate, and **`metrics.ngmi`**, the normalized GMI used as the soft-decision FEC threshold. Post-FEC BER is `ber(res.bits, reference.info_bits)`.
32
+ - **`save_npz` / `load_npz`** store a frame's payload source bits and code block (`frame_payload_info_bits`), so a shaped frame round-trips; a frame with a `payload_code` raises, as code objects are not serialized (store `extract_payload(signal)` instead).
33
+ - **Notebooks:** [07 · Channel coding](examples/07-channel-coding.ipynb) (codes as objects, hard and soft decoding, Reed-Solomon, LDPC waterfalls, NGMI as a threshold, polar, convolutional and turbo codes), [08 · Probabilistic shaping and coded modulation](examples/08-probabilistic-shaping-and-coded-modulation.ipynb) (the CCDM, the shaping gain, PAS with LDPC against uniform 16-QAM) and [21 · FEC in optical standards](examples/21-fec-in-optical-standards.ipynb) (the codes of IEEE 802.3, G.709, 400ZR and PON at their thresholds). The end-to-end transceiver (22) carries an LDPC-coded payload and reports post-FEC BER and FER.
34
+
35
+ ### Fixed
36
+
37
+ - **`compute_llr` compiled its CPU kernels in every process.** They are declared with Numba's disk cache, but their kernels called helpers built in the same function, which Numba cannot cache, so the first call of every Python process spent about 3 s compiling. They now load from the cache (about 0.2 s, Numba import included); the results are unchanged.
38
+
39
+ ### Development
40
+
41
+ - `benchmarks/bench_coding.py` times LDPC decoding (min-sum on both devices) and KP4 decoding; the `ldpc` kernel is in the sanitizer pass of AGENTS §2, and the coding tests of the Numba decoders and the CCDM run in the `NUMBA_DISABLE_JIT` coverage job.
42
+ - Baseline `0007` (`v3_0`) is the new benchmark reference, the first with the coding benchmarks: min-sum decoding of DVB-S2 codewords takes 16 ms on the GPU against 133 ms on the CPU, KP4 decoding 41 ms against 410 ms. The benchmarks without coding match `0006` (geometric mean of the ratios 0.97; the slowest moves reproduced within 10% in isolation, or were within the GPU's run-to-run swing on a 0.1 ms call).
43
+ - A session fixture keeps Numba's thread pool busy for half a second before the first benchmark: kernels now loaded from the cache reached the timed rounds while the OpenMP workers were still coming up, which made the first parallel benchmark of a file 4-8x slower.
44
+ - The Numba kernels of the coding module and of `compute_llr` live in private modules (`coding/_numba_loops.py`, `mapping/_numba_llr.py`), so Numba's disk cache keeps them; the coding decoders compiled again in every process too, costing up to 6 s on their first call.
45
+ - Every helper in the package has a docstring, with one-line summaries throughout.
46
+ - `backend._device_tables` caches small per-object tables (generators, interleavers, field tables) on each device, shared by the coding module and the earlier users; its upload is a documented site of the transfer audit.
47
+
48
+ ## [2.3.0] - 2026-10-28
49
+
50
+ A performance and robustness release. GPU block equalizers and the CPU decision-directed equalizers with inline carrier recovery run substantially faster. `iir_filter` keeps single precision on both devices. Max-log LLRs of uniform square QAM have a closed form. The CUDA code was reviewed end to end. A few changes need code updates; see the first section.
51
+
52
+ ### Changes that may need code updates
53
+
54
+ - **`build_pilot_ref`** takes `num_symbols=` and `num_channels=` instead of `n_sym=` and `num_ch=`, the library's glossary names. Rename the keywords at call sites.
55
+ - **`EqualizerResult` is a frozen dataclass**, like every other multi-value result. Assigning to a field raises; build a modified copy with `dataclasses.replace(result, field=value)`.
56
+ - **GPU failures raise.** A CUDA kernel that fails to compile or load, or a CUDA-graph capture that fails, used to log one warning and fall back to a slower path. It now raises, because it means a broken install or a bug.
57
+ - **`SingleCarrierFrame` validates its pilots on construction.** `pilot_pattern="block"` without `pilot_block_len` raises; it used to silently build a frame with no pilots. A pilot period that does not tile the body also raises at construction instead of on first use.
58
+ - **`moving_average(mode="valid")`** raises on a record shorter than the window, instead of returning NumPy's swapped convolution.
59
+ - **Numerical results move at rounding level** in a few places, from reordered or fused arithmetic. Seeds reproduce results within one version only. The largest moves:
60
+ - fiber propagation on the CPU: about 3e-6 relative;
61
+ - `lms` with inline carrier recovery: up to 5e-6 relative;
62
+ - `correct_iq_imbalance`: 5e-7 relative in complex64;
63
+ - `rde`: 2.5e-7 relative.
64
+
65
+ Everything else changed in this release is bit-identical or within float32 rounding of before.
66
+
67
+ ### Added
68
+
69
+ - **Single-precision `iir_filter` on both devices.**
70
+ - CPU: a Numba kernel that reads and writes the samples in their own dtype and keeps the sections and the state in double precision. Double input still matches SciPy bit for bit.
71
+ - GPU: a block-parallel CUDA kernel. Blocks are filtered in parallel and the state is carried across them in two levels, in double precision.
72
+ - There is no longer a double-precision copy of the record: complex64 is 0.32–0.41x of the previous GPU path.
73
+ - **Closed-form max-log LLRs** for uniform separable QAM, `compute_llr`'s default case. Each bit's LLR comes from the nearest level of each bit value, so an axis costs 1 + 2k metrics instead of sqrt(M). It works for any per-axis labeling, not only Gray, and gives the same values as before: bit for bit on the CPU, within 3e-7 on the GPU.
74
+ - GPU, every order: 0.87x at 16-QAM, 0.74x at 256-QAM.
75
+ - CPU, from 256-QAM (where it starts to pay): 0.65x at 256-QAM, 0.40x at 1024-QAM, 0.23x at 4096-QAM.
76
+ - Exact LLRs and shaped constellations keep the per-level computation.
77
+ - `TimingEstimate` is listed in `commkit.timing.__all__`, like the other estimate classes.
78
+ - `pad_mode="reflect"` is documented for the equalizers and `apply_taps`; they already accepted it.
79
+
80
+ ### Performance
81
+
82
+ Equalizers:
83
+
84
+ - **Block equalizers on the GPU:** each block's window load, training staging and output stores now run inside the replayed CUDA graph, so the host launches one graph per block. Baseline `0006` against `0005`:
85
+ - `block_lms` at `block_size=256`: 0.36x without carrier recovery, 0.78x with BPS, 0.65x with BPS and cycle slips;
86
+ - `block_cma`: 0.50x;
87
+ - `block_rde`: 0.66x.
88
+
89
+ At 256 symbols per block the GPU now runs `block_lms` about 2x faster than the CPU without carrier recovery, and 5x faster with BPS.
90
+ - **`block_lms` per block:** one phasor per block and one divergence check after the loop. The cycle-slip kernel binds its buffers once per run, cutting the per-block cost from 17 µs to 10–14 µs. On the GPU, cycle-slip correction runs for any channel count, where above 1024 channels it went through the host every block.
91
+ - **`lms` and `rls` with inline carrier recovery (CPU):** the tap and distance loops compile with fast math while the phase unwrap stays strict. RLS with BPS 0.63x, LMS with PLL 0.87x.
92
+ - **`rde` and `block_rde`:** the ring is decided by squared midpoints, with no square root. On the GPU this is one fused kernel: 112 µs → 10 µs per 2 × 1024-symbol block.
93
+
94
+ Carrier and frequency recovery:
95
+
96
+ - **BPS of square QAM on the CPU:** one Numba pass instead of a chunked NumPy loop: 0.39x.
97
+ - **PLL:** reads the samples in their own precision, with no complex128 copy of the record: 0.95–0.97x.
98
+ - **Blockwise `MengaliMorelli`:** one Numba call for all blocks instead of one per block. CPU 0.85x, GPU 0.62x.
99
+ - **Blockwise `correct_frequency_offset` on the GPU:** builds its phase trajectory on the GPU, where it used to make a round trip through the host.
100
+
101
+ Mapping and LLRs:
102
+
103
+ - **Exact LLRs on the GPU:** one exponential per point instead of one per point and bit. With the third pass over the points also gone, 128-cross QAM is 0.79x of baseline `0005`.
104
+ - **Square QAM on the GPU:** demapped per axis, 2·sqrt(M) metrics instead of M.
105
+ - **Hard decisions of PSK and cross QAM on the GPU:** one kernel launch instead of a Python loop over chunks.
106
+ - **`Constellation.map`:** packs the bits in one pass. On a 4-core machine: 16-QAM 33 → 5.8 ms, 256-QAM 55 → 10.5 ms, 2^20 symbols.
107
+ - **`Constellation.rotational_symmetry`:** 1024-QAM in 0.026 s instead of 2.2 s.
108
+
109
+ Other functions:
110
+
111
+ - **`correct_iq_imbalance`:** all channels at once from closed-form 2×2 statistics, CPU about 0.45x.
112
+ - **`evm` and `snr`:** measure the reference power once: 0.74–0.78x.
113
+ - **`moving_average(mode="valid")`:** one pass over all channels. CPU 0.11–0.15x, GPU 0.21–0.71x.
114
+ - **`estimate_timing` with several templates:** transforms the signal once: CPU 0.62–0.72x.
115
+ - **`plot_eye_diagram` histograms:** CPU 0.33x, GPU 0.55x.
116
+ - **Fiber propagation on the CPU:** NumPy stages replace the Numba passes, which competed with the multithreaded FFTs (0.56–0.92x for 2^14 to 2^20 samples). On the GPU, the plain CuPy stages that complex128 input runs are 0.53x for DBP.
117
+ - **Fewer GPU copies:** frequency grids, windows and the polarization demultiplexer's grid are built on the GPU instead of being copied there on every call.
118
+
119
+ ### Fixed
120
+
121
+ - `plot_eye_diagram` ignored `vmin` and `vmax`.
122
+ - BPS on the GPU failed with `CUDA_ERROR_INVALID_VALUE` for more than 65535 channels.
123
+ - Single-precision input was computed in double precision in BPS distances, `block_rde`'s ring distances, the block equalizers' forward filter and `iir_filter`, against the library's precision contract.
124
+
125
+ ### Development
126
+
127
+ - **Test plugins:**
128
+ - `tests/plugins/transfer_trace.py` audits host-device copies in a GPU run.
129
+ - `tests/plugins/precision_trace.py` now follows single-precision data through the FFTs.
130
+ - **CI:** the JIT-disabled kernel-coverage job measures the equalizers' Numba kernels again, and strict mypy covers every module of the `filtering` package.
131
+ - **Benchmarks:** new benchmarks for the IIR filter, moving average, PLL, blockwise Mengali-Morelli and IQ correction. Baseline `0006` is the new reference.
132
+ - **Code organization:** `filtering` and `analysis.linewidth` are packages, and every public name imports from the same place as before. The sequential equalizers share their Numba steps, CMA and RDE share one body, and duplicated helpers are merged.
133
+ - **Docs:** docstrings follow one style throughout. AGENTS.md documents the CUDA review rules and how to run `compute-sanitizer`.
134
+
135
+ ## [2.2.0] - 2026-10-23
136
+
137
+ See the [GitHub release](https://github.com/lokgar/commkit/releases/tag/v2.2.0) and the [changes since 2.1.0](https://github.com/lokgar/commkit/compare/v2.1.0...v2.2.0).
138
+
139
+ ## [2.1.0] - 2026-10-21
140
+
141
+ See the [GitHub release](https://github.com/lokgar/commkit/releases/tag/v2.1.0) and the [changes since 2.0.0](https://github.com/lokgar/commkit/compare/v2.0.0...v2.1.0).
142
+
143
+ ## [2.0.0] - 2026-10-07
144
+
145
+ See the [GitHub release](https://github.com/lokgar/commkit/releases/tag/v2.0.0) and the [changes since 1.1.0](https://github.com/lokgar/commkit/compare/v1.1.0...v2.0.0).
146
+
147
+ ## [1.1.0] - 2026-09-03
148
+
149
+ See the [GitHub release](https://github.com/lokgar/commkit/releases/tag/v1.1.0) and the [changes since 1.0.0](https://github.com/lokgar/commkit/compare/v1.0.0...v1.1.0).
150
+
151
+ ## [1.0.0] - 2026-08-31
152
+
153
+ First release. See the [GitHub release](https://github.com/lokgar/commkit/releases/tag/v1.0.0).
154
+
155
+ [Unreleased]: https://github.com/lokgar/commkit/compare/v3.0.0...HEAD
156
+ [3.0.0]: https://github.com/lokgar/commkit/compare/v2.3.0...v3.0.0
157
+ [2.3.0]: https://github.com/lokgar/commkit/compare/v2.2.0...v2.3.0
158
+ [2.2.0]: https://github.com/lokgar/commkit/compare/v2.1.0...v2.2.0
159
+ [2.1.0]: https://github.com/lokgar/commkit/compare/v2.0.0...v2.1.0
160
+ [2.0.0]: https://github.com/lokgar/commkit/compare/v1.1.0...v2.0.0
161
+ [1.1.0]: https://github.com/lokgar/commkit/compare/v1.0.0...v1.1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: commkit
3
- Version: 2.3.0
3
+ Version: 3.0.0
4
4
  Summary: A Python library for high-performance digital communications research.
5
5
  Project-URL: Homepage, https://github.com/lokgar/commkit
6
6
  Project-URL: Repository, https://github.com/lokgar/commkit
@@ -52,7 +52,7 @@ CommKit covers the receiver chain of coherent and IM/DD links (waveform generati
52
52
  Data is a NumPy or CuPy array, or a `Signal` that wraps the samples together with what is known about them. Algorithms are plain functions that take the data and return a new `Signal` or array, or a typed result such as `EqualizerResult` or an `*Estimate`. Plotting functions draw those results; they never compute them.
53
53
 
54
54
  - **Things that describe** a modulation, a pulse or a sub-algorithm are small frozen objects written in the call: `lms(rx, cpr=BPS(test_phases=64))`. The same object works standalone: `correct_carrier_phase(y, BPS())`.
55
- - **A `Signal` carries facts and ground truth.** Facts are `sampling_rate`, `symbol_rate` and `center_frequency`. The description is `constellation` and `pulse`. The ground truth is `reference` (transmitted symbols and bits). Functions take facts from the Signal and raise on a conflicting argument. Choices such as the decision constellation default to the Signal's and can be overridden.
55
+ - **A `Signal` carries facts and ground truth.** Facts are `sampling_rate`, `symbol_rate` and `center_frequency`. The description is `constellation` and `pulse`. The ground truth is `reference`: the transmitted symbols, the bits on the air and the source bits before coding or distribution matching (the same array when there is neither). Functions take facts from the Signal and raise on a conflicting argument. Choices such as the decision constellation default to the Signal's and can be overridden.
56
56
  - **Results with several values are frozen dataclasses** with named fields. Metrics return host floats: one per channel for `(C, N)` input.
57
57
  - **The device follows the data.** Processing functions have no `backend=` or `device=` argument. Only factories, which have no input data to follow, take `device=`: `generate(..., device="gpu")`. Move existing data with `sig.to("gpu")`.
58
58
  - **Plots draw results.** `plot_*` functions take a Signal, an array or a result object; numerical code never imports plotting.
@@ -114,7 +114,7 @@ CommKit never picks a device for you: data stays where you put it, and each func
114
114
 
115
115
  | Module | Contents |
116
116
  | --- | --- |
117
- | [`commkit.core`](https://github.com/lokgar/commkit/tree/main/commkit/core) | `Signal`, `Reference`, `generate`, `SingleCarrierFrame` and `Preamble` (pilots, guard intervals, MIMO streams), `extract_payload`. |
117
+ | [`commkit.core`](https://github.com/lokgar/commkit/tree/main/commkit/core) | `Signal`, `Reference`, `generate` (uniform, shaped through a distribution matcher, or coded with `code=`), `SingleCarrierFrame` and `Preamble` (pilots, guard intervals, MIMO streams), `extract_payload`. |
118
118
  | [`commkit.mapping`](https://github.com/lokgar/commkit/tree/main/commkit/mapping) | `Constellation` (QAM, PSK, PAM, arbitrary points; Gray labels; probabilistic shaping with `.shaped()`), bit mapping, hard demapping, max-log and exact LLRs. |
119
119
  | [`commkit.filtering`](https://github.com/lokgar/commkit/tree/main/commkit/filtering) | Pulses (`RRC`, `RC`, `Gaussian`, `Rect`, `SmoothRect`), FIR and IIR design (Butterworth, Chebyshev I/II, elliptic, Bessel), `fir_filter`, `iir_filter`, `matched_filter`, overlap-save, chromatic-dispersion compensation and digital backpropagation (`correct_fiber_propagation`). |
120
120
  | [`commkit.multirate`](https://github.com/lokgar/commkit/blob/main/commkit/multirate.py) | `resample` (fractional), `decimate`, `upsample`, `decimate_to_symbol_rate`. |
@@ -124,13 +124,13 @@ CommKit never picks a device for you: data stays where you put it, and each func
124
124
  | [`commkit.frequency`](https://github.com/lokgar/commkit/blob/main/commkit/frequency.py) | `estimate_frequency_offset` / `correct_frequency_offset` with `MthPower`, `MengaliMorelli`, `PilotSymbols` and `BiasTone`; static and blockwise. |
125
125
  | [`commkit.recovery`](https://github.com/lokgar/commkit/tree/main/commkit/recovery) | `estimate_carrier_phase` / `correct_carrier_phase` with `ViterbiViterbi`, `BPS`, `PLL`, `Tikhonov`, `DataAided`, `PilotAided`, `PilotTone(s)`; cycle-slip correction; `resolve_phase_ambiguity` and `resolve_channel_permutation`. |
126
126
  | [`commkit.equalization`](https://github.com/lokgar/commkit/tree/main/commkit/equalization) | Sequential `lms`, `rls`, `cma`, `rde` (Numba) and frequency-domain `block_lms`, `block_cma`, `block_rde` (CuPy, CUDA graphs), butterfly MIMO, inline carrier recovery (`cpr=PLL()` / `BPS()`), continuation with `state=`, `zf_equalizer`, polarization-tone demultiplexing. |
127
- | [`commkit.metrics`](https://github.com/lokgar/commkit/blob/main/commkit/metrics.py) | `evm`, `snr`, `ber`, `ser`, `gmi`, `mi`, including shaped constellations; host values per channel. |
127
+ | [`commkit.metrics`](https://github.com/lokgar/commkit/blob/main/commkit/metrics.py) | `evm`, `snr`, `ber`, `fer`, `ser`, `gmi`, `ngmi`, `mi`, including shaped constellations; pre-FEC and post-FEC; host values per channel. |
128
128
  | [`commkit.analysis`](https://github.com/lokgar/commkit/tree/main/commkit/analysis) | Laser and carrier-phase characterization: `estimate_linewidth` (increment slope, β-separation, delayed self-heterodyne FM-PSD / increment / Lorentzian), FM-noise PSDs, drift separation, Allan deviation. |
129
129
  | [`commkit.math`](https://github.com/lokgar/commkit/blob/main/commkit/math.py) | `rms`, `normalize`, dB conversions. |
130
130
  | [`commkit.smoothing`](https://github.com/lokgar/commkit/blob/main/commkit/smoothing.py) | Display and estimation smoothers (moving average, Savitzky-Golay, 2-D density). Signal-chain filters are in `filtering`. |
131
131
  | [`commkit.io`](https://github.com/lokgar/commkit/blob/main/commkit/io.py) | `save_npz` / `load_npz` for Signals. |
132
132
  | [`commkit.plotting`](https://github.com/lokgar/commkit/tree/main/commkit/plotting) | Constellations, eye diagrams, spectra, filter responses, equalizer convergence, and synchronization and laser diagnostics that draw the estimates. Imported on first use. |
133
- | [`commkit.coding`](https://github.com/lokgar/commkit/tree/main/commkit/coding) | Channel coding and FEC: **planned, not implemented**. |
133
+ | [`commkit.coding`](https://github.com/lokgar/commkit/tree/main/commkit/coding) | Channel coding: `encode` / `decode` dispatched on a code object, `LinearBlockCode` (Hamming, extended and shortened), syndrome and maximum-likelihood decoding; `LDPCCode` (DVB-S2, the 802.3ca / 50G-PON code, any parity-check or quasi-cyclic matrix) with layered min-sum and sum-product decoding on the CPU (Numba) and GPU (CUDA); `ReedSolomonCode` (G.709, KR4, KP4, 10G-EPON, XGS-PON) and `BCHCode` with Berlekamp-Massey decoding; Chase-Pyndiah soft decoding; the G.709.2 `StaircaseCode` and `Concatenated` codes (400ZR C-FEC with its exact Hamming(128,119), 802.3dj); `PolarCode` (5G NR information set) with SC and CRC-aided SCL decoding; `ConvolutionalCode` (K = 7, terminated or tail-biting) with Viterbi and BCJR, `TurboCode` (LTE); `CRC` (NR, LTE, Ethernet), `Interleaver`, `Punctured` and `Shortened`; distribution matching (`CCDM`) and probabilistic amplitude shaping (`PAS`). Coded payloads through `generate(..., code=)`. |
134
134
 
135
135
  Importing `commkit` has no side effects: it configures no logging, Matplotlib or warning filters, and does not touch the GPU.
136
136
 
@@ -160,15 +160,15 @@ With [`uv`](https://github.com/astral-sh/uv), use `uv pip install` in place of `
160
160
 
161
161
  ## Examples
162
162
 
163
- [`examples/`](https://github.com/lokgar/commkit/tree/main/examples) is a course in 26 Jupyter notebooks, one topic each, in teaching order (install the `notebook` extra and run `jupyter lab examples`):
163
+ [`examples/`](https://github.com/lokgar/commkit/tree/main/examples) is a course in 29 Jupyter notebooks, one topic each, in teaching order (install the `notebook` extra and run `jupyter lab examples`):
164
164
 
165
165
  - **00-05, signals and DSP**: the `Signal`, constellations and bit mapping, spectra, filters, pulse shaping and matched filtering, resampling;
166
- - **06-08, channel and performance**: AWGN and the metrics (SNR, BER, LLRs, GMI), laser phase noise, front-end impairments;
167
- - **09-16, receiver algorithms**: frame synchronization, frequency-offset estimation, blind and pilot-aided carrier recovery, linear, adaptive and blind equalization, polarization demultiplexing;
168
- - **17-19, optical fiber systems**: chromatic dispersion, the nonlinear fiber with digital backpropagation, and an end-to-end coherent transceiver;
169
- - **20-25, characterization**: carrier phase and laser linewidth measurements, with templates for real captures.
166
+ - **06-10, channel, coding and performance**: AWGN and the metrics (SNR, BER, LLRs, GMI), channel coding, probabilistic shaping and coded modulation, laser phase noise, front-end impairments;
167
+ - **11-18, receiver algorithms**: frame synchronization, frequency-offset estimation, blind and pilot-aided carrier recovery, linear, adaptive and blind equalization, polarization demultiplexing;
168
+ - **19-22, optical fiber systems**: chromatic dispersion, the nonlinear fiber with digital backpropagation, FEC in optical standards, and an end-to-end coherent transceiver;
169
+ - **23-28, characterization**: carrier phase and laser linewidth measurements, with templates for real captures.
170
170
 
171
- [`examples/README.md`](https://github.com/lokgar/commkit/blob/main/examples/README.md) lists every notebook with its topics. The quickstart above appears cell by cell in `00-getting-started` and `14-adaptive-equalization`. The notebooks are committed without outputs and run in CI.
171
+ [`examples/README.md`](https://github.com/lokgar/commkit/blob/main/examples/README.md) lists every notebook with its topics. The quickstart above appears cell by cell in `00-getting-started` and `16-adaptive-equalization`. The notebooks are committed without outputs and run in CI.
172
172
 
173
173
  ## Development
174
174
 
@@ -16,7 +16,7 @@ CommKit covers the receiver chain of coherent and IM/DD links (waveform generati
16
16
  Data is a NumPy or CuPy array, or a `Signal` that wraps the samples together with what is known about them. Algorithms are plain functions that take the data and return a new `Signal` or array, or a typed result such as `EqualizerResult` or an `*Estimate`. Plotting functions draw those results; they never compute them.
17
17
 
18
18
  - **Things that describe** a modulation, a pulse or a sub-algorithm are small frozen objects written in the call: `lms(rx, cpr=BPS(test_phases=64))`. The same object works standalone: `correct_carrier_phase(y, BPS())`.
19
- - **A `Signal` carries facts and ground truth.** Facts are `sampling_rate`, `symbol_rate` and `center_frequency`. The description is `constellation` and `pulse`. The ground truth is `reference` (transmitted symbols and bits). Functions take facts from the Signal and raise on a conflicting argument. Choices such as the decision constellation default to the Signal's and can be overridden.
19
+ - **A `Signal` carries facts and ground truth.** Facts are `sampling_rate`, `symbol_rate` and `center_frequency`. The description is `constellation` and `pulse`. The ground truth is `reference`: the transmitted symbols, the bits on the air and the source bits before coding or distribution matching (the same array when there is neither). Functions take facts from the Signal and raise on a conflicting argument. Choices such as the decision constellation default to the Signal's and can be overridden.
20
20
  - **Results with several values are frozen dataclasses** with named fields. Metrics return host floats: one per channel for `(C, N)` input.
21
21
  - **The device follows the data.** Processing functions have no `backend=` or `device=` argument. Only factories, which have no input data to follow, take `device=`: `generate(..., device="gpu")`. Move existing data with `sig.to("gpu")`.
22
22
  - **Plots draw results.** `plot_*` functions take a Signal, an array or a result object; numerical code never imports plotting.
@@ -78,7 +78,7 @@ CommKit never picks a device for you: data stays where you put it, and each func
78
78
 
79
79
  | Module | Contents |
80
80
  | --- | --- |
81
- | [`commkit.core`](https://github.com/lokgar/commkit/tree/main/commkit/core) | `Signal`, `Reference`, `generate`, `SingleCarrierFrame` and `Preamble` (pilots, guard intervals, MIMO streams), `extract_payload`. |
81
+ | [`commkit.core`](https://github.com/lokgar/commkit/tree/main/commkit/core) | `Signal`, `Reference`, `generate` (uniform, shaped through a distribution matcher, or coded with `code=`), `SingleCarrierFrame` and `Preamble` (pilots, guard intervals, MIMO streams), `extract_payload`. |
82
82
  | [`commkit.mapping`](https://github.com/lokgar/commkit/tree/main/commkit/mapping) | `Constellation` (QAM, PSK, PAM, arbitrary points; Gray labels; probabilistic shaping with `.shaped()`), bit mapping, hard demapping, max-log and exact LLRs. |
83
83
  | [`commkit.filtering`](https://github.com/lokgar/commkit/tree/main/commkit/filtering) | Pulses (`RRC`, `RC`, `Gaussian`, `Rect`, `SmoothRect`), FIR and IIR design (Butterworth, Chebyshev I/II, elliptic, Bessel), `fir_filter`, `iir_filter`, `matched_filter`, overlap-save, chromatic-dispersion compensation and digital backpropagation (`correct_fiber_propagation`). |
84
84
  | [`commkit.multirate`](https://github.com/lokgar/commkit/blob/main/commkit/multirate.py) | `resample` (fractional), `decimate`, `upsample`, `decimate_to_symbol_rate`. |
@@ -88,13 +88,13 @@ CommKit never picks a device for you: data stays where you put it, and each func
88
88
  | [`commkit.frequency`](https://github.com/lokgar/commkit/blob/main/commkit/frequency.py) | `estimate_frequency_offset` / `correct_frequency_offset` with `MthPower`, `MengaliMorelli`, `PilotSymbols` and `BiasTone`; static and blockwise. |
89
89
  | [`commkit.recovery`](https://github.com/lokgar/commkit/tree/main/commkit/recovery) | `estimate_carrier_phase` / `correct_carrier_phase` with `ViterbiViterbi`, `BPS`, `PLL`, `Tikhonov`, `DataAided`, `PilotAided`, `PilotTone(s)`; cycle-slip correction; `resolve_phase_ambiguity` and `resolve_channel_permutation`. |
90
90
  | [`commkit.equalization`](https://github.com/lokgar/commkit/tree/main/commkit/equalization) | Sequential `lms`, `rls`, `cma`, `rde` (Numba) and frequency-domain `block_lms`, `block_cma`, `block_rde` (CuPy, CUDA graphs), butterfly MIMO, inline carrier recovery (`cpr=PLL()` / `BPS()`), continuation with `state=`, `zf_equalizer`, polarization-tone demultiplexing. |
91
- | [`commkit.metrics`](https://github.com/lokgar/commkit/blob/main/commkit/metrics.py) | `evm`, `snr`, `ber`, `ser`, `gmi`, `mi`, including shaped constellations; host values per channel. |
91
+ | [`commkit.metrics`](https://github.com/lokgar/commkit/blob/main/commkit/metrics.py) | `evm`, `snr`, `ber`, `fer`, `ser`, `gmi`, `ngmi`, `mi`, including shaped constellations; pre-FEC and post-FEC; host values per channel. |
92
92
  | [`commkit.analysis`](https://github.com/lokgar/commkit/tree/main/commkit/analysis) | Laser and carrier-phase characterization: `estimate_linewidth` (increment slope, β-separation, delayed self-heterodyne FM-PSD / increment / Lorentzian), FM-noise PSDs, drift separation, Allan deviation. |
93
93
  | [`commkit.math`](https://github.com/lokgar/commkit/blob/main/commkit/math.py) | `rms`, `normalize`, dB conversions. |
94
94
  | [`commkit.smoothing`](https://github.com/lokgar/commkit/blob/main/commkit/smoothing.py) | Display and estimation smoothers (moving average, Savitzky-Golay, 2-D density). Signal-chain filters are in `filtering`. |
95
95
  | [`commkit.io`](https://github.com/lokgar/commkit/blob/main/commkit/io.py) | `save_npz` / `load_npz` for Signals. |
96
96
  | [`commkit.plotting`](https://github.com/lokgar/commkit/tree/main/commkit/plotting) | Constellations, eye diagrams, spectra, filter responses, equalizer convergence, and synchronization and laser diagnostics that draw the estimates. Imported on first use. |
97
- | [`commkit.coding`](https://github.com/lokgar/commkit/tree/main/commkit/coding) | Channel coding and FEC: **planned, not implemented**. |
97
+ | [`commkit.coding`](https://github.com/lokgar/commkit/tree/main/commkit/coding) | Channel coding: `encode` / `decode` dispatched on a code object, `LinearBlockCode` (Hamming, extended and shortened), syndrome and maximum-likelihood decoding; `LDPCCode` (DVB-S2, the 802.3ca / 50G-PON code, any parity-check or quasi-cyclic matrix) with layered min-sum and sum-product decoding on the CPU (Numba) and GPU (CUDA); `ReedSolomonCode` (G.709, KR4, KP4, 10G-EPON, XGS-PON) and `BCHCode` with Berlekamp-Massey decoding; Chase-Pyndiah soft decoding; the G.709.2 `StaircaseCode` and `Concatenated` codes (400ZR C-FEC with its exact Hamming(128,119), 802.3dj); `PolarCode` (5G NR information set) with SC and CRC-aided SCL decoding; `ConvolutionalCode` (K = 7, terminated or tail-biting) with Viterbi and BCJR, `TurboCode` (LTE); `CRC` (NR, LTE, Ethernet), `Interleaver`, `Punctured` and `Shortened`; distribution matching (`CCDM`) and probabilistic amplitude shaping (`PAS`). Coded payloads through `generate(..., code=)`. |
98
98
 
99
99
  Importing `commkit` has no side effects: it configures no logging, Matplotlib or warning filters, and does not touch the GPU.
100
100
 
@@ -124,15 +124,15 @@ With [`uv`](https://github.com/astral-sh/uv), use `uv pip install` in place of `
124
124
 
125
125
  ## Examples
126
126
 
127
- [`examples/`](https://github.com/lokgar/commkit/tree/main/examples) is a course in 26 Jupyter notebooks, one topic each, in teaching order (install the `notebook` extra and run `jupyter lab examples`):
127
+ [`examples/`](https://github.com/lokgar/commkit/tree/main/examples) is a course in 29 Jupyter notebooks, one topic each, in teaching order (install the `notebook` extra and run `jupyter lab examples`):
128
128
 
129
129
  - **00-05, signals and DSP**: the `Signal`, constellations and bit mapping, spectra, filters, pulse shaping and matched filtering, resampling;
130
- - **06-08, channel and performance**: AWGN and the metrics (SNR, BER, LLRs, GMI), laser phase noise, front-end impairments;
131
- - **09-16, receiver algorithms**: frame synchronization, frequency-offset estimation, blind and pilot-aided carrier recovery, linear, adaptive and blind equalization, polarization demultiplexing;
132
- - **17-19, optical fiber systems**: chromatic dispersion, the nonlinear fiber with digital backpropagation, and an end-to-end coherent transceiver;
133
- - **20-25, characterization**: carrier phase and laser linewidth measurements, with templates for real captures.
130
+ - **06-10, channel, coding and performance**: AWGN and the metrics (SNR, BER, LLRs, GMI), channel coding, probabilistic shaping and coded modulation, laser phase noise, front-end impairments;
131
+ - **11-18, receiver algorithms**: frame synchronization, frequency-offset estimation, blind and pilot-aided carrier recovery, linear, adaptive and blind equalization, polarization demultiplexing;
132
+ - **19-22, optical fiber systems**: chromatic dispersion, the nonlinear fiber with digital backpropagation, FEC in optical standards, and an end-to-end coherent transceiver;
133
+ - **23-28, characterization**: carrier phase and laser linewidth measurements, with templates for real captures.
134
134
 
135
- [`examples/README.md`](https://github.com/lokgar/commkit/blob/main/examples/README.md) lists every notebook with its topics. The quickstart above appears cell by cell in `00-getting-started` and `14-adaptive-equalization`. The notebooks are committed without outputs and run in CI.
135
+ [`examples/README.md`](https://github.com/lokgar/commkit/blob/main/examples/README.md) lists every notebook with its topics. The quickstart above appears cell by cell in `00-getting-started` and `16-adaptive-equalization`. The notebooks are committed without outputs and run in CI.
136
136
 
137
137
  ## Development
138
138