commkit 2.1.0__tar.gz → 2.2.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 (239) hide show
  1. {commkit-2.1.0 → commkit-2.2.0}/.github/workflows/ci.yml +1 -1
  2. {commkit-2.1.0 → commkit-2.2.0}/.gitignore +13 -0
  3. {commkit-2.1.0 → commkit-2.2.0}/AGENTS.md +48 -17
  4. {commkit-2.1.0 → commkit-2.2.0}/PKG-INFO +11 -11
  5. {commkit-2.1.0 → commkit-2.2.0}/README.md +10 -10
  6. commkit-2.2.0/benchmarks/baselines/Linux-CPython-3.14-64bit/0004_fiber_dbp.json +3676 -0
  7. {commkit-2.1.0 → commkit-2.2.0}/benchmarks/bench_block_blind.py +3 -2
  8. commkit-2.2.0/benchmarks/bench_fiber.py +62 -0
  9. {commkit-2.1.0 → commkit-2.2.0}/benchmarks/workloads.py +17 -0
  10. {commkit-2.1.0 → commkit-2.2.0}/commkit/__init__.py +3 -1
  11. {commkit-2.1.0 → commkit-2.2.0}/commkit/_array.py +47 -10
  12. {commkit-2.1.0 → commkit-2.2.0}/commkit/_circular.py +5 -4
  13. commkit-2.2.0/commkit/_cuda/graphs.py +121 -0
  14. {commkit-2.1.0 → commkit-2.2.0}/commkit/_dispersion.py +57 -6
  15. commkit-2.2.0/commkit/_fft.py +80 -0
  16. commkit-2.2.0/commkit/_fiber.py +806 -0
  17. {commkit-2.1.0 → commkit-2.2.0}/commkit/_overlap_save.py +3 -2
  18. {commkit-2.1.0 → commkit-2.2.0}/commkit/_random.py +17 -0
  19. {commkit-2.1.0 → commkit-2.2.0}/commkit/analysis/drift.py +3 -3
  20. {commkit-2.1.0 → commkit-2.2.0}/commkit/analysis/fm_noise.py +2 -1
  21. {commkit-2.1.0 → commkit-2.2.0}/commkit/analysis/interferometry.py +6 -2
  22. {commkit-2.1.0 → commkit-2.2.0}/commkit/analysis/linewidth.py +95 -48
  23. {commkit-2.1.0 → commkit-2.2.0}/commkit/analysis/trajectory.py +2 -0
  24. {commkit-2.1.0 → commkit-2.2.0}/commkit/backend.py +12 -0
  25. {commkit-2.1.0 → commkit-2.2.0}/commkit/equalization/_block/_blind.py +9 -9
  26. {commkit-2.1.0 → commkit-2.2.0}/commkit/equalization/_block/_dd.py +34 -39
  27. {commkit-2.1.0 → commkit-2.2.0}/commkit/equalization/_block/_engine.py +22 -61
  28. {commkit-2.1.0 → commkit-2.2.0}/commkit/equalization/blind.py +8 -2
  29. {commkit-2.1.0 → commkit-2.2.0}/commkit/equalization/linear.py +55 -19
  30. {commkit-2.1.0 → commkit-2.2.0}/commkit/equalization/polarization.py +2 -1
  31. {commkit-2.1.0 → commkit-2.2.0}/commkit/equalization/result.py +29 -56
  32. {commkit-2.1.0 → commkit-2.2.0}/commkit/equalization/sequential/_blind.py +4 -4
  33. {commkit-2.1.0 → commkit-2.2.0}/commkit/equalization/sequential/_dd.py +3 -3
  34. {commkit-2.1.0 → commkit-2.2.0}/commkit/equalization/sequential/_setup.py +3 -2
  35. {commkit-2.1.0 → commkit-2.2.0}/commkit/filtering.py +153 -36
  36. {commkit-2.1.0 → commkit-2.2.0}/commkit/frequency.py +12 -26
  37. {commkit-2.1.0 → commkit-2.2.0}/commkit/impairments/__init__.py +6 -1
  38. {commkit-2.1.0 → commkit-2.2.0}/commkit/impairments/channel/__init__.py +6 -3
  39. {commkit-2.1.0 → commkit-2.2.0}/commkit/impairments/channel/linear.py +19 -19
  40. commkit-2.2.0/commkit/impairments/channel/nonlinear.py +180 -0
  41. {commkit-2.1.0 → commkit-2.2.0}/commkit/impairments/noise.py +2 -3
  42. {commkit-2.1.0 → commkit-2.2.0}/commkit/impairments/source.py +21 -11
  43. {commkit-2.1.0 → commkit-2.2.0}/commkit/mapping/constellation.py +74 -16
  44. {commkit-2.1.0 → commkit-2.2.0}/commkit/mapping/gray.py +0 -36
  45. commkit-2.2.0/commkit/mapping/llr.py +417 -0
  46. {commkit-2.1.0 → commkit-2.2.0}/commkit/math.py +69 -1
  47. {commkit-2.1.0 → commkit-2.2.0}/commkit/metrics.py +48 -37
  48. {commkit-2.1.0 → commkit-2.2.0}/commkit/plotting/analysis.py +1 -1
  49. {commkit-2.1.0 → commkit-2.2.0}/commkit/plotting/equalizer.py +3 -2
  50. {commkit-2.1.0 → commkit-2.2.0}/commkit/plotting/filter_response.py +2 -2
  51. {commkit-2.1.0 → commkit-2.2.0}/commkit/plotting/sync.py +2 -1
  52. {commkit-2.1.0 → commkit-2.2.0}/commkit/recovery/_common.py +6 -8
  53. {commkit-2.1.0 → commkit-2.2.0}/commkit/recovery/bps.py +14 -6
  54. {commkit-2.1.0 → commkit-2.2.0}/commkit/recovery/carrier_phase.py +4 -41
  55. {commkit-2.1.0 → commkit-2.2.0}/commkit/recovery/corrections.py +58 -11
  56. {commkit-2.1.0 → commkit-2.2.0}/commkit/recovery/pilots.py +10 -6
  57. {commkit-2.1.0 → commkit-2.2.0}/commkit/recovery/pll.py +1 -1
  58. {commkit-2.1.0 → commkit-2.2.0}/commkit/recovery/tikhonov.py +1 -1
  59. {commkit-2.1.0 → commkit-2.2.0}/commkit/recovery/viterbi_viterbi.py +1 -1
  60. {commkit-2.1.0 → commkit-2.2.0}/commkit/smoothing.py +3 -1
  61. {commkit-2.1.0 → commkit-2.2.0}/commkit/spectral.py +7 -21
  62. {commkit-2.1.0 → commkit-2.2.0}/commkit/timing.py +14 -18
  63. commkit-2.2.0/examples/00-getting-started.ipynb +352 -0
  64. commkit-2.2.0/examples/01-constellations-and-bit-mapping.ipynb +282 -0
  65. commkit-2.2.0/examples/02-spectral-analysis.ipynb +304 -0
  66. commkit-2.2.0/examples/03-digital-filters.ipynb +347 -0
  67. commkit-2.2.0/examples/04-pulse-shaping-and-matched-filtering.ipynb +387 -0
  68. commkit-2.2.0/examples/05-multirate-and-resampling.ipynb +340 -0
  69. commkit-2.2.0/examples/06-awgn-and-performance-metrics.ipynb +381 -0
  70. commkit-2.2.0/examples/07-laser-phase-noise.ipynb +319 -0
  71. commkit-2.2.0/examples/08-front-end-impairments.ipynb +294 -0
  72. commkit-2.2.0/examples/09-frame-synchronization-and-timing.ipynb +310 -0
  73. commkit-2.2.0/examples/10-frequency-offset-estimation.ipynb +397 -0
  74. commkit-2.2.0/examples/11-blind-carrier-phase-recovery.ipynb +367 -0
  75. commkit-2.2.0/examples/12-pilot-aided-carrier-recovery.ipynb +324 -0
  76. commkit-2.2.0/examples/13-linear-channel-equalization.ipynb +293 -0
  77. commkit-2.2.0/examples/14-adaptive-equalization.ipynb +433 -0
  78. commkit-2.2.0/examples/15-blind-equalization.ipynb +275 -0
  79. commkit-2.2.0/examples/16-polarization-multiplexing.ipynb +314 -0
  80. commkit-2.2.0/examples/17-chromatic-dispersion.ipynb +286 -0
  81. commkit-2.2.0/examples/18-nonlinear-fiber-and-backpropagation.ipynb +228 -0
  82. commkit-2.2.0/examples/19-coherent-transceiver-end-to-end.ipynb +397 -0
  83. commkit-2.1.0/examples/carrier_phase_analysis.ipynb → commkit-2.2.0/examples/20-carrier-phase-characterization.ipynb +22 -10
  84. commkit-2.1.0/examples/laser_linewidth_dsh.ipynb → commkit-2.2.0/examples/21-laser-linewidth-self-heterodyne.ipynb +32 -306
  85. commkit-2.2.0/examples/22-laser-linewidth-short-delay-and-flicker.ipynb +387 -0
  86. commkit-2.1.0/examples/laser_linewidth_homodyne_iq.ipynb → commkit-2.2.0/examples/23-laser-linewidth-self-homodyne-iq.ipynb +18 -8
  87. commkit-2.1.0/examples/measurement_laser_linewidth_dsh.ipynb → commkit-2.2.0/examples/24-measurement-template-self-heterodyne.ipynb +11 -11
  88. commkit-2.1.0/examples/measurement_laser_linewidth_homodyne_iq.ipynb → commkit-2.2.0/examples/25-measurement-template-self-homodyne-iq.ipynb +9 -9
  89. commkit-2.2.0/examples/README.md +63 -0
  90. {commkit-2.1.0 → commkit-2.2.0}/pyproject.toml +8 -5
  91. {commkit-2.1.0 → commkit-2.2.0}/tests/analysis/test_drift.py +10 -0
  92. {commkit-2.1.0 → commkit-2.2.0}/tests/analysis/test_interferometry.py +21 -0
  93. {commkit-2.1.0 → commkit-2.2.0}/tests/analysis/test_linewidth.py +82 -7
  94. {commkit-2.1.0 → commkit-2.2.0}/tests/common/reference_impl.py +5 -2
  95. {commkit-2.1.0 → commkit-2.2.0}/tests/common/signals.py +3 -3
  96. {commkit-2.1.0 → commkit-2.2.0}/tests/conftest.py +6 -1
  97. {commkit-2.1.0 → commkit-2.2.0}/tests/core/test_signal.py +1 -1
  98. {commkit-2.1.0 → commkit-2.2.0}/tests/equalization/test_blind.py +42 -0
  99. {commkit-2.1.0 → commkit-2.2.0}/tests/equalization/test_block.py +34 -1
  100. {commkit-2.1.0 → commkit-2.2.0}/tests/equalization/test_cpr.py +8 -8
  101. {commkit-2.1.0 → commkit-2.2.0}/tests/equalization/test_linear.py +67 -4
  102. {commkit-2.1.0 → commkit-2.2.0}/tests/equalization/test_sequential.py +24 -0
  103. {commkit-2.1.0 → commkit-2.2.0}/tests/equalization/test_state.py +24 -0
  104. commkit-2.2.0/tests/impairments/channel/test_channel_nonlinear.py +401 -0
  105. {commkit-2.1.0 → commkit-2.2.0}/tests/impairments/test_source.py +3 -4
  106. {commkit-2.1.0 → commkit-2.2.0}/tests/mapping/test_constellation.py +4 -1
  107. {commkit-2.1.0 → commkit-2.2.0}/tests/mapping/test_gray.py +27 -29
  108. {commkit-2.1.0 → commkit-2.2.0}/tests/mapping/test_llr.py +71 -10
  109. commkit-2.2.0/tests/plugins/__init__.py +0 -0
  110. commkit-2.2.0/tests/plugins/precision_trace.py +176 -0
  111. {commkit-2.1.0 → commkit-2.2.0}/tests/recovery/test_bps.py +25 -0
  112. {commkit-2.1.0 → commkit-2.2.0}/tests/recovery/test_corrections.py +75 -1
  113. {commkit-2.1.0 → commkit-2.2.0}/tests/test_api_contracts.py +25 -2
  114. {commkit-2.1.0 → commkit-2.2.0}/tests/test_array.py +45 -2
  115. {commkit-2.1.0 → commkit-2.2.0}/tests/test_cuda_infra.py +49 -0
  116. {commkit-2.1.0 → commkit-2.2.0}/tests/test_dispersion.py +29 -1
  117. commkit-2.2.0/tests/test_fiber.py +248 -0
  118. {commkit-2.1.0 → commkit-2.2.0}/tests/test_filtering.py +127 -0
  119. {commkit-2.1.0 → commkit-2.2.0}/tests/test_metrics.py +4 -2
  120. {commkit-2.1.0 → commkit-2.2.0}/tests/test_pulse_shaping.py +3 -3
  121. {commkit-2.1.0 → commkit-2.2.0}/uv.lock +1 -1
  122. commkit-2.1.0/commkit/impairments/channel/nonlinear.py +0 -11
  123. commkit-2.1.0/commkit/mapping/llr.py +0 -255
  124. commkit-2.1.0/examples/qam_receiver_quickstart.ipynb +0 -313
  125. {commkit-2.1.0 → commkit-2.2.0}/.gitattributes +0 -0
  126. {commkit-2.1.0 → commkit-2.2.0}/.github/workflows/publish.yml +0 -0
  127. {commkit-2.1.0 → commkit-2.2.0}/.python-version +0 -0
  128. {commkit-2.1.0 → commkit-2.2.0}/LICENSE +0 -0
  129. {commkit-2.1.0 → commkit-2.2.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0002_pre_v2.json +0 -0
  130. {commkit-2.1.0 → commkit-2.2.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0003_v2_0.json +0 -0
  131. {commkit-2.1.0 → commkit-2.2.0}/benchmarks/bench_block_lms.py +0 -0
  132. {commkit-2.1.0 → commkit-2.2.0}/benchmarks/bench_bps.py +0 -0
  133. {commkit-2.1.0 → commkit-2.2.0}/benchmarks/bench_equalizers.py +0 -0
  134. {commkit-2.1.0 → commkit-2.2.0}/benchmarks/bench_import.py +0 -0
  135. {commkit-2.1.0 → commkit-2.2.0}/benchmarks/bench_llr.py +0 -0
  136. {commkit-2.1.0 → commkit-2.2.0}/benchmarks/bench_signal_pipeline.py +0 -0
  137. {commkit-2.1.0 → commkit-2.2.0}/benchmarks/bench_sync_misc.py +0 -0
  138. {commkit-2.1.0 → commkit-2.2.0}/benchmarks/benchutils.py +0 -0
  139. {commkit-2.1.0 → commkit-2.2.0}/benchmarks/conftest.py +0 -0
  140. {commkit-2.1.0 → commkit-2.2.0}/benchmarks/record_baseline.py +0 -0
  141. {commkit-2.1.0 → commkit-2.2.0}/commkit/_cuda/__init__.py +0 -0
  142. {commkit-2.1.0 → commkit-2.2.0}/commkit/_cuda/compiler.py +0 -0
  143. {commkit-2.1.0 → commkit-2.2.0}/commkit/_cuda/src/bps_anchor.cu +0 -0
  144. {commkit-2.1.0 → commkit-2.2.0}/commkit/_cuda/src/bps_min_d2.cu +0 -0
  145. {commkit-2.1.0 → commkit-2.2.0}/commkit/_cuda/src/cs_block.cu +0 -0
  146. {commkit-2.1.0 → commkit-2.2.0}/commkit/_cuda/src/llr.cu +0 -0
  147. {commkit-2.1.0 → commkit-2.2.0}/commkit/_cuda/src/selftest.cu +0 -0
  148. {commkit-2.1.0 → commkit-2.2.0}/commkit/_sequences.py +0 -0
  149. {commkit-2.1.0 → commkit-2.2.0}/commkit/analysis/__init__.py +0 -0
  150. {commkit-2.1.0 → commkit-2.2.0}/commkit/analysis/_common.py +0 -0
  151. {commkit-2.1.0 → commkit-2.2.0}/commkit/analysis/allan.py +0 -0
  152. {commkit-2.1.0 → commkit-2.2.0}/commkit/coding/__init__.py +0 -0
  153. {commkit-2.1.0 → commkit-2.2.0}/commkit/coding/base.py +0 -0
  154. {commkit-2.1.0 → commkit-2.2.0}/commkit/coding/bch.py +0 -0
  155. {commkit-2.1.0 → commkit-2.2.0}/commkit/coding/convolutional.py +0 -0
  156. {commkit-2.1.0 → commkit-2.2.0}/commkit/coding/crc.py +0 -0
  157. {commkit-2.1.0 → commkit-2.2.0}/commkit/coding/galois.py +0 -0
  158. {commkit-2.1.0 → commkit-2.2.0}/commkit/coding/hamming.py +0 -0
  159. {commkit-2.1.0 → commkit-2.2.0}/commkit/coding/interleaving.py +0 -0
  160. {commkit-2.1.0 → commkit-2.2.0}/commkit/coding/ldpc.py +0 -0
  161. {commkit-2.1.0 → commkit-2.2.0}/commkit/coding/polar.py +0 -0
  162. {commkit-2.1.0 → commkit-2.2.0}/commkit/coding/ratematch.py +0 -0
  163. {commkit-2.1.0 → commkit-2.2.0}/commkit/coding/reed_solomon.py +0 -0
  164. {commkit-2.1.0 → commkit-2.2.0}/commkit/coding/turbo.py +0 -0
  165. {commkit-2.1.0 → commkit-2.2.0}/commkit/core/__init__.py +0 -0
  166. {commkit-2.1.0 → commkit-2.2.0}/commkit/core/_signal_adapter.py +0 -0
  167. {commkit-2.1.0 → commkit-2.2.0}/commkit/core/frame.py +0 -0
  168. {commkit-2.1.0 → commkit-2.2.0}/commkit/core/generation.py +0 -0
  169. {commkit-2.1.0 → commkit-2.2.0}/commkit/core/signal.py +0 -0
  170. {commkit-2.1.0 → commkit-2.2.0}/commkit/equalization/__init__.py +0 -0
  171. {commkit-2.1.0 → commkit-2.2.0}/commkit/equalization/_block/__init__.py +0 -0
  172. {commkit-2.1.0 → commkit-2.2.0}/commkit/equalization/_common.py +0 -0
  173. {commkit-2.1.0 → commkit-2.2.0}/commkit/equalization/_kernels_numba.py +0 -0
  174. {commkit-2.1.0 → commkit-2.2.0}/commkit/equalization/sequential/__init__.py +0 -0
  175. {commkit-2.1.0 → commkit-2.2.0}/commkit/impairments/frontend.py +0 -0
  176. {commkit-2.1.0 → commkit-2.2.0}/commkit/io.py +0 -0
  177. {commkit-2.1.0 → commkit-2.2.0}/commkit/logger.py +0 -0
  178. {commkit-2.1.0 → commkit-2.2.0}/commkit/mapping/__init__.py +0 -0
  179. {commkit-2.1.0 → commkit-2.2.0}/commkit/mapping/bits.py +0 -0
  180. {commkit-2.1.0 → commkit-2.2.0}/commkit/mapping/shaping.py +0 -0
  181. {commkit-2.1.0 → commkit-2.2.0}/commkit/multirate.py +0 -0
  182. {commkit-2.1.0 → commkit-2.2.0}/commkit/plotting/__init__.py +0 -0
  183. {commkit-2.1.0 → commkit-2.2.0}/commkit/plotting/constellation.py +0 -0
  184. {commkit-2.1.0 → commkit-2.2.0}/commkit/plotting/eye.py +0 -0
  185. {commkit-2.1.0 → commkit-2.2.0}/commkit/plotting/spectral.py +0 -0
  186. {commkit-2.1.0 → commkit-2.2.0}/commkit/plotting/theme.py +0 -0
  187. {commkit-2.1.0 → commkit-2.2.0}/commkit/plotting/waveform.py +0 -0
  188. {commkit-2.1.0 → commkit-2.2.0}/commkit/py.typed +0 -0
  189. {commkit-2.1.0 → commkit-2.2.0}/commkit/recovery/__init__.py +0 -0
  190. {commkit-2.1.0 → commkit-2.2.0}/tests/analysis/test_allan.py +0 -0
  191. {commkit-2.1.0 → commkit-2.2.0}/tests/analysis/test_fm_noise.py +0 -0
  192. {commkit-2.1.0 → commkit-2.2.0}/tests/analysis/test_trajectory.py +0 -0
  193. {commkit-2.1.0 → commkit-2.2.0}/tests/common/__init__.py +0 -0
  194. {commkit-2.1.0 → commkit-2.2.0}/tests/common/conversions.py +0 -0
  195. {commkit-2.1.0 → commkit-2.2.0}/tests/common/kernel_utils.py +0 -0
  196. {commkit-2.1.0 → commkit-2.2.0}/tests/common/metrics.py +0 -0
  197. {commkit-2.1.0 → commkit-2.2.0}/tests/common/periodic.py +0 -0
  198. {commkit-2.1.0 → commkit-2.2.0}/tests/core/test_frame.py +0 -0
  199. {commkit-2.1.0 → commkit-2.2.0}/tests/core/test_generation.py +0 -0
  200. {commkit-2.1.0 → commkit-2.2.0}/tests/core/test_psqam.py +0 -0
  201. {commkit-2.1.0 → commkit-2.2.0}/tests/core/test_signal_adapter.py +0 -0
  202. {commkit-2.1.0 → commkit-2.2.0}/tests/core/test_signal_mimo.py +0 -0
  203. {commkit-2.1.0 → commkit-2.2.0}/tests/equalization/test_bps_kernel.py +0 -0
  204. {commkit-2.1.0 → commkit-2.2.0}/tests/equalization/test_cs_kernel.py +0 -0
  205. {commkit-2.1.0 → commkit-2.2.0}/tests/equalization/test_equalizer_oracles.py +0 -0
  206. {commkit-2.1.0 → commkit-2.2.0}/tests/equalization/test_mimo.py +0 -0
  207. {commkit-2.1.0 → commkit-2.2.0}/tests/equalization/test_polarization.py +0 -0
  208. {commkit-2.1.0 → commkit-2.2.0}/tests/equalization/test_winit.py +0 -0
  209. {commkit-2.1.0 → commkit-2.2.0}/tests/impairments/channel/test_channel_linear.py +0 -0
  210. {commkit-2.1.0 → commkit-2.2.0}/tests/impairments/test_frontend.py +0 -0
  211. {commkit-2.1.0 → commkit-2.2.0}/tests/impairments/test_noise.py +0 -0
  212. {commkit-2.1.0 → commkit-2.2.0}/tests/mapping/test_bits.py +0 -0
  213. {commkit-2.1.0 → commkit-2.2.0}/tests/plotting/test_plot_analysis.py +0 -0
  214. {commkit-2.1.0 → commkit-2.2.0}/tests/plotting/test_plot_constellation.py +0 -0
  215. {commkit-2.1.0 → commkit-2.2.0}/tests/plotting/test_plot_equalizer.py +0 -0
  216. {commkit-2.1.0 → commkit-2.2.0}/tests/plotting/test_plot_eye.py +0 -0
  217. {commkit-2.1.0 → commkit-2.2.0}/tests/plotting/test_plot_filter_response.py +0 -0
  218. {commkit-2.1.0 → commkit-2.2.0}/tests/plotting/test_plot_spectral.py +0 -0
  219. {commkit-2.1.0 → commkit-2.2.0}/tests/plotting/test_plot_sync.py +0 -0
  220. {commkit-2.1.0 → commkit-2.2.0}/tests/plotting/test_plot_theme.py +0 -0
  221. {commkit-2.1.0 → commkit-2.2.0}/tests/plotting/test_plot_waveform.py +0 -0
  222. {commkit-2.1.0 → commkit-2.2.0}/tests/recovery/test_joint.py +0 -0
  223. {commkit-2.1.0 → commkit-2.2.0}/tests/recovery/test_pilots.py +0 -0
  224. {commkit-2.1.0 → commkit-2.2.0}/tests/recovery/test_pll.py +0 -0
  225. {commkit-2.1.0 → commkit-2.2.0}/tests/recovery/test_recovery_oracles.py +0 -0
  226. {commkit-2.1.0 → commkit-2.2.0}/tests/recovery/test_tikhonov.py +0 -0
  227. {commkit-2.1.0 → commkit-2.2.0}/tests/recovery/test_viterbi_viterbi.py +0 -0
  228. {commkit-2.1.0 → commkit-2.2.0}/tests/test_backend.py +0 -0
  229. {commkit-2.1.0 → commkit-2.2.0}/tests/test_examples.py +0 -0
  230. {commkit-2.1.0 → commkit-2.2.0}/tests/test_frequency.py +0 -0
  231. {commkit-2.1.0 → commkit-2.2.0}/tests/test_import_hygiene.py +0 -0
  232. {commkit-2.1.0 → commkit-2.2.0}/tests/test_io.py +0 -0
  233. {commkit-2.1.0 → commkit-2.2.0}/tests/test_logger.py +0 -0
  234. {commkit-2.1.0 → commkit-2.2.0}/tests/test_math.py +0 -0
  235. {commkit-2.1.0 → commkit-2.2.0}/tests/test_multirate.py +0 -0
  236. {commkit-2.1.0 → commkit-2.2.0}/tests/test_signal_pipeline.py +0 -0
  237. {commkit-2.1.0 → commkit-2.2.0}/tests/test_smoothing.py +0 -0
  238. {commkit-2.1.0 → commkit-2.2.0}/tests/test_spectral.py +0 -0
  239. {commkit-2.1.0 → commkit-2.2.0}/tests/test_timing.py +0 -0
@@ -110,7 +110,7 @@ jobs:
110
110
  - name: Run kernel tests with JIT disabled
111
111
  env:
112
112
  NUMBA_DISABLE_JIT: "1"
113
- run: uv run pytest tests/equalization tests/recovery tests/test_frequency.py --device=cpu --cov=commkit --cov-report=
113
+ run: uv run pytest tests/equalization tests/recovery tests/test_frequency.py tests/test_fiber.py --device=cpu --cov=commkit --cov-report=
114
114
 
115
115
  - name: Kernel coverage report
116
116
  run: >-
@@ -8,6 +8,11 @@ wheels/
8
8
  *.egg-info
9
9
  .ruff_cache
10
10
  .benchmarks
11
+ .coverage
12
+ .coverage.*
13
+ coverage.xml
14
+ coverage.json
15
+ htmlcov/
11
16
 
12
17
  # Virtual environments
13
18
  .venv
@@ -17,6 +22,14 @@ wheels/
17
22
 
18
23
  .vscode/
19
24
 
25
+ # Jupyter (uv run jupyter lab examples)
26
+ .ipynb_checkpoints/
27
+
28
+ # Nsight profiles (benchmarks/benchutils.nvtx_range)
29
+ *.nsys-rep
30
+ *.qdrep
31
+ *.ncu-rep
32
+
20
33
  tmp/
21
34
 
22
35
  # DSP captures / large binaries
@@ -20,6 +20,7 @@ Users are researchers and engineers. Design for them:
20
20
 
21
21
  - **Everything is expressed in Python.** No configuration files, registries, global switches or multi-step setup.
22
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
+ - **Scope is the reference model and its inverse**, not a catalogue of research variants: the fiber channel and standard digital backpropagation (`correct_fiber_propagation`, with `steps_per_span` and `nonlinear_scale`), like chromatic dispersion and its compensation. Reduced-complexity nonlinearity compensation (filtered or enhanced split step, perturbation-based) is left to user code built on these, and learned DBP, which needs gradients, belongs in commax.
23
24
 
24
25
  ---
25
26
 
@@ -35,7 +36,9 @@ uv run pytest --device=cpu # what CI runs
35
36
  uv run pytest tests/equalization/ -k lms # subset
36
37
  uv run pytest --cov=commkit # coverage
37
38
  NUMBA_DISABLE_JIT=1 uv run pytest tests/equalization tests/recovery \
38
- tests/test_frequency.py --device=cpu --cov=commkit # real kernel coverage
39
+ tests/test_frequency.py tests/test_fiber.py --device=cpu --cov=commkit # real kernel coverage
40
+ uv run pytest --device=cpu -p tests.plugins.precision_trace \
41
+ --ignore=tests/test_examples.py # precision audit (see Numerics); the suite passes under it
39
42
  ```
40
43
 
41
44
  **Commit gate.** CI fails on any of these, so run all of them before every commit, with `ruff format` last:
@@ -90,7 +93,8 @@ Dependencies point downward only:
90
93
  - `analysis` holds laser and phase characterization.
91
94
  - `metrics` is the reporting layer.
92
95
  - **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".
96
+ - **Placeholder modules are kept on purpose.** `commkit.coding` is a reminder of planned work. Never delete it or treat it as dead code. It is excluded from coverage and from the contract registry, and the README labels it "planned, not implemented".
97
+ - **Shared physics lives in one private module**, used by the channel and by its compensation: `_dispersion` (β₂, β₃, the dispersion response) and `_fiber` (`Fiber` and the split-step solver behind `apply_fiber_propagation` and `correct_fiber_propagation`).
94
98
 
95
99
  ---
96
100
 
@@ -121,7 +125,8 @@ Dependencies point downward only:
121
125
  - Equalizer continuation uses `state=result.state`.
122
126
  - **Randomness:** `rng: int | np.random.Generator | None` (SciPy SPEC 7).
123
127
  - 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.
128
+ - What the channel adds (AWGN, phase noise, ASE): draw on the data's device through `_random.standard_normal` / `complex_normal`. Realizations differ between devices; statistics agree, and tests assert statistics.
129
+ - Random parameters of a realization (the PMD waveplates of `Fiber`) are few and drawn on the host, so a seed gives the same fiber on every device.
125
130
  - Never use a global RNG.
126
131
  - **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
132
  - **Device follows the data.** There are no `backend=` arguments and no global switches. Unsupported array types (JAX, PyTorch) raise `TypeError`.
@@ -165,7 +170,14 @@ Dependencies point downward only:
165
170
 
166
171
  ## 5. Numerics
167
172
 
168
- - **Storage dtypes:** `complex64` for IQ samples and `float32` for real signals.
173
+ - **Storage dtypes:** `complex64` for IQ samples and `float32` for real signals; `generate`, frames and `load_npz` produce them, so the GPU's single-precision path is the default.
174
+ - **Precision contract.** Every function computes in a predictable precision, and none promotes single-precision data on its own:
175
+ - **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.
176
+ - **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.
177
+ - **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 (poles near z = 1), 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).
178
+ - **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.
179
+ - 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.
180
+ - **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).
169
181
  - **Accumulation precision:**
170
182
  - LMS/CMA/RDE: inputs and weights are `complex64`, but dot products and gradient updates in the hot loop are accumulated in `complex128`.
171
183
  - 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.
@@ -183,14 +195,18 @@ Dependencies point downward only:
183
195
  | Vectorized: filters, resampling, spectra, impairments, metrics, LLR, frequency-domain equalizers | NumPy/CuPy via `x, xp, sp = dispatch(samples)` | follows the input |
184
196
  | 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
197
  | Hot spots limited by intermediates, launches or round trips | CUDA C++ via CuPy `RawModule` (`commkit/_cuda`) | GPU |
198
+ | Elementwise chains with many temporaries, where a paired A/B shows the gain end to end (the PMD stage of `_fiber`: 0.61x on 4 cores, but not on the reference machine; see the roadmap) | Numba `@njit(parallel=True)` in one pass, real arithmetic (a `1j` literal promotes complex64 to complex128) | CPU |
186
199
 
187
200
  **Host-sync hygiene:**
188
201
 
189
202
  - 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
203
  - Prefer on-device gathers (`xp.take_along_axis`).
204
+ - Every FFT goes through `commkit._fft` (`fft`, `ifft`, `rfft`, `irfft`), never `np.fft` or `xp.fft` directly: on the CPU it runs `scipy.fft` on every core and keeps complex64 in single precision (NumPy 2 computes it in double, about 3x slower per core); on the GPU it is `cupy.fft`. Frequency grids (`xp.fft.fftfreq`) are exempt.
205
+ - Frequency responses (dispersion, delays, Jones matrices) are computed at float64 phase accuracy and cast to the spectrum's precision before the multiply, so complex64 data is never promoted.
191
206
  - Chunk `(N, M)` intermediates over N with an on-device accumulator.
192
207
  - Reduce on the device before transferring, including for plotting.
193
208
  - Pass whole records to compiled code. There are no Python loops over symbols or small blocks, except inside a CUDA graph.
209
+ - Repeated GPU work with fixed buffers (the block equalizers' blocks, the fiber solver's spans) is replayed from CUDA graphs through `_cuda.graphs.GraphRunner`, the one driver: warm once, capture on the second run, replay, fall back to eager on a failed capture. Its stream is reused per device and thread, because CuPy's pool caches freed blocks per stream and a fresh stream per call grows the pool every call.
194
210
 
195
211
  **Kernel policy:**
196
212
 
@@ -215,13 +231,16 @@ Dependencies point downward only:
215
231
  - **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
232
  - **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
233
  - **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.
234
+ - They form a course: one topic per notebook, named `NN-lowercase-words.ipynb`, numbered in teaching order (signals and DSP, channel and metrics, receiver algorithms, fiber systems, characterization). Each opens with what it covers, links the notebooks it builds on and ends with a link to the next. `examples/README.md` indexes them; a new notebook takes its place in the order and updates the index and the neighbours' links.
235
+ - Every claim in the text matches what the cells print: run the notebook and read its numbers before committing it. Keep each under about 20 s on the CPU.
236
+ - Plots of hand-computed arrays go through `to_device(x, "cpu")`, so the notebooks run unchanged on the GPU.
218
237
  - **Coverage** must not drop below the CI floor. Numba kernel coverage is measured by a separate `NUMBA_DISABLE_JIT=1` job.
219
238
 
220
239
  ---
221
240
 
222
241
  ## 8. Benchmarks
223
242
 
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.
243
+ `benchmarks/` tracks the GPU-relevant hot paths. Baselines are committed under `benchmarks/baselines/`. The current reference is `0004` (`fiber_dbp`) on the reference machine: RTX 4070 Ti, Ryzen 7 7800X3D, WSL2. It 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.
225
244
 
226
245
  - **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
246
  - **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.
@@ -234,7 +253,7 @@ Dependencies point downward only:
234
253
  - **Tools:** `benchmarks/benchutils.py` provides `CudaEventTimer` and `nvtx_range` for `nsys` profiling.
235
254
  - **Logging** is set to WARNING in `benchmarks/conftest.py`.
236
255
  - **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.
256
+ - A commit touching a hot path quotes its delta against `0004`. Tolerance is 5% on CPU and 10% on GPU.
238
257
 
239
258
  ---
240
259
 
@@ -244,21 +263,33 @@ What is not done yet, in suggested order. Unlike the sections above, this is a t
244
263
 
245
264
  ### Planned modules
246
265
 
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.
266
+ 1. **Channel coding** (`coding`, a placeholder package). Resolve the "Before channel coding" items below first, so the decoder is built on soft demapping that is already at its final speed on both devices. Start with one soft decoder (LDPC) that consumes `compute_llr` output (positive LLR = bit 0) and reports post-FEC BER next to `gmi`, then add encoders. The package stays out of `commkit/__init__.py` until a real encode/decode entry point exists. Decoders are iterative message passing over many codewords: batch codewords on the GPU from the start.
249
267
 
250
- ### Usability gaps
268
+ ### Nonlinear fiber channel: future features
251
269
 
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.
270
+ Each is an addition that leaves the current API unchanged.
271
+
272
+ - **Batched realizations**: an extra leading axis `(B, 2, N)` (launch-power sweeps, Monte Carlo over seeds) so cuFFT batches them and short records fill the GPU.
273
+ - **Adaptive step rules**: a `step_rule=` object (maximum nonlinear phase per step, local-error method) next to the equal `steps_per_span`, which measured 3x more accurate than logarithmic steps on a 32 GBd, 80 km span.
274
+ - **WDM helpers**: combining frequency-shifted channels into one wideband field and selecting one after propagation; the solver already handles the wide field (with `dispersion_slope_ps_nm2_km` set).
275
+ - **Distributed (Raman) amplification and per-span fiber types** in one call (chaining calls already works: every call speaks launch-relative units).
276
+ - **cuFFT callbacks** fusing the linear stage into the transforms (CuPy's support is experimental and compiles with nvcc at run time).
277
+ - **Self-steepening and Raman scattering** for ultra-wideband fields.
278
+
279
+ ### Before channel coding: mapping and soft demapping
280
+
281
+ The decoder's input path. The GPU items need a CUDA device to write, test (`--device=all`, against the Numba reference) and time.
282
+
283
+ - **GPU LLRs of separable QAM.** `Constellation.llr` takes the per-axis path (2·sqrt(M) metrics instead of M) only on the CPU; on the GPU, 256-QAM evaluates 256 metrics per symbol instead of 32. Port `llr_separable` to a CUDA kernel.
284
+ - **One exponential per point in the CUDA exact LLR.** `llr.cu` evaluates every metric twice and calls `expf` once per point and bit (M·k per symbol, 2048 for 256-QAM). The Numba kernel scales by one peak per symbol and needs M (with the underflow fallback of `_EXACT_FLOOR`). Together with the item above: 32 exponentials per 256-QAM symbol instead of 2048.
285
+ - **CUDA LLR stores and per-call tables.** Each thread writes its k LLRs at stride k (uncoalesced), and every call rebuilds and copies the points, packed labels and log prior to the device; cache them per constellation and device.
286
+ - **Non-square hard decisions on the GPU.** `gray._nearest_index` (used by `Constellation.demap`, `evm(blind=True)` and `ser` for PSK, cross QAM and arbitrary points) loops in Python over 4096-symbol chunks: about 250 iterations of several launches per 10^6 symbols. On the CPU that chunk and `abs` are the fastest NumPy form (measured); on the GPU, time it against a larger element-budget chunk or the nearest-point mode of `bps_min_d2`.
287
+ - **`Constellation.map` intermediates.** It casts the bits to int32 groups and multiplies and sums them: two `(N, k)` int32 temporaries for `N·k` input bits, four times the bits' own size in int8. Encoders hand it long bit records; pack the labels in one pass.
288
+ - **Closed-form max-log LLRs of Gray QAM** (optional, CPU and GPU): per bit, the nearest point of each half is found by snapping, O(k) instead of O(sqrt(M)). Expected gain under 2x on the CPU; measure before adopting.
254
289
 
255
290
  ### Performance
256
291
 
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
292
  - **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.
293
+ - **The fiber solver's Numba passes lose end to end on the reference machine.** On 4 cores (where they were tuned) the hybrid Kerr stage won from `_fiber._HYBRID_KERR_MIN = 2^18` and the trig-free Jones (PMD) pass won end to end (0.61x). On the reference machine (8 cores, 16 threads), whole propagations with NumPy stages and multi-core `scipy.fft` are faster: hybrid Kerr 1.74x slower at 2^18, 1.32x at 2^19, 1.12x at 2^20; Numba Jones 1.35x at 2^14, 2.19x at 2^16, 1.54x at 2^18, even at 2^20. The cause is contention: Numba's OpenMP workers spin after each pass and take the cores from the FFT workers (`OMP_WAIT_POLICY=passive` cuts the Kerr loss at 2^18 to 1.09x; one Numba thread makes Jones even with NumPy). The stage timed alone hides this. Options: choose the passes by core count, run them single-threaded, or keep NumPy stages only; decide by end-to-end A/Bs on both machines.
294
+ - **A fully fused Numba Kerr stage** may beat the hybrid one where Numba has SVML (vector sin/cos); neither machine measured so far has it (`numba.core.config.USING_SVML` is False), so it is untimed.
259
295
  - **`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
  Metadata-Version: 2.5
2
2
  Name: commkit
3
- Version: 2.1.0
3
+ Version: 2.2.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
@@ -79,7 +79,7 @@ res = ck.equalization.lms(
79
79
  y = res.signal # 1-SPS Signal, reference aligned
80
80
  print(f"EVM {ck.metrics.evm(y, num_skip_symbols=2000):.1f} %, "
81
81
  f"BER {ck.metrics.ber(y, num_skip_symbols=2000):.1e}")
82
- # EVM 12.8 %, BER 2.5e-04
82
+ # EVM 12.7 %, BER 2.4e-04 (on the CPU; the GPU draws other noise)
83
83
 
84
84
  # The next record continues from the converged taps and CPR state.
85
85
  res2 = ck.equalization.lms(rx, num_taps=21, step_size=1e-3, cpr=cpr, state=res.state)
@@ -116,10 +116,10 @@ CommKit never picks a device for you: data stays where you put it, and each func
116
116
  | --- | --- |
117
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
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. |
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 and digital backpropagation (`correct_fiber_propagation`). |
120
120
  | [`commkit.multirate`](https://github.com/lokgar/commkit/blob/main/commkit/multirate.py) | `resample` (fractional), `decimate`, `upsample`, `decimate_to_symbol_rate`. |
121
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**. |
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, and nonlinear fiber propagation over amplified spans (split-step Kerr effect with dispersion, loss, PMD and ASE) of a `Fiber`. |
123
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
124
  | [`commkit.frequency`](https://github.com/lokgar/commkit/blob/main/commkit/frequency.py) | `estimate_frequency_offset` / `correct_frequency_offset` with `MthPower`, `MengaliMorelli`, `PilotSymbols` and `BiasTone`; static and blockwise. |
125
125
  | [`commkit.recovery`](https://github.com/lokgar/commkit/tree/main/commkit/recovery) | `estimate_carrier_phase` / `correct_carrier_phase` with `ViterbiViterbi`, `BPS`, `PLL`, `Tikhonov`, `DataAided`, `PilotAided`, `PilotTone(s)`; cycle-slip correction; `resolve_phase_ambiguity` and `resolve_channel_permutation`. |
@@ -160,15 +160,15 @@ With [`uv`](https://github.com/astral-sh/uv), use `uv pip install` in place of `
160
160
 
161
161
  ## Examples
162
162
 
163
- Jupyter notebooks in [`examples/`](https://github.com/lokgar/commkit/tree/main/examples) (install the `notebook` extra and run `jupyter lab examples`):
163
+ [`examples/`](https://github.com/lokgar/commkit/tree/main/examples) is a course in 26 Jupyter notebooks, one topic each, in teaching order (install the `notebook` extra and run `jupyter lab examples`):
164
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.
165
+ - **00-05, signals and DSP**: the `Signal`, constellations and bit mapping, spectra, filters, pulse shaping and matched filtering, resampling;
166
+ - **06-08, channel and performance**: AWGN and the metrics (SNR, BER, LLRs, GMI), laser phase noise, front-end impairments;
167
+ - **09-16, receiver algorithms**: frame synchronization, frequency-offset estimation, blind and pilot-aided carrier recovery, linear, adaptive and blind equalization, polarization demultiplexing;
168
+ - **17-19, optical fiber systems**: chromatic dispersion, the nonlinear fiber with digital backpropagation, and an end-to-end coherent transceiver;
169
+ - **20-25, characterization**: carrier phase and laser linewidth measurements, with templates for real captures.
170
170
 
171
- The notebooks are committed without outputs and run in CI.
171
+ [`examples/README.md`](https://github.com/lokgar/commkit/blob/main/examples/README.md) lists every notebook with its topics. The quickstart above appears cell by cell in `00-getting-started` and `14-adaptive-equalization`. The notebooks are committed without outputs and run in CI.
172
172
 
173
173
  ## Development
174
174
 
@@ -43,7 +43,7 @@ res = ck.equalization.lms(
43
43
  y = res.signal # 1-SPS Signal, reference aligned
44
44
  print(f"EVM {ck.metrics.evm(y, num_skip_symbols=2000):.1f} %, "
45
45
  f"BER {ck.metrics.ber(y, num_skip_symbols=2000):.1e}")
46
- # EVM 12.8 %, BER 2.5e-04
46
+ # EVM 12.7 %, BER 2.4e-04 (on the CPU; the GPU draws other noise)
47
47
 
48
48
  # The next record continues from the converged taps and CPR state.
49
49
  res2 = ck.equalization.lms(rx, num_taps=21, step_size=1e-3, cpr=cpr, state=res.state)
@@ -80,10 +80,10 @@ CommKit never picks a device for you: data stays where you put it, and each func
80
80
  | --- | --- |
81
81
  | [`commkit.core`](https://github.com/lokgar/commkit/tree/main/commkit/core) | `Signal`, `Reference`, `generate`, `SingleCarrierFrame` and `Preamble` (pilots, guard intervals, MIMO streams), `extract_payload`. |
82
82
  | [`commkit.mapping`](https://github.com/lokgar/commkit/tree/main/commkit/mapping) | `Constellation` (QAM, PSK, PAM, arbitrary points; Gray labels; probabilistic shaping with `.shaped()`), bit mapping, hard demapping, max-log and exact LLRs. |
83
- | [`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. |
83
+ | [`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 and digital backpropagation (`correct_fiber_propagation`). |
84
84
  | [`commkit.multirate`](https://github.com/lokgar/commkit/blob/main/commkit/multirate.py) | `resample` (fractional), `decimate`, `upsample`, `decimate_to_symbol_rate`. |
85
85
  | [`commkit.spectral`](https://github.com/lokgar/commkit/blob/main/commkit/spectral.py) | Welch PSD, spectrograms, frequency shifting, pilot tones. |
86
- | [`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**. |
86
+ | [`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, and nonlinear fiber propagation over amplified spans (split-step Kerr effect with dispersion, loss, PMD and ASE) of a `Fiber`. |
87
87
  | [`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. |
88
88
  | [`commkit.frequency`](https://github.com/lokgar/commkit/blob/main/commkit/frequency.py) | `estimate_frequency_offset` / `correct_frequency_offset` with `MthPower`, `MengaliMorelli`, `PilotSymbols` and `BiasTone`; static and blockwise. |
89
89
  | [`commkit.recovery`](https://github.com/lokgar/commkit/tree/main/commkit/recovery) | `estimate_carrier_phase` / `correct_carrier_phase` with `ViterbiViterbi`, `BPS`, `PLL`, `Tikhonov`, `DataAided`, `PilotAided`, `PilotTone(s)`; cycle-slip correction; `resolve_phase_ambiguity` and `resolve_channel_permutation`. |
@@ -124,15 +124,15 @@ With [`uv`](https://github.com/astral-sh/uv), use `uv pip install` in place of `
124
124
 
125
125
  ## Examples
126
126
 
127
- Jupyter notebooks in [`examples/`](https://github.com/lokgar/commkit/tree/main/examples) (install the `notebook` extra and run `jupyter lab examples`):
127
+ [`examples/`](https://github.com/lokgar/commkit/tree/main/examples) is a course in 26 Jupyter notebooks, one topic each, in teaching order (install the `notebook` extra and run `jupyter lab examples`):
128
128
 
129
- - [`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;
130
- - [`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;
131
- - [`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)
132
- - laser linewidth from delayed self-heterodyne and homodyne IQ captures;
133
- - `measurement_laser_linewidth_*` - lean templates for real captures.
129
+ - **00-05, signals and DSP**: the `Signal`, constellations and bit mapping, spectra, filters, pulse shaping and matched filtering, resampling;
130
+ - **06-08, channel and performance**: AWGN and the metrics (SNR, BER, LLRs, GMI), laser phase noise, front-end impairments;
131
+ - **09-16, receiver algorithms**: frame synchronization, frequency-offset estimation, blind and pilot-aided carrier recovery, linear, adaptive and blind equalization, polarization demultiplexing;
132
+ - **17-19, optical fiber systems**: chromatic dispersion, the nonlinear fiber with digital backpropagation, and an end-to-end coherent transceiver;
133
+ - **20-25, characterization**: carrier phase and laser linewidth measurements, with templates for real captures.
134
134
 
135
- The notebooks are committed without outputs and run in CI.
135
+ [`examples/README.md`](https://github.com/lokgar/commkit/blob/main/examples/README.md) lists every notebook with its topics. The quickstart above appears cell by cell in `00-getting-started` and `14-adaptive-equalization`. The notebooks are committed without outputs and run in CI.
136
136
 
137
137
  ## Development
138
138