commkit 3.0.0__tar.gz → 3.1.1__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.
- {commkit-3.0.0 → commkit-3.1.1}/.github/workflows/ci.yml +1 -1
- {commkit-3.0.0 → commkit-3.1.1}/AGENTS.md +25 -20
- {commkit-3.0.0 → commkit-3.1.1}/CHANGELOG.md +118 -1
- {commkit-3.0.0 → commkit-3.1.1}/PKG-INFO +8 -8
- {commkit-3.0.0 → commkit-3.1.1}/README.md +5 -5
- commkit-3.1.1/benchmarks/baselines/Linux-CPython-3.14-64bit/0008_v3_1.json +6082 -0
- commkit-3.1.1/benchmarks/bench_coding.py +145 -0
- {commkit-3.0.0 → commkit-3.1.1}/benchmarks/bench_fiber.py +1 -0
- {commkit-3.0.0 → commkit-3.1.1}/benchmarks/bench_signal_pipeline.py +3 -3
- {commkit-3.0.0 → commkit-3.1.1}/benchmarks/workloads.py +1 -1
- {commkit-3.0.0 → commkit-3.1.1}/commkit/__init__.py +1 -1
- commkit-3.1.1/commkit/_constants.py +3 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/_cuda/__init__.py +70 -0
- commkit-3.1.1/commkit/_cuda/src/algebraic.cu +148 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/_cuda/src/ldpc.cu +4 -1
- {commkit-3.0.0 → commkit-3.1.1}/commkit/_dispersion.py +1 -3
- commkit-3.1.1/commkit/_overlap_save.py +66 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/_sequences.py +34 -34
- {commkit-3.0.0 → commkit-3.1.1}/commkit/analysis/_common.py +13 -9
- {commkit-3.0.0 → commkit-3.1.1}/commkit/analysis/allan.py +6 -6
- {commkit-3.0.0 → commkit-3.1.1}/commkit/analysis/linewidth/dsh.py +4 -4
- {commkit-3.0.0 → commkit-3.1.1}/commkit/analysis/linewidth/phase.py +8 -6
- {commkit-3.0.0 → commkit-3.1.1}/commkit/analysis/linewidth/result.py +4 -4
- {commkit-3.0.0 → commkit-3.1.1}/commkit/coding/_gf2.py +15 -1
- {commkit-3.0.0 → commkit-3.1.1}/commkit/coding/_numba_loops.py +443 -134
- {commkit-3.0.0 → commkit-3.1.1}/commkit/coding/algebraic.py +97 -133
- {commkit-3.0.0 → commkit-3.1.1}/commkit/coding/base.py +22 -2
- {commkit-3.0.0 → commkit-3.1.1}/commkit/coding/convolutional.py +29 -15
- {commkit-3.0.0 → commkit-3.1.1}/commkit/coding/crc.py +16 -16
- {commkit-3.0.0 → commkit-3.1.1}/commkit/coding/ldpc.py +61 -23
- {commkit-3.0.0 → commkit-3.1.1}/commkit/coding/linear.py +84 -34
- {commkit-3.0.0 → commkit-3.1.1}/commkit/coding/polar.py +12 -4
- {commkit-3.0.0 → commkit-3.1.1}/commkit/coding/product.py +82 -54
- {commkit-3.0.0 → commkit-3.1.1}/commkit/coding/shaping.py +43 -30
- {commkit-3.0.0 → commkit-3.1.1}/commkit/coding/turbo.py +34 -20
- {commkit-3.0.0 → commkit-3.1.1}/commkit/core/_signal_adapter.py +27 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/core/frame.py +356 -167
- {commkit-3.0.0 → commkit-3.1.1}/commkit/core/generation.py +121 -24
- {commkit-3.0.0 → commkit-3.1.1}/commkit/core/signal.py +130 -9
- {commkit-3.0.0 → commkit-3.1.1}/commkit/equalization/__init__.py +11 -13
- {commkit-3.0.0 → commkit-3.1.1}/commkit/equalization/_block/_dd.py +2 -1
- {commkit-3.0.0 → commkit-3.1.1}/commkit/equalization/_common.py +11 -20
- {commkit-3.0.0 → commkit-3.1.1}/commkit/equalization/_numba_loops.py +12 -14
- {commkit-3.0.0 → commkit-3.1.1}/commkit/equalization/blind.py +19 -111
- {commkit-3.0.0 → commkit-3.1.1}/commkit/equalization/linear.py +65 -57
- commkit-3.1.1/commkit/equalization/pilots.py +115 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/equalization/polarization.py +247 -381
- {commkit-3.0.0 → commkit-3.1.1}/commkit/equalization/sequential/_blind.py +38 -71
- {commkit-3.0.0 → commkit-3.1.1}/commkit/equalization/sequential/_dd.py +29 -25
- {commkit-3.0.0 → commkit-3.1.1}/commkit/equalization/sequential/_setup.py +1 -1
- {commkit-3.0.0 → commkit-3.1.1}/commkit/filtering/__init__.py +2 -14
- {commkit-3.0.0 → commkit-3.1.1}/commkit/filtering/fiber.py +16 -9
- {commkit-3.0.0 → commkit-3.1.1}/commkit/filtering/linear.py +58 -48
- {commkit-3.0.0 → commkit-3.1.1}/commkit/filtering/pulses.py +40 -48
- {commkit-3.0.0 → commkit-3.1.1}/commkit/frequency.py +54 -14
- {commkit-3.0.0 → commkit-3.1.1}/commkit/impairments/__init__.py +13 -3
- {commkit-3.0.0 → commkit-3.1.1}/commkit/impairments/channel/__init__.py +3 -1
- {commkit-3.0.0 → commkit-3.1.1}/commkit/impairments/channel/linear.py +39 -27
- {commkit-3.0.0 → commkit-3.1.1}/commkit/impairments/channel/nonlinear.py +7 -3
- {commkit-3.0.0 → commkit-3.1.1}/commkit/impairments/frontend.py +150 -21
- commkit-3.1.1/commkit/impairments/noise.py +184 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/io.py +136 -65
- {commkit-3.0.0 → commkit-3.1.1}/commkit/mapping/__init__.py +3 -3
- {commkit-3.0.0 → commkit-3.1.1}/commkit/mapping/_numba_llr.py +5 -2
- {commkit-3.0.0 → commkit-3.1.1}/commkit/mapping/bits.py +5 -5
- {commkit-3.0.0 → commkit-3.1.1}/commkit/mapping/constellation.py +35 -10
- {commkit-3.0.0 → commkit-3.1.1}/commkit/mapping/gray.py +13 -7
- {commkit-3.0.0 → commkit-3.1.1}/commkit/mapping/llr.py +67 -31
- {commkit-3.0.0 → commkit-3.1.1}/commkit/metrics.py +181 -49
- {commkit-3.0.0 → commkit-3.1.1}/commkit/plotting/analysis.py +1 -1
- {commkit-3.0.0 → commkit-3.1.1}/commkit/plotting/equalizer.py +28 -20
- {commkit-3.0.0 → commkit-3.1.1}/commkit/plotting/filter_response.py +6 -6
- {commkit-3.0.0 → commkit-3.1.1}/commkit/plotting/spectral.py +48 -26
- {commkit-3.0.0 → commkit-3.1.1}/commkit/plotting/sync.py +11 -11
- {commkit-3.0.0 → commkit-3.1.1}/commkit/plotting/theme.py +17 -14
- {commkit-3.0.0 → commkit-3.1.1}/commkit/recovery/carrier_phase.py +4 -3
- {commkit-3.0.0 → commkit-3.1.1}/commkit/recovery/pilots.py +32 -1
- {commkit-3.0.0 → commkit-3.1.1}/commkit/recovery/pll.py +15 -10
- {commkit-3.0.0 → commkit-3.1.1}/commkit/recovery/tikhonov.py +11 -10
- {commkit-3.0.0 → commkit-3.1.1}/commkit/spectral.py +1 -1
- {commkit-3.0.0 → commkit-3.1.1}/commkit/timing.py +33 -12
- {commkit-3.0.0 → commkit-3.1.1}/examples/00-getting-started.ipynb +1 -1
- {commkit-3.0.0 → commkit-3.1.1}/examples/01-constellations-and-bit-mapping.ipynb +2 -2
- {commkit-3.0.0 → commkit-3.1.1}/examples/03-digital-filters.ipynb +9 -7
- {commkit-3.0.0 → commkit-3.1.1}/examples/06-awgn-and-performance-metrics.ipynb +3 -3
- {commkit-3.0.0 → commkit-3.1.1}/examples/07-channel-coding.ipynb +116 -19
- {commkit-3.0.0 → commkit-3.1.1}/examples/08-probabilistic-shaping-and-coded-modulation.ipynb +6 -8
- {commkit-3.0.0 → commkit-3.1.1}/examples/10-front-end-impairments.ipynb +16 -10
- {commkit-3.0.0 → commkit-3.1.1}/examples/11-frame-synchronization-and-timing.ipynb +12 -7
- {commkit-3.0.0 → commkit-3.1.1}/examples/13-blind-carrier-phase-recovery.ipynb +3 -3
- {commkit-3.0.0 → commkit-3.1.1}/examples/14-pilot-aided-carrier-recovery.ipynb +2 -2
- {commkit-3.0.0 → commkit-3.1.1}/examples/15-linear-channel-equalization.ipynb +15 -15
- {commkit-3.0.0 → commkit-3.1.1}/examples/16-adaptive-equalization.ipynb +2 -2
- {commkit-3.0.0 → commkit-3.1.1}/examples/18-polarization-multiplexing.ipynb +5 -8
- {commkit-3.0.0 → commkit-3.1.1}/examples/20-nonlinear-fiber-and-backpropagation.ipynb +3 -1
- {commkit-3.0.0 → commkit-3.1.1}/examples/21-fec-in-optical-standards.ipynb +2 -3
- {commkit-3.0.0 → commkit-3.1.1}/examples/22-coherent-transceiver-end-to-end.ipynb +25 -14
- {commkit-3.0.0 → commkit-3.1.1}/examples/23-carrier-phase-characterization.ipynb +1 -1
- {commkit-3.0.0 → commkit-3.1.1}/examples/24-laser-linewidth-self-heterodyne.ipynb +4 -4
- {commkit-3.0.0 → commkit-3.1.1}/examples/25-laser-linewidth-short-delay-and-flicker.ipynb +2 -2
- {commkit-3.0.0 → commkit-3.1.1}/examples/26-laser-linewidth-self-homodyne-iq.ipynb +2 -2
- {commkit-3.0.0 → commkit-3.1.1}/examples/27-measurement-template-self-heterodyne.ipynb +1 -1
- {commkit-3.0.0 → commkit-3.1.1}/examples/28-measurement-template-self-homodyne-iq.ipynb +1 -1
- {commkit-3.0.0 → commkit-3.1.1}/pyproject.toml +3 -3
- {commkit-3.0.0 → commkit-3.1.1}/tests/analysis/test_allan.py +2 -2
- {commkit-3.0.0 → commkit-3.1.1}/tests/analysis/test_linewidth.py +4 -4
- {commkit-3.0.0 → commkit-3.1.1}/tests/coding/test_algebraic.py +52 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/coding/test_coding_base.py +46 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/coding/test_convolutional.py +27 -2
- {commkit-3.0.0 → commkit-3.1.1}/tests/coding/test_crc.py +1 -1
- {commkit-3.0.0 → commkit-3.1.1}/tests/coding/test_ldpc.py +14 -3
- {commkit-3.0.0 → commkit-3.1.1}/tests/coding/test_ldpc_kernel.py +3 -3
- {commkit-3.0.0 → commkit-3.1.1}/tests/coding/test_ldpc_tables.py +1 -1
- {commkit-3.0.0 → commkit-3.1.1}/tests/coding/test_polar.py +9 -9
- {commkit-3.0.0 → commkit-3.1.1}/tests/coding/test_product.py +14 -3
- {commkit-3.0.0 → commkit-3.1.1}/tests/coding/test_shaping.py +43 -14
- {commkit-3.0.0 → commkit-3.1.1}/tests/coding/test_turbo.py +8 -1
- commkit-3.1.1/tests/common/memory.py +20 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/common/reference_impl.py +122 -5
- {commkit-3.0.0 → commkit-3.1.1}/tests/common/signals.py +5 -5
- {commkit-3.0.0 → commkit-3.1.1}/tests/core/test_frame.py +301 -114
- {commkit-3.0.0 → commkit-3.1.1}/tests/core/test_generation.py +59 -3
- {commkit-3.0.0 → commkit-3.1.1}/tests/core/test_signal.py +80 -8
- {commkit-3.0.0 → commkit-3.1.1}/tests/core/test_signal_mimo.py +18 -16
- {commkit-3.0.0 → commkit-3.1.1}/tests/equalization/test_blind.py +5 -9
- {commkit-3.0.0 → commkit-3.1.1}/tests/equalization/test_cpr.py +5 -5
- {commkit-3.0.0 → commkit-3.1.1}/tests/equalization/test_equalizer_oracles.py +4 -4
- {commkit-3.0.0 → commkit-3.1.1}/tests/equalization/test_linear.py +49 -18
- {commkit-3.0.0 → commkit-3.1.1}/tests/equalization/test_polarization.py +89 -110
- {commkit-3.0.0 → commkit-3.1.1}/tests/equalization/test_sequential.py +45 -65
- {commkit-3.0.0 → commkit-3.1.1}/tests/equalization/test_state.py +9 -7
- {commkit-3.0.0 → commkit-3.1.1}/tests/impairments/channel/test_channel_linear.py +56 -4
- {commkit-3.0.0 → commkit-3.1.1}/tests/impairments/channel/test_channel_nonlinear.py +20 -2
- {commkit-3.0.0 → commkit-3.1.1}/tests/impairments/test_frontend.py +76 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/impairments/test_noise.py +63 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/mapping/test_bits.py +10 -12
- {commkit-3.0.0 → commkit-3.1.1}/tests/mapping/test_constellation.py +31 -1
- {commkit-3.0.0 → commkit-3.1.1}/tests/mapping/test_gray.py +17 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/mapping/test_llr.py +1 -1
- {commkit-3.0.0 → commkit-3.1.1}/tests/plotting/test_plot_analysis.py +1 -1
- {commkit-3.0.0 → commkit-3.1.1}/tests/plotting/test_plot_equalizer.py +29 -2
- {commkit-3.0.0 → commkit-3.1.1}/tests/plotting/test_plot_filter_response.py +3 -3
- {commkit-3.0.0 → commkit-3.1.1}/tests/plotting/test_plot_spectral.py +21 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/plotting/test_plot_sync.py +7 -5
- {commkit-3.0.0 → commkit-3.1.1}/tests/plugins/precision_trace.py +48 -8
- {commkit-3.0.0 → commkit-3.1.1}/tests/plugins/transfer_trace.py +1 -1
- {commkit-3.0.0 → commkit-3.1.1}/tests/recovery/test_corrections.py +3 -2
- {commkit-3.0.0 → commkit-3.1.1}/tests/recovery/test_joint.py +2 -3
- {commkit-3.0.0 → commkit-3.1.1}/tests/recovery/test_pilots.py +29 -4
- {commkit-3.0.0 → commkit-3.1.1}/tests/recovery/test_pll.py +7 -3
- {commkit-3.0.0 → commkit-3.1.1}/tests/recovery/test_tikhonov.py +36 -15
- {commkit-3.0.0 → commkit-3.1.1}/tests/recovery/test_viterbi_viterbi.py +6 -2
- {commkit-3.0.0 → commkit-3.1.1}/tests/test_api_contracts.py +18 -16
- {commkit-3.0.0 → commkit-3.1.1}/tests/test_dispersion.py +1 -1
- {commkit-3.0.0 → commkit-3.1.1}/tests/test_fiber.py +9 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/test_filtering.py +97 -29
- {commkit-3.0.0 → commkit-3.1.1}/tests/test_frequency.py +20 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/test_io.py +247 -11
- {commkit-3.0.0 → commkit-3.1.1}/tests/test_metrics.py +45 -1
- {commkit-3.0.0 → commkit-3.1.1}/tests/test_pulse_shaping.py +0 -30
- {commkit-3.0.0 → commkit-3.1.1}/tests/test_signal_pipeline.py +11 -9
- {commkit-3.0.0 → commkit-3.1.1}/tests/test_timing.py +40 -16
- commkit-3.1.1/tests/test_working_memory.py +65 -0
- {commkit-3.0.0 → commkit-3.1.1}/uv.lock +19 -14
- commkit-3.0.0/benchmarks/bench_coding.py +0 -64
- commkit-3.0.0/commkit/_overlap_save.py +0 -93
- commkit-3.0.0/commkit/impairments/noise.py +0 -108
- {commkit-3.0.0 → commkit-3.1.1}/.gitattributes +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/.github/workflows/publish.yml +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/.gitignore +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/.python-version +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/LICENSE +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/benchmarks/baselines/Linux-CPython-3.14-64bit/0002_pre_v2.json +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/benchmarks/baselines/Linux-CPython-3.14-64bit/0003_v2_0.json +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/benchmarks/baselines/Linux-CPython-3.14-64bit/0004_fiber_dbp.json +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/benchmarks/baselines/Linux-CPython-3.14-64bit/0005_gpu_decoder_path.json +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/benchmarks/baselines/Linux-CPython-3.14-64bit/0006_perf_cleanup.json +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/benchmarks/baselines/Linux-CPython-3.14-64bit/0007_v3_0.json +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/benchmarks/bench_block_blind.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/benchmarks/bench_block_lms.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/benchmarks/bench_bps.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/benchmarks/bench_equalizers.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/benchmarks/bench_filters.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/benchmarks/bench_import.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/benchmarks/bench_llr.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/benchmarks/bench_mapping.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/benchmarks/bench_sync_misc.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/benchmarks/benchutils.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/benchmarks/conftest.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/benchmarks/record_baseline.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/_array.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/_circular.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/_cuda/compiler.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/_cuda/graphs.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/_cuda/src/bps_anchor.cu +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/_cuda/src/bps_min_d2.cu +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/_cuda/src/cs_block.cu +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/_cuda/src/iir.cu +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/_cuda/src/llr.cu +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/_cuda/src/selftest.cu +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/_fft.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/_fiber.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/_random.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/analysis/__init__.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/analysis/drift.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/analysis/fm_noise.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/analysis/interferometry.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/analysis/linewidth/__init__.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/analysis/trajectory.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/backend.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/coding/__init__.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/coding/_galois.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/coding/_ldpc_tables.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/coding/interleaving.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/coding/ratematch.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/core/__init__.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/equalization/_block/__init__.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/equalization/_block/_blind.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/equalization/_block/_engine.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/equalization/_kernels_numba.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/equalization/result.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/equalization/sequential/__init__.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/impairments/source.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/logger.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/mapping/shaping.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/math.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/multirate.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/plotting/__init__.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/plotting/constellation.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/plotting/eye.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/plotting/waveform.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/py.typed +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/recovery/__init__.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/recovery/_common.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/recovery/bps.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/recovery/corrections.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/recovery/viterbi_viterbi.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/commkit/smoothing.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/examples/02-spectral-analysis.ipynb +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/examples/04-pulse-shaping-and-matched-filtering.ipynb +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/examples/05-multirate-and-resampling.ipynb +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/examples/09-laser-phase-noise.ipynb +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/examples/12-frequency-offset-estimation.ipynb +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/examples/17-blind-equalization.ipynb +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/examples/19-chromatic-dispersion.ipynb +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/examples/README.md +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/analysis/test_drift.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/analysis/test_fm_noise.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/analysis/test_interferometry.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/analysis/test_trajectory.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/coding/test_gf2.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/coding/test_interleaving.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/coding/test_linear_codes.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/coding/test_ratematch.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/common/__init__.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/common/coding.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/common/conversions.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/common/kernel_utils.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/common/metrics.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/common/periodic.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/conftest.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/core/test_psqam.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/core/test_signal_adapter.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/equalization/test_block.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/equalization/test_bps_kernel.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/equalization/test_cs_kernel.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/equalization/test_mimo.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/equalization/test_winit.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/impairments/test_source.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/plotting/test_plot_constellation.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/plotting/test_plot_eye.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/plotting/test_plot_theme.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/plotting/test_plot_waveform.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/plugins/__init__.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/recovery/test_bps.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/recovery/test_recovery_oracles.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/test_array.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/test_backend.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/test_cuda_infra.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/test_examples.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/test_import_hygiene.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/test_logger.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/test_math.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/test_multirate.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/test_smoothing.py +0 -0
- {commkit-3.0.0 → commkit-3.1.1}/tests/test_spectral.py +0 -0
|
@@ -112,7 +112,7 @@ jobs:
|
|
|
112
112
|
NUMBA_DISABLE_JIT: "1"
|
|
113
113
|
run: >-
|
|
114
114
|
uv run pytest tests/equalization tests/recovery tests/test_frequency.py tests/mapping/test_llr.py
|
|
115
|
-
tests/coding/test_ldpc.py tests/coding/test_shaping.py::TestCCDM
|
|
115
|
+
tests/coding/test_ldpc.py tests/coding/test_shaping.py::TestCCDM tests/coding/test_algebraic.py
|
|
116
116
|
tests/coding/test_polar.py::TestEncoding
|
|
117
117
|
tests/coding/test_polar.py::TestDecoding::test_sc_matches_the_recursive_reference
|
|
118
118
|
tests/coding/test_polar.py::TestDecoding::test_full_list_is_maximum_likelihood
|
|
@@ -37,7 +37,7 @@ uv run pytest tests/equalization/ -k lms # subset
|
|
|
37
37
|
uv run pytest --cov=commkit # coverage
|
|
38
38
|
NUMBA_DISABLE_JIT=1 uv run pytest tests/equalization tests/recovery \
|
|
39
39
|
tests/test_frequency.py tests/mapping/test_llr.py tests/coding/test_ldpc.py tests/coding/test_shaping.py::TestCCDM \
|
|
40
|
-
tests/coding/test_polar.py::TestEncoding "tests/coding/test_polar.py::TestDecoding::test_sc_matches_the_recursive_reference" \
|
|
40
|
+
tests/coding/test_algebraic.py tests/coding/test_polar.py::TestEncoding "tests/coding/test_polar.py::TestDecoding::test_sc_matches_the_recursive_reference" \
|
|
41
41
|
"tests/coding/test_polar.py::TestDecoding::test_full_list_is_maximum_likelihood" \
|
|
42
42
|
"tests/coding/test_polar.py::TestDecoding::test_crc_failure_keeps_the_best_path" \
|
|
43
43
|
tests/coding/test_convolutional.py tests/coding/test_turbo.py::TestEncoding \
|
|
@@ -56,11 +56,12 @@ uv run pytest --device=gpu -p tests.plugins.transfer_trace \
|
|
|
56
56
|
uv pip install --target /tmp/sanitizer "nvidia-cuda-sanitizer-api==13.*"
|
|
57
57
|
S=/tmp/sanitizer/nvidia/cu13/bin/compute-sanitizer
|
|
58
58
|
OWN=(--kernel-name kns=commkit_ --kernel-name kns=bps_ --kernel-name kns=cs_block \
|
|
59
|
-
--kernel-name kns=llr --kernel-name kns=iir_ --kernel-name kns=selftest --kernel-name kns=ldpc
|
|
59
|
+
--kernel-name kns=llr --kernel-name kns=iir_ --kernel-name kns=selftest --kernel-name kns=ldpc \
|
|
60
|
+
--kernel-name kns=algebraic)
|
|
60
61
|
FILES="tests/test_cuda_infra.py tests/equalization/test_cs_kernel.py tests/equalization/test_bps_kernel.py
|
|
61
62
|
tests/equalization/test_blind.py tests/mapping/test_llr.py tests/mapping/test_gray.py tests/recovery/test_bps.py
|
|
62
63
|
tests/test_filtering.py tests/test_fiber.py tests/impairments/channel/test_channel_nonlinear.py
|
|
63
|
-
tests/coding/test_ldpc_kernel.py"
|
|
64
|
+
tests/coding/test_ldpc_kernel.py tests/coding/test_algebraic.py"
|
|
64
65
|
BLOCK=$(.venv/bin/python -m pytest --device=gpu -q --collect-only tests/equalization/test_block.py | grep '::')
|
|
65
66
|
for tool in memcheck racecheck synccheck initcheck; do
|
|
66
67
|
filter=(); [[ $tool == racecheck || $tool == synccheck ]] && filter=("${OWN[@]}")
|
|
@@ -104,7 +105,7 @@ Timed runs:
|
|
|
104
105
|
```bash
|
|
105
106
|
uv run pytest benchmarks/bench_bps.py --benchmark-only --device=all
|
|
106
107
|
uv run pytest benchmarks/ --benchmark-only --device=all \
|
|
107
|
-
--benchmark-compare=
|
|
108
|
+
--benchmark-compare=0008 --benchmark-storage=file://benchmarks/baselines
|
|
108
109
|
uv run python benchmarks/record_baseline.py NAME # record a new baseline
|
|
109
110
|
```
|
|
110
111
|
|
|
@@ -126,7 +127,7 @@ Dependencies point downward only:
|
|
|
126
127
|
| --- | --- | --- |
|
|
127
128
|
| Infrastructure | `backend`, `logger`, `_cuda` | NumPy, CuPy |
|
|
128
129
|
| Array and math helpers | `_array` (shape and validation), `math` (power, normalization, dB, linear trend) | infrastructure |
|
|
129
|
-
| Value objects | `mapping.Constellation`, pulse classes, `recovery` CPR configs, `coding` (code objects with their `encode` / `decode`; array-only, never imports `core`) | layers above |
|
|
130
|
+
| Value objects | `mapping.Constellation`, pulse classes, `recovery` CPR configs, `coding` (code objects with their `encode` / `decode`; array-only, never imports `core`; `io` stores a code as its class and constructor fields, so those fields are numbers, strings, tuples, host arrays, constellations or other codes) | layers above |
|
|
130
131
|
| Core | `core` (`Signal`, frames, generation), `io` | layers above |
|
|
131
132
|
| DSP | `mapping`, `filtering`, `multirate`, `spectral`, `smoothing`, `impairments`, `timing`, `frequency`, `recovery`, `equalization`, `metrics`, `analysis` | layers above, and lower DSP modules |
|
|
132
133
|
| Presentation | `plotting` | everything; **nothing imports plotting** |
|
|
@@ -137,6 +138,7 @@ Dependencies point downward only:
|
|
|
137
138
|
- `analysis` holds laser and phase characterization.
|
|
138
139
|
- `metrics` is the reporting layer.
|
|
139
140
|
- **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.
|
|
141
|
+
- **One documented home per public name.** A package re-exports its subpackages' names, and users and documentation import them from the package (`commkit.impairments.Fiber`, `commkit.impairments.apply_pmd`), not from the subpackage that defines them.
|
|
140
142
|
- **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`).
|
|
141
143
|
|
|
142
144
|
---
|
|
@@ -145,7 +147,7 @@ Dependencies point downward only:
|
|
|
145
147
|
|
|
146
148
|
### Signal
|
|
147
149
|
|
|
148
|
-
- `Signal` holds: samples (NumPy or CuPy, `(N,)` or `(C, N)`, time on the last axis), the **facts** `sampling_rate`, `symbol_rate` and `
|
|
150
|
+
- `Signal` holds: samples (NumPy or CuPy, `(N,)` or `(C, N)`, time on the last axis), the **facts** `sampling_rate`, `symbol_rate`, `center_frequency` and `domain` (`"DIG"`, `"RF"` or `"OPT"`; only RF and optical signals have a carrier; an optical carrier also gives the derived `center_wavelength_nm`, which the dispersion, fiber and OSNR functions take as a fact), the **description** `constellation` and `pulse`, and the ground truth `reference` (transmitted symbols, the bits on the air `bits` and the source bits `info_bits`, the same array when nothing is coded or matched, and the `code` between them) plus the `frame` layout.
|
|
149
151
|
- It never holds pipeline results.
|
|
150
152
|
- Construction does no hidden work: no mapping, no normalization, no device move, no shape guessing. Factories (`generate`, frames) do that work.
|
|
151
153
|
- `sig.to("gpu")` and `sig.replace(...)` return new Signals. Arrays are shared, not copied; use `clone()` for an independent copy.
|
|
@@ -153,7 +155,7 @@ Dependencies point downward only:
|
|
|
153
155
|
|
|
154
156
|
### Function signatures
|
|
155
157
|
|
|
156
|
-
- `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 (`
|
|
158
|
+
- `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 (`fir_taps(sampling_rate=..., num_taps=..., cutoff=...)`). The data-argument count is recorded per function in the contract registry.
|
|
157
159
|
- **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.
|
|
158
160
|
- Functions on *derived* quantities (phase trajectories, correlations, taps, PSD bins, results) and synthesis primitives stay array-only.
|
|
159
161
|
- **Metadata rule: facts versus choices.**
|
|
@@ -175,7 +177,7 @@ Dependencies point downward only:
|
|
|
175
177
|
- **Device follows the data.** There are no `backend=` arguments and no global switches. Unsupported array types (JAX, PyTorch) raise `TypeError`.
|
|
176
178
|
- 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".
|
|
177
179
|
- **No `debug_plot`.** Numerical code never imports plotting. Plot functions consume results or recompute through public compute functions.
|
|
178
|
-
- **Design and apply are separate functions**, for example `
|
|
180
|
+
- **Design and apply are separate functions**, for example `RRC(...).taps(sps)` and `fir_filter`, or `butterworth_sos` and `iir_filter`.
|
|
179
181
|
|
|
180
182
|
### Returns
|
|
181
183
|
|
|
@@ -201,8 +203,11 @@ Dependencies point downward only:
|
|
|
201
203
|
- The algorithm is chosen by an object, never by the function name or a string: `estimate_carrier_phase(y, BPS())`, not `recover_carrier_phase_bps(y)`. The verb function dispatches on the object's type through a private table in its module. There is no `recover_*` or `compensate_*`.
|
|
202
204
|
- Computations use plain nouns (`allan_deviation`).
|
|
203
205
|
- **Glossary**, one name each:
|
|
204
|
-
- `num_taps`, `step_size`, `sps`, `constellation`, `pulse`, `rng`, `num_symbols`, `num_channels
|
|
205
|
-
-
|
|
206
|
+
- `num_taps`, `step_size`, `sps`, `constellation`, `pulse`, `rng`, `num_symbols`, `num_channels` (never streams), `sampling_rate`, `symbol_rate`, `noise_var`, `nfft`, `regularization`, `max_iterations`.
|
|
207
|
+
- A size names its unit: `num_symbols`, `num_blocks` (code blocks, codewords of a FEC code), `info_bits` (a code's information length where it is a parameter), `blocks_per_frame`, `num_variables`, `num_segments`. In `coding`, `length` and `n` mean the codeword length in bits and `k` the information length, nothing else.
|
|
208
|
+
- A seeded part of a factory takes `<part>_rng` (`payload_rng`), with the same int / Generator / None semantics as `rng`.
|
|
209
|
+
- A variant of one algorithm is a `method=` string only where it is a numerical flavor, not an algorithm (`method="maxlog" | "exact"`); algorithms are objects.
|
|
210
|
+
- Values are in SI units without a suffix: Hz, s, rad, rad/s, W (`linewidth` and `reference_bandwidth` in Hz, `drift_rate` in rad/s, `taus` in s). A result that holds one quantity in two units suffixes the SI one (`lags` in samples, `lag_s` in seconds). A conventional non-SI unit goes into the name: `_db`, `_dbm`, `_deg`, `_nm`, `_km`, `_ps_nm_km`. Normalized quantities say so (`normalized_bandwidth`). Do not add second names for one quantity in another unit (no `center_frequency_ghz` next to `center_frequency`); `center_wavelength_nm` is a different quantity, derived from the carrier.
|
|
206
211
|
|
|
207
212
|
### Docstrings
|
|
208
213
|
|
|
@@ -232,8 +237,8 @@ Docstrings describe the code as it is; a change in behavior updates them in the
|
|
|
232
237
|
- **Precision contract.** Every function computes in a predictable precision, and none promotes single-precision data on its own:
|
|
233
238
|
- **Transforms keep the input's precision**: complex64 in, complex64 out and computed throughout (filters, impairments, the fiber solver and DBP, resampling, delays, carrier-phase correction); complex128 in, complex128 out.
|
|
234
239
|
- **Fixed-precision blocks** compute in single precision whatever the input: the equalizers in complex64 (with complex128 accumulation inside, below), `compute_llr` / `Constellation.llr` in float32. Double input to them emits `commkit.PrecisionWarning` (`_array.warn_if_double`, attributed to the caller's line; Python shows it once per call site). Users who want strictness turn it into an error with `warnings.simplefilter("error", commkit.PrecisionWarning)`; the test suite does (`pytest_configure` in `tests/conftest.py`; never in pyproject's `filterwarnings`, which imports commkit before coverage starts and drops every module-level line from the report), so a library path that feeds double data to these blocks fails CI. Training and reference symbols are ideal values and are not checked.
|
|
235
|
-
- **Deliberate float64**, documented where it happens, always on phases, small arrays or accumulators, never on sample-rate data that is then cast back: phases before `unwrap`, phase trajectories and phase ramps (wrapped in float64, rotated in the data's precision), frequency grids and estimates, the recursions' state (PLL, Tikhonov, RLS `P`), the IIR filter's sections and state (poles near z = 1; the samples keep their precision on both devices), and sums: the metrics compute per element in the input's precision and accumulate in float64 (`gmi` / `mi` in single precision moved by under
|
|
236
|
-
- **Audit with `tests/plugins/precision_trace.py`** after touching numerics: it lists every float64/complex128 array of sample-rate size that a NumPy path of the library creates from single-precision data or from scratch, with its source line. Every reported site must be a deliberate use from the list above (or a test passing complex128 on purpose). Output dtypes alone do not show this: a function that computes in complex128 and casts back returns complex64.
|
|
240
|
+
- **Deliberate float64**, documented where it happens, always on phases, small arrays or accumulators, never on sample-rate data that is then cast back: phases before `unwrap`, phase trajectories and phase ramps (wrapped in float64, rotated in the data's precision), frequency grids and estimates, the recursions' state (PLL, Tikhonov, RLS `P`), the IIR filter's sections and state (poles near z = 1; the samples keep their precision on both devices), and sums: the metrics compute per element in the input's precision and accumulate in float64 (`gmi` / `mi` in single precision moved by under 5e-8 bit, far below the Monte Carlo error).
|
|
241
|
+
- **Audit with `tests/plugins/precision_trace.py`** after touching numerics: it lists every float64/complex128 array of sample-rate size that a NumPy path of the library creates from single-precision data, from integer or bool data (bits, hard decisions; in `coding` from the input of `encode` / `decode`) or from scratch, including through `astype`, with its source line. Every reported site must be a deliberate use from the list above (or a test passing complex128 on purpose). Output dtypes alone do not show this: a function that computes in complex128 and casts back returns complex64.
|
|
237
242
|
- complex128 pays only where single-precision rounding accumulates over many FFT passes, as in the fiber solver (6e-4 after 10^4 steps in complex64, 1e-12 in complex128); there the GPU runs its plain CuPy stages, not the fused complex64 kernels.
|
|
238
243
|
- **The NEP 50 trap**: under NumPy 2 a NumPy scalar is not weak, so `np.float64(a) * x64` (any `np.sqrt`, `np.pi * ...` result, or a float64 array such as `xp.fft.fftfreq`) promotes complex64 data to complex128. Combine single-precision data only with Python scalars or with arrays cast to its precision; rotate with `math._derotate` (float64 phase wrap, single-precision phasor, one fused kernel on the GPU).
|
|
239
244
|
- **Accumulation precision:**
|
|
@@ -255,7 +260,8 @@ Docstrings describe the code as it is; a change in behavior updates them in the
|
|
|
255
260
|
| Sequential recursions: LMS/RLS/CMA/RDE, PLL, Tikhonov, cycle slips, the Mengali-Morelli bootstrap over the lags, per-codeword decoders on trellises and trees (CCDM matching, polar SC/SCL, Viterbi, BCJR, turbo decoding, recursive systematic encoding; independent blocks in parallel) | Numba `@njit(cache=True, fastmath=True, nogil=True)`; independent rows in parallel (`prange`: the bootstrap's blocks). The equalizers' kernel with inline carrier recovery compiles strict (its unwrap and running sums) and calls its tap and distance loops as separate fast-math functions, which keep their flags when inlined (an `inline="always"` helper takes its caller's) | CPU; GPU input does one round trip to the host and back |
|
|
256
261
|
| Linear recursions: IIR filtering | CPU: Numba kernel without `fastmath`, so double input gives SciPy's result bit for bit. GPU: CUDA kernel (`iir`), blocks filtered in parallel and the state carried across them in two levels, in double precision | follows the input |
|
|
257
262
|
| Hot spots limited by intermediates, launches or round trips | CUDA C++ via CuPy `RawModule` (`commkit/_cuda`) | GPU |
|
|
258
|
-
| Binary linear algebra of codes: encoding, syndromes, the correlations of maximum-likelihood decoding, the
|
|
263
|
+
| Binary linear algebra of codes: encoding, syndromes, the correlations of maximum-likelihood decoding, the syndromes of algebraic decoding, Chase's test patterns through a hard decoder | one float32 GEMM per chunk (`coding._gf2.matmul_mod2`: BLAS, cuBLAS), exact below an inner dimension of 2^24, its parity taken in place on the CPU and by one elementwise kernel writing int8 on the GPU; small tables (generators, coset leaders) cached per code object and device (`backend._device_tables`) | follows the input |
|
|
264
|
+
| Algebraic decoding from the syndromes (BCH, Reed-Solomon): Berlekamp-Massey, a Chien search that stops at the locator's degree, Forney at the roots | CPU: Numba `parallel=True` over codewords. GPU: CUDA kernel (`algebraic_correct`), one thread per codeword, the same steps in integer table arithmetic, so both agree exactly (a CuPy version running Berlekamp-Massey in lockstep over the codewords, about 450 launches per decode, was 12x slower on KP4) | follows the input |
|
|
259
265
|
| Iterative decoding of many codewords: layered LDPC belief propagation (min-sum, sum-product) | CPU: Numba `parallel=True` over codewords, all layers and iterations inside, without fast-math. GPU: CUDA kernel (`ldpc_layered`), one thread block per codeword, the rows of a layer in parallel, early stop per codeword inside the kernel; the same rows in the same order on both, so min-sum agrees bit for bit (a CuPy version, about 12 launches per layer, was 7-23x slower) | follows the input |
|
|
260
266
|
| Elementwise chains between multi-core FFTs (the fiber solver's Kerr and PMD stages) | NumPy: a Numba `parallel=True` pass there competes with `scipy.fft`'s threads (the solver's Numba passes measured 1.1-1.8x slower end to end, 2^14-2^20); adopt one only on a paired end-to-end A/B | CPU |
|
|
261
267
|
|
|
@@ -308,10 +314,10 @@ Docstrings describe the code as it is; a change in behavior updates them in the
|
|
|
308
314
|
|
|
309
315
|
## 8. Benchmarks
|
|
310
316
|
|
|
311
|
-
`benchmarks/` tracks the GPU-relevant hot paths. Baselines are committed under `benchmarks/baselines/`. The current reference is `
|
|
317
|
+
`benchmarks/` tracks the GPU-relevant hot paths. Baselines are committed under `benchmarks/baselines/`. The current reference is `0008` (`v3_1`) on the reference machine: RTX 4070 Ti, Ryzen 7 7800X3D, WSL2. It is the first baseline with the polar, Viterbi, turbo, Chase and CCDM benchmarks, recorded at the 3.1 state, best of ten passes (two recordings of five merged per benchmark: five still kept GPU outliers of 2x on a few benchmarks whose code had not changed); `0007` (`v3_0`) is the first baseline with `bench_coding.py` (LDPC encoding and decoding, KP4 decoding), recorded at the 3.0 state with the session warmup of Numba's thread pool, best of five passes; `0006` (`perf_cleanup`) is the first with `bench_filters.py`, `bench_pll`, `bench_mengali_morelli_blockwise` and `bench_correct_iq_imbalance`, recorded after the performance pass of the IIR kernel and the block equalizers' in-graph block copies, best of five passes; `0005` (`gpu_decoder_path`) is the first with `bench_mapping.py` and `bench_compute_llr_nonseparable`, recorded after the GPU LLR, mapping and decision kernels; `0004` (`fiber_dbp`) adds the fiber and DBP benchmarks to `0003` (`v2_0`, the 2.0 state); `0002` (`pre_v2`) is the 1.x state. `0002` ran the CPR equalizer and Viterbi-Viterbi benchmarks on workloads that did not converge, so compare those only from `0003` on.
|
|
312
318
|
|
|
313
319
|
- **Workloads must converge.** A workload the algorithm cannot handle times a failure mode (slip storms, divergence), not the operating point. Every equalizer benchmark asserts its symbol error rate with `benchutils.assert_converged`.
|
|
314
|
-
- **Baselines** are recorded with `benchmarks/record_baseline.py`: each file in its own process, best of three passes (`--passes`; `0006` and `0007` used five, as three still kept outliers of 2-4x on a few small benchmarks). In one full-suite process, small GPU benchmarks after the large equalizer workloads ran 2-12x slower than alone, so never record a baseline from a single `pytest benchmarks/` run.
|
|
320
|
+
- **Baselines** are recorded with `benchmarks/record_baseline.py`: each file in its own process, best of three passes (`--passes`; `0006` and `0007` used five and `0008` ten, as three still kept outliers of 2-4x on a few small benchmarks). In one full-suite process, small GPU benchmarks after the large equalizer workloads ran 2-12x slower than alone, so never record a baseline from a single `pytest benchmarks/` run.
|
|
315
321
|
|
|
316
322
|
- **IDs** are `[cpu]` or `[gpu]`, meaning the input device.
|
|
317
323
|
- **Timing:**
|
|
@@ -321,7 +327,7 @@ Docstrings describe the code as it is; a change in behavior updates them in the
|
|
|
321
327
|
- **Tools:** `benchmarks/benchutils.py` provides `CudaEventTimer` and `nvtx_range` for `nsys` profiling.
|
|
322
328
|
- **Logging** is set to WARNING in `benchmarks/conftest.py`.
|
|
323
329
|
- **Trust deltas, not single runs.** A ±20-40% swing has been seen under load. Re-run a suspected regression in isolation, or do a controlled A/B with at least 7 repetitions, alternating old and new per repetition and comparing the paired ratios. When even unchanged benchmarks move, time the changed part itself with `CudaEventTimer` and quote it against the step it sits in.
|
|
324
|
-
- A commit touching a hot path quotes its delta against `
|
|
330
|
+
- A commit touching a hot path quotes its delta against `0008`. Tolerance is 5% on CPU and 10% on GPU.
|
|
325
331
|
|
|
326
332
|
---
|
|
327
333
|
|
|
@@ -344,7 +350,7 @@ Steps, once CuPy 14.3 (with cp315 wheels for `cupy-cuda13x`) and a fixed `jupyte
|
|
|
344
350
|
- `pyproject.toml`: `requires-python = ">=3.15"`, classifiers 3.15 only, ruff `target-version = "py315"` (if ruff supports it), raise the dependency minimums to the first releases with 3.15 wheels (`cupy-cuda13x[ctk]>=14.3`, `numba>=0.68`, `numpy>=2.5`, `scipy>=1.18`; recheck each).
|
|
345
351
|
- `ci.yml`: test matrix `python-version: ["3.15"]` (keep both `locked` and `lowest-direct`); the static-analysis, kernel-coverage and notebook jobs on 3.15. `publish.yml`: build on 3.15.
|
|
346
352
|
- `uv lock`; README install requirements; §2 here ("Python 3.12+").
|
|
347
|
-
- Benchmarks: record a new baseline on 3.15 (it lands in `benchmarks/baselines/Linux-CPython-3.15-64bit/`), compare it with `
|
|
353
|
+
- Benchmarks: record a new baseline on 3.15 (it lands in `benchmarks/baselines/Linux-CPython-3.15-64bit/`), compare it with `0008` by a paired A/B of 3.14 against 3.15 on unchanged code, and make it the reference.
|
|
348
354
|
3. **Lazy imports (PEP 810)**. Confirm the rules on the interpreter first. As specified, `lazy` is allowed only for module-level imports: not inside functions, class bodies or `try` blocks, and not with `*`.
|
|
349
355
|
- About 60 function-local imports exist to keep `import commkit` light or optional: `import cupy as cp` (11 sites), `import numba` (9), `from . import compiler` in `_cuda` (5), and local imports that break cycles (`Constellation`, `Signal`, `normalize`). Move each to a module-level `lazy import` / `lazy from`. Concentrated in `_cuda/__init__.py`, `backend.py`, `io.py`, `recovery/`, `core/`, `plotting/`.
|
|
350
356
|
- CuPy stays optional: a lazy import of a missing module fails only when used, so every use must stay behind the existing availability checks (`is_cupy_available`, `dispatch`). `backend.py`'s probing needs care: decide per site whether the probe imports eagerly inside `try`.
|
|
@@ -359,9 +365,8 @@ Steps, once CuPy 14.3 (with cp315 wheels for `cupy-cuda13x`) and a fixed `jupyte
|
|
|
359
365
|
|
|
360
366
|
### Channel coding: open items
|
|
361
367
|
|
|
362
|
-
1. **
|
|
363
|
-
2. **
|
|
364
|
-
3. **NR rate matching for polar codes** (sub-block interleaving, the circular buffer, puncturing and shortening with their frozen-set rules, parity-check bits) and **LTE rate matching for turbo codes** are not included: `PolarCode` and `TurboCode` are the mother codes. `Punctured` and `Shortened` cover generic rate matching.
|
|
368
|
+
1. **oFEC** (OpenROADM, ITU-T G.709.3) is not implemented: its block structure is specified in the OpenROADM MSA documents, which were not available in a form to implement from. It would build on `BCHCode` (extended BCH(256, 239)), `Chase` with soft output and the staircase decoder's iteration over a window.
|
|
369
|
+
2. **NR rate matching for polar codes** (sub-block interleaving, the circular buffer, puncturing and shortening with their frozen-set rules, parity-check bits) and **LTE rate matching for turbo codes** are not included: `PolarCode` and `TurboCode` are the mother codes. `Punctured` and `Shortened` cover generic rate matching.
|
|
365
370
|
|
|
366
371
|
### Nonlinear fiber channel: future features
|
|
367
372
|
|
|
@@ -4,6 +4,122 @@ Notable changes to CommKit, newest first. Performance figures are from the refer
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [3.1.1] - 2026-10-29
|
|
8
|
+
|
|
9
|
+
API consistency. One name per concept and a unit in every size, defaults that model the physics, physical units where the rest of the API has them, and features that use what the Signal and the frame already carry: the optical carrier for dispersion, fiber and OSNR noise, the frame's pilots for the pilot-aided algorithms, the code of the reference for post-FEC metrics. Most changes are renames that fail loudly with `TypeError`; the four default changes and the speed of light alter numbers and are listed first.
|
|
10
|
+
|
|
11
|
+
### Changes that alter results
|
|
12
|
+
|
|
13
|
+
- **`iir_filter` is causal by default** (`zero_phase=False`): a DAC, driver or photodiode bandwidth has group delay and an unsquared magnitude response. The old default filtered forward and backward, squaring the response. Pass `zero_phase=True` for offline zero-phase filtering.
|
|
14
|
+
- **`correct_timing` zero-fills by default** (`mode="zero"`); `mode="circular"` wrapped the end of a capture onto its start, right only for a periodic record.
|
|
15
|
+
- **`SingleCarrierFrame` draws a fresh payload by default** (`payload_rng=None`), so a Monte Carlo loop over frames no longer repeats one payload. Pass `payload_rng=<int>` for a fixed payload; the frame keeps the seed it used, so copies, `replace()` and saved frames rebuild the same one. The pilots keep their fixed `pilot_seed`.
|
|
16
|
+
- **`SingleCarrierFrame.to_signal` and `Preamble.to_signal` require `symbol_rate`** and default to `sps=1`, as `generate` does; they used to default to 1 MBd at 4 samples per symbol.
|
|
17
|
+
- **One exact speed of light** (299 792 458 m/s): dispersion used 2.998e8, which moves chromatic-dispersion results by 7e-5 relative.
|
|
18
|
+
|
|
19
|
+
### Changes that may need code updates
|
|
20
|
+
|
|
21
|
+
- **Code sizes name their unit**: `CRC`, `ConvolutionalCode` and `TurboCode` (and `CRC.nr`, `CRC.ethernet`, `ConvolutionalCode.k7`, `TurboCode.lte`) take `info_bits` instead of `block_length`; `CCDM` takes `num_symbols`; `StaircaseCode`, `StaircaseCode.g709_2` and `Concatenated.cfec` take `blocks_per_frame` instead of `num_blocks` (which `generate` uses for codewords); `LDPCCode`'s mother length is `num_variables`; `PolarCode.nr(k=, n=)` is keyword-only. The Reed-Solomon `length` property (in code symbols) is gone: read `symbols` or `n`.
|
|
22
|
+
- **`num_streams` is `num_channels`** in `SingleCarrierFrame`, `Preamble` and `Signal`.
|
|
23
|
+
- **One name per concept**:
|
|
24
|
+
- `noise_var` in `zf_equalizer` and `plot_zf_equalizer_response` (was `noise_variance`); `LinewidthEstimate.awgn_var`, a phase term in rad², is `angle_noise_var`.
|
|
25
|
+
- `nfft` in `estimate_transfer_function` (was `n_fft`) and `ols_fir_filter` (was `fft_size`).
|
|
26
|
+
- `regularization` in `estimate_transfer_function` (was `reg`) and `rls` (was `delta`).
|
|
27
|
+
- `zf_equalizer(center_tap=)` (was `center`); `ols_fir_filter(causal=False)` replaces `center=True`.
|
|
28
|
+
- `num_train_symbols` and `num_impulse_samples` in the equalizer plots (were `n_train`, `n_impulse`).
|
|
29
|
+
- `HardIterations(max_iterations=)` (was `iterations`, though it stops early); `BCJR` and `TurboIterations` take `method="maxlog" | "exact"` like `compute_llr` (was `exact=False | True`).
|
|
30
|
+
- `barker_sequence` and `zadoff_chu_sequence` take `num_symbols` like `Preamble` (was `length`).
|
|
31
|
+
- `AllanDeviation.taus` matches `allan_deviation(taus=)` (was `tau_s`); `LinewidthEstimate.num_segments` (was `n_segments`).
|
|
32
|
+
- **`estimate_transfer_function(received, reference)`**: the data under test first, as everywhere else (was `reference, received`).
|
|
33
|
+
- **Physical units**: `apply_polarization_mixing(drift_rate=)` in rad/s with the sampling rate from the Signal (was `drift_rad_per_sample`); `Tikhonov(linewidth=)` in Hz like `apply_phase_noise`, the symbol period taken from its one-sample-per-symbol input (was `linewidth_symbol_periods`); `PLL(normalized_bandwidth=)` (was `bandwidth`), so a bandwidth in Hz cannot be passed by mistake.
|
|
34
|
+
- **One entry point per concept**: the pulse objects (`RRC`, `RC`, `Gaussian`, `Rect`, `SmoothRect`) are the only way to build pulse taps; `rrc_taps`, `rc_taps`, `gaussian_taps`, `rect_taps` and `smoothrect_taps` are private, as their defaults disagreed with the objects'. `demap_symbols_hard` is `demap_symbols`, the twin of `Constellation.demap`.
|
|
35
|
+
- **Polarization demultiplexing on pilot tones is an estimate / correct pair**: `estimate_polarization_tones(samples, tone_frequencies=, track_bandwidth=)` returns a `JonesTrack` (one unmixing matrix by default, or matrices tracked over time), and `correct_polarization(samples, track)` applies it. This replaces `demultiplex_polarization_tones_static` / `_dynamic`, whose return type changed with `return_matrix` and `apply`, and the public `apply_interpolated_matrix`.
|
|
36
|
+
- **The optical functions read the carrier**: `apply_chromatic_dispersion`, `correct_chromatic_dispersion`, `apply_fiber_propagation` and `correct_fiber_propagation` take `center_wavelength_nm` from an optical Signal's `center_frequency`; an explicit value must agree. Arrays and other Signals must pass it: the fiber functions no longer assume 1550 nm silently.
|
|
37
|
+
- **Pilots for the blind equalizers are one object**: `EqualizerPilots(values, mask, gain_db)` replaces the `pilot_ref`, `pilot_mask` and `pilot_gain_db` arguments of `cma`, `rde`, `block_cma` and `block_rde` (now `pilots=`) and the `build_pilot_ref` helper.
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- **`Signal.center_wavelength_nm`**, the wavelength of an optical Signal's carrier.
|
|
42
|
+
- **`apply_awgn(osnr_db=, reference_bandwidth=12.5e9)`** adds ASE to an optical Signal at an OSNR, counted over both polarizations in the reference bandwidth, so `Es/N0 = 2 B_ref / (p R_s) · OSNR` whatever the oversampling.
|
|
43
|
+
- **`EqualizerPilots.from_frame`, `PilotAided.from_frame` and `PilotSymbols.from_frame`** take the pilot positions, symbols and gain from a `SingleCarrierFrame`.
|
|
44
|
+
- **Post-FEC metrics from a `DecodeResult`**: `ber(decoded, sig)` and `fer(decoded, sig)` compare with the reference's `info_bits`, and `fer` takes the block length from the code.
|
|
45
|
+
- **`estimate_noise_variance`**, the data-aided noise variance that `compute_llr`, `gmi` and `mi` take, without the round trip through `snr`.
|
|
46
|
+
- **`estimate_iq_imbalance`** measures the amplitude (dB) and phase (degrees) of an IQ imbalance blindly, per channel, as an `IQImbalanceEstimate`; `correct_iq_imbalance` also takes the estimate and inverts the model exactly.
|
|
47
|
+
- `SingleCarrierFrame.pilot_mask` and `payload_mask`, the symbol masks over the whole frame.
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- `apply_awgn` rejects a non-positive `reference_bandwidth`.
|
|
52
|
+
- `zadoff_chu_sequence`'s length error names the argument.
|
|
53
|
+
|
|
54
|
+
## [3.1.0] - 2026-10-29
|
|
55
|
+
|
|
56
|
+
Review fixes and the completion of the coding release. Coded records are sized in codewords (`generate(num_blocks=)`, `SingleCarrierFrame(payload_blocks=)`) or padded to any length, the reference carries its code, `save_npz` stores everything a Signal holds (codes included), and `Signal.domain` is back. Reed-Solomon and BCH decoding run in per-codeword kernels (KP4 23 times faster on the GPU, 16 on the CPU), and the remaining decoders, LLRs and several DSP paths got faster. Correctness fixes cover bit validation in `encode`, staircase and CCDM validity, complex FIR taps, ZF precision and timing correction. Frame lengths in symbols are renamed `*_num_symbols`; see the first section.
|
|
57
|
+
|
|
58
|
+
### Changes that may need code updates
|
|
59
|
+
|
|
60
|
+
- **The `gpu` extra requires CuPy 14.2** (`cupy-cuda13x>=14.2`), the first release with sparse arrays (`cupyx.scipy.sparse.csr_array`), which the LDPC encoder now uses on the GPU.
|
|
61
|
+
- **Lengths in symbols are named `*_num_symbols` throughout the frame**, as in `generate` and `SingleCarrierFrame.num_symbols`: `SingleCarrierFrame(payload_len=, guard_len=, pilot_block_len=)` are now `payload_num_symbols`, `guard_num_symbols` and `pilot_block_num_symbols`, and `Preamble(length=)` is `Preamble(num_symbols=)`. Rename the arguments and attribute reads.
|
|
62
|
+
- **`encode` rejects bits other than 0 and 1** with `ValueError`. Before, linear codes silently encoded another payload (`[2, 0, 0, 0]` gave the all-zero codeword), a value of 256 wrapped to 0, and the turbo encoder indexed its trellis with the value.
|
|
63
|
+
- **`TurboCode` and `LDPCCode` reject non-integer parameters** with `TypeError` (`TurboCode(40.0)` was accepted; float LDPC indices were truncated into a different code).
|
|
64
|
+
- **`fir_filter` and `ols_fir_filter` raise for complex taps on real samples.** The imaginary part was dropped (the taps `[1j]` gave zeros). Pass complex samples for a complex output.
|
|
65
|
+
- **`correct_timing(mode="slice")` raises for offsets outside `[0, N)`**: a negative offset kept only the last samples. Use `mode="zero"` or `"circular"` for negative offsets.
|
|
66
|
+
- **`LDPCCode.parity_check()` returns a `scipy.sparse.csr_array`** instead of a `csr_matrix`, as SciPy plans to deprecate its matrix interface. `@` is unchanged, but `*` is now elementwise and indexing a row gives a 1-D array; use `@` for products, or `scipy.sparse.csr_matrix(code.parity_check())` for the old type. `LDPCCode.from_parity_check` accepts either.
|
|
67
|
+
- **`gray_code`, `gray_to_binary` return read-only arrays.** They are cached, so writing to one changed every later call's result; copy before modifying.
|
|
68
|
+
|
|
69
|
+
### Added
|
|
70
|
+
|
|
71
|
+
- **`Reference.code`**, the code that turned `info_bits` into `bits` (the FEC code, or the matcher `generate` picks for a shaped constellation), so `decode(llrs, code=sig.reference.code)` needs nothing else; `code_block` is taken from it. `generate` and `extract_payload` set it, and the Signal summary names it.
|
|
72
|
+
- **`save_npz` stores everything a Signal holds**: a frame's `payload_code` and a reference's `code` (a FEC code, distribution matcher or their composition) as their class and constructor fields, with their arrays (parity checks, information positions, ...) as `frame_payload_code.*` and `reference_code.*` entries, and the frame's pilot bits. Still loaded with `allow_pickle=False`; only the classes of `commkit.coding` are rebuilt. A frame with a payload code used to raise.
|
|
73
|
+
- **`Signal.domain`: `"DIG"` (default), `"RF"` or `"OPT"`**, where the samples live. An RF or optical Signal carries its `center_frequency` (a digital one keeps 0, and setting a carrier on it raises); the summary shows the domain and the carrier (with its wavelength for an optical signal), `save_npz`/`load_npz` round-trip it, and `plot_psd`/`plot_spectrogram` take it from the Signal, so an optical spectrum is drawn at its physical frequencies or wavelengths without arguments.
|
|
74
|
+
- **`generate(..., code=, num_blocks=)` sizes a coded record in code blocks** (codewords of a FEC code) instead of symbols, so `generate(QPSK, symbol_rate=..., code=code, num_blocks=100)` replaces `num_codewords * code.n // bits_per_symbol`. Give either `num_symbols` or `num_blocks`.
|
|
75
|
+
- **`pad=True` fits a coded record to any length**, such as an arbitrary waveform generator's record: `generate(..., num_symbols=N, code=code, pad=True)` carries as many whole codewords as fit, then random uncoded filler bits; `SingleCarrierFrame(payload_pad=True)` does the same for a frame's payload. Not with a distribution matcher, whose composition the filler would change.
|
|
76
|
+
- `Reference.num_blocks`, the whole code blocks per channel: decode them with `decode(llrs[..., : ref.num_blocks * code.n], code=code)`.
|
|
77
|
+
- **`SingleCarrierFrame(payload_blocks=)` sizes a coded payload in codewords** instead of `payload_num_symbols` (which it then sets); the codewords must fill whole symbols and pilot periods, and the error names the nearest valid counts. `payload_code` takes distribution matchers and PAS too, with the shaped payload constellation they match onto.
|
|
78
|
+
- `SingleCarrierFrame.num_symbols`, the frame length in symbols (preamble, body, guard): `to_signal` gives `num_symbols * sps` samples per stream. A `payload_num_symbols` that does not fill whole codewords now names the nearest valid lengths, pilot periods included.
|
|
79
|
+
- The Signal summary (`print(sig)`) shows the code block, the blocks per channel with any filler, and the information rate of a coded reference, and a frame's payload code.
|
|
80
|
+
- `generate`'s docstring explains coded payloads (blocks, sizing, decoding) and that a shaped constellation without a code goes through one CCDM block of the whole record, the ideal-matcher limit, with how to model a transmitter's shorter matcher.
|
|
81
|
+
|
|
82
|
+
### Performance
|
|
83
|
+
|
|
84
|
+
Paired A/B of alternating processes against the previous commit of each change:
|
|
85
|
+
|
|
86
|
+
- Reed-Solomon decoding: KP4, 2048 hard-decision codewords at BER 1e-3, CPU 410 to 26 ms (direct XOR reduction 0.80, Forney's formula at the error positions only 0.42, hard input converted in place 0.79, a per-codeword Numba decoder from the syndromes 0.27), GPU 41 to 1.8 ms (a CUDA kernel with one thread per codeword in place of Berlekamp-Massey's 450 launches, 0.080).
|
|
87
|
+
- BCH decoding through the same per-codeword decoders: Chase-II of BCH(1000, 970) 0.26 on the CPU and 0.22 on the GPU, staircase decoding 0.35 and 0.14.
|
|
88
|
+
- Chase decoding: 0.53 on the CPU, 0.30-0.51 on the GPU (no LLR round trip or re-encoding, no 4-D flip mask, larger GPU chunks).
|
|
89
|
+
- LDPC sum-product decoding on the CPU: DVB-S2 1/2, 64 codewords, 0.70 (`phi(|Q|)` kept from a row's first pass instead of computed again).
|
|
90
|
+
- Products of bit arrays modulo 2 (encoding, syndromes): encoding 2^17 OIF 400ZR codewords 0.85 on the CPU and 0.64 on the GPU (no int32 temporaries for the parity).
|
|
91
|
+
- CCDM matching: 8 blocks of 20000 PS-64-QAM symbols, matching 0.76 and dematching 0.84 (one division per point and symbol; the dematcher's interval start in 32-bit limbs instead of one byte per bit).
|
|
92
|
+
- Polar decoding: SC of N = 1024 0.32, SCL(8) 0.70, SCL(32) 0.68 (paths copied only when they change slot).
|
|
93
|
+
- Tail-biting Viterbi: K = 7, 0.035 (all start states scored in one pass); decision storage a quarter of before at every termination.
|
|
94
|
+
- Turbo decoding: LTE K = 1024, max-log 0.82, exact log-MAP 0.65.
|
|
95
|
+
- Cached code tables: interleaving 2^20 values with one `Interleaver` 0.19 on the CPU, 0.013 on the GPU (identity shortcut in the code objects' equality, which every cached lookup called).
|
|
96
|
+
- `mi`: one exponential per element, 256-QAM 0.82 on the CPU and 0.33 on the GPU, 16-QAM 0.52 on the GPU.
|
|
97
|
+
- LMS/RLS with inline carrier recovery: 2 x 50000 16-QAM symbols, PLL 0.95, BPS 0.97 (each symbol's phasor computed once).
|
|
98
|
+
- `Constellation.map` / `demap` on the GPU: about 0.65 per call for 64-QAM and 8-PSK, 4096 to 2^20 symbols (tables cached per device; the hash cached and an identity shortcut in the equality the cache calls).
|
|
99
|
+
- LLRs on the CPU (`compute_llr`, `Constellation.llr`): 0.07-0.26 for 4096 symbols (tables cached per constellation; 256-QAM's closed-form tables were most of the call), 0.84 for 2^20 (no copies of the real and imaginary parts).
|
|
100
|
+
- `correct_frequency_offset` with a static offset: 2 x 2^22 samples, 0.83 on the CPU and 0.69 on the GPU (one float64 ramp instead of three, and no host sync for an offset on the GPU).
|
|
101
|
+
- `plot_equalizer_result` of 2 x 10^6 symbols: 0.08 on both devices (curves smoothed and cut to their min/max envelope on the result's device; about 4000 points per line are transferred and drawn instead of every symbol).
|
|
102
|
+
- White-FM plateau detection of the linewidth estimators: 0.26 for 2^19 PSD bins, 0.91 for 4097 (each log-spaced cell read as a slice instead of a scan over all bins).
|
|
103
|
+
- `apply_pmd`: 2 x 2^22 samples, 0.91 on the CPU and 0.90 on the GPU (the second phasor is the conjugate of the first).
|
|
104
|
+
- `block_lms` with BPS: 2 x 100000 16-QAM symbols, block 256, 0.97 on the CPU and 0.86 on the GPU (the phasor from a real cos and sin).
|
|
105
|
+
|
|
106
|
+
### Fixed
|
|
107
|
+
|
|
108
|
+
- **A shaped `Constellation` rebuilt from its fields is equal to the original.** The constructor divided the pmf by its sum every time, which moved an already normalized pmf by an ulp, so a shaped constellation loaded with `load_npz` could compare unequal to the saved one. A pmf that sums to 1 within rounding is now kept as given.
|
|
109
|
+
- **Staircase `valid` reflected an earlier state of the frame**: a block corrected through the next one kept its failed flag, so `HardIterations(1)` could return the right payload with `valid=False`. Success is now checked on the final frame.
|
|
110
|
+
- **CCDM (and so PAS) `valid` accepted sequences no payload maps to**: a composition has more sequences than the 2^k payloads, and all were valid. Only the matcher's image is valid now.
|
|
111
|
+
- **`zf_equalizer` with a float64 channel estimate returned complex128** for complex64 samples; it now computes in the samples' precision.
|
|
112
|
+
- **`correct_timing(mode="zero")`** repeated each channel's first sample for a negative per-channel offset instead of zero-filling, and returned zeros for a scalar offset of 0.
|
|
113
|
+
- **`mi` and `ols_fir_filter` / `zf_equalizer` bound their working memory.** `mi`'s intermediates reached GiBs for many channels of high-order QAM (1 GiB of differences for two channels of 1024-QAM); they are now capped at 2^24 elements. Overlap-save transformed the whole record at once, against its docstring, about 8 times the input; it now runs in batches of blocks (3.5 times the input on long records; about 5% slower on the CPU for 2^25 samples).
|
|
114
|
+
- **A code object unpickled in another process missed its cached device tables**: it carried the hash of the first process (hashes of bytes differ between processes), so it compared equal to the same code but hashed differently. The cached hash is no longer pickled.
|
|
115
|
+
- `load_npz` closes its archive; `ber`, `fer` and `ser` transfer their error counts once.
|
|
116
|
+
|
|
117
|
+
### Development
|
|
118
|
+
|
|
119
|
+
- Benchmarks for polar, Viterbi, turbo, Chase and CCDM decoding in `bench_coding.py`; `ldpc_workload` is now `coded_workload`.
|
|
120
|
+
- Baseline `0008` (`v3_1`) is the new benchmark reference, the first with those benchmarks, best of ten passes. Against `0007` the geometric mean of the time ratios is 0.88 (0.92 without coding); no benchmark is slower beyond the GPU's run-to-run swing (the slowest, 1.08, on a 0.26 ms GPU call). KP4 decoding takes 1.8 ms on the GPU and 26 ms on the CPU, against 41 and 410 ms.
|
|
121
|
+
- The algebraic decoders' kernel (`algebraic`) joins the sanitizer pass of AGENTS §2, and their Numba decoder the `NUMBA_DISABLE_JIT` coverage job.
|
|
122
|
+
|
|
7
123
|
## [3.0.0] - 2026-10-28
|
|
8
124
|
|
|
9
125
|
The channel-coding release. `commkit.coding` grows from an empty scaffold into a full FEC toolbox: one `encode` / `decode` pair dispatched on code objects covers linear block, LDPC, BCH and Reed-Solomon, polar, convolutional and turbo codes, the FEC structures of the optical standards (KP4, the 802.3dj and 400ZR concatenated codes, the G.709.2 staircase code, PON LDPC), CRCs, interleavers, rate matching and probabilistic amplitude shaping. `generate` and frames produce coded payloads, the reference keeps the source bits next to the bits on the air, and three new notebooks teach it. Shaped `generate` now goes through a distribution matcher, and the example notebooks are renumbered; see the first section.
|
|
@@ -152,7 +268,8 @@ See the [GitHub release](https://github.com/lokgar/commkit/releases/tag/v1.1.0)
|
|
|
152
268
|
|
|
153
269
|
First release. See the [GitHub release](https://github.com/lokgar/commkit/releases/tag/v1.0.0).
|
|
154
270
|
|
|
155
|
-
[Unreleased]: https://github.com/lokgar/commkit/compare/v3.
|
|
271
|
+
[Unreleased]: https://github.com/lokgar/commkit/compare/v3.1.0...HEAD
|
|
272
|
+
[3.1.0]: https://github.com/lokgar/commkit/compare/v3.0.0...v3.1.0
|
|
156
273
|
[3.0.0]: https://github.com/lokgar/commkit/compare/v2.3.0...v3.0.0
|
|
157
274
|
[2.3.0]: https://github.com/lokgar/commkit/compare/v2.2.0...v2.3.0
|
|
158
275
|
[2.2.0]: https://github.com/lokgar/commkit/compare/v2.1.0...v2.2.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: commkit
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.1.1
|
|
4
4
|
Summary: A Python library for high-performance digital communications research.
|
|
5
5
|
Project-URL: Homepage, https://github.com/lokgar/commkit
|
|
6
6
|
Project-URL: Repository, https://github.com/lokgar/commkit
|
|
@@ -26,10 +26,10 @@ Requires-Dist: numba>=0.65
|
|
|
26
26
|
Requires-Dist: numpy>=2.4
|
|
27
27
|
Requires-Dist: scipy>=1.17
|
|
28
28
|
Provides-Extra: full
|
|
29
|
-
Requires-Dist: cupy-cuda13x[ctk]>=14.
|
|
29
|
+
Requires-Dist: cupy-cuda13x[ctk]>=14.2; extra == 'full'
|
|
30
30
|
Requires-Dist: jupyter>=1.1.1; extra == 'full'
|
|
31
31
|
Provides-Extra: gpu
|
|
32
|
-
Requires-Dist: cupy-cuda13x[ctk]>=14.
|
|
32
|
+
Requires-Dist: cupy-cuda13x[ctk]>=14.2; extra == 'gpu'
|
|
33
33
|
Provides-Extra: notebook
|
|
34
34
|
Requires-Dist: jupyter>=1.1.1; extra == 'notebook'
|
|
35
35
|
Description-Content-Type: text/markdown
|
|
@@ -52,7 +52,7 @@ CommKit covers the receiver chain of coherent and IM/DD links (waveform generati
|
|
|
52
52
|
Data is a NumPy or CuPy array, or a `Signal` that wraps the samples together with what is known about them. Algorithms are plain functions that take the data and return a new `Signal` or array, or a typed result such as `EqualizerResult` or an `*Estimate`. Plotting functions draw those results; they never compute them.
|
|
53
53
|
|
|
54
54
|
- **Things that describe** a modulation, a pulse or a sub-algorithm are small frozen objects written in the call: `lms(rx, cpr=BPS(test_phases=64))`. The same object works standalone: `correct_carrier_phase(y, BPS())`.
|
|
55
|
-
- **A `Signal` carries facts and ground truth.** Facts are `sampling_rate`, `symbol_rate` and `
|
|
55
|
+
- **A `Signal` carries facts and ground truth.** Facts are `sampling_rate`, `symbol_rate`, `center_frequency` and `domain`; the carrier of an optical Signal gives its `center_wavelength_nm`, which dispersion, fiber propagation and OSNR noise read. The description is `constellation` and `pulse`. The ground truth is `reference`: the transmitted symbols, the bits on the air and the source bits before coding or distribution matching (the same array when there is neither). Functions take facts from the Signal and raise on a conflicting argument. Choices such as the decision constellation default to the Signal's and can be overridden.
|
|
56
56
|
- **Results with several values are frozen dataclasses** with named fields. Metrics return host floats: one per channel for `(C, N)` input.
|
|
57
57
|
- **The device follows the data.** Processing functions have no `backend=` or `device=` argument. Only factories, which have no input data to follow, take `device=`: `generate(..., device="gpu")`. Move existing data with `sig.to("gpu")`.
|
|
58
58
|
- **Plots draw results.** `plot_*` functions take a Signal, an array or a result object; numerical code never imports plotting.
|
|
@@ -114,17 +114,17 @@ CommKit never picks a device for you: data stays where you put it, and each func
|
|
|
114
114
|
|
|
115
115
|
| Module | Contents |
|
|
116
116
|
| --- | --- |
|
|
117
|
-
| [`commkit.core`](https://github.com/lokgar/commkit/tree/main/commkit/core) | `Signal`, `Reference`, `generate` (uniform, shaped through a distribution matcher, or coded with `code=`), `SingleCarrierFrame` and `Preamble` (pilots, guard intervals, MIMO
|
|
117
|
+
| [`commkit.core`](https://github.com/lokgar/commkit/tree/main/commkit/core) | `Signal`, `Reference`, `generate` (uniform, shaped through a distribution matcher, or coded with `code=`), `SingleCarrierFrame` and `Preamble` (pilots, guard intervals, MIMO channels), `extract_payload`. |
|
|
118
118
|
| [`commkit.mapping`](https://github.com/lokgar/commkit/tree/main/commkit/mapping) | `Constellation` (QAM, PSK, PAM, arbitrary points; Gray labels; probabilistic shaping with `.shaped()`), bit mapping, hard demapping, max-log and exact LLRs. |
|
|
119
119
|
| [`commkit.filtering`](https://github.com/lokgar/commkit/tree/main/commkit/filtering) | Pulses (`RRC`, `RC`, `Gaussian`, `Rect`, `SmoothRect`), FIR and IIR design (Butterworth, Chebyshev I/II, elliptic, Bessel), `fir_filter`, `iir_filter`, `matched_filter`, overlap-save, chromatic-dispersion compensation and digital backpropagation (`correct_fiber_propagation`). |
|
|
120
120
|
| [`commkit.multirate`](https://github.com/lokgar/commkit/blob/main/commkit/multirate.py) | `resample` (fractional), `decimate`, `upsample`, `decimate_to_symbol_rate`. |
|
|
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 (
|
|
122
|
+
| [`commkit.impairments`](https://github.com/lokgar/commkit/tree/main/commkit/impairments) | AWGN at an Es/N0 or an OSNR, phase noise, IQ imbalance (blind estimate; 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`. |
|
|
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
|
|
127
|
-
| [`commkit.metrics`](https://github.com/lokgar/commkit/blob/main/commkit/metrics.py) | `evm`, `snr`, `ber`, `fer`, `ser`, `gmi`, `ngmi`, `mi`, including shaped constellations; pre-FEC and post-FEC; host values per channel. |
|
|
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`; pilot-aided CMA and RDE from a frame's pilots (`EqualizerPilots`); polarization demultiplexing on pilot tones (`estimate_polarization_tones` / `correct_polarization`). |
|
|
127
|
+
| [`commkit.metrics`](https://github.com/lokgar/commkit/blob/main/commkit/metrics.py) | `evm`, `snr`, `ber`, `fer`, `ser`, `gmi`, `ngmi`, `mi`, including shaped constellations; pre-FEC and post-FEC (`ber(decoded, sig)`); `estimate_noise_variance` for LLRs; host values per channel. |
|
|
128
128
|
| [`commkit.analysis`](https://github.com/lokgar/commkit/tree/main/commkit/analysis) | Laser and carrier-phase characterization: `estimate_linewidth` (increment slope, β-separation, delayed self-heterodyne FM-PSD / increment / Lorentzian), FM-noise PSDs, drift separation, Allan deviation. |
|
|
129
129
|
| [`commkit.math`](https://github.com/lokgar/commkit/blob/main/commkit/math.py) | `rms`, `normalize`, dB conversions. |
|
|
130
130
|
| [`commkit.smoothing`](https://github.com/lokgar/commkit/blob/main/commkit/smoothing.py) | Display and estimation smoothers (moving average, Savitzky-Golay, 2-D density). Signal-chain filters are in `filtering`. |
|
|
@@ -16,7 +16,7 @@ CommKit covers the receiver chain of coherent and IM/DD links (waveform generati
|
|
|
16
16
|
Data is a NumPy or CuPy array, or a `Signal` that wraps the samples together with what is known about them. Algorithms are plain functions that take the data and return a new `Signal` or array, or a typed result such as `EqualizerResult` or an `*Estimate`. Plotting functions draw those results; they never compute them.
|
|
17
17
|
|
|
18
18
|
- **Things that describe** a modulation, a pulse or a sub-algorithm are small frozen objects written in the call: `lms(rx, cpr=BPS(test_phases=64))`. The same object works standalone: `correct_carrier_phase(y, BPS())`.
|
|
19
|
-
- **A `Signal` carries facts and ground truth.** Facts are `sampling_rate`, `symbol_rate` and `
|
|
19
|
+
- **A `Signal` carries facts and ground truth.** Facts are `sampling_rate`, `symbol_rate`, `center_frequency` and `domain`; the carrier of an optical Signal gives its `center_wavelength_nm`, which dispersion, fiber propagation and OSNR noise read. The description is `constellation` and `pulse`. The ground truth is `reference`: the transmitted symbols, the bits on the air and the source bits before coding or distribution matching (the same array when there is neither). Functions take facts from the Signal and raise on a conflicting argument. Choices such as the decision constellation default to the Signal's and can be overridden.
|
|
20
20
|
- **Results with several values are frozen dataclasses** with named fields. Metrics return host floats: one per channel for `(C, N)` input.
|
|
21
21
|
- **The device follows the data.** Processing functions have no `backend=` or `device=` argument. Only factories, which have no input data to follow, take `device=`: `generate(..., device="gpu")`. Move existing data with `sig.to("gpu")`.
|
|
22
22
|
- **Plots draw results.** `plot_*` functions take a Signal, an array or a result object; numerical code never imports plotting.
|
|
@@ -78,17 +78,17 @@ CommKit never picks a device for you: data stays where you put it, and each func
|
|
|
78
78
|
|
|
79
79
|
| Module | Contents |
|
|
80
80
|
| --- | --- |
|
|
81
|
-
| [`commkit.core`](https://github.com/lokgar/commkit/tree/main/commkit/core) | `Signal`, `Reference`, `generate` (uniform, shaped through a distribution matcher, or coded with `code=`), `SingleCarrierFrame` and `Preamble` (pilots, guard intervals, MIMO
|
|
81
|
+
| [`commkit.core`](https://github.com/lokgar/commkit/tree/main/commkit/core) | `Signal`, `Reference`, `generate` (uniform, shaped through a distribution matcher, or coded with `code=`), `SingleCarrierFrame` and `Preamble` (pilots, guard intervals, MIMO channels), `extract_payload`. |
|
|
82
82
|
| [`commkit.mapping`](https://github.com/lokgar/commkit/tree/main/commkit/mapping) | `Constellation` (QAM, PSK, PAM, arbitrary points; Gray labels; probabilistic shaping with `.shaped()`), bit mapping, hard demapping, max-log and exact LLRs. |
|
|
83
83
|
| [`commkit.filtering`](https://github.com/lokgar/commkit/tree/main/commkit/filtering) | Pulses (`RRC`, `RC`, `Gaussian`, `Rect`, `SmoothRect`), FIR and IIR design (Butterworth, Chebyshev I/II, elliptic, Bessel), `fir_filter`, `iir_filter`, `matched_filter`, overlap-save, chromatic-dispersion compensation and digital backpropagation (`correct_fiber_propagation`). |
|
|
84
84
|
| [`commkit.multirate`](https://github.com/lokgar/commkit/blob/main/commkit/multirate.py) | `resample` (fractional), `decimate`, `upsample`, `decimate_to_symbol_rate`. |
|
|
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 (
|
|
86
|
+
| [`commkit.impairments`](https://github.com/lokgar/commkit/tree/main/commkit/impairments) | AWGN at an Es/N0 or an OSNR, phase noise, IQ imbalance (blind estimate; 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`. |
|
|
90
|
-
| [`commkit.equalization`](https://github.com/lokgar/commkit/tree/main/commkit/equalization) | Sequential `lms`, `rls`, `cma`, `rde` (Numba) and frequency-domain `block_lms`, `block_cma`, `block_rde` (CuPy, CUDA graphs), butterfly MIMO, inline carrier recovery (`cpr=PLL()` / `BPS()`), continuation with `state=`, `zf_equalizer
|
|
91
|
-
| [`commkit.metrics`](https://github.com/lokgar/commkit/blob/main/commkit/metrics.py) | `evm`, `snr`, `ber`, `fer`, `ser`, `gmi`, `ngmi`, `mi`, including shaped constellations; pre-FEC and post-FEC; host values per channel. |
|
|
90
|
+
| [`commkit.equalization`](https://github.com/lokgar/commkit/tree/main/commkit/equalization) | Sequential `lms`, `rls`, `cma`, `rde` (Numba) and frequency-domain `block_lms`, `block_cma`, `block_rde` (CuPy, CUDA graphs), butterfly MIMO, inline carrier recovery (`cpr=PLL()` / `BPS()`), continuation with `state=`, `zf_equalizer`; pilot-aided CMA and RDE from a frame's pilots (`EqualizerPilots`); polarization demultiplexing on pilot tones (`estimate_polarization_tones` / `correct_polarization`). |
|
|
91
|
+
| [`commkit.metrics`](https://github.com/lokgar/commkit/blob/main/commkit/metrics.py) | `evm`, `snr`, `ber`, `fer`, `ser`, `gmi`, `ngmi`, `mi`, including shaped constellations; pre-FEC and post-FEC (`ber(decoded, sig)`); `estimate_noise_variance` for LLRs; host values per channel. |
|
|
92
92
|
| [`commkit.analysis`](https://github.com/lokgar/commkit/tree/main/commkit/analysis) | Laser and carrier-phase characterization: `estimate_linewidth` (increment slope, β-separation, delayed self-heterodyne FM-PSD / increment / Lorentzian), FM-noise PSDs, drift separation, Allan deviation. |
|
|
93
93
|
| [`commkit.math`](https://github.com/lokgar/commkit/blob/main/commkit/math.py) | `rms`, `normalize`, dB conversions. |
|
|
94
94
|
| [`commkit.smoothing`](https://github.com/lokgar/commkit/blob/main/commkit/smoothing.py) | Display and estimation smoothers (moving average, Savitzky-Golay, 2-D density). Signal-chain filters are in `filtering`. |
|