commkit 3.0.0__tar.gz → 3.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 (283) hide show
  1. {commkit-3.0.0 → commkit-3.1.0}/.github/workflows/ci.yml +1 -1
  2. {commkit-3.0.0 → commkit-3.1.0}/AGENTS.md +18 -17
  3. {commkit-3.0.0 → commkit-3.1.0}/CHANGELOG.md +71 -1
  4. {commkit-3.0.0 → commkit-3.1.0}/PKG-INFO +3 -3
  5. commkit-3.1.0/benchmarks/baselines/Linux-CPython-3.14-64bit/0008_v3_1.json +6082 -0
  6. commkit-3.1.0/benchmarks/bench_coding.py +145 -0
  7. {commkit-3.0.0 → commkit-3.1.0}/benchmarks/bench_signal_pipeline.py +2 -2
  8. {commkit-3.0.0 → commkit-3.1.0}/benchmarks/workloads.py +1 -1
  9. {commkit-3.0.0 → commkit-3.1.0}/commkit/__init__.py +1 -1
  10. {commkit-3.0.0 → commkit-3.1.0}/commkit/_cuda/__init__.py +70 -0
  11. commkit-3.1.0/commkit/_cuda/src/algebraic.cu +148 -0
  12. {commkit-3.0.0 → commkit-3.1.0}/commkit/_cuda/src/ldpc.cu +4 -1
  13. commkit-3.1.0/commkit/_overlap_save.py +66 -0
  14. {commkit-3.0.0 → commkit-3.1.0}/commkit/analysis/_common.py +11 -7
  15. {commkit-3.0.0 → commkit-3.1.0}/commkit/coding/_gf2.py +15 -1
  16. {commkit-3.0.0 → commkit-3.1.0}/commkit/coding/_numba_loops.py +443 -134
  17. {commkit-3.0.0 → commkit-3.1.0}/commkit/coding/algebraic.py +90 -126
  18. {commkit-3.0.0 → commkit-3.1.0}/commkit/coding/base.py +22 -2
  19. {commkit-3.0.0 → commkit-3.1.0}/commkit/coding/convolutional.py +9 -2
  20. {commkit-3.0.0 → commkit-3.1.0}/commkit/coding/ldpc.py +49 -11
  21. {commkit-3.0.0 → commkit-3.1.0}/commkit/coding/linear.py +84 -34
  22. {commkit-3.0.0 → commkit-3.1.0}/commkit/coding/polar.py +11 -3
  23. {commkit-3.0.0 → commkit-3.1.0}/commkit/coding/product.py +55 -24
  24. {commkit-3.0.0 → commkit-3.1.0}/commkit/coding/shaping.py +20 -5
  25. {commkit-3.0.0 → commkit-3.1.0}/commkit/coding/turbo.py +6 -1
  26. {commkit-3.0.0 → commkit-3.1.0}/commkit/core/frame.py +249 -108
  27. {commkit-3.0.0 → commkit-3.1.0}/commkit/core/generation.py +119 -22
  28. {commkit-3.0.0 → commkit-3.1.0}/commkit/core/signal.py +120 -7
  29. {commkit-3.0.0 → commkit-3.1.0}/commkit/equalization/_block/_dd.py +2 -1
  30. {commkit-3.0.0 → commkit-3.1.0}/commkit/equalization/_common.py +11 -20
  31. {commkit-3.0.0 → commkit-3.1.0}/commkit/equalization/_numba_loops.py +12 -14
  32. {commkit-3.0.0 → commkit-3.1.0}/commkit/equalization/linear.py +20 -14
  33. {commkit-3.0.0 → commkit-3.1.0}/commkit/equalization/sequential/_setup.py +1 -1
  34. {commkit-3.0.0 → commkit-3.1.0}/commkit/filtering/linear.py +33 -25
  35. {commkit-3.0.0 → commkit-3.1.0}/commkit/frequency.py +13 -13
  36. {commkit-3.0.0 → commkit-3.1.0}/commkit/impairments/channel/linear.py +2 -4
  37. {commkit-3.0.0 → commkit-3.1.0}/commkit/io.py +136 -65
  38. {commkit-3.0.0 → commkit-3.1.0}/commkit/mapping/_numba_llr.py +5 -2
  39. {commkit-3.0.0 → commkit-3.1.0}/commkit/mapping/constellation.py +35 -10
  40. {commkit-3.0.0 → commkit-3.1.0}/commkit/mapping/gray.py +13 -7
  41. {commkit-3.0.0 → commkit-3.1.0}/commkit/mapping/llr.py +67 -31
  42. {commkit-3.0.0 → commkit-3.1.0}/commkit/metrics.py +21 -12
  43. {commkit-3.0.0 → commkit-3.1.0}/commkit/plotting/equalizer.py +25 -17
  44. {commkit-3.0.0 → commkit-3.1.0}/commkit/plotting/spectral.py +46 -24
  45. {commkit-3.0.0 → commkit-3.1.0}/commkit/plotting/theme.py +17 -14
  46. {commkit-3.0.0 → commkit-3.1.0}/commkit/timing.py +25 -7
  47. {commkit-3.0.0 → commkit-3.1.0}/examples/00-getting-started.ipynb +1 -1
  48. {commkit-3.0.0 → commkit-3.1.0}/examples/06-awgn-and-performance-metrics.ipynb +2 -2
  49. {commkit-3.0.0 → commkit-3.1.0}/examples/07-channel-coding.ipynb +115 -18
  50. {commkit-3.0.0 → commkit-3.1.0}/examples/08-probabilistic-shaping-and-coded-modulation.ipynb +4 -6
  51. {commkit-3.0.0 → commkit-3.1.0}/examples/11-frame-synchronization-and-timing.ipynb +8 -6
  52. {commkit-3.0.0 → commkit-3.1.0}/examples/21-fec-in-optical-standards.ipynb +2 -3
  53. {commkit-3.0.0 → commkit-3.1.0}/examples/22-coherent-transceiver-end-to-end.ipynb +7 -5
  54. {commkit-3.0.0 → commkit-3.1.0}/pyproject.toml +3 -3
  55. {commkit-3.0.0 → commkit-3.1.0}/tests/coding/test_algebraic.py +52 -0
  56. {commkit-3.0.0 → commkit-3.1.0}/tests/coding/test_coding_base.py +46 -0
  57. {commkit-3.0.0 → commkit-3.1.0}/tests/coding/test_convolutional.py +20 -0
  58. {commkit-3.0.0 → commkit-3.1.0}/tests/coding/test_ldpc.py +14 -3
  59. {commkit-3.0.0 → commkit-3.1.0}/tests/coding/test_product.py +11 -0
  60. {commkit-3.0.0 → commkit-3.1.0}/tests/coding/test_shaping.py +29 -0
  61. {commkit-3.0.0 → commkit-3.1.0}/tests/coding/test_turbo.py +5 -0
  62. commkit-3.1.0/tests/common/memory.py +20 -0
  63. {commkit-3.0.0 → commkit-3.1.0}/tests/common/reference_impl.py +109 -0
  64. {commkit-3.0.0 → commkit-3.1.0}/tests/common/signals.py +5 -5
  65. {commkit-3.0.0 → commkit-3.1.0}/tests/core/test_frame.py +243 -90
  66. {commkit-3.0.0 → commkit-3.1.0}/tests/core/test_generation.py +58 -2
  67. {commkit-3.0.0 → commkit-3.1.0}/tests/core/test_signal.py +73 -1
  68. {commkit-3.0.0 → commkit-3.1.0}/tests/core/test_signal_mimo.py +8 -6
  69. {commkit-3.0.0 → commkit-3.1.0}/tests/equalization/test_linear.py +29 -0
  70. {commkit-3.0.0 → commkit-3.1.0}/tests/equalization/test_sequential.py +2 -2
  71. {commkit-3.0.0 → commkit-3.1.0}/tests/mapping/test_constellation.py +31 -1
  72. {commkit-3.0.0 → commkit-3.1.0}/tests/mapping/test_gray.py +17 -0
  73. {commkit-3.0.0 → commkit-3.1.0}/tests/plotting/test_plot_equalizer.py +27 -0
  74. {commkit-3.0.0 → commkit-3.1.0}/tests/plotting/test_plot_spectral.py +21 -0
  75. {commkit-3.0.0 → commkit-3.1.0}/tests/plotting/test_plot_sync.py +4 -2
  76. {commkit-3.0.0 → commkit-3.1.0}/tests/plugins/precision_trace.py +48 -8
  77. {commkit-3.0.0 → commkit-3.1.0}/tests/plugins/transfer_trace.py +1 -1
  78. {commkit-3.0.0 → commkit-3.1.0}/tests/test_filtering.py +56 -0
  79. {commkit-3.0.0 → commkit-3.1.0}/tests/test_io.py +247 -11
  80. {commkit-3.0.0 → commkit-3.1.0}/tests/test_metrics.py +19 -1
  81. {commkit-3.0.0 → commkit-3.1.0}/tests/test_signal_pipeline.py +7 -5
  82. {commkit-3.0.0 → commkit-3.1.0}/tests/test_timing.py +34 -13
  83. commkit-3.1.0/tests/test_working_memory.py +65 -0
  84. {commkit-3.0.0 → commkit-3.1.0}/uv.lock +19 -14
  85. commkit-3.0.0/benchmarks/bench_coding.py +0 -64
  86. commkit-3.0.0/commkit/_overlap_save.py +0 -93
  87. {commkit-3.0.0 → commkit-3.1.0}/.gitattributes +0 -0
  88. {commkit-3.0.0 → commkit-3.1.0}/.github/workflows/publish.yml +0 -0
  89. {commkit-3.0.0 → commkit-3.1.0}/.gitignore +0 -0
  90. {commkit-3.0.0 → commkit-3.1.0}/.python-version +0 -0
  91. {commkit-3.0.0 → commkit-3.1.0}/LICENSE +0 -0
  92. {commkit-3.0.0 → commkit-3.1.0}/README.md +0 -0
  93. {commkit-3.0.0 → commkit-3.1.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0002_pre_v2.json +0 -0
  94. {commkit-3.0.0 → commkit-3.1.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0003_v2_0.json +0 -0
  95. {commkit-3.0.0 → commkit-3.1.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0004_fiber_dbp.json +0 -0
  96. {commkit-3.0.0 → commkit-3.1.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0005_gpu_decoder_path.json +0 -0
  97. {commkit-3.0.0 → commkit-3.1.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0006_perf_cleanup.json +0 -0
  98. {commkit-3.0.0 → commkit-3.1.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0007_v3_0.json +0 -0
  99. {commkit-3.0.0 → commkit-3.1.0}/benchmarks/bench_block_blind.py +0 -0
  100. {commkit-3.0.0 → commkit-3.1.0}/benchmarks/bench_block_lms.py +0 -0
  101. {commkit-3.0.0 → commkit-3.1.0}/benchmarks/bench_bps.py +0 -0
  102. {commkit-3.0.0 → commkit-3.1.0}/benchmarks/bench_equalizers.py +0 -0
  103. {commkit-3.0.0 → commkit-3.1.0}/benchmarks/bench_fiber.py +0 -0
  104. {commkit-3.0.0 → commkit-3.1.0}/benchmarks/bench_filters.py +0 -0
  105. {commkit-3.0.0 → commkit-3.1.0}/benchmarks/bench_import.py +0 -0
  106. {commkit-3.0.0 → commkit-3.1.0}/benchmarks/bench_llr.py +0 -0
  107. {commkit-3.0.0 → commkit-3.1.0}/benchmarks/bench_mapping.py +0 -0
  108. {commkit-3.0.0 → commkit-3.1.0}/benchmarks/bench_sync_misc.py +0 -0
  109. {commkit-3.0.0 → commkit-3.1.0}/benchmarks/benchutils.py +0 -0
  110. {commkit-3.0.0 → commkit-3.1.0}/benchmarks/conftest.py +0 -0
  111. {commkit-3.0.0 → commkit-3.1.0}/benchmarks/record_baseline.py +0 -0
  112. {commkit-3.0.0 → commkit-3.1.0}/commkit/_array.py +0 -0
  113. {commkit-3.0.0 → commkit-3.1.0}/commkit/_circular.py +0 -0
  114. {commkit-3.0.0 → commkit-3.1.0}/commkit/_cuda/compiler.py +0 -0
  115. {commkit-3.0.0 → commkit-3.1.0}/commkit/_cuda/graphs.py +0 -0
  116. {commkit-3.0.0 → commkit-3.1.0}/commkit/_cuda/src/bps_anchor.cu +0 -0
  117. {commkit-3.0.0 → commkit-3.1.0}/commkit/_cuda/src/bps_min_d2.cu +0 -0
  118. {commkit-3.0.0 → commkit-3.1.0}/commkit/_cuda/src/cs_block.cu +0 -0
  119. {commkit-3.0.0 → commkit-3.1.0}/commkit/_cuda/src/iir.cu +0 -0
  120. {commkit-3.0.0 → commkit-3.1.0}/commkit/_cuda/src/llr.cu +0 -0
  121. {commkit-3.0.0 → commkit-3.1.0}/commkit/_cuda/src/selftest.cu +0 -0
  122. {commkit-3.0.0 → commkit-3.1.0}/commkit/_dispersion.py +0 -0
  123. {commkit-3.0.0 → commkit-3.1.0}/commkit/_fft.py +0 -0
  124. {commkit-3.0.0 → commkit-3.1.0}/commkit/_fiber.py +0 -0
  125. {commkit-3.0.0 → commkit-3.1.0}/commkit/_random.py +0 -0
  126. {commkit-3.0.0 → commkit-3.1.0}/commkit/_sequences.py +0 -0
  127. {commkit-3.0.0 → commkit-3.1.0}/commkit/analysis/__init__.py +0 -0
  128. {commkit-3.0.0 → commkit-3.1.0}/commkit/analysis/allan.py +0 -0
  129. {commkit-3.0.0 → commkit-3.1.0}/commkit/analysis/drift.py +0 -0
  130. {commkit-3.0.0 → commkit-3.1.0}/commkit/analysis/fm_noise.py +0 -0
  131. {commkit-3.0.0 → commkit-3.1.0}/commkit/analysis/interferometry.py +0 -0
  132. {commkit-3.0.0 → commkit-3.1.0}/commkit/analysis/linewidth/__init__.py +0 -0
  133. {commkit-3.0.0 → commkit-3.1.0}/commkit/analysis/linewidth/dsh.py +0 -0
  134. {commkit-3.0.0 → commkit-3.1.0}/commkit/analysis/linewidth/phase.py +0 -0
  135. {commkit-3.0.0 → commkit-3.1.0}/commkit/analysis/linewidth/result.py +0 -0
  136. {commkit-3.0.0 → commkit-3.1.0}/commkit/analysis/trajectory.py +0 -0
  137. {commkit-3.0.0 → commkit-3.1.0}/commkit/backend.py +0 -0
  138. {commkit-3.0.0 → commkit-3.1.0}/commkit/coding/__init__.py +0 -0
  139. {commkit-3.0.0 → commkit-3.1.0}/commkit/coding/_galois.py +0 -0
  140. {commkit-3.0.0 → commkit-3.1.0}/commkit/coding/_ldpc_tables.py +0 -0
  141. {commkit-3.0.0 → commkit-3.1.0}/commkit/coding/crc.py +0 -0
  142. {commkit-3.0.0 → commkit-3.1.0}/commkit/coding/interleaving.py +0 -0
  143. {commkit-3.0.0 → commkit-3.1.0}/commkit/coding/ratematch.py +0 -0
  144. {commkit-3.0.0 → commkit-3.1.0}/commkit/core/__init__.py +0 -0
  145. {commkit-3.0.0 → commkit-3.1.0}/commkit/core/_signal_adapter.py +0 -0
  146. {commkit-3.0.0 → commkit-3.1.0}/commkit/equalization/__init__.py +0 -0
  147. {commkit-3.0.0 → commkit-3.1.0}/commkit/equalization/_block/__init__.py +0 -0
  148. {commkit-3.0.0 → commkit-3.1.0}/commkit/equalization/_block/_blind.py +0 -0
  149. {commkit-3.0.0 → commkit-3.1.0}/commkit/equalization/_block/_engine.py +0 -0
  150. {commkit-3.0.0 → commkit-3.1.0}/commkit/equalization/_kernels_numba.py +0 -0
  151. {commkit-3.0.0 → commkit-3.1.0}/commkit/equalization/blind.py +0 -0
  152. {commkit-3.0.0 → commkit-3.1.0}/commkit/equalization/polarization.py +0 -0
  153. {commkit-3.0.0 → commkit-3.1.0}/commkit/equalization/result.py +0 -0
  154. {commkit-3.0.0 → commkit-3.1.0}/commkit/equalization/sequential/__init__.py +0 -0
  155. {commkit-3.0.0 → commkit-3.1.0}/commkit/equalization/sequential/_blind.py +0 -0
  156. {commkit-3.0.0 → commkit-3.1.0}/commkit/equalization/sequential/_dd.py +0 -0
  157. {commkit-3.0.0 → commkit-3.1.0}/commkit/filtering/__init__.py +0 -0
  158. {commkit-3.0.0 → commkit-3.1.0}/commkit/filtering/fiber.py +0 -0
  159. {commkit-3.0.0 → commkit-3.1.0}/commkit/filtering/pulses.py +0 -0
  160. {commkit-3.0.0 → commkit-3.1.0}/commkit/impairments/__init__.py +0 -0
  161. {commkit-3.0.0 → commkit-3.1.0}/commkit/impairments/channel/__init__.py +0 -0
  162. {commkit-3.0.0 → commkit-3.1.0}/commkit/impairments/channel/nonlinear.py +0 -0
  163. {commkit-3.0.0 → commkit-3.1.0}/commkit/impairments/frontend.py +0 -0
  164. {commkit-3.0.0 → commkit-3.1.0}/commkit/impairments/noise.py +0 -0
  165. {commkit-3.0.0 → commkit-3.1.0}/commkit/impairments/source.py +0 -0
  166. {commkit-3.0.0 → commkit-3.1.0}/commkit/logger.py +0 -0
  167. {commkit-3.0.0 → commkit-3.1.0}/commkit/mapping/__init__.py +0 -0
  168. {commkit-3.0.0 → commkit-3.1.0}/commkit/mapping/bits.py +0 -0
  169. {commkit-3.0.0 → commkit-3.1.0}/commkit/mapping/shaping.py +0 -0
  170. {commkit-3.0.0 → commkit-3.1.0}/commkit/math.py +0 -0
  171. {commkit-3.0.0 → commkit-3.1.0}/commkit/multirate.py +0 -0
  172. {commkit-3.0.0 → commkit-3.1.0}/commkit/plotting/__init__.py +0 -0
  173. {commkit-3.0.0 → commkit-3.1.0}/commkit/plotting/analysis.py +0 -0
  174. {commkit-3.0.0 → commkit-3.1.0}/commkit/plotting/constellation.py +0 -0
  175. {commkit-3.0.0 → commkit-3.1.0}/commkit/plotting/eye.py +0 -0
  176. {commkit-3.0.0 → commkit-3.1.0}/commkit/plotting/filter_response.py +0 -0
  177. {commkit-3.0.0 → commkit-3.1.0}/commkit/plotting/sync.py +0 -0
  178. {commkit-3.0.0 → commkit-3.1.0}/commkit/plotting/waveform.py +0 -0
  179. {commkit-3.0.0 → commkit-3.1.0}/commkit/py.typed +0 -0
  180. {commkit-3.0.0 → commkit-3.1.0}/commkit/recovery/__init__.py +0 -0
  181. {commkit-3.0.0 → commkit-3.1.0}/commkit/recovery/_common.py +0 -0
  182. {commkit-3.0.0 → commkit-3.1.0}/commkit/recovery/bps.py +0 -0
  183. {commkit-3.0.0 → commkit-3.1.0}/commkit/recovery/carrier_phase.py +0 -0
  184. {commkit-3.0.0 → commkit-3.1.0}/commkit/recovery/corrections.py +0 -0
  185. {commkit-3.0.0 → commkit-3.1.0}/commkit/recovery/pilots.py +0 -0
  186. {commkit-3.0.0 → commkit-3.1.0}/commkit/recovery/pll.py +0 -0
  187. {commkit-3.0.0 → commkit-3.1.0}/commkit/recovery/tikhonov.py +0 -0
  188. {commkit-3.0.0 → commkit-3.1.0}/commkit/recovery/viterbi_viterbi.py +0 -0
  189. {commkit-3.0.0 → commkit-3.1.0}/commkit/smoothing.py +0 -0
  190. {commkit-3.0.0 → commkit-3.1.0}/commkit/spectral.py +0 -0
  191. {commkit-3.0.0 → commkit-3.1.0}/examples/01-constellations-and-bit-mapping.ipynb +0 -0
  192. {commkit-3.0.0 → commkit-3.1.0}/examples/02-spectral-analysis.ipynb +0 -0
  193. {commkit-3.0.0 → commkit-3.1.0}/examples/03-digital-filters.ipynb +0 -0
  194. {commkit-3.0.0 → commkit-3.1.0}/examples/04-pulse-shaping-and-matched-filtering.ipynb +0 -0
  195. {commkit-3.0.0 → commkit-3.1.0}/examples/05-multirate-and-resampling.ipynb +0 -0
  196. {commkit-3.0.0 → commkit-3.1.0}/examples/09-laser-phase-noise.ipynb +0 -0
  197. {commkit-3.0.0 → commkit-3.1.0}/examples/10-front-end-impairments.ipynb +0 -0
  198. {commkit-3.0.0 → commkit-3.1.0}/examples/12-frequency-offset-estimation.ipynb +0 -0
  199. {commkit-3.0.0 → commkit-3.1.0}/examples/13-blind-carrier-phase-recovery.ipynb +0 -0
  200. {commkit-3.0.0 → commkit-3.1.0}/examples/14-pilot-aided-carrier-recovery.ipynb +0 -0
  201. {commkit-3.0.0 → commkit-3.1.0}/examples/15-linear-channel-equalization.ipynb +0 -0
  202. {commkit-3.0.0 → commkit-3.1.0}/examples/16-adaptive-equalization.ipynb +0 -0
  203. {commkit-3.0.0 → commkit-3.1.0}/examples/17-blind-equalization.ipynb +0 -0
  204. {commkit-3.0.0 → commkit-3.1.0}/examples/18-polarization-multiplexing.ipynb +0 -0
  205. {commkit-3.0.0 → commkit-3.1.0}/examples/19-chromatic-dispersion.ipynb +0 -0
  206. {commkit-3.0.0 → commkit-3.1.0}/examples/20-nonlinear-fiber-and-backpropagation.ipynb +0 -0
  207. {commkit-3.0.0 → commkit-3.1.0}/examples/23-carrier-phase-characterization.ipynb +0 -0
  208. {commkit-3.0.0 → commkit-3.1.0}/examples/24-laser-linewidth-self-heterodyne.ipynb +0 -0
  209. {commkit-3.0.0 → commkit-3.1.0}/examples/25-laser-linewidth-short-delay-and-flicker.ipynb +0 -0
  210. {commkit-3.0.0 → commkit-3.1.0}/examples/26-laser-linewidth-self-homodyne-iq.ipynb +0 -0
  211. {commkit-3.0.0 → commkit-3.1.0}/examples/27-measurement-template-self-heterodyne.ipynb +0 -0
  212. {commkit-3.0.0 → commkit-3.1.0}/examples/28-measurement-template-self-homodyne-iq.ipynb +0 -0
  213. {commkit-3.0.0 → commkit-3.1.0}/examples/README.md +0 -0
  214. {commkit-3.0.0 → commkit-3.1.0}/tests/analysis/test_allan.py +0 -0
  215. {commkit-3.0.0 → commkit-3.1.0}/tests/analysis/test_drift.py +0 -0
  216. {commkit-3.0.0 → commkit-3.1.0}/tests/analysis/test_fm_noise.py +0 -0
  217. {commkit-3.0.0 → commkit-3.1.0}/tests/analysis/test_interferometry.py +0 -0
  218. {commkit-3.0.0 → commkit-3.1.0}/tests/analysis/test_linewidth.py +0 -0
  219. {commkit-3.0.0 → commkit-3.1.0}/tests/analysis/test_trajectory.py +0 -0
  220. {commkit-3.0.0 → commkit-3.1.0}/tests/coding/test_crc.py +0 -0
  221. {commkit-3.0.0 → commkit-3.1.0}/tests/coding/test_gf2.py +0 -0
  222. {commkit-3.0.0 → commkit-3.1.0}/tests/coding/test_interleaving.py +0 -0
  223. {commkit-3.0.0 → commkit-3.1.0}/tests/coding/test_ldpc_kernel.py +0 -0
  224. {commkit-3.0.0 → commkit-3.1.0}/tests/coding/test_ldpc_tables.py +0 -0
  225. {commkit-3.0.0 → commkit-3.1.0}/tests/coding/test_linear_codes.py +0 -0
  226. {commkit-3.0.0 → commkit-3.1.0}/tests/coding/test_polar.py +0 -0
  227. {commkit-3.0.0 → commkit-3.1.0}/tests/coding/test_ratematch.py +0 -0
  228. {commkit-3.0.0 → commkit-3.1.0}/tests/common/__init__.py +0 -0
  229. {commkit-3.0.0 → commkit-3.1.0}/tests/common/coding.py +0 -0
  230. {commkit-3.0.0 → commkit-3.1.0}/tests/common/conversions.py +0 -0
  231. {commkit-3.0.0 → commkit-3.1.0}/tests/common/kernel_utils.py +0 -0
  232. {commkit-3.0.0 → commkit-3.1.0}/tests/common/metrics.py +0 -0
  233. {commkit-3.0.0 → commkit-3.1.0}/tests/common/periodic.py +0 -0
  234. {commkit-3.0.0 → commkit-3.1.0}/tests/conftest.py +0 -0
  235. {commkit-3.0.0 → commkit-3.1.0}/tests/core/test_psqam.py +0 -0
  236. {commkit-3.0.0 → commkit-3.1.0}/tests/core/test_signal_adapter.py +0 -0
  237. {commkit-3.0.0 → commkit-3.1.0}/tests/equalization/test_blind.py +0 -0
  238. {commkit-3.0.0 → commkit-3.1.0}/tests/equalization/test_block.py +0 -0
  239. {commkit-3.0.0 → commkit-3.1.0}/tests/equalization/test_bps_kernel.py +0 -0
  240. {commkit-3.0.0 → commkit-3.1.0}/tests/equalization/test_cpr.py +0 -0
  241. {commkit-3.0.0 → commkit-3.1.0}/tests/equalization/test_cs_kernel.py +0 -0
  242. {commkit-3.0.0 → commkit-3.1.0}/tests/equalization/test_equalizer_oracles.py +0 -0
  243. {commkit-3.0.0 → commkit-3.1.0}/tests/equalization/test_mimo.py +0 -0
  244. {commkit-3.0.0 → commkit-3.1.0}/tests/equalization/test_polarization.py +0 -0
  245. {commkit-3.0.0 → commkit-3.1.0}/tests/equalization/test_state.py +0 -0
  246. {commkit-3.0.0 → commkit-3.1.0}/tests/equalization/test_winit.py +0 -0
  247. {commkit-3.0.0 → commkit-3.1.0}/tests/impairments/channel/test_channel_linear.py +0 -0
  248. {commkit-3.0.0 → commkit-3.1.0}/tests/impairments/channel/test_channel_nonlinear.py +0 -0
  249. {commkit-3.0.0 → commkit-3.1.0}/tests/impairments/test_frontend.py +0 -0
  250. {commkit-3.0.0 → commkit-3.1.0}/tests/impairments/test_noise.py +0 -0
  251. {commkit-3.0.0 → commkit-3.1.0}/tests/impairments/test_source.py +0 -0
  252. {commkit-3.0.0 → commkit-3.1.0}/tests/mapping/test_bits.py +0 -0
  253. {commkit-3.0.0 → commkit-3.1.0}/tests/mapping/test_llr.py +0 -0
  254. {commkit-3.0.0 → commkit-3.1.0}/tests/plotting/test_plot_analysis.py +0 -0
  255. {commkit-3.0.0 → commkit-3.1.0}/tests/plotting/test_plot_constellation.py +0 -0
  256. {commkit-3.0.0 → commkit-3.1.0}/tests/plotting/test_plot_eye.py +0 -0
  257. {commkit-3.0.0 → commkit-3.1.0}/tests/plotting/test_plot_filter_response.py +0 -0
  258. {commkit-3.0.0 → commkit-3.1.0}/tests/plotting/test_plot_theme.py +0 -0
  259. {commkit-3.0.0 → commkit-3.1.0}/tests/plotting/test_plot_waveform.py +0 -0
  260. {commkit-3.0.0 → commkit-3.1.0}/tests/plugins/__init__.py +0 -0
  261. {commkit-3.0.0 → commkit-3.1.0}/tests/recovery/test_bps.py +0 -0
  262. {commkit-3.0.0 → commkit-3.1.0}/tests/recovery/test_corrections.py +0 -0
  263. {commkit-3.0.0 → commkit-3.1.0}/tests/recovery/test_joint.py +0 -0
  264. {commkit-3.0.0 → commkit-3.1.0}/tests/recovery/test_pilots.py +0 -0
  265. {commkit-3.0.0 → commkit-3.1.0}/tests/recovery/test_pll.py +0 -0
  266. {commkit-3.0.0 → commkit-3.1.0}/tests/recovery/test_recovery_oracles.py +0 -0
  267. {commkit-3.0.0 → commkit-3.1.0}/tests/recovery/test_tikhonov.py +0 -0
  268. {commkit-3.0.0 → commkit-3.1.0}/tests/recovery/test_viterbi_viterbi.py +0 -0
  269. {commkit-3.0.0 → commkit-3.1.0}/tests/test_api_contracts.py +0 -0
  270. {commkit-3.0.0 → commkit-3.1.0}/tests/test_array.py +0 -0
  271. {commkit-3.0.0 → commkit-3.1.0}/tests/test_backend.py +0 -0
  272. {commkit-3.0.0 → commkit-3.1.0}/tests/test_cuda_infra.py +0 -0
  273. {commkit-3.0.0 → commkit-3.1.0}/tests/test_dispersion.py +0 -0
  274. {commkit-3.0.0 → commkit-3.1.0}/tests/test_examples.py +0 -0
  275. {commkit-3.0.0 → commkit-3.1.0}/tests/test_fiber.py +0 -0
  276. {commkit-3.0.0 → commkit-3.1.0}/tests/test_frequency.py +0 -0
  277. {commkit-3.0.0 → commkit-3.1.0}/tests/test_import_hygiene.py +0 -0
  278. {commkit-3.0.0 → commkit-3.1.0}/tests/test_logger.py +0 -0
  279. {commkit-3.0.0 → commkit-3.1.0}/tests/test_math.py +0 -0
  280. {commkit-3.0.0 → commkit-3.1.0}/tests/test_multirate.py +0 -0
  281. {commkit-3.0.0 → commkit-3.1.0}/tests/test_pulse_shaping.py +0 -0
  282. {commkit-3.0.0 → commkit-3.1.0}/tests/test_smoothing.py +0 -0
  283. {commkit-3.0.0 → commkit-3.1.0}/tests/test_spectral.py +0 -0
@@ -112,7 +112,7 @@ jobs:
112
112
  NUMBA_DISABLE_JIT: "1"
113
113
  run: >-
114
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
115
+ tests/coding/test_ldpc.py tests/coding/test_shaping.py::TestCCDM tests/coding/test_algebraic.py
116
116
  tests/coding/test_polar.py::TestEncoding
117
117
  tests/coding/test_polar.py::TestDecoding::test_sc_matches_the_recursive_reference
118
118
  tests/coding/test_polar.py::TestDecoding::test_full_list_is_maximum_likelihood
@@ -37,7 +37,7 @@ 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
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" \
40
+ tests/coding/test_algebraic.py tests/coding/test_polar.py::TestEncoding "tests/coding/test_polar.py::TestDecoding::test_sc_matches_the_recursive_reference" \
41
41
  "tests/coding/test_polar.py::TestDecoding::test_full_list_is_maximum_likelihood" \
42
42
  "tests/coding/test_polar.py::TestDecoding::test_crc_failure_keeps_the_best_path" \
43
43
  tests/coding/test_convolutional.py tests/coding/test_turbo.py::TestEncoding \
@@ -56,11 +56,12 @@ uv run pytest --device=gpu -p tests.plugins.transfer_trace \
56
56
  uv pip install --target /tmp/sanitizer "nvidia-cuda-sanitizer-api==13.*"
57
57
  S=/tmp/sanitizer/nvidia/cu13/bin/compute-sanitizer
58
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)
59
+ --kernel-name kns=llr --kernel-name kns=iir_ --kernel-name kns=selftest --kernel-name kns=ldpc \
60
+ --kernel-name kns=algebraic)
60
61
  FILES="tests/test_cuda_infra.py tests/equalization/test_cs_kernel.py tests/equalization/test_bps_kernel.py
61
62
  tests/equalization/test_blind.py tests/mapping/test_llr.py tests/mapping/test_gray.py tests/recovery/test_bps.py
62
63
  tests/test_filtering.py tests/test_fiber.py tests/impairments/channel/test_channel_nonlinear.py
63
- tests/coding/test_ldpc_kernel.py"
64
+ tests/coding/test_ldpc_kernel.py tests/coding/test_algebraic.py"
64
65
  BLOCK=$(.venv/bin/python -m pytest --device=gpu -q --collect-only tests/equalization/test_block.py | grep '::')
65
66
  for tool in memcheck racecheck synccheck initcheck; do
66
67
  filter=(); [[ $tool == racecheck || $tool == synccheck ]] && filter=("${OWN[@]}")
@@ -104,7 +105,7 @@ Timed runs:
104
105
  ```bash
105
106
  uv run pytest benchmarks/bench_bps.py --benchmark-only --device=all
106
107
  uv run pytest benchmarks/ --benchmark-only --device=all \
107
- --benchmark-compare=0007 --benchmark-storage=file://benchmarks/baselines
108
+ --benchmark-compare=0008 --benchmark-storage=file://benchmarks/baselines
108
109
  uv run python benchmarks/record_baseline.py NAME # record a new baseline
109
110
  ```
110
111
 
@@ -126,7 +127,7 @@ Dependencies point downward only:
126
127
  | --- | --- | --- |
127
128
  | Infrastructure | `backend`, `logger`, `_cuda` | NumPy, CuPy |
128
129
  | Array and math helpers | `_array` (shape and validation), `math` (power, normalization, dB, linear trend) | infrastructure |
129
- | Value objects | `mapping.Constellation`, pulse classes, `recovery` CPR configs, `coding` (code objects with their `encode` / `decode`; array-only, never imports `core`) | layers above |
130
+ | Value objects | `mapping.Constellation`, pulse classes, `recovery` CPR configs, `coding` (code objects with their `encode` / `decode`; array-only, never imports `core`; `io` stores a code as its class and constructor fields, so those fields are numbers, strings, tuples, host arrays, constellations or other codes) | layers above |
130
131
  | Core | `core` (`Signal`, frames, generation), `io` | layers above |
131
132
  | DSP | `mapping`, `filtering`, `multirate`, `spectral`, `smoothing`, `impairments`, `timing`, `frequency`, `recovery`, `equalization`, `metrics`, `analysis` | layers above, and lower DSP modules |
132
133
  | Presentation | `plotting` | everything; **nothing imports plotting** |
@@ -145,7 +146,7 @@ Dependencies point downward only:
145
146
 
146
147
  ### Signal
147
148
 
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.
149
+ - `Signal` holds: samples (NumPy or CuPy, `(N,)` or `(C, N)`, time on the last axis), the **facts** `sampling_rate`, `symbol_rate`, `center_frequency` and `domain` (`"DIG"`, `"RF"` or `"OPT"`; only RF and optical signals have a carrier), 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, and the `code` between them) plus the `frame` layout.
149
150
  - It never holds pipeline results.
150
151
  - Construction does no hidden work: no mapping, no normalization, no device move, no shape guessing. Factories (`generate`, frames) do that work.
151
152
  - `sig.to("gpu")` and `sig.replace(...)` return new Signals. Arrays are shared, not copied; use `clone()` for an independent copy.
@@ -201,7 +202,7 @@ Dependencies point downward only:
201
202
  - 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_*`.
202
203
  - Computations use plain nouns (`allan_deviation`).
203
204
  - **Glossary**, one name each:
204
- - `num_taps`, `step_size`, `sps`, `constellation`, `pulse`, `rng`, `num_symbols`, `num_channels`, `sampling_rate`, `symbol_rate`.
205
+ - `num_taps`, `step_size`, `sps`, `constellation`, `pulse`, `rng`, `num_symbols`, `num_blocks` (code blocks, codewords of a FEC code), `num_channels`, `sampling_rate`, `symbol_rate`.
205
206
  - Units are in the docstring. Use `_db` and `_hz` suffixes where the unit is ambiguous.
206
207
 
207
208
  ### Docstrings
@@ -232,8 +233,8 @@ Docstrings describe the code as it is; a change in behavior updates them in the
232
233
  - **Precision contract.** Every function computes in a predictable precision, and none promotes single-precision data on its own:
233
234
  - **Transforms keep the input's precision**: complex64 in, complex64 out and computed throughout (filters, impairments, the fiber solver and DBP, resampling, delays, carrier-phase correction); complex128 in, complex128 out.
234
235
  - **Fixed-precision blocks** compute in single precision whatever the input: the equalizers in complex64 (with complex128 accumulation inside, below), `compute_llr` / `Constellation.llr` in float32. Double input to them emits `commkit.PrecisionWarning` (`_array.warn_if_double`, attributed to the caller's line; Python shows it once per call site). Users who want strictness turn it into an error with `warnings.simplefilter("error", commkit.PrecisionWarning)`; the test suite does (`pytest_configure` in `tests/conftest.py`; never in pyproject's `filterwarnings`, which imports commkit before coverage starts and drops every module-level line from the report), so a library path that feeds double data to these blocks fails CI. Training and reference symbols are ideal values and are not checked.
235
- - **Deliberate float64**, documented where it happens, always on phases, small arrays or accumulators, never on sample-rate data that is then cast back: phases before `unwrap`, phase trajectories and phase ramps (wrapped in float64, rotated in the data's precision), frequency grids and estimates, the recursions' state (PLL, Tikhonov, RLS `P`), the IIR filter's sections and state (poles near z = 1; the samples keep their precision on both devices), and sums: the metrics compute per element in the input's precision and accumulate in float64 (`gmi` / `mi` in single precision moved by under 4e-8 bit, far below the Monte Carlo error).
236
- - **Audit with `tests/plugins/precision_trace.py`** after touching numerics: it lists every float64/complex128 array of sample-rate size that a NumPy path of the library creates from single-precision data or from scratch, with its source line. Every reported site must be a deliberate use from the list above (or a test passing complex128 on purpose). Output dtypes alone do not show this: a function that computes in complex128 and casts back returns complex64.
236
+ - **Deliberate float64**, documented where it happens, always on phases, small arrays or accumulators, never on sample-rate data that is then cast back: phases before `unwrap`, phase trajectories and phase ramps (wrapped in float64, rotated in the data's precision), frequency grids and estimates, the recursions' state (PLL, Tikhonov, RLS `P`), the IIR filter's sections and state (poles near z = 1; the samples keep their precision on both devices), and sums: the metrics compute per element in the input's precision and accumulate in float64 (`gmi` / `mi` in single precision moved by under 5e-8 bit, far below the Monte Carlo error).
237
+ - **Audit with `tests/plugins/precision_trace.py`** after touching numerics: it lists every float64/complex128 array of sample-rate size that a NumPy path of the library creates from single-precision data, from integer or bool data (bits, hard decisions; in `coding` from the input of `encode` / `decode`) or from scratch, including through `astype`, with its source line. Every reported site must be a deliberate use from the list above (or a test passing complex128 on purpose). Output dtypes alone do not show this: a function that computes in complex128 and casts back returns complex64.
237
238
  - complex128 pays only where single-precision rounding accumulates over many FFT passes, as in the fiber solver (6e-4 after 10^4 steps in complex64, 1e-12 in complex128); there the GPU runs its plain CuPy stages, not the fused complex64 kernels.
238
239
  - **The NEP 50 trap**: under NumPy 2 a NumPy scalar is not weak, so `np.float64(a) * x64` (any `np.sqrt`, `np.pi * ...` result, or a float64 array such as `xp.fft.fftfreq`) promotes complex64 data to complex128. Combine single-precision data only with Python scalars or with arrays cast to its precision; rotate with `math._derotate` (float64 phase wrap, single-precision phasor, one fused kernel on the GPU).
239
240
  - **Accumulation precision:**
@@ -255,7 +256,8 @@ Docstrings describe the code as it is; a change in behavior updates them in the
255
256
  | 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 |
256
257
  | 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 |
257
258
  | 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
+ | Binary linear algebra of codes: encoding, syndromes, the correlations of maximum-likelihood decoding, the syndromes of algebraic decoding, 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, its parity taken in place on the CPU and by one elementwise kernel writing int8 on the GPU; small tables (generators, coset leaders) cached per code object and device (`backend._device_tables`) | follows the input |
260
+ | Algebraic decoding from the syndromes (BCH, Reed-Solomon): Berlekamp-Massey, a Chien search that stops at the locator's degree, Forney at the roots | CPU: Numba `parallel=True` over codewords. GPU: CUDA kernel (`algebraic_correct`), one thread per codeword, the same steps in integer table arithmetic, so both agree exactly (a CuPy version running Berlekamp-Massey in lockstep over the codewords, about 450 launches per decode, was 12x slower on KP4) | follows the input |
259
261
  | 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 |
260
262
  | 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 |
261
263
 
@@ -308,10 +310,10 @@ Docstrings describe the code as it is; a change in behavior updates them in the
308
310
 
309
311
  ## 8. Benchmarks
310
312
 
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.
313
+ `benchmarks/` tracks the GPU-relevant hot paths. Baselines are committed under `benchmarks/baselines/`. The current reference is `0008` (`v3_1`) on the reference machine: RTX 4070 Ti, Ryzen 7 7800X3D, WSL2. It is the first baseline with the polar, Viterbi, turbo, Chase and CCDM benchmarks, recorded at the 3.1 state, best of ten passes (two recordings of five merged per benchmark: five still kept GPU outliers of 2x on a few benchmarks whose code had not changed); `0007` (`v3_0`) 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.
312
314
 
313
315
  - **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`.
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.
316
+ - **Baselines** are recorded with `benchmarks/record_baseline.py`: each file in its own process, best of three passes (`--passes`; `0006` and `0007` used five and `0008` ten, 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.
315
317
 
316
318
  - **IDs** are `[cpu]` or `[gpu]`, meaning the input device.
317
319
  - **Timing:**
@@ -321,7 +323,7 @@ Docstrings describe the code as it is; a change in behavior updates them in the
321
323
  - **Tools:** `benchmarks/benchutils.py` provides `CudaEventTimer` and `nvtx_range` for `nsys` profiling.
322
324
  - **Logging** is set to WARNING in `benchmarks/conftest.py`.
323
325
  - **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.
324
- - A commit touching a hot path quotes its delta against `0007`. Tolerance is 5% on CPU and 10% on GPU.
326
+ - A commit touching a hot path quotes its delta against `0008`. Tolerance is 5% on CPU and 10% on GPU.
325
327
 
326
328
  ---
327
329
 
@@ -344,7 +346,7 @@ Steps, once CuPy 14.3 (with cp315 wheels for `cupy-cuda13x`) and a fixed `jupyte
344
346
  - `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).
345
347
  - `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.
346
348
  - `uv lock`; README install requirements; §2 here ("Python 3.12+").
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.
349
+ - Benchmarks: record a new baseline on 3.15 (it lands in `benchmarks/baselines/Linux-CPython-3.15-64bit/`), compare it with `0008` by a paired A/B of 3.14 against 3.15 on unchanged code, and make it the reference.
348
350
  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 `*`.
349
351
  - 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/`.
350
352
  - 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`.
@@ -359,9 +361,8 @@ Steps, once CuPy 14.3 (with cp315 wheels for `cupy-cuda13x`) and a fixed `jupyte
359
361
 
360
362
  ### Channel coding: open items
361
363
 
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.
364
+ 1. **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.
365
+ 2. **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.
365
366
 
366
367
  ### Nonlinear fiber channel: future features
367
368
 
@@ -4,6 +4,75 @@ Notable changes to CommKit, newest first. Performance figures are from the refer
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [3.1.0] - 2026-10-29
8
+
9
+ Review fixes and the completion of the coding release. Coded records are sized in codewords (`generate(num_blocks=)`, `SingleCarrierFrame(payload_blocks=)`) or padded to any length, the reference carries its code, `save_npz` stores everything a Signal holds (codes included), and `Signal.domain` is back. Reed-Solomon and BCH decoding run in per-codeword kernels (KP4 23 times faster on the GPU, 16 on the CPU), and the remaining decoders, LLRs and several DSP paths got faster. Correctness fixes cover bit validation in `encode`, staircase and CCDM validity, complex FIR taps, ZF precision and timing correction. Frame lengths in symbols are renamed `*_num_symbols`; see the first section.
10
+
11
+ ### Changes that may need code updates
12
+
13
+ - **The `gpu` extra requires CuPy 14.2** (`cupy-cuda13x>=14.2`), the first release with sparse arrays (`cupyx.scipy.sparse.csr_array`), which the LDPC encoder now uses on the GPU.
14
+ - **Lengths in symbols are named `*_num_symbols` throughout the frame**, as in `generate` and `SingleCarrierFrame.num_symbols`: `SingleCarrierFrame(payload_len=, guard_len=, pilot_block_len=)` are now `payload_num_symbols`, `guard_num_symbols` and `pilot_block_num_symbols`, and `Preamble(length=)` is `Preamble(num_symbols=)`. Rename the arguments and attribute reads.
15
+ - **`encode` rejects bits other than 0 and 1** with `ValueError`. Before, linear codes silently encoded another payload (`[2, 0, 0, 0]` gave the all-zero codeword), a value of 256 wrapped to 0, and the turbo encoder indexed its trellis with the value.
16
+ - **`TurboCode` and `LDPCCode` reject non-integer parameters** with `TypeError` (`TurboCode(40.0)` was accepted; float LDPC indices were truncated into a different code).
17
+ - **`fir_filter` and `ols_fir_filter` raise for complex taps on real samples.** The imaginary part was dropped (the taps `[1j]` gave zeros). Pass complex samples for a complex output.
18
+ - **`correct_timing(mode="slice")` raises for offsets outside `[0, N)`**: a negative offset kept only the last samples. Use `mode="zero"` or `"circular"` for negative offsets.
19
+ - **`LDPCCode.parity_check()` returns a `scipy.sparse.csr_array`** instead of a `csr_matrix`, as SciPy plans to deprecate its matrix interface. `@` is unchanged, but `*` is now elementwise and indexing a row gives a 1-D array; use `@` for products, or `scipy.sparse.csr_matrix(code.parity_check())` for the old type. `LDPCCode.from_parity_check` accepts either.
20
+ - **`gray_code`, `gray_to_binary` return read-only arrays.** They are cached, so writing to one changed every later call's result; copy before modifying.
21
+
22
+ ### Added
23
+
24
+ - **`Reference.code`**, the code that turned `info_bits` into `bits` (the FEC code, or the matcher `generate` picks for a shaped constellation), so `decode(llrs, code=sig.reference.code)` needs nothing else; `code_block` is taken from it. `generate` and `extract_payload` set it, and the Signal summary names it.
25
+ - **`save_npz` stores everything a Signal holds**: a frame's `payload_code` and a reference's `code` (a FEC code, distribution matcher or their composition) as their class and constructor fields, with their arrays (parity checks, information positions, ...) as `frame_payload_code.*` and `reference_code.*` entries, and the frame's pilot bits. Still loaded with `allow_pickle=False`; only the classes of `commkit.coding` are rebuilt. A frame with a payload code used to raise.
26
+ - **`Signal.domain`: `"DIG"` (default), `"RF"` or `"OPT"`**, where the samples live. An RF or optical Signal carries its `center_frequency` (a digital one keeps 0, and setting a carrier on it raises); the summary shows the domain and the carrier (with its wavelength for an optical signal), `save_npz`/`load_npz` round-trip it, and `plot_psd`/`plot_spectrogram` take it from the Signal, so an optical spectrum is drawn at its physical frequencies or wavelengths without arguments.
27
+ - **`generate(..., code=, num_blocks=)` sizes a coded record in code blocks** (codewords of a FEC code) instead of symbols, so `generate(QPSK, symbol_rate=..., code=code, num_blocks=100)` replaces `num_codewords * code.n // bits_per_symbol`. Give either `num_symbols` or `num_blocks`.
28
+ - **`pad=True` fits a coded record to any length**, such as an arbitrary waveform generator's record: `generate(..., num_symbols=N, code=code, pad=True)` carries as many whole codewords as fit, then random uncoded filler bits; `SingleCarrierFrame(payload_pad=True)` does the same for a frame's payload. Not with a distribution matcher, whose composition the filler would change.
29
+ - `Reference.num_blocks`, the whole code blocks per channel: decode them with `decode(llrs[..., : ref.num_blocks * code.n], code=code)`.
30
+ - **`SingleCarrierFrame(payload_blocks=)` sizes a coded payload in codewords** instead of `payload_num_symbols` (which it then sets); the codewords must fill whole symbols and pilot periods, and the error names the nearest valid counts. `payload_code` takes distribution matchers and PAS too, with the shaped payload constellation they match onto.
31
+ - `SingleCarrierFrame.num_symbols`, the frame length in symbols (preamble, body, guard): `to_signal` gives `num_symbols * sps` samples per stream. A `payload_num_symbols` that does not fill whole codewords now names the nearest valid lengths, pilot periods included.
32
+ - The Signal summary (`print(sig)`) shows the code block, the blocks per channel with any filler, and the information rate of a coded reference, and a frame's payload code.
33
+ - `generate`'s docstring explains coded payloads (blocks, sizing, decoding) and that a shaped constellation without a code goes through one CCDM block of the whole record, the ideal-matcher limit, with how to model a transmitter's shorter matcher.
34
+
35
+ ### Performance
36
+
37
+ Paired A/B of alternating processes against the previous commit of each change:
38
+
39
+ - Reed-Solomon decoding: KP4, 2048 hard-decision codewords at BER 1e-3, CPU 410 to 26 ms (direct XOR reduction 0.80, Forney's formula at the error positions only 0.42, hard input converted in place 0.79, a per-codeword Numba decoder from the syndromes 0.27), GPU 41 to 1.8 ms (a CUDA kernel with one thread per codeword in place of Berlekamp-Massey's 450 launches, 0.080).
40
+ - BCH decoding through the same per-codeword decoders: Chase-II of BCH(1000, 970) 0.26 on the CPU and 0.22 on the GPU, staircase decoding 0.35 and 0.14.
41
+ - Chase decoding: 0.53 on the CPU, 0.30-0.51 on the GPU (no LLR round trip or re-encoding, no 4-D flip mask, larger GPU chunks).
42
+ - LDPC sum-product decoding on the CPU: DVB-S2 1/2, 64 codewords, 0.70 (`phi(|Q|)` kept from a row's first pass instead of computed again).
43
+ - Products of bit arrays modulo 2 (encoding, syndromes): encoding 2^17 OIF 400ZR codewords 0.85 on the CPU and 0.64 on the GPU (no int32 temporaries for the parity).
44
+ - CCDM matching: 8 blocks of 20000 PS-64-QAM symbols, matching 0.76 and dematching 0.84 (one division per point and symbol; the dematcher's interval start in 32-bit limbs instead of one byte per bit).
45
+ - Polar decoding: SC of N = 1024 0.32, SCL(8) 0.70, SCL(32) 0.68 (paths copied only when they change slot).
46
+ - Tail-biting Viterbi: K = 7, 0.035 (all start states scored in one pass); decision storage a quarter of before at every termination.
47
+ - Turbo decoding: LTE K = 1024, max-log 0.82, exact log-MAP 0.65.
48
+ - Cached code tables: interleaving 2^20 values with one `Interleaver` 0.19 on the CPU, 0.013 on the GPU (identity shortcut in the code objects' equality, which every cached lookup called).
49
+ - `mi`: one exponential per element, 256-QAM 0.82 on the CPU and 0.33 on the GPU, 16-QAM 0.52 on the GPU.
50
+ - LMS/RLS with inline carrier recovery: 2 x 50000 16-QAM symbols, PLL 0.95, BPS 0.97 (each symbol's phasor computed once).
51
+ - `Constellation.map` / `demap` on the GPU: about 0.65 per call for 64-QAM and 8-PSK, 4096 to 2^20 symbols (tables cached per device; the hash cached and an identity shortcut in the equality the cache calls).
52
+ - LLRs on the CPU (`compute_llr`, `Constellation.llr`): 0.07-0.26 for 4096 symbols (tables cached per constellation; 256-QAM's closed-form tables were most of the call), 0.84 for 2^20 (no copies of the real and imaginary parts).
53
+ - `correct_frequency_offset` with a static offset: 2 x 2^22 samples, 0.83 on the CPU and 0.69 on the GPU (one float64 ramp instead of three, and no host sync for an offset on the GPU).
54
+ - `plot_equalizer_result` of 2 x 10^6 symbols: 0.08 on both devices (curves smoothed and cut to their min/max envelope on the result's device; about 4000 points per line are transferred and drawn instead of every symbol).
55
+ - White-FM plateau detection of the linewidth estimators: 0.26 for 2^19 PSD bins, 0.91 for 4097 (each log-spaced cell read as a slice instead of a scan over all bins).
56
+ - `apply_pmd`: 2 x 2^22 samples, 0.91 on the CPU and 0.90 on the GPU (the second phasor is the conjugate of the first).
57
+ - `block_lms` with BPS: 2 x 100000 16-QAM symbols, block 256, 0.97 on the CPU and 0.86 on the GPU (the phasor from a real cos and sin).
58
+
59
+ ### Fixed
60
+
61
+ - **A shaped `Constellation` rebuilt from its fields is equal to the original.** The constructor divided the pmf by its sum every time, which moved an already normalized pmf by an ulp, so a shaped constellation loaded with `load_npz` could compare unequal to the saved one. A pmf that sums to 1 within rounding is now kept as given.
62
+ - **Staircase `valid` reflected an earlier state of the frame**: a block corrected through the next one kept its failed flag, so `HardIterations(1)` could return the right payload with `valid=False`. Success is now checked on the final frame.
63
+ - **CCDM (and so PAS) `valid` accepted sequences no payload maps to**: a composition has more sequences than the 2^k payloads, and all were valid. Only the matcher's image is valid now.
64
+ - **`zf_equalizer` with a float64 channel estimate returned complex128** for complex64 samples; it now computes in the samples' precision.
65
+ - **`correct_timing(mode="zero")`** repeated each channel's first sample for a negative per-channel offset instead of zero-filling, and returned zeros for a scalar offset of 0.
66
+ - **`mi` and `ols_fir_filter` / `zf_equalizer` bound their working memory.** `mi`'s intermediates reached GiBs for many channels of high-order QAM (1 GiB of differences for two channels of 1024-QAM); they are now capped at 2^24 elements. Overlap-save transformed the whole record at once, against its docstring, about 8 times the input; it now runs in batches of blocks (3.5 times the input on long records; about 5% slower on the CPU for 2^25 samples).
67
+ - **A code object unpickled in another process missed its cached device tables**: it carried the hash of the first process (hashes of bytes differ between processes), so it compared equal to the same code but hashed differently. The cached hash is no longer pickled.
68
+ - `load_npz` closes its archive; `ber`, `fer` and `ser` transfer their error counts once.
69
+
70
+ ### Development
71
+
72
+ - Benchmarks for polar, Viterbi, turbo, Chase and CCDM decoding in `bench_coding.py`; `ldpc_workload` is now `coded_workload`.
73
+ - Baseline `0008` (`v3_1`) is the new benchmark reference, the first with those benchmarks, best of ten passes. Against `0007` the geometric mean of the time ratios is 0.88 (0.92 without coding); no benchmark is slower beyond the GPU's run-to-run swing (the slowest, 1.08, on a 0.26 ms GPU call). KP4 decoding takes 1.8 ms on the GPU and 26 ms on the CPU, against 41 and 410 ms.
74
+ - The algebraic decoders' kernel (`algebraic`) joins the sanitizer pass of AGENTS §2, and their Numba decoder the `NUMBA_DISABLE_JIT` coverage job.
75
+
7
76
  ## [3.0.0] - 2026-10-28
8
77
 
9
78
  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.
@@ -152,7 +221,8 @@ See the [GitHub release](https://github.com/lokgar/commkit/releases/tag/v1.1.0)
152
221
 
153
222
  First release. See the [GitHub release](https://github.com/lokgar/commkit/releases/tag/v1.0.0).
154
223
 
155
- [Unreleased]: https://github.com/lokgar/commkit/compare/v3.0.0...HEAD
224
+ [Unreleased]: https://github.com/lokgar/commkit/compare/v3.1.0...HEAD
225
+ [3.1.0]: https://github.com/lokgar/commkit/compare/v3.0.0...v3.1.0
156
226
  [3.0.0]: https://github.com/lokgar/commkit/compare/v2.3.0...v3.0.0
157
227
  [2.3.0]: https://github.com/lokgar/commkit/compare/v2.2.0...v2.3.0
158
228
  [2.2.0]: https://github.com/lokgar/commkit/compare/v2.1.0...v2.2.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: commkit
3
- Version: 3.0.0
3
+ Version: 3.1.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
@@ -26,10 +26,10 @@ Requires-Dist: numba>=0.65
26
26
  Requires-Dist: numpy>=2.4
27
27
  Requires-Dist: scipy>=1.17
28
28
  Provides-Extra: full
29
- Requires-Dist: cupy-cuda13x[ctk]>=14.1; extra == 'full'
29
+ Requires-Dist: cupy-cuda13x[ctk]>=14.2; extra == 'full'
30
30
  Requires-Dist: jupyter>=1.1.1; extra == 'full'
31
31
  Provides-Extra: gpu
32
- Requires-Dist: cupy-cuda13x[ctk]>=14.1; extra == 'gpu'
32
+ Requires-Dist: cupy-cuda13x[ctk]>=14.2; extra == 'gpu'
33
33
  Provides-Extra: notebook
34
34
  Requires-Dist: jupyter>=1.1.1; extra == 'notebook'
35
35
  Description-Content-Type: text/markdown