commkit 1.1.0__tar.gz → 2.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 (292) hide show
  1. commkit-2.1.0/.gitattributes +1 -0
  2. {commkit-1.1.0 → commkit-2.1.0}/.github/workflows/ci.yml +48 -1
  3. commkit-2.1.0/AGENTS.md +264 -0
  4. {commkit-1.1.0 → commkit-2.1.0}/LICENSE +1 -1
  5. commkit-2.1.0/PKG-INFO +190 -0
  6. commkit-2.1.0/README.md +154 -0
  7. commkit-2.1.0/benchmarks/baselines/Linux-CPython-3.14-64bit/0002_pre_v2.json +3257 -0
  8. commkit-2.1.0/benchmarks/baselines/Linux-CPython-3.14-64bit/0003_v2_0.json +3157 -0
  9. {commkit-1.1.0 → commkit-2.1.0}/benchmarks/bench_block_blind.py +24 -8
  10. {commkit-1.1.0 → commkit-2.1.0}/benchmarks/bench_block_lms.py +36 -20
  11. {commkit-1.1.0 → commkit-2.1.0}/benchmarks/bench_bps.py +15 -12
  12. commkit-2.1.0/benchmarks/bench_equalizers.py +93 -0
  13. commkit-2.1.0/benchmarks/bench_import.py +30 -0
  14. commkit-2.1.0/benchmarks/bench_llr.py +52 -0
  15. commkit-2.1.0/benchmarks/bench_signal_pipeline.py +146 -0
  16. {commkit-1.1.0 → commkit-2.1.0}/benchmarks/bench_sync_misc.py +10 -5
  17. {commkit-1.1.0 → commkit-2.1.0}/benchmarks/benchutils.py +20 -0
  18. {commkit-1.1.0 → commkit-2.1.0}/benchmarks/conftest.py +1 -9
  19. commkit-2.1.0/benchmarks/record_baseline.py +74 -0
  20. {commkit-1.1.0 → commkit-2.1.0}/benchmarks/workloads.py +40 -12
  21. commkit-2.1.0/commkit/__init__.py +106 -0
  22. commkit-2.1.0/commkit/_array.py +294 -0
  23. commkit-2.1.0/commkit/_circular.py +74 -0
  24. {commkit-1.1.0 → commkit-2.1.0}/commkit/_cuda/__init__.py +160 -14
  25. {commkit-1.1.0 → commkit-2.1.0}/commkit/_cuda/compiler.py +4 -1
  26. commkit-2.1.0/commkit/_cuda/src/bps_anchor.cu +146 -0
  27. commkit-2.1.0/commkit/_cuda/src/cs_block.cu +248 -0
  28. commkit-2.1.0/commkit/_cuda/src/llr.cu +110 -0
  29. commkit-2.1.0/commkit/_dispersion.py +84 -0
  30. commkit-2.1.0/commkit/_overlap_save.py +94 -0
  31. commkit-2.1.0/commkit/_random.py +39 -0
  32. commkit-2.1.0/commkit/_sequences.py +146 -0
  33. {commkit-1.1.0 → commkit-2.1.0}/commkit/analysis/__init__.py +36 -17
  34. {commkit-1.1.0 → commkit-2.1.0}/commkit/analysis/_common.py +73 -0
  35. {commkit-1.1.0 → commkit-2.1.0}/commkit/analysis/allan.py +36 -26
  36. commkit-2.1.0/commkit/analysis/drift.py +187 -0
  37. commkit-2.1.0/commkit/analysis/fm_noise.py +321 -0
  38. commkit-2.1.0/commkit/analysis/interferometry.py +366 -0
  39. commkit-2.1.0/commkit/analysis/linewidth.py +1159 -0
  40. {commkit-1.1.0 → commkit-2.1.0}/commkit/analysis/trajectory.py +28 -12
  41. commkit-2.1.0/commkit/backend.py +270 -0
  42. {commkit-1.1.0 → commkit-2.1.0}/commkit/coding/__init__.py +1 -1
  43. {commkit-1.1.0 → commkit-2.1.0}/commkit/core/__init__.py +6 -14
  44. commkit-2.1.0/commkit/core/_signal_adapter.py +164 -0
  45. {commkit-1.1.0 → commkit-2.1.0}/commkit/core/frame.py +332 -442
  46. commkit-2.1.0/commkit/core/generation.py +236 -0
  47. commkit-2.1.0/commkit/core/signal.py +454 -0
  48. {commkit-1.1.0 → commkit-2.1.0}/commkit/equalization/__init__.py +8 -11
  49. {commkit-1.1.0 → commkit-2.1.0}/commkit/equalization/_block/__init__.py +2 -14
  50. commkit-2.1.0/commkit/equalization/_block/_blind.py +169 -0
  51. commkit-2.1.0/commkit/equalization/_block/_dd.py +867 -0
  52. commkit-2.1.0/commkit/equalization/_block/_engine.py +374 -0
  53. commkit-2.1.0/commkit/equalization/_common.py +378 -0
  54. {commkit-1.1.0 → commkit-2.1.0}/commkit/equalization/_kernels_numba.py +108 -64
  55. {commkit-1.1.0 → commkit-2.1.0}/commkit/equalization/blind.py +48 -124
  56. {commkit-1.1.0 → commkit-2.1.0}/commkit/equalization/linear.py +29 -54
  57. {commkit-1.1.0 → commkit-2.1.0}/commkit/equalization/polarization.py +101 -144
  58. commkit-2.1.0/commkit/equalization/result.py +302 -0
  59. {commkit-1.1.0 → commkit-2.1.0}/commkit/equalization/sequential/__init__.py +1 -2
  60. commkit-2.1.0/commkit/equalization/sequential/_blind.py +494 -0
  61. commkit-2.1.0/commkit/equalization/sequential/_dd.py +732 -0
  62. commkit-2.1.0/commkit/equalization/sequential/_setup.py +454 -0
  63. {commkit-1.1.0 → commkit-2.1.0}/commkit/filtering.py +388 -406
  64. commkit-2.1.0/commkit/frequency.py +914 -0
  65. {commkit-1.1.0 → commkit-2.1.0}/commkit/impairments/__init__.py +6 -10
  66. {commkit-1.1.0 → commkit-2.1.0}/commkit/impairments/channel/linear.py +78 -142
  67. commkit-2.1.0/commkit/impairments/frontend.py +211 -0
  68. {commkit-1.1.0 → commkit-2.1.0}/commkit/impairments/noise.py +33 -56
  69. {commkit-1.1.0 → commkit-2.1.0}/commkit/impairments/source.py +107 -89
  70. commkit-2.1.0/commkit/io.py +347 -0
  71. {commkit-1.1.0 → commkit-2.1.0}/commkit/logger.py +26 -37
  72. commkit-2.1.0/commkit/mapping/__init__.py +33 -0
  73. commkit-2.1.0/commkit/mapping/bits.py +90 -0
  74. commkit-2.1.0/commkit/mapping/constellation.py +393 -0
  75. {commkit-1.1.0 → commkit-2.1.0}/commkit/mapping/gray.py +15 -21
  76. commkit-2.1.0/commkit/mapping/llr.py +255 -0
  77. commkit-2.1.0/commkit/mapping/shaping.py +185 -0
  78. commkit-2.1.0/commkit/math.py +324 -0
  79. commkit-2.1.0/commkit/metrics.py +685 -0
  80. commkit-2.1.0/commkit/multirate.py +381 -0
  81. {commkit-1.1.0 → commkit-2.1.0}/commkit/plotting/__init__.py +4 -11
  82. {commkit-1.1.0 → commkit-2.1.0}/commkit/plotting/analysis.py +135 -150
  83. commkit-2.1.0/commkit/plotting/constellation.py +430 -0
  84. {commkit-1.1.0 → commkit-2.1.0}/commkit/plotting/equalizer.py +7 -4
  85. {commkit-1.1.0 → commkit-2.1.0}/commkit/plotting/eye.py +81 -51
  86. {commkit-1.1.0 → commkit-2.1.0}/commkit/plotting/filter_response.py +3 -0
  87. {commkit-1.1.0 → commkit-2.1.0}/commkit/plotting/spectral.py +164 -77
  88. {commkit-1.1.0 → commkit-2.1.0}/commkit/plotting/sync.py +456 -27
  89. {commkit-1.1.0 → commkit-2.1.0}/commkit/plotting/theme.py +2 -0
  90. {commkit-1.1.0 → commkit-2.1.0}/commkit/plotting/waveform.py +92 -48
  91. commkit-2.1.0/commkit/recovery/__init__.py +54 -0
  92. commkit-2.1.0/commkit/recovery/_common.py +256 -0
  93. commkit-2.1.0/commkit/recovery/bps.py +306 -0
  94. commkit-2.1.0/commkit/recovery/carrier_phase.py +283 -0
  95. {commkit-1.1.0 → commkit-2.1.0}/commkit/recovery/corrections.py +273 -397
  96. commkit-2.1.0/commkit/recovery/pilots.py +630 -0
  97. commkit-2.1.0/commkit/recovery/pll.py +378 -0
  98. commkit-2.1.0/commkit/recovery/tikhonov.py +274 -0
  99. commkit-2.1.0/commkit/recovery/viterbi_viterbi.py +123 -0
  100. {commkit-1.1.0 → commkit-2.1.0}/commkit/smoothing.py +6 -5
  101. {commkit-1.1.0 → commkit-2.1.0}/commkit/spectral.py +170 -164
  102. {commkit-1.1.0 → commkit-2.1.0}/commkit/timing.py +343 -342
  103. commkit-2.1.0/examples/carrier_phase_analysis.ipynb +515 -0
  104. commkit-2.1.0/examples/laser_linewidth_dsh.ipynb +787 -0
  105. commkit-2.1.0/examples/laser_linewidth_homodyne_iq.ipynb +497 -0
  106. commkit-2.1.0/examples/measurement_laser_linewidth_dsh.ipynb +318 -0
  107. commkit-2.1.0/examples/measurement_laser_linewidth_homodyne_iq.ipynb +336 -0
  108. commkit-2.1.0/examples/qam_receiver_quickstart.ipynb +313 -0
  109. {commkit-1.1.0 → commkit-2.1.0}/pyproject.toml +83 -20
  110. commkit-2.1.0/tests/analysis/test_allan.py +34 -0
  111. commkit-2.1.0/tests/analysis/test_drift.py +45 -0
  112. commkit-2.1.0/tests/analysis/test_fm_noise.py +60 -0
  113. commkit-2.1.0/tests/analysis/test_interferometry.py +146 -0
  114. commkit-2.1.0/tests/analysis/test_linewidth.py +568 -0
  115. commkit-2.1.0/tests/analysis/test_trajectory.py +112 -0
  116. commkit-2.1.0/tests/common/__init__.py +62 -0
  117. commkit-2.1.0/tests/common/conversions.py +47 -0
  118. commkit-2.1.0/tests/common/kernel_utils.py +140 -0
  119. commkit-2.1.0/tests/common/metrics.py +131 -0
  120. commkit-2.1.0/tests/common/periodic.py +40 -0
  121. commkit-2.1.0/tests/common/reference_impl.py +539 -0
  122. commkit-2.1.0/tests/common/signals.py +412 -0
  123. {commkit-1.1.0 → commkit-2.1.0}/tests/conftest.py +59 -17
  124. commkit-2.1.0/tests/core/test_frame.py +555 -0
  125. commkit-2.1.0/tests/core/test_generation.py +226 -0
  126. commkit-2.1.0/tests/core/test_psqam.py +272 -0
  127. commkit-2.1.0/tests/core/test_signal.py +875 -0
  128. commkit-2.1.0/tests/core/test_signal_adapter.py +107 -0
  129. commkit-2.1.0/tests/core/test_signal_mimo.py +169 -0
  130. {commkit-1.1.0 → commkit-2.1.0}/tests/equalization/test_blind.py +65 -81
  131. commkit-2.1.0/tests/equalization/test_block.py +760 -0
  132. commkit-2.1.0/tests/equalization/test_bps_kernel.py +333 -0
  133. commkit-2.1.0/tests/equalization/test_cpr.py +753 -0
  134. commkit-2.1.0/tests/equalization/test_cs_kernel.py +116 -0
  135. commkit-2.1.0/tests/equalization/test_equalizer_oracles.py +300 -0
  136. {commkit-1.1.0 → commkit-2.1.0}/tests/equalization/test_linear.py +17 -38
  137. {commkit-1.1.0 → commkit-2.1.0}/tests/equalization/test_mimo.py +44 -67
  138. {commkit-1.1.0 → commkit-2.1.0}/tests/equalization/test_polarization.py +146 -122
  139. {commkit-1.1.0 → commkit-2.1.0}/tests/equalization/test_sequential.py +359 -432
  140. commkit-2.1.0/tests/equalization/test_state.py +226 -0
  141. commkit-2.1.0/tests/equalization/test_winit.py +319 -0
  142. {commkit-1.1.0 → commkit-2.1.0}/tests/impairments/channel/test_channel_linear.py +95 -41
  143. {commkit-1.1.0 → commkit-2.1.0}/tests/impairments/test_frontend.py +48 -41
  144. {commkit-1.1.0 → commkit-2.1.0}/tests/impairments/test_noise.py +36 -10
  145. {commkit-1.1.0 → commkit-2.1.0}/tests/impairments/test_source.py +119 -35
  146. commkit-2.1.0/tests/mapping/test_bits.py +137 -0
  147. commkit-2.1.0/tests/mapping/test_constellation.py +296 -0
  148. commkit-2.1.0/tests/mapping/test_gray.py +166 -0
  149. commkit-2.1.0/tests/mapping/test_llr.py +314 -0
  150. commkit-2.1.0/tests/plotting/test_plot_analysis.py +159 -0
  151. commkit-2.1.0/tests/plotting/test_plot_constellation.py +195 -0
  152. commkit-2.1.0/tests/plotting/test_plot_equalizer.py +191 -0
  153. commkit-2.1.0/tests/plotting/test_plot_eye.py +181 -0
  154. commkit-2.1.0/tests/plotting/test_plot_filter_response.py +71 -0
  155. commkit-2.1.0/tests/plotting/test_plot_spectral.py +264 -0
  156. commkit-2.1.0/tests/plotting/test_plot_sync.py +214 -0
  157. commkit-2.1.0/tests/plotting/test_plot_theme.py +28 -0
  158. commkit-2.1.0/tests/plotting/test_plot_waveform.py +113 -0
  159. commkit-2.1.0/tests/recovery/test_bps.py +252 -0
  160. commkit-2.1.0/tests/recovery/test_corrections.py +620 -0
  161. commkit-2.1.0/tests/recovery/test_joint.py +85 -0
  162. {commkit-1.1.0 → commkit-2.1.0}/tests/recovery/test_pilots.py +339 -270
  163. commkit-2.1.0/tests/recovery/test_pll.py +216 -0
  164. commkit-2.1.0/tests/recovery/test_recovery_oracles.py +125 -0
  165. commkit-2.1.0/tests/recovery/test_tikhonov.py +269 -0
  166. commkit-2.1.0/tests/recovery/test_viterbi_viterbi.py +229 -0
  167. commkit-2.1.0/tests/test_api_contracts.py +933 -0
  168. commkit-2.1.0/tests/test_array.py +153 -0
  169. commkit-2.1.0/tests/test_backend.py +117 -0
  170. commkit-2.1.0/tests/test_cuda_infra.py +111 -0
  171. commkit-2.1.0/tests/test_dispersion.py +107 -0
  172. commkit-2.1.0/tests/test_examples.py +64 -0
  173. commkit-2.1.0/tests/test_filtering.py +453 -0
  174. {commkit-1.1.0 → commkit-2.1.0}/tests/test_frequency.py +407 -354
  175. commkit-2.1.0/tests/test_import_hygiene.py +116 -0
  176. commkit-2.1.0/tests/test_io.py +360 -0
  177. commkit-2.1.0/tests/test_logger.py +64 -0
  178. commkit-2.1.0/tests/test_math.py +169 -0
  179. commkit-2.1.0/tests/test_metrics.py +650 -0
  180. commkit-2.1.0/tests/test_multirate.py +159 -0
  181. commkit-2.1.0/tests/test_pulse_shaping.py +227 -0
  182. commkit-2.1.0/tests/test_signal_pipeline.py +339 -0
  183. commkit-2.1.0/tests/test_smoothing.py +56 -0
  184. commkit-2.1.0/tests/test_spectral.py +379 -0
  185. commkit-2.1.0/tests/test_timing.py +882 -0
  186. {commkit-1.1.0 → commkit-2.1.0}/uv.lock +90 -268
  187. commkit-1.1.0/.gitattributes +0 -1
  188. commkit-1.1.0/CLAUDE.md +0 -539
  189. commkit-1.1.0/PKG-INFO +0 -165
  190. commkit-1.1.0/README.md +0 -138
  191. commkit-1.1.0/benchmarks/baselines/Linux-CPython-3.14-64bit/0001_commkit_baseline.json +0 -2420
  192. commkit-1.1.0/benchmarks/bench_equalizers.py +0 -174
  193. commkit-1.1.0/commkit/__init__.py +0 -74
  194. commkit-1.1.0/commkit/_cuda/src/cs_block.cu +0 -119
  195. commkit-1.1.0/commkit/analysis/drift.py +0 -225
  196. commkit-1.1.0/commkit/analysis/interferometry.py +0 -948
  197. commkit-1.1.0/commkit/analysis/linewidth.py +0 -472
  198. commkit-1.1.0/commkit/backend.py +0 -519
  199. commkit-1.1.0/commkit/core/generation.py +0 -770
  200. commkit-1.1.0/commkit/core/signal.py +0 -727
  201. commkit-1.1.0/commkit/equalization/_block/_blind.py +0 -371
  202. commkit-1.1.0/commkit/equalization/_block/_dd.py +0 -1162
  203. commkit-1.1.0/commkit/equalization/_block/_seqmode.py +0 -421
  204. commkit-1.1.0/commkit/equalization/_common.py +0 -588
  205. commkit-1.1.0/commkit/equalization/_kernels_jax.py +0 -1720
  206. commkit-1.1.0/commkit/equalization/result.py +0 -193
  207. commkit-1.1.0/commkit/equalization/sequential/_blind.py +0 -1089
  208. commkit-1.1.0/commkit/equalization/sequential/_dd.py +0 -1980
  209. commkit-1.1.0/commkit/frequency.py +0 -1451
  210. commkit-1.1.0/commkit/helpers.py +0 -1108
  211. commkit-1.1.0/commkit/impairments/frontend.py +0 -246
  212. commkit-1.1.0/commkit/io.py +0 -308
  213. commkit-1.1.0/commkit/mapping/__init__.py +0 -58
  214. commkit-1.1.0/commkit/mapping/bits.py +0 -273
  215. commkit-1.1.0/commkit/mapping/constellation.py +0 -147
  216. commkit-1.1.0/commkit/mapping/llr.py +0 -302
  217. commkit-1.1.0/commkit/mapping/shaping.py +0 -257
  218. commkit-1.1.0/commkit/metrics.py +0 -1041
  219. commkit-1.1.0/commkit/multirate.py +0 -434
  220. commkit-1.1.0/commkit/plotting/constellation.py +0 -483
  221. commkit-1.1.0/commkit/recovery/__init__.py +0 -51
  222. commkit-1.1.0/commkit/recovery/_common.py +0 -119
  223. commkit-1.1.0/commkit/recovery/bps.py +0 -376
  224. commkit-1.1.0/commkit/recovery/pilots.py +0 -866
  225. commkit-1.1.0/commkit/recovery/pll.py +0 -516
  226. commkit-1.1.0/commkit/recovery/tikhonov.py +0 -434
  227. commkit-1.1.0/commkit/recovery/viterbi_viterbi.py +0 -215
  228. commkit-1.1.0/examples/carrier_phase_analysis.py +0 -378
  229. commkit-1.1.0/examples/laser_linewidth_dsh.py +0 -498
  230. commkit-1.1.0/examples/laser_linewidth_homodyne_iq.py +0 -358
  231. commkit-1.1.0/examples/measurement_laser_linewidth_dsh.py +0 -206
  232. commkit-1.1.0/examples/measurement_laser_linewidth_homodyne_iq.py +0 -212
  233. commkit-1.1.0/tests/analysis/test_allan.py +0 -25
  234. commkit-1.1.0/tests/analysis/test_drift.py +0 -44
  235. commkit-1.1.0/tests/analysis/test_interferometry.py +0 -289
  236. commkit-1.1.0/tests/analysis/test_linewidth.py +0 -135
  237. commkit-1.1.0/tests/analysis/test_trajectory.py +0 -97
  238. commkit-1.1.0/tests/core/test_frame.py +0 -484
  239. commkit-1.1.0/tests/core/test_generation.py +0 -58
  240. commkit-1.1.0/tests/core/test_psqam.py +0 -345
  241. commkit-1.1.0/tests/core/test_signal.py +0 -904
  242. commkit-1.1.0/tests/core/test_signal_mimo.py +0 -197
  243. commkit-1.1.0/tests/equalization/test_block.py +0 -1054
  244. commkit-1.1.0/tests/equalization/test_block_update.py +0 -373
  245. commkit-1.1.0/tests/equalization/test_bps_kernel.py +0 -252
  246. commkit-1.1.0/tests/equalization/test_cpr.py +0 -961
  247. commkit-1.1.0/tests/equalization/test_cs_kernel.py +0 -173
  248. commkit-1.1.0/tests/equalization/test_sequential_jax.py +0 -560
  249. commkit-1.1.0/tests/equalization/test_winit.py +0 -407
  250. commkit-1.1.0/tests/mapping/test_bits.py +0 -159
  251. commkit-1.1.0/tests/mapping/test_constellation.py +0 -68
  252. commkit-1.1.0/tests/mapping/test_gray.py +0 -187
  253. commkit-1.1.0/tests/mapping/test_llr.py +0 -330
  254. commkit-1.1.0/tests/recovery/test_bps.py +0 -169
  255. commkit-1.1.0/tests/recovery/test_corrections.py +0 -534
  256. commkit-1.1.0/tests/recovery/test_joint.py +0 -94
  257. commkit-1.1.0/tests/recovery/test_pll.py +0 -222
  258. commkit-1.1.0/tests/recovery/test_tikhonov.py +0 -295
  259. commkit-1.1.0/tests/recovery/test_viterbi_viterbi.py +0 -202
  260. commkit-1.1.0/tests/test_backend.py +0 -285
  261. commkit-1.1.0/tests/test_cuda_infra.py +0 -106
  262. commkit-1.1.0/tests/test_filtering.py +0 -542
  263. commkit-1.1.0/tests/test_helpers.py +0 -587
  264. commkit-1.1.0/tests/test_io.py +0 -410
  265. commkit-1.1.0/tests/test_logger.py +0 -11
  266. commkit-1.1.0/tests/test_metrics.py +0 -572
  267. commkit-1.1.0/tests/test_multirate.py +0 -111
  268. commkit-1.1.0/tests/test_plotting.py +0 -924
  269. commkit-1.1.0/tests/test_pulse_shaping.py +0 -154
  270. commkit-1.1.0/tests/test_smoothing.py +0 -54
  271. commkit-1.1.0/tests/test_spectral.py +0 -345
  272. commkit-1.1.0/tests/test_timing.py +0 -1033
  273. {commkit-1.1.0 → commkit-2.1.0}/.github/workflows/publish.yml +0 -0
  274. {commkit-1.1.0 → commkit-2.1.0}/.gitignore +0 -0
  275. {commkit-1.1.0 → commkit-2.1.0}/.python-version +0 -0
  276. {commkit-1.1.0 → commkit-2.1.0}/commkit/_cuda/src/bps_min_d2.cu +0 -0
  277. {commkit-1.1.0 → commkit-2.1.0}/commkit/_cuda/src/selftest.cu +0 -0
  278. {commkit-1.1.0 → commkit-2.1.0}/commkit/coding/base.py +0 -0
  279. {commkit-1.1.0 → commkit-2.1.0}/commkit/coding/bch.py +0 -0
  280. {commkit-1.1.0 → commkit-2.1.0}/commkit/coding/convolutional.py +0 -0
  281. {commkit-1.1.0 → commkit-2.1.0}/commkit/coding/crc.py +0 -0
  282. {commkit-1.1.0 → commkit-2.1.0}/commkit/coding/galois.py +0 -0
  283. {commkit-1.1.0 → commkit-2.1.0}/commkit/coding/hamming.py +0 -0
  284. {commkit-1.1.0 → commkit-2.1.0}/commkit/coding/interleaving.py +0 -0
  285. {commkit-1.1.0 → commkit-2.1.0}/commkit/coding/ldpc.py +0 -0
  286. {commkit-1.1.0 → commkit-2.1.0}/commkit/coding/polar.py +0 -0
  287. {commkit-1.1.0 → commkit-2.1.0}/commkit/coding/ratematch.py +0 -0
  288. {commkit-1.1.0 → commkit-2.1.0}/commkit/coding/reed_solomon.py +0 -0
  289. {commkit-1.1.0 → commkit-2.1.0}/commkit/coding/turbo.py +0 -0
  290. {commkit-1.1.0 → commkit-2.1.0}/commkit/impairments/channel/__init__.py +0 -0
  291. {commkit-1.1.0 → commkit-2.1.0}/commkit/impairments/channel/nonlinear.py +0 -0
  292. {commkit-1.1.0 → commkit-2.1.0}/commkit/py.typed +0 -0
@@ -0,0 +1 @@
1
+ *.ipynb filter=nbstripout diff=ipynb
@@ -38,6 +38,22 @@ jobs:
38
38
  - name: Type check
39
39
  run: uv run mypy commkit/
40
40
 
41
+ notebooks:
42
+ name: Notebooks carry no outputs
43
+ # Outputs (plots are base64 PNGs) would bloat the repository. The
44
+ # nbstripout git filter strips them on commit once a clone runs
45
+ # 'uv run nbstripout --install'; this required check catches a clone
46
+ # without it before anything reaches main.
47
+ runs-on: ubuntu-latest
48
+ steps:
49
+ - uses: actions/checkout@v4
50
+
51
+ - name: Install uv
52
+ uses: astral-sh/setup-uv@v5
53
+
54
+ - name: Every committed notebook is stripped
55
+ run: uvx nbstripout@0.9.1 --verify $(git ls-files '*.ipynb')
56
+
41
57
  test:
42
58
  name: Test (Python ${{ matrix.python-version }} | ${{ matrix.resolution }})
43
59
  runs-on: ubuntu-latest
@@ -68,7 +84,38 @@ jobs:
68
84
  run: uv sync --python ${{ matrix.python-version }} --resolution lowest-direct
69
85
 
70
86
  - name: Run tests (CPU) with coverage
71
- run: uv run pytest --device=cpu --cov=commkit --cov-report=term-missing
87
+ # Floor = total at the start of the 2.0 work; raise it, never lower it.
88
+ run: uv run pytest --device=cpu --cov=commkit --cov-report=term-missing --cov-fail-under=78
89
+
90
+ kernel-coverage:
91
+ name: Numba kernel coverage (JIT disabled)
92
+ # Compiled Numba code is invisible to coverage, so the main job reports the
93
+ # kernels as almost untested. Here the same tests run the kernels as plain
94
+ # Python, measuring their real line coverage.
95
+ runs-on: ubuntu-latest
96
+ steps:
97
+ - uses: actions/checkout@v4
98
+
99
+ - name: Install uv
100
+ uses: astral-sh/setup-uv@v5
101
+ with:
102
+ enable-cache: true
103
+
104
+ - name: Set up Python
105
+ run: uv python install 3.12
106
+
107
+ - name: Sync environment
108
+ run: uv sync
109
+
110
+ - name: Run kernel tests with JIT disabled
111
+ env:
112
+ NUMBA_DISABLE_JIT: "1"
113
+ run: uv run pytest tests/equalization tests/recovery tests/test_frequency.py --device=cpu --cov=commkit --cov-report=
114
+
115
+ - name: Kernel coverage report
116
+ run: >-
117
+ uv run coverage report --show-missing --fail-under=85
118
+ --include="commkit/equalization/_kernels_numba.py,commkit/equalization/sequential/*,commkit/frequency.py,commkit/recovery/*"
72
119
 
73
120
  # GPU legs (--device=gpu / --device=all) require CuPy + CUDA and are run
74
121
  # manually or on a self-hosted runner; they are intentionally not part of
@@ -0,0 +1,264 @@
1
+ # AGENTS.md
2
+
3
+ Guide for anyone, human or coding agent, changing the **CommKit** repository.
4
+
5
+ The rules below describe the code as it is. New code follows them; a change that breaks one changes this file in the same commit.
6
+
7
+ ---
8
+
9
+ ## 1. What CommKit is
10
+
11
+ A Python library for digital-communications research: simulation, receiver DSP, metrics, plotting and experimental captures, on NumPy (CPU) and CuPy (GPU) data. It is **not differentiable**. A separate JAX library, *commax*, will hold the blocks that must sit inside a gradient. commax depends on commkit, never the reverse, and takes its conventions (`Constellation`, bit labels, pulse taps, frame layouts) from commkit.
12
+
13
+ Users are researchers and engineers. Design for them:
14
+
15
+ - **Algorithms are plain functions.** Data is a `Signal` or an array. Things that *describe* a modulation, a pulse or an embedded sub-algorithm are small frozen value objects written inline in the call:
16
+
17
+ ```python
18
+ res = lms(rx, num_taps=21, step_size=1e-3, cpr=BPS(test_phases=64))
19
+ ```
20
+
21
+ - **Everything is expressed in Python.** No configuration files, registries, global switches or multi-step setup.
22
+ - **We deliberately do not build** pipeline or receiver objects, DSP methods on `Signal`, string shorthands for constellations, or public "config objects per algorithm".
23
+
24
+ ---
25
+
26
+ ## 2. Commands
27
+
28
+ Always use `uv`. Python 3.12+.
29
+
30
+ ```bash
31
+ uv sync --all-extras # environment incl. GPU packages
32
+ uv run nbstripout --install # once per clone: strip notebook outputs on commit
33
+ uv run pytest # CPU + GPU tests (default --device=all)
34
+ uv run pytest --device=cpu # what CI runs
35
+ uv run pytest tests/equalization/ -k lms # subset
36
+ uv run pytest --cov=commkit # coverage
37
+ NUMBA_DISABLE_JIT=1 uv run pytest tests/equalization tests/recovery \
38
+ tests/test_frequency.py --device=cpu --cov=commkit # real kernel coverage
39
+ ```
40
+
41
+ **Commit gate.** CI fails on any of these, so run all of them before every commit, with `ruff format` last:
42
+
43
+ ```bash
44
+ uv run ruff check . --fix
45
+ uv run mypy commkit/
46
+ uv run pytest
47
+ uv run ruff format .
48
+ ```
49
+
50
+ **Benchmarks** are explicit-only; `uv run pytest` never collects them. A commit that changes a public signature also smoke-runs them once, without timing, so their call sites cannot rot unnoticed:
51
+
52
+ ```bash
53
+ uv run pytest benchmarks/ --benchmark-disable --device=all
54
+ ```
55
+
56
+ Timed runs:
57
+
58
+ ```bash
59
+ uv run pytest benchmarks/bench_bps.py --benchmark-only --device=all
60
+ uv run pytest benchmarks/ --benchmark-only --device=all \
61
+ --benchmark-compare=0003 --benchmark-storage=file://benchmarks/baselines
62
+ uv run python benchmarks/record_baseline.py NAME # record a new baseline
63
+ ```
64
+
65
+ **Release:**
66
+
67
+ 1. On `main`: the CPU and GPU suites pass with no failures or skips (`uv run pytest -rs`), `uv build` succeeds and `uv run --with twine twine check dist/*` passes.
68
+ 2. `uv run bump-my-version bump {patch|minor|major}`. This commits the new version and tags it `vX.Y.Z`.
69
+ 3. `git push origin main --tags`.
70
+ 4. Publish a GitHub Release for the tag (`gh release create vX.Y.Z --generate-notes`). Publishing the release runs `.github/workflows/publish.yml`, which builds and uploads to PyPI through Trusted Publishing; pushing a tag alone publishes nothing.
71
+
72
+ ---
73
+
74
+ ## 3. Architecture
75
+
76
+ Dependencies point downward only:
77
+
78
+ | Layer | Modules | May import |
79
+ | --- | --- | --- |
80
+ | Infrastructure | `backend`, `logger`, `_cuda` | NumPy, CuPy |
81
+ | Array and math helpers | `_array` (shape and validation), `math` (power, normalization, dB, linear trend) | infrastructure |
82
+ | Value objects | `mapping.Constellation`, pulse classes, `recovery` CPR configs | layers above |
83
+ | Core | `core` (`Signal`, frames, generation), `io` | layers above |
84
+ | DSP | `mapping`, `filtering`, `multirate`, `spectral`, `smoothing`, `impairments`, `timing`, `frequency`, `recovery`, `equalization`, `metrics`, `analysis` | layers above, and lower DSP modules |
85
+ | Presentation | `plotting` | everything; **nothing imports plotting** |
86
+
87
+ - **Responsibilities:**
88
+ - `filtering` holds signal-chain filters with a real frequency response.
89
+ - `smoothing` holds display and estimation smoothers only.
90
+ - `analysis` holds laser and phase characterization.
91
+ - `metrics` is the reporting layer.
92
+ - **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.
93
+ - **Placeholder modules are kept on purpose.** `commkit.coding` and `commkit.impairments.channel.nonlinear` are reminders of planned work. Never delete them or treat them as dead code. They are excluded from coverage and from the contract registry, and the README labels them "planned, not implemented".
94
+
95
+ ---
96
+
97
+ ## 4. API rules
98
+
99
+ ### Signal
100
+
101
+ - `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.
102
+ - It never holds pipeline results.
103
+ - Construction does no hidden work: no mapping, no normalization, no device move, no shape guessing. Factories (`generate`, frames) do that work.
104
+ - `sig.to("gpu")` and `sig.replace(...)` return new Signals. Arrays are shared, not copied; use `clone()` for an independent copy.
105
+ - **Alignment invariant:** reference symbol *k* corresponds to symbol period *k* of the samples. Any function that drops or shifts symbol periods slices the reference to match. Metrics raise on mismatch; they never guess.
106
+
107
+ ### Function signatures
108
+
109
+ - `f(data, other_data, *, params)`: everything after the data arguments is keyword-only. A function without data arguments may keep its single primary parameter positional (`barker_sequence(13)`, `gray_code(5)`); with several parameters, all are keyword-only (`rrc_taps(sps=2, rolloff=0.1, span=10)`). The data-argument count is recorded per function in the contract registry.
110
+ - **Signal awareness.** A function whose main argument is waveform data (or a field a Signal really carries) accepts an array or a Signal. It unwraps once with `adapt_signal()` (in `core/_signal_adapter.py`), works on arrays, and returns through `wrap_samples()`. Never re-enter the public function recursively.
111
+ - Functions on *derived* quantities (phase trajectories, correlations, taps, PSD bins, results) and synthesis primitives stay array-only.
112
+ - **Metadata rule: facts versus choices.**
113
+ - Facts (`sampling_rate`, `symbol_rate`, and so `sps`) come from the Signal. A conflicting argument raises `ValueError`; an equal one is fine.
114
+ - Choices (decision `constellation`, `noise_var`, `pulse`) default to the Signal's value, and an explicit argument wins.
115
+ - A missing fact for array input raises. Neither case warns.
116
+ - Integer-SPS algorithms validate with `require_integer_sps()` before any cast. Never truncate 1.5 SPS to 1.
117
+ - **Value objects are frozen dataclasses** with read-only NumPy arrays:
118
+ - `Constellation`: `.qam(M)`, `.psk(M)`, `.pam(M, unipolar=)`, or arbitrary `points`; `.shaped(nu=|entropy=)` keeps unit power. Functions accept the object only, never strings.
119
+ - Pulses: `RRC`, `RC`, `Gaussian`, `Rect`, `SmoothRect`. A raw taps array is accepted wherever a pulse is.
120
+ - Algorithm objects, one per estimation method (`BPS`, `PLL`, `ViterbiViterbi`, `MthPower`, `CycleSlip`, ...). The same object is used standalone (`correct_carrier_phase(y, BPS())`) and nested (`lms(..., cpr=BPS())`). It lives next to its kernel and validates its parameters on construction. Class names are unique across the package.
121
+ - Equalizer continuation uses `state=result.state`.
122
+ - **Randomness:** `rng: int | np.random.Generator | None` (SciPy SPEC 7).
123
+ - What is transmitted (bits, symbols, pilots): draw on the host with the Generator, then transfer. A seed gives the same payload on CPU and GPU.
124
+ - What the channel adds (AWGN, phase noise): draw on the data's device through `_random.standard_normal`. Realizations differ between devices; statistics agree, and tests assert statistics.
125
+ - Never use a global RNG.
126
+ - **Types:** a transform is written `def f(samples: S, ...) -> S` with the TypeVar `S` from `core/_signal_adapter.py` (bound to `np.ndarray | Signal`), so a Signal gives a Signal and an array an array. Do not reassign the `samples` parameter; name the unwrapped array `x`.
127
+ - **Device follows the data.** There are no `backend=` arguments and no global switches. Unsupported array types (JAX, PyTorch) raise `TypeError`.
128
+ - Only factories with no input data to follow (`generate`, `generate_phase_noise`, `Preamble`/`SingleCarrierFrame.to_signal`, `load_npz`) take `device: str = "cpu"`, validated with `require_device()`. They draw randomness as the rule above says and do the vectorized work (mapping, shaping) on `device`. The default is never "GPU if available".
129
+ - **No `debug_plot`.** Numerical code never imports plotting. Plot functions consume results or recompute through public compute functions.
130
+ - **Design and apply are separate functions**, for example `rrc_taps` and `fir_filter`, or `butterworth_sos` and `iir_filter`.
131
+
132
+ ### Returns
133
+
134
+ - **Transforms:** array in gives array out on the same device; Signal in gives a new Signal out (update `sampling_rate` when the rate changes).
135
+ - **Estimators** reduce over the time axis by input rank: `(N,)` gives 0-d and `(C, N)` gives `(C,)`. Results stay on the input device.
136
+ - **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.
137
+ - **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.
138
+ - **More than one value** is returned as a frozen dataclass with named fields. No dicts, and no tuples longer than two.
139
+ - **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.
140
+
141
+ ### Naming
142
+
143
+ | Prefix | Meaning |
144
+ | --- | --- |
145
+ | `apply_*` | add an impairment or apply a model |
146
+ | `estimate_<quantity>(x, method)` | measure, never change the data; returns a frozen `<Quantity>Estimate` (value plus diagnostics) |
147
+ | `correct_<quantity>(x, how)` | remove the impairment; returns only the corrected data. `how` is an estimate or an algorithm object (which estimates first) |
148
+ | `resolve_*` | pick among discrete candidates (π/2 rotation, channel permutation); nothing else |
149
+ | `generate` | synthesis from parameters, takes `rng` |
150
+ | `plot_*` | every public plotting function (only `apply_default_theme` is exempt) |
151
+
152
+ - 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_*`.
153
+ - Computations use plain nouns (`allan_deviation`).
154
+ - **Glossary**, one name each:
155
+ - `num_taps`, `step_size`, `sps`, `constellation`, `pulse`, `rng`, `num_symbols`, `num_channels`, `sampling_rate`, `symbol_rate`.
156
+ - Units are in the docstring. Use `_db` and `_hz` suffixes where the unit is ambiguous.
157
+
158
+ ### Errors and logging
159
+
160
+ - Wrong input raises, with a message naming the argument.
161
+ - `logger` is for diagnostics only. Per-channel logging that needs a device-to-host transfer is wrapped in `if logger.isEnabledFor(logging.INFO):`.
162
+ - The library never configures handlers, Matplotlib or warning filters at import time.
163
+
164
+ ---
165
+
166
+ ## 5. Numerics
167
+
168
+ - **Storage dtypes:** `complex64` for IQ samples and `float32` for real signals.
169
+ - **Accumulation precision:**
170
+ - LMS/CMA/RDE: inputs and weights are `complex64`, but dot products and gradient updates in the hot loop are accumulated in `complex128`.
171
+ - RLS keeps `P`, the gain `k` and the regressor buffers in `complex128` throughout. Single precision loses the Hermitian positive-definite property and the filter diverges.
172
+ - Carrier phase: promote angles to `float64` before `xp.unwrap()`, because float32 rounding causes spurious quadrant slips. Tikhonov/Kalman block transitions also run in `float64`.
173
+ - **Normalization invariant:** at `sps` samples per symbol, `E[|x|²] = 1/sps`; at symbol rate, `E[|x|²] = 1`. Any rate-changing block applies the exact gain correction.
174
+ - **Shapes:** SISO is `(N,)` and MIMO is `(C, N)`. Use `as_2d`, `restore_1d`, `broadcast_channels` and `require_channels`; never hand-roll the promote/squeeze idiom.
175
+ - **Reproducibility:** a seed is reproducible within a library version only. Tests and baselines assert statistics, never exact noise realizations.
176
+
177
+ ---
178
+
179
+ ## 6. Performance
180
+
181
+ | Algorithm structure | Implementation | Device |
182
+ | --- | --- | --- |
183
+ | Vectorized: filters, resampling, spectra, impairments, metrics, LLR, frequency-domain equalizers | NumPy/CuPy via `x, xp, sp = dispatch(samples)` | follows the input |
184
+ | Sequential recursions: LMS/RLS/CMA/RDE, PLL, Tikhonov, cycle slips | Numba `@njit(cache=True, fastmath=True, nogil=True)` | CPU; GPU input does one round trip to the host and back |
185
+ | Hot spots limited by intermediates, launches or round trips | CUDA C++ via CuPy `RawModule` (`commkit/_cuda`) | GPU |
186
+
187
+ **Host-sync hygiene:**
188
+
189
+ - 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.
190
+ - Prefer on-device gathers (`xp.take_along_axis`).
191
+ - Chunk `(N, M)` intermediates over N with an on-device accumulator.
192
+ - Reduce on the device before transferring, including for plotting.
193
+ - Pass whole records to compiled code. There are no Python loops over symbols or small blocks, except inside a CUDA graph.
194
+
195
+ **Kernel policy:**
196
+
197
+ - A `.cu` kernel needs a benchmark showing that CuPy is limited by intermediates, launch count or round trips.
198
+ - It optimizes a CPU reference (Numba or NumPy) and is tested against it with `--device=all`.
199
+ - Wrappers check dtype, contiguity, shape and size before launch.
200
+ - `float32` with explicit literals.
201
+ - `--use_fast_math` only per kernel (through `options`), after an accuracy check; the compiler default does not pass it.
202
+ - Short elementwise chains use `ElementwiseKernel` or `cupy.fuse`.
203
+ - Sequential recursions over a whole record are never ported to the GPU; one record has no parallel work per step. A short per-block scan may be, as one launch, when it replaces a host round trip per block (`cs_block`, `bps_anchor`); keep its parallel part parallel (float64 is 1/64 of float32 throughput on consumer GPUs).
204
+
205
+ ---
206
+
207
+ ## 7. Tests
208
+
209
+ - **Fixtures:** use `backend_device`, `xp` and `xpt` from `tests/conftest.py`. Assert with `xpt.assert_allclose(...)`, because `numpy.testing` raises on CuPy arrays. Cast expectations with `xp.asarray`, and reductions with `float(...)`.
210
+ - **Layout mirrors the source:** `tests/<subpackage>/test_<module>.py`, and `tests/test_<module>.py` for flat modules. Test basenames must be globally unique (pytest prepend import mode), hence names like `channel/test_channel_linear.py`. Split a large test file by concern.
211
+ - **Shared builders** live in `tests/common/` and use the public API.
212
+ - **Contract registry** (`tests/test_api_contracts.py`): every public function has a row. Generic checks run per row: array in/out, Signal in/out, device preserved, input not mutated, keyword-only parameters, `TypeError` on unsupported arrays, fact-conflict errors, return rules.
213
+ - Do not duplicate these checks in per-module tests.
214
+ - **Oracles:** sequential kernels are tested against plain-Python reference implementations in `tests/common/reference_impl.py`. CUDA kernels are tested against their CPU reference.
215
+ - **Mathematical meaning:** test documented definitions, such as a pulse width, a dispersion sign, or noise-variance scaling, with independently derived expectations, not only shapes and energies.
216
+ - **API changes never change expected values.** A commit that changes a signature only changes how tests call the code. Numerical changes land in their own commit with independent validation.
217
+ - **Examples** are Jupyter notebooks in `examples/`, committed without outputs: `.gitattributes` routes them through the `nbstripout` filter, which each clone enables once with `uv run nbstripout --install` (git does not ship filter configuration). `tests/test_examples.py` executes each one, so an API change updates the notebooks in the same commit.
218
+ - **Coverage** must not drop below the CI floor. Numba kernel coverage is measured by a separate `NUMBA_DISABLE_JIT=1` job.
219
+
220
+ ---
221
+
222
+ ## 8. Benchmarks
223
+
224
+ `benchmarks/` tracks the GPU-relevant hot paths. Baselines are committed under `benchmarks/baselines/`. The current reference is `0003` (`v2_0`) on the reference machine: RTX 4070 Ti, Ryzen 7 7800X3D, WSL2; `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.
225
+
226
+ - **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`.
227
+ - **Baselines** are recorded with `benchmarks/record_baseline.py`: each file in its own process, best of three passes. 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.
228
+
229
+ - **IDs** are `[cpu]` or `[gpu]`, meaning the input device.
230
+ - **Timing:**
231
+ - Timed bodies end with the `sync` fixture; otherwise GPU timings measure launches, not execution.
232
+ - Each benchmark has one warmup round, so compilation and pool growth are excluded.
233
+ - **Workloads** come from `benchmarks/workloads.py` with fixed seeds; never generate data inline.
234
+ - **Tools:** `benchmarks/benchutils.py` provides `CudaEventTimer` and `nvtx_range` for `nsys` profiling.
235
+ - **Logging** is set to WARNING in `benchmarks/conftest.py`.
236
+ - **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.
237
+ - A commit touching a hot path quotes its delta against `0003`. Tolerance is 5% on CPU and 10% on GPU.
238
+
239
+ ---
240
+
241
+ ## 9. Roadmap and open items
242
+
243
+ What is not done yet, in suggested order. Unlike the sections above, this is a to-do list, not a description of the code: remove an item in the commit that resolves it, and add one when a known gap is left open.
244
+
245
+ ### Planned modules
246
+
247
+ 1. **Nonlinear fiber channel** (`impairments.channel.nonlinear`, a placeholder). Split-step Fourier propagation of the Kerr effect together with chromatic dispersion, then digital backpropagation as its compensation counterpart in `filtering`. Comes first because it builds on what exists: the dispersion operator (`_dispersion`), overlap-save and FFT-heavy GPU code. It completes the coherent optical link model.
248
+ 2. **Channel coding** (`coding`, a placeholder package). 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.
249
+
250
+ ### Usability gaps
251
+
252
+ - **`block_lms` default `step_size`** (2e-4) converges too slowly under strong polarization mixing (30 degrees needs about 3e-3 at `block_size=256`), and the stable step falls as `block_size` grows (5e-4 at 2048). Consider a default that scales with `block_size`, or at least a clearer warning when the error does not drop.
253
+ - **RDE from a cold start** on 16-QAM settles at a few percent SER at large block sizes. CMA pre-convergence works today in two calls (`initial_taps=` or `state=` from a `cma`/`block_cma` result); a worked example in the docs or notebooks would make it discoverable.
254
+
255
+ ### Performance
256
+
257
+ - **Exact LLRs on the CPU**: 256-QAM takes about 330 ms per 2^18 symbols (max-log about 18 ms), dominated by one `exp` per point and bit.
258
+ - **Block equalizers at small `block_size` on the GPU**: a fixed cost per block; at 256 the GPU only ties the CPU without carrier recovery.
259
+ - **`cs_block` on phases with frequent slip decisions**: one speculation pass per changed decision (about 1 pass per block on a realistic link, more on pathological input). Outside a CUDA graph the Python wrapper (validation, scratch allocation) costs about 18 us per launch.
260
+
261
+ ### Known limitations
262
+
263
+ - The DSH-input warning of `estimate_linewidth` misses a drift-removed phase residual when `f_shift` is unknown: a real-valued record is valid input for both kinds of method, so the type alone cannot tell them apart.
264
+ - Blind carrier recovery on 8-PSK near ±π/8 can lock to the wrong rotation. Inherent to blind recovery; training symbols or `resolve_phase_ambiguity` resolve it.
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 lokgar
3
+ Copyright (c) 2026 Denis Fatkhiev
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
commkit-2.1.0/PKG-INFO ADDED
@@ -0,0 +1,190 @@
1
+ Metadata-Version: 2.5
2
+ Name: commkit
3
+ Version: 2.1.0
4
+ Summary: A Python library for high-performance digital communications research.
5
+ Project-URL: Homepage, https://github.com/lokgar/commkit
6
+ Project-URL: Repository, https://github.com/lokgar/commkit
7
+ Project-URL: Issues, https://github.com/lokgar/commkit/issues
8
+ Author: Denis Fatkhiev
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: CuPy,DSP,GPU,coherent optics,digital communications,equalization
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Communications
21
+ Classifier: Topic :: Scientific/Engineering
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.12
24
+ Requires-Dist: matplotlib>=3.11
25
+ Requires-Dist: numba>=0.65
26
+ Requires-Dist: numpy>=2.4
27
+ Requires-Dist: scipy>=1.17
28
+ Provides-Extra: full
29
+ Requires-Dist: cupy-cuda13x[ctk]>=14.1; extra == 'full'
30
+ Requires-Dist: jupyter>=1.1.1; extra == 'full'
31
+ Provides-Extra: gpu
32
+ Requires-Dist: cupy-cuda13x[ctk]>=14.1; extra == 'gpu'
33
+ Provides-Extra: notebook
34
+ Requires-Dist: jupyter>=1.1.1; extra == 'notebook'
35
+ Description-Content-Type: text/markdown
36
+
37
+ # CommKit
38
+
39
+ **Digital-communications research kit for Python, on CPU and GPU.**
40
+
41
+ ![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue)
42
+ ![Backends](https://img.shields.io/badge/backend-NumPy%20%7C%20CuPy-orange)
43
+ ![License](https://img.shields.io/badge/license-MIT-green)
44
+ ![CUDA](https://img.shields.io/badge/CUDA-13.x-76B900?logo=nvidia)
45
+
46
+ CommKit covers the receiver chain of coherent and IM/DD links (waveform generation, channel impairments, synchronization, carrier recovery, adaptive equalization, metrics, laser characterization and plotting) on NumPy or CuPy arrays. Computation runs on the device the data lives on.
47
+
48
+ ---
49
+
50
+ ## The model
51
+
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
+
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.
56
+ - **Results with several values are frozen dataclasses** with named fields. Metrics return host floats: one per channel for `(C, N)` input.
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
+ - **Plots draw results.** `plot_*` functions take a Signal, an array or a result object; numerical code never imports plotting.
59
+ - There is no pipeline object, receiver class or configuration file: the orchestration stays in your script.
60
+
61
+ ## Quickstart
62
+
63
+ ```python
64
+ import commkit as ck
65
+ from commkit import RRC, Constellation
66
+ from commkit.recovery import BPS, CycleSlip
67
+
68
+ tx = ck.generate(Constellation.qam(16), num_symbols=2**16, symbol_rate=32e9,
69
+ sps=2, pulse=RRC(rolloff=0.1), rng=1, device="gpu")
70
+ rx = ck.impairments.apply_phase_noise(tx, linewidth=100e3, rng=2)
71
+ rx = ck.impairments.apply_awgn(rx, esn0_db=18, rng=3) # on the GPU, like tx
72
+ rx = ck.filtering.matched_filter(rx) # pulse from rx
73
+
74
+ cpr = BPS(test_phases=64, cycle_slip=CycleSlip(history=100))
75
+ res = ck.equalization.lms(
76
+ rx, num_taps=21, step_size=1e-3,
77
+ training_symbols=rx.reference.symbols[..., :2000], cpr=cpr,
78
+ )
79
+ y = res.signal # 1-SPS Signal, reference aligned
80
+ print(f"EVM {ck.metrics.evm(y, num_skip_symbols=2000):.1f} %, "
81
+ f"BER {ck.metrics.ber(y, num_skip_symbols=2000):.1e}")
82
+ # EVM 12.8 %, BER 2.5e-04
83
+
84
+ # The next record continues from the converged taps and CPR state.
85
+ res2 = ck.equalization.lms(rx, num_taps=21, step_size=1e-3, cpr=cpr, state=res.state)
86
+ ```
87
+
88
+ Without CuPy, pass `device="cpu"` (the default): the same code runs on the CPU.
89
+
90
+ ## CPU and GPU
91
+
92
+ CommKit never picks a device for you: data stays where you put it, and each function runs on the device of its input. A few habits get the speed out of a GPU:
93
+
94
+ - **Put data on the GPU once, at the source.** Build it there with `generate(..., device="gpu")`, `frame.to_signal(..., device="gpu")` or `load_npz(path, device="gpu")`; move a capture with `sig.to("gpu")`. Do not move data back and forth between stages. Building on the GPU is faster than `generate(...).to("gpu")`: about 26 ms instead of 370 ms for 4M symbols, because mapping and pulse shaping run there.
95
+ - **You do not need to move results back.** Metrics (`evm`, `ber`, ...) and analysis summaries return host floats, and plots reduce on the device and transfer only what they draw.
96
+ - **Know what runs where.**
97
+
98
+ | Work | GPU input |
99
+ | --- | --- |
100
+ | Generation, filters, resampling, spectra, impairments, BPS, LLRs, metrics, analysis | runs on the GPU |
101
+ | Frequency-domain equalizers `block_lms`, `block_cma`, `block_rde` | runs on the GPU (CUDA graphs) |
102
+ | Sequential equalizers `lms`, `rls`, `cma`, `rde`; `PLL`, `Tikhonov`, cycle-slip correction | runs on the CPU (Numba): one copy to the host and back, no speedup |
103
+
104
+ A sample-by-sample recursion has no parallel work per step. For long records on the GPU, prefer the block equalizers with `block_size=1024` or more. Each block costs a fixed overhead: at 256 the GPU only ties the CPU without carrier recovery, at 2048 it is 6-9x faster. A larger block also lowers the stable `step_size`.
105
+ - **Short records do not pay off.** Launch overhead dominates below roughly 10⁴-10⁵ samples, where the CPU is as fast.
106
+ - **Batch channels and records.** Pass `(C, N)` arrays rather than looping over channels in Python: one call does the work of C.
107
+ - **Keep scalars on the device inside loops.** `float(x)`, `x.item()` and `if x > 0:` on a CuPy array wait for the GPU and copy. Collect values in an array and transfer once.
108
+ - **The first call compiles.** Numba and CUDA kernels compile on first use and are cached on disk. Warm up once before timing anything.
109
+ - **Seeds and devices.** The same `rng` gives the same bits and symbols on both devices. Channel noise (AWGN, phase noise) is drawn on the device, so its realization differs between CPU and GPU while its statistics match. For the exact same phase-noise trajectory on both, draw it on the CPU with `generate_phase_noise(...)` and move it.
110
+
111
+ ---
112
+
113
+ ## Modules
114
+
115
+ | Module | Contents |
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`. |
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
+ | [`commkit.filtering`](https://github.com/lokgar/commkit/blob/main/commkit/filtering.py) | 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. |
120
+ | [`commkit.multirate`](https://github.com/lokgar/commkit/blob/main/commkit/multirate.py) | `resample` (fractional), `decimate`, `upsample`, `decimate_to_symbol_rate`. |
121
+ | [`commkit.spectral`](https://github.com/lokgar/commkit/blob/main/commkit/spectral.py) | Welch PSD, spectrograms, frequency shifting, pilot tones. |
122
+ | [`commkit.impairments`](https://github.com/lokgar/commkit/tree/main/commkit/impairments) | AWGN, phase noise, IQ imbalance (with Löwdin and Gram-Schmidt correction), chromatic dispersion, PMD and polarization mixing. Nonlinear channel models: **planned, not implemented**. |
123
+ | [`commkit.timing`](https://github.com/lokgar/commkit/blob/main/commkit/timing.py) | Barker and Zadoff-Chu sequences, `estimate_timing` / `correct_timing`, fractional delay estimation and correction. |
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
+ | [`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
+ | [`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. |
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
+ | [`commkit.math`](https://github.com/lokgar/commkit/blob/main/commkit/math.py) | `rms`, `normalize`, dB conversions. |
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
+ | [`commkit.io`](https://github.com/lokgar/commkit/blob/main/commkit/io.py) | `save_npz` / `load_npz` for Signals. |
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**. |
134
+
135
+ Importing `commkit` has no side effects: it configures no logging, Matplotlib or warning filters, and does not touch the GPU.
136
+
137
+ ---
138
+
139
+ ## Installation
140
+
141
+ **Requires Python 3.12+.**
142
+
143
+ ```bash
144
+ pip install commkit # CPU
145
+ pip install "commkit[gpu]" # CuPy with the CUDA 13 toolkit libraries
146
+ pip install "commkit[notebook]" # to run the example notebooks
147
+ pip install "commkit[full]" # everything
148
+ ```
149
+
150
+ With [`uv`](https://github.com/astral-sh/uv), use `uv pip install` in place of `pip install`. Extras combine, e.g. `commkit[gpu,notebook]`.
151
+
152
+ > [!NOTE]
153
+ > **WSL2 and CUDA.** With NVIDIA drivers and CUDA installed on Windows, there is no need to install CUDA inside WSL2. To let the Python CUDA packages find the bundled NVIDIA libraries, add to `~/.bashrc`:
154
+ >
155
+ > ```bash
156
+ > export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(echo $HOME/commkit/.venv/lib/python3.*/site-packages/nvidia/cu13/lib)
157
+ > ```
158
+ >
159
+ > *(This assumes the repository is cloned to `$HOME/commkit`; adjust the path otherwise.)*
160
+
161
+ ## Examples
162
+
163
+ Jupyter notebooks in [`examples/`](https://github.com/lokgar/commkit/tree/main/examples) (install the `notebook` extra and run `jupyter lab examples`):
164
+
165
+ - [`qam_receiver_quickstart`](https://github.com/lokgar/commkit/blob/main/examples/qam_receiver_quickstart.ipynb) - the quickstart above, cell by cell, with the constellation, spectrum and equalizer plots;
166
+ - [`carrier_phase_analysis`](https://github.com/lokgar/commkit/blob/main/examples/carrier_phase_analysis.ipynb) - drift, linewidth and Allan deviation of a recovered carrier phase;
167
+ - [`laser_linewidth_dsh`](https://github.com/lokgar/commkit/blob/main/examples/laser_linewidth_dsh.ipynb) and [`laser_linewidth_homodyne_iq`](https://github.com/lokgar/commkit/blob/main/examples/laser_linewidth_homodyne_iq.ipynb)
168
+ - laser linewidth from delayed self-heterodyne and homodyne IQ captures;
169
+ - `measurement_laser_linewidth_*` - lean templates for real captures.
170
+
171
+ The notebooks are committed without outputs and run in CI.
172
+
173
+ ## Development
174
+
175
+ ```bash
176
+ git clone https://github.com/lokgar/commkit.git
177
+ cd commkit
178
+ uv sync --all-extras
179
+ uv run nbstripout --install # once per clone: notebooks are committed without outputs
180
+
181
+ uv run pytest # CPU and GPU tests
182
+ uv run pytest --device=cpu # what CI runs
183
+ uv run ruff check . && uv run mypy commkit/
184
+ ```
185
+
186
+ Contributor and coding-agent guidance (architecture, API rules, numerics, performance and test conventions) is in [AGENTS.md](https://github.com/lokgar/commkit/blob/main/AGENTS.md).
187
+
188
+ ## License
189
+
190
+ [MIT](https://github.com/lokgar/commkit/blob/main/LICENSE)