commkit 3.1.3__tar.gz → 3.2.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.1.3 → commkit-3.2.1}/AGENTS.md +21 -3
- {commkit-3.1.3 → commkit-3.2.1}/CHANGELOG.md +45 -2
- {commkit-3.1.3 → commkit-3.2.1}/PKG-INFO +3 -3
- {commkit-3.1.3 → commkit-3.2.1}/README.md +2 -2
- {commkit-3.1.3 → commkit-3.2.1}/benchmarks/bench_block_blind.py +13 -6
- {commkit-3.1.3 → commkit-3.2.1}/benchmarks/bench_fiber.py +6 -4
- {commkit-3.1.3 → commkit-3.2.1}/benchmarks/benchutils.py +11 -3
- {commkit-3.1.3 → commkit-3.2.1}/benchmarks/conftest.py +14 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/__init__.py +1 -1
- {commkit-3.1.3 → commkit-3.2.1}/commkit/coding/__init__.py +21 -1
- {commkit-3.1.3 → commkit-3.2.1}/commkit/coding/base.py +23 -1
- commkit-3.2.1/commkit/coding/exit.py +946 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/coding/ldpc.py +3 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/coding/linear.py +3 -1
- {commkit-3.1.3 → commkit-3.2.1}/commkit/core/frame.py +14 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/equalization/_block/_blind.py +1 -1
- {commkit-3.1.3 → commkit-3.2.1}/commkit/equalization/_block/_engine.py +1 -1
- {commkit-3.1.3 → commkit-3.2.1}/commkit/equalization/_common.py +2 -2
- {commkit-3.1.3 → commkit-3.2.1}/commkit/equalization/_numba_loops.py +4 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/equalization/blind.py +4 -2
- {commkit-3.1.3 → commkit-3.2.1}/commkit/equalization/result.py +3 -1
- {commkit-3.1.3 → commkit-3.2.1}/commkit/equalization/sequential/_dd.py +20 -10
- {commkit-3.1.3 → commkit-3.2.1}/commkit/equalization/sequential/_setup.py +3 -1
- {commkit-3.1.3 → commkit-3.2.1}/commkit/filtering/linear.py +7 -5
- {commkit-3.1.3 → commkit-3.2.1}/commkit/frequency.py +0 -4
- {commkit-3.1.3 → commkit-3.2.1}/commkit/impairments/frontend.py +8 -2
- {commkit-3.1.3 → commkit-3.2.1}/commkit/io.py +3 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/plotting/__init__.py +6 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/plotting/analysis.py +8 -11
- commkit-3.2.1/commkit/plotting/coding.py +361 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/plotting/equalizer.py +2 -4
- {commkit-3.1.3 → commkit-3.2.1}/commkit/plotting/eye.py +1 -1
- {commkit-3.1.3 → commkit-3.2.1}/commkit/plotting/filter_response.py +4 -3
- {commkit-3.1.3 → commkit-3.2.1}/commkit/plotting/spectral.py +3 -3
- {commkit-3.1.3 → commkit-3.2.1}/commkit/plotting/sync.py +2 -2
- {commkit-3.1.3 → commkit-3.2.1}/commkit/plotting/theme.py +12 -10
- {commkit-3.1.3 → commkit-3.2.1}/commkit/plotting/waveform.py +1 -1
- {commkit-3.1.3 → commkit-3.2.1}/commkit/recovery/corrections.py +24 -6
- {commkit-3.1.3 → commkit-3.2.1}/commkit/recovery/pll.py +28 -3
- {commkit-3.1.3 → commkit-3.2.1}/commkit/recovery/tikhonov.py +38 -4
- {commkit-3.1.3 → commkit-3.2.1}/commkit/timing.py +3 -3
- {commkit-3.1.3 → commkit-3.2.1}/examples/00-getting-started.ipynb +18 -4
- {commkit-3.1.3 → commkit-3.2.1}/examples/06-awgn-and-performance-metrics.ipynb +43 -28
- {commkit-3.1.3 → commkit-3.2.1}/examples/07-channel-coding.ipynb +133 -6
- {commkit-3.1.3 → commkit-3.2.1}/examples/17-blind-equalization.ipynb +3 -3
- {commkit-3.1.3 → commkit-3.2.1}/examples/README.md +2 -2
- {commkit-3.1.3 → commkit-3.2.1}/pyproject.toml +4 -2
- commkit-3.2.1/tests/analysis/test_allan.py +75 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/analysis/test_linewidth.py +3 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/coding/test_coding_base.py +27 -0
- commkit-3.2.1/tests/coding/test_exit.py +258 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/coding/test_ldpc.py +24 -6
- commkit-3.2.1/tests/coding/test_ldpc_exit.py +172 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/coding/test_product.py +87 -20
- {commkit-3.1.3 → commkit-3.2.1}/tests/coding/test_ratematch.py +30 -9
- {commkit-3.1.3 → commkit-3.2.1}/tests/coding/test_shaping.py +12 -5
- {commkit-3.1.3 → commkit-3.2.1}/tests/coding/test_turbo.py +41 -17
- {commkit-3.1.3 → commkit-3.2.1}/tests/common/__init__.py +2 -2
- {commkit-3.1.3 → commkit-3.2.1}/tests/common/metrics.py +0 -27
- commkit-3.2.1/tests/common/scoring.py +129 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/common/signals.py +91 -13
- commkit-3.2.1/tests/common/theory.py +84 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/conftest.py +64 -7
- {commkit-3.1.3 → commkit-3.2.1}/tests/core/test_frame.py +11 -43
- {commkit-3.1.3 → commkit-3.2.1}/tests/core/test_signal.py +13 -34
- {commkit-3.1.3 → commkit-3.2.1}/tests/equalization/test_blind.py +99 -66
- {commkit-3.1.3 → commkit-3.2.1}/tests/equalization/test_block.py +177 -333
- {commkit-3.1.3 → commkit-3.2.1}/tests/equalization/test_cpr.py +241 -291
- {commkit-3.1.3 → commkit-3.2.1}/tests/equalization/test_equalizer_oracles.py +14 -11
- {commkit-3.1.3 → commkit-3.2.1}/tests/equalization/test_linear.py +65 -89
- commkit-3.2.1/tests/equalization/test_mimo.py +164 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/equalization/test_polarization.py +13 -0
- commkit-3.2.1/tests/equalization/test_sequential.py +662 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/equalization/test_state.py +2 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/equalization/test_winit.py +36 -151
- {commkit-3.1.3 → commkit-3.2.1}/tests/impairments/channel/test_channel_linear.py +68 -69
- {commkit-3.1.3 → commkit-3.2.1}/tests/impairments/channel/test_channel_nonlinear.py +2 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/impairments/test_frontend.py +0 -42
- {commkit-3.1.3 → commkit-3.2.1}/tests/impairments/test_noise.py +0 -7
- {commkit-3.1.3 → commkit-3.2.1}/tests/impairments/test_source.py +0 -12
- {commkit-3.1.3 → commkit-3.2.1}/tests/mapping/test_bits.py +0 -16
- {commkit-3.1.3 → commkit-3.2.1}/tests/plotting/test_plot_analysis.py +1 -0
- commkit-3.2.1/tests/plotting/test_plot_coding.py +182 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/plotting/test_plot_filter_response.py +2 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/plotting/test_plot_spectral.py +4 -1
- {commkit-3.1.3 → commkit-3.2.1}/tests/plotting/test_plot_sync.py +2 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/plotting/test_plot_waveform.py +2 -1
- {commkit-3.1.3 → commkit-3.2.1}/tests/recovery/test_bps.py +35 -64
- {commkit-3.1.3 → commkit-3.2.1}/tests/recovery/test_corrections.py +34 -38
- {commkit-3.1.3 → commkit-3.2.1}/tests/recovery/test_pilots.py +14 -57
- {commkit-3.1.3 → commkit-3.2.1}/tests/recovery/test_pll.py +24 -47
- {commkit-3.1.3 → commkit-3.2.1}/tests/recovery/test_tikhonov.py +129 -106
- {commkit-3.1.3 → commkit-3.2.1}/tests/recovery/test_viterbi_viterbi.py +38 -85
- {commkit-3.1.3 → commkit-3.2.1}/tests/test_api_contracts.py +17 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/test_filtering.py +75 -37
- {commkit-3.1.3 → commkit-3.2.1}/tests/test_frequency.py +72 -68
- {commkit-3.1.3 → commkit-3.2.1}/tests/test_io.py +16 -4
- {commkit-3.1.3 → commkit-3.2.1}/tests/test_metrics.py +51 -34
- {commkit-3.1.3 → commkit-3.2.1}/tests/test_multirate.py +96 -36
- {commkit-3.1.3 → commkit-3.2.1}/tests/test_pulse_shaping.py +23 -32
- commkit-3.2.1/tests/test_receiver_chain.py +244 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/test_spectral.py +83 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/test_timing.py +54 -39
- {commkit-3.1.3 → commkit-3.2.1}/uv.lock +1 -1
- commkit-3.1.3/tests/analysis/test_allan.py +0 -34
- commkit-3.1.3/tests/equalization/test_mimo.py +0 -218
- commkit-3.1.3/tests/equalization/test_sequential.py +0 -1170
- {commkit-3.1.3 → commkit-3.2.1}/.gitattributes +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/.github/workflows/ci.yml +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/.github/workflows/publish.yml +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/.gitignore +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/.python-version +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/LICENSE +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/benchmarks/baselines/Linux-CPython-3.14-64bit/0002_pre_v2.json +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/benchmarks/baselines/Linux-CPython-3.14-64bit/0003_v2_0.json +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/benchmarks/baselines/Linux-CPython-3.14-64bit/0004_fiber_dbp.json +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/benchmarks/baselines/Linux-CPython-3.14-64bit/0005_gpu_decoder_path.json +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/benchmarks/baselines/Linux-CPython-3.14-64bit/0006_perf_cleanup.json +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/benchmarks/baselines/Linux-CPython-3.14-64bit/0007_v3_0.json +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/benchmarks/baselines/Linux-CPython-3.14-64bit/0008_v3_1.json +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/benchmarks/bench_block_lms.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/benchmarks/bench_bps.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/benchmarks/bench_coding.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/benchmarks/bench_equalizers.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/benchmarks/bench_filters.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/benchmarks/bench_import.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/benchmarks/bench_llr.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/benchmarks/bench_mapping.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/benchmarks/bench_signal_pipeline.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/benchmarks/bench_sync_misc.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/benchmarks/record_baseline.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/benchmarks/workloads.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/_array.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/_circular.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/_constants.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/_cuda/__init__.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/_cuda/compiler.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/_cuda/graphs.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/_cuda/src/algebraic.cu +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/_cuda/src/bps_anchor.cu +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/_cuda/src/bps_min_d2.cu +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/_cuda/src/cs_block.cu +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/_cuda/src/iir.cu +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/_cuda/src/ldpc.cu +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/_cuda/src/llr.cu +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/_cuda/src/selftest.cu +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/_dispersion.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/_fft.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/_fiber.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/_overlap_save.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/_random.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/_sequences.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/analysis/__init__.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/analysis/_common.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/analysis/allan.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/analysis/drift.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/analysis/fm_noise.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/analysis/interferometry.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/analysis/linewidth/__init__.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/analysis/linewidth/dsh.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/analysis/linewidth/phase.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/analysis/linewidth/result.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/analysis/trajectory.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/backend.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/coding/_galois.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/coding/_gf2.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/coding/_ldpc_tables.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/coding/_numba_loops.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/coding/algebraic.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/coding/convolutional.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/coding/crc.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/coding/interleaving.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/coding/polar.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/coding/product.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/coding/ratematch.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/coding/shaping.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/coding/turbo.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/core/__init__.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/core/_signal_adapter.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/core/generation.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/core/signal.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/equalization/__init__.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/equalization/_block/__init__.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/equalization/_block/_dd.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/equalization/_kernels_numba.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/equalization/linear.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/equalization/pilots.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/equalization/polarization.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/equalization/sequential/__init__.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/equalization/sequential/_blind.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/filtering/__init__.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/filtering/fiber.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/filtering/pulses.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/impairments/__init__.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/impairments/channel/__init__.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/impairments/channel/linear.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/impairments/channel/nonlinear.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/impairments/noise.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/impairments/source.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/logger.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/mapping/__init__.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/mapping/_numba_llr.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/mapping/bits.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/mapping/constellation.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/mapping/gray.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/mapping/llr.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/mapping/shaping.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/math.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/metrics.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/multirate.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/plotting/constellation.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/py.typed +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/recovery/__init__.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/recovery/_common.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/recovery/bps.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/recovery/carrier_phase.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/recovery/pilots.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/recovery/viterbi_viterbi.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/smoothing.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/commkit/spectral.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/01-constellations-and-bit-mapping.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/02-spectral-analysis.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/03-digital-filters.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/04-pulse-shaping-and-matched-filtering.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/05-multirate-and-resampling.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/08-probabilistic-shaping-and-coded-modulation.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/09-laser-phase-noise.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/10-front-end-impairments.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/11-frame-synchronization-and-timing.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/12-frequency-offset-estimation.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/13-blind-carrier-phase-recovery.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/14-pilot-aided-carrier-recovery.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/15-linear-channel-equalization.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/16-adaptive-equalization.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/18-polarization-multiplexing.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/19-chromatic-dispersion.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/20-nonlinear-fiber-and-backpropagation.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/21-fec-in-optical-standards.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/22-coherent-transceiver-end-to-end.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/23-carrier-phase-characterization.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/24-laser-linewidth-self-heterodyne.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/25-laser-linewidth-short-delay-and-flicker.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/26-laser-linewidth-self-homodyne-iq.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/27-measurement-template-self-heterodyne.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/examples/28-measurement-template-self-homodyne-iq.ipynb +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/analysis/test_drift.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/analysis/test_fm_noise.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/analysis/test_interferometry.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/analysis/test_trajectory.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/coding/test_algebraic.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/coding/test_convolutional.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/coding/test_crc.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/coding/test_gf2.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/coding/test_interleaving.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/coding/test_ldpc_kernel.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/coding/test_ldpc_tables.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/coding/test_linear_codes.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/coding/test_polar.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/common/coding.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/common/conversions.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/common/kernel_utils.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/common/memory.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/common/periodic.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/common/reference_impl.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/core/test_generation.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/core/test_psqam.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/core/test_signal_adapter.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/core/test_signal_mimo.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/equalization/test_bps_kernel.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/equalization/test_cs_kernel.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/mapping/test_constellation.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/mapping/test_gray.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/mapping/test_llr.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/plotting/test_plot_constellation.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/plotting/test_plot_equalizer.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/plotting/test_plot_eye.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/plotting/test_plot_theme.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/plugins/__init__.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/plugins/precision_trace.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/plugins/transfer_trace.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/recovery/test_joint.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/recovery/test_recovery_oracles.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/test_array.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/test_backend.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/test_cuda_infra.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/test_dispersion.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/test_examples.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/test_fiber.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/test_import_hygiene.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/test_logger.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/test_math.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/test_signal_pipeline.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/test_smoothing.py +0 -0
- {commkit-3.1.3 → commkit-3.2.1}/tests/test_working_memory.py +0 -0
|
@@ -33,6 +33,7 @@ uv sync --all-extras # environment incl. GPU packages
|
|
|
33
33
|
uv run nbstripout --install # once per clone: strip notebook outputs on commit
|
|
34
34
|
uv run pytest # CPU + GPU tests (default --device=all)
|
|
35
35
|
uv run pytest --device=cpu # what CI runs
|
|
36
|
+
uv run pytest --run-slow # also the long statistical validation (marker slow)
|
|
36
37
|
uv run pytest tests/equalization/ -k lms # subset
|
|
37
38
|
uv run pytest --cov=commkit # coverage
|
|
38
39
|
NUMBA_DISABLE_JIT=1 uv run pytest tests/equalization tests/recovery \
|
|
@@ -111,7 +112,7 @@ uv run python benchmarks/record_baseline.py NAME # record a new baseline
|
|
|
111
112
|
|
|
112
113
|
**Release:**
|
|
113
114
|
|
|
114
|
-
1. On `main`: the CPU and GPU suites pass with no failures or skips (`uv run pytest -rs
|
|
115
|
+
1. On `main`: the CPU and GPU suites pass with no failures or skips, slow validation included (`uv run pytest -rs --run-slow`; strict xfails, if any, are listed in §9), `uv build` succeeds and `uv run --with twine twine check dist/*` passes.
|
|
115
116
|
2. `CHANGELOG.md`: a `## [X.Y.Z] - YYYY-MM-DD` section for the release, written for users. Changes that may need code updates come first, with how to migrate, then Added, Performance (measured, as a ratio of new to old time), Fixed and Development. Add its compare link at the bottom, and commit it.
|
|
116
117
|
3. `uv run bump-my-version bump {patch|minor|major}`. This commits the new version and tags it `vX.Y.Z`.
|
|
117
118
|
4. `git push origin main --tags`. The ruleset on `main` requires pull requests; repository admins bypass it for these two commits.
|
|
@@ -175,7 +176,7 @@ Dependencies point downward only:
|
|
|
175
176
|
- Never use a global RNG.
|
|
176
177
|
- **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`.
|
|
177
178
|
- **Device follows the data.** There are no `backend=` arguments and no global switches. Unsupported array types (JAX, PyTorch) raise `TypeError`.
|
|
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()
|
|
179
|
+
- Only factories with no input data to follow (`generate`, `generate_phase_noise`, `Preamble`/`SingleCarrierFrame.to_signal`, `load_npz`, and the Monte Carlo EXIT curves `decoder_exit_curve` and `demapper_exit_curve`) take `device: str = "cpu"`, validated with `require_device()` (in `coding`, which cannot import `core`, by the same check in `coding.exit`). They draw randomness as the rule above says and do the vectorized work (mapping, shaping) on `device`. The default is never "GPU if available".
|
|
179
180
|
- **No `debug_plot`.** Numerical code never imports plotting. Plot functions consume results or recompute through public compute functions.
|
|
180
181
|
- **Design and apply are separate functions**, for example `RRC(...).taps(sps)` and `fir_filter`, or `butterworth_sos` and `iir_filter`.
|
|
181
182
|
|
|
@@ -227,6 +228,7 @@ Docstrings describe the code as it is; a change in behavior updates them in the
|
|
|
227
228
|
- Wrong input raises, with a message naming the argument.
|
|
228
229
|
- **No fallbacks for a broken environment.** Code never catches a failure of CuPy, a CUDA kernel (compile, load, launch), a CUDA-graph capture or any other dependency in order to run a slower or different implementation; the failure raises to the caller. The kernels and graphs are tested on a GPU before every release (§2), a broken CuPy already fails when data moves to the GPU, and a path kept "just in case" is code that no test or user runs. Dispatch on the *input* is different and allowed: a kernel's documented limits (dtype, number of points, phases or channels) may route to the general implementation where that implementation exists anyway (the CPU's), and the dispatch says so where it happens. A GPU-only path that would serve only inputs beyond a kernel's limit is not written; such input raises (`compute_llr` above 16 bits per symbol).
|
|
229
230
|
- `logger` is for diagnostics only. Per-channel logging that needs a device-to-host transfer is wrapped in `if logger.isEnabledFor(logging.INFO):`.
|
|
231
|
+
- **Levels:** `DEBUG` traces what a call is about to do (method, parameters, sizes). `INFO` is one summary per public call of what a user would print next: the values a metric or estimator found, the parameters an impairment applied, an equalizer's exit MSE, a decoder's failed checks and iterations, what `generate`, a frame or `io` produced. `WARNING` flags a result that is returned but suspect (no convergence, a fit outside its valid range, a near-zero power, a code with 4-cycles). Value objects and pure helpers (taps, tables, code construction) do not log. `warnings.warn` is used only for `PrecisionWarning`, which callers filter.
|
|
230
232
|
- The library never configures handlers, Matplotlib or warning filters at import time.
|
|
231
233
|
|
|
232
234
|
---
|
|
@@ -263,6 +265,7 @@ Docstrings describe the code as it is; a change in behavior updates them in the
|
|
|
263
265
|
| 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
266
|
| 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 |
|
|
265
267
|
| 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 |
|
|
268
|
+
| Analysis by Monte Carlo (EXIT curves) | the work is the existing compiled decoders and vectorized NumPy/CuPy (the demapper's distance table in chunks, histograms on the device); the closed-form parts (the `J` table, LDPC node curves, the chart's trajectory) are host NumPy on grids of a few thousand points, about 0.1 s. No kernel of its own: a whole curve takes 0.05-0.3 s on the GPU | follows `device` |
|
|
266
269
|
| 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 |
|
|
267
270
|
|
|
268
271
|
**Numba kernels live at module level**, in a private module that a getter imports on first use (`equalization/_numba_loops.py`, `coding/_numba_loops.py`, `mapping/_numba_llr.py`), so `import commkit` does not load Numba and the disk cache (`cache=True`) keeps them across processes. A kernel defined inside a function that calls another kernel defined there captures that dispatcher in its closure, which Numba's cache key does not reproduce in a new process: it compiled again in every process (3 s for `compute_llr`, 6 s for polar list decoding). A getter may build a kernel in a closure that captures only constants (`_get_numba_sos`).
|
|
@@ -296,13 +299,24 @@ Docstrings describe the code as it is; a change in behavior updates them in the
|
|
|
296
299
|
|
|
297
300
|
## 7. Tests
|
|
298
301
|
|
|
299
|
-
- **Fixtures:** use `backend_device`, `xp` and `xpt` from `tests/conftest.py`. Assert with `xpt.assert_allclose(...)`, because `numpy.testing` raises on CuPy arrays. Cast expectations with `xp.asarray`, and reductions with `float(...)`.
|
|
302
|
+
- **Fixtures:** use `backend_device`, `xp` and `xpt` from `tests/conftest.py`. The GPU legs skip when CuPy is not installed, except under an explicit `--device=gpu`, which fails; an installed CuPy that does not work fails under any setting. That is the only skip: a test that does not belong to the selected run is deselected at collection (`pytest_collection_modifyitems`: `gpu_only` under `--device=cpu`, `cpu_only` under `--device=gpu`, `slow` without `--run-slow`), so a run with CuPy reports no skips, and a new test never calls `pytest.skip` for a configuration. Assert with `xpt.assert_allclose(...)`, because `numpy.testing` raises on CuPy arrays. Cast expectations with `xp.asarray`, and reductions with `float(...)`.
|
|
300
303
|
- **Layout mirrors the source:** `tests/<subpackage>/test_<module>.py`, and `tests/test_<module>.py` for flat modules. Test basenames must be globally unique (pytest prepend import mode), hence names like `channel/test_channel_linear.py`. Split a large test file by concern.
|
|
301
304
|
- **Shared builders** live in `tests/common/` and use the public API.
|
|
302
305
|
- **Contract registry** (`tests/test_api_contracts.py`): every public function has a row. Generic checks run per row: array in/out, Signal in/out, device preserved, input not mutated, keyword-only parameters, `TypeError` on unsupported arrays, fact-conflict errors, return rules.
|
|
303
306
|
- Do not duplicate these checks in per-module tests.
|
|
304
307
|
- **Oracles:** sequential kernels are tested against plain-Python reference implementations in `tests/common/reference_impl.py`. CUDA kernels are tested against their CPU reference. Kernel tests skip only on the CPU parametrization (`skip_unless_gpu`), never because a kernel is unavailable, so a kernel that fails to compile fails its tests.
|
|
305
308
|
- **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.
|
|
309
|
+
- **A test of what an algorithm achieves** (an equalizer converges, carrier recovery tracks, a decoder corrects, an estimator is calibrated) must fail on a plausibly wrong implementation:
|
|
310
|
+
- **Score against the truth**: the transmitted reference, the true phase, delay or offset, never the receiver's own decisions or another estimate from the same family. Constellation symmetry hides a wrong quadrant, a slip or a duplicated stream from a distance to the nearest point. `tests/common/scoring.py` has the helpers (`ser`, `blind_ser`, `phase_rms`, `block_rotations`, `stream_correlation`).
|
|
311
|
+
- **Resolve only the legitimate ambiguities**, each once over the whole scored interval: one rotation per stream (by the constellation's symmetry, or any angle for a phase-blind equalizer), one bijective channel permutation for blind MIMO, a documented fixed delay. Never per segment.
|
|
312
|
+
- **A baseline must fail**: the same test runs without the block it credits (step size 0, `cpr=None`, the corrector off, no compensation) and asserts that this fails.
|
|
313
|
+
- **Tolerances are derived**, from AWGN theory, a Cramér–Rao bound, a Wiener or loop-noise prediction or a binomial interval, and the docstring states the derivation and the measured value. `tests/common/theory.py` holds the closed forms, written with NumPy and SciPy only.
|
|
314
|
+
- **Real channels**: convergence tests use `make_channel_signal` (RRC pulses, ISI, a fractional delay, polarization rotation, DGD, AWGN). The repeat-upsampled builders have no channel and serve plumbing tests only.
|
|
315
|
+
- **Check the test by breaking the code**: while writing it, monkeypatch a plausible wrong result (zeros, a factor of two, the feature disabled) and confirm the test fails.
|
|
316
|
+
- A strengthened test that exposes a library fault is marked `pytest.mark.xfail(strict=True, reason=...)` with the measured numbers and listed in §9; the fix lands in its own commit, which removes the mark.
|
|
317
|
+
- **Monte Carlo tests** state what their sample bounds (0 failures in 12 frames bounds the FER below 0.22 at 95 %) and check the operating point (the pre-FEC BER against theory). The default run keeps a cheap smoke size; a `slow` parametrization (deselected unless `--run-slow`) repeats it with enough trials to mean something.
|
|
318
|
+
- **End-to-end chains** in `tests/test_receiver_chain.py` run several impairments and their receiver at once, scored against the source bits or AWGN theory, each with one correction left out as a failing baseline.
|
|
319
|
+
- **Warnings are failures**: a WARNING from the `commkit` logger during a test fails it, unless the test is marked `allow_warnings` (it provokes a suspect result on purpose) or takes `caplog` to assert the log. There is no list of tolerated messages: a WARNING that reports what the caller asked for, not a suspect result, is a library log-level bug, fixed in the library.
|
|
306
320
|
- **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.
|
|
307
321
|
- **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.
|
|
308
322
|
- 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 neighbors' links.
|
|
@@ -368,6 +382,10 @@ Steps, once CuPy 14.3 (with cp315 wheels for `cupy-cuda13x`) and a fixed `jupyte
|
|
|
368
382
|
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
383
|
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.
|
|
370
384
|
|
|
385
|
+
### Test suite: open items
|
|
386
|
+
|
|
387
|
+
- **Standard tables against the standards.** The NR polar reliability sequence (TS 38.212 Table 5.3.1.2-1), the LTE QPP interleaver parameters (TS 36.212 Table 5.1.3-3) and the DVB-S2 and PON parity-check address tables are tested against commkit's own tables. They should be compared, in full, with fixtures under `tests/data/standards/` transcribed from the specification documents, each with a provenance header (document, version, table). ETSI's download pages refuse scripted access; the documents have to be supplied.
|
|
388
|
+
|
|
371
389
|
### Nonlinear fiber channel: future features
|
|
372
390
|
|
|
373
391
|
Each is an addition that leaves the current API unchanged.
|
|
@@ -2,7 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
Notable changes to CommKit, newest first. Performance figures are from the reference machine (RTX 4070 Ti, Ryzen 7 7800X3D, WSL2) unless stated otherwise, as a ratio of new to old time (below 1 is faster).
|
|
4
4
|
|
|
5
|
-
## [
|
|
5
|
+
## [3.2.1] - 2026-11-02
|
|
6
|
+
|
|
7
|
+
Fixes found by a rework of the test suite, which now checks what each algorithm achieves against the transmitted truth and theory. Five numerical defaults or models were wrong and change results (PLL cycle-slip repair, Tikhonov smoothing, `block_cma`'s step, `ols_fir_filter`'s alignment of even taps, `MthPower` near 0 Hz); no signature changes. Plots take their sizes from the theme.
|
|
8
|
+
|
|
9
|
+
### Changes that alter results
|
|
10
|
+
|
|
11
|
+
- **`Tikhonov` uses the actual variance of a Viterbi-Viterbi block phase** as its observation variance, computed from the constellation and `snr_db`: the noise through the M-th power plus the data self-noise of QAM. It used `1/(M²·SNR·N)`, too small by `M²/2` for PSK and by three to four orders of magnitude for QAM, so the smoother followed the raw block phases: on 16-QAM it now lowers the Viterbi-Viterbi error (static phase, 30 dB, 16-symbol blocks: 0.101 rad before and after smoothing, now 0.048 rad).
|
|
12
|
+
- **`PLL(cycle_slip=CycleSlip())` defaults to a history of `8/μ` symbols** (twice the loop memory, between 10 and 640), standalone and inline in `lms` / `rls`; it used 640 symbols, as the block methods do. A PLL trajectory is smoothed by the loop, and the long linear fit lagged the phase noise enough to declare false slips: 16-QAM, `2π·Δν·T = 2π·1e-4`, 16 dB, `B·T = 2e-2`, four records of 2·10⁴ symbols: 10 rotation changes against the transmitted symbols with the repair, 3 without; now 3 with it. The repair does not undo the loop's own slips, which drift over about `1/(B·T)` symbols; the `CycleSlip` docstring says so. An explicit `history` is unchanged.
|
|
13
|
+
- **`block_cma` defaults to `step_size=min(1e-3, 0.5 / block_size)`**, as `block_rde` does; it used 2e-4. On QPSK, where CMA and RDE are the same algorithm, the old default left the worst channel at 2.3 times the AWGN symbol error rate over the second half of 8·10⁴ symbols (dual polarization through rotation and DGD, Es/N0 10 dB; now 1.3 times) and at 16 times on 2·10⁴ symbols (now 2 times); on 16-QAM at 18 dB the SER of 2·10⁴-symbol records falls from 0.13 to 0.008. Above 2500-symbol blocks the new default is the smaller step, inside the stability ceiling that 2e-4 crossed (16-QAM, 4096-symbol blocks: SER 0.02 before, 0.004 now). Pass `step_size=2e-4` for the previous behavior.
|
|
14
|
+
- **`ols_fir_filter` centres even-length taps on tap `(L - 1) // 2`**, as `fir_filter` (SciPy's `mode="same"`) does and as its docstring promised; it used `L // 2`, one sample later. Odd-length taps and `causal=True` are unchanged. The docstrings of both now state the centre tap; they said `L // 2`.
|
|
15
|
+
|
|
16
|
+
- **Plots take line widths, marker sizes and fonts from the theme.** The plot functions no longer set their own line widths and marker sizes, so `apply_default_theme` and the user's rcParams control them; the theme's text is larger (16 pt, legends 14 pt, titles 18 pt) with longer ticks. SI-prefix tick labels show the prefix only (`10 k`, not `10 kHz`), as the axis labels already carry the unit.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **`MthPower` estimates offsets near 0 Hz.** It zeroed bin 0 of the M-th power spectrum and, for constellations without a constant envelope, subtracted the mean of `x^M`, which for QAM is the tone itself: an offset with `|M Δf|` under half a bin was never found (QPSK, 4096 symbols at 1 MS/s: 0 Hz read as -82 kHz, 10 Hz as 30.5 Hz). Both steps are removed; at other offsets the estimates do not change, also with a DC offset of -20 dB in the input.
|
|
21
|
+
- Messages that report what the caller asked for log at INFO, not WARNING, so a WARNING from commkit always means a suspect result: the block equalizers' advice to move CPU data to the GPU, RLS's tail trim, `correct_timing(mode="slice")` shortening the record and the plotting theme's font fallback. The advice to use more taps for fractionally spaced equalization is no longer logged at `sps=1`, where it does not apply.
|
|
22
|
+
- `rls()`: `weights_history` has one row per returned symbol, so it matches `y_hat` after the tail trim (it kept the `num_taps // 2` trimmed rows). A run whose inverse correlation matrix loses positive definiteness (a forgetting factor far below 1) raises the documented `RuntimeError` instead of a bare `ZeroDivisionError` from the kernel, and the message now advises raising the forgetting factor toward 1, not lowering it.
|
|
23
|
+
|
|
24
|
+
### Development
|
|
25
|
+
|
|
26
|
+
- The test suite checks what the code achieves, not how it is written. Equalizers converge on RRC records through ISI, fractional delays, polarization rotation and DGD, scored against the transmitted symbols with only the legitimate rotation and permutation removed, and each "improves" test also runs a baseline that must fail; carrier recovery is scored against the true phase; estimators and metrics against Cramér–Rao bounds and AWGN theory; Chase and shortened-code decoding against brute force. New end-to-end receiver chains (coded single carrier, dual polarization, 80 km of fiber) each show that leaving out one correction breaks them. Monte Carlo tests state what their frame counts bound and gain `--run-slow` validation runs. A WARNING log from commkit fails the test. Tests that only repeated the contract registry's shape and device checks are removed.
|
|
27
|
+
- The library faults these tests found are fixed above: the PLL's cycle-slip repair adding slips, Tikhonov's noise model on QAM, the dead zone of `MthPower` around 0 Hz, the alignment of even-length taps (`ols_fir_filter` against `fir_filter`) and RLS's `weights_history` length.
|
|
28
|
+
- `benchutils.assert_converged` checks the symbol error rate of the worst channel (it compared bit errors before), and the 2048-tap block RDE benchmark uses a workload that converges.
|
|
29
|
+
|
|
30
|
+
## [3.2.0] - 2026-10-30
|
|
31
|
+
|
|
32
|
+
EXIT analysis and coding diagnostics. EXIT charts for any soft-in soft-out component, measured through the existing decoders or the demapper, or in closed form for LDPC codes with their threshold; plots of LLR distributions, parity-check matrices and EXIT charts; and INFO logging in the modules where it was missing. Nothing changes for existing code.
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- **EXIT analysis** in `commkit.coding.exit`. `ExitCurve` is the transfer curve of one soft-in soft-out component and `exit_chart(first, second)` the `ExitChart` of two, with the decoding trajectory, whether the tunnel is open and where it closes. Curves come from `decoder_exit_curve` (measured through `decode` for any code whose decoder returns a-posteriori LLRs: LDPC, BCJR, Chase), `demapper_exit_curve` (measured, for iterative demapping on AWGN) and `ldpc_exit_curves` (the variable and check nodes of an LDPC code in closed form), with `ldpc_threshold`, the belief-propagation threshold in the Gaussian approximation (0.498 for the DVB-S2 rate-1/2 short code, the NGMI at which simulated QPSK frames start to decode). Measured curves run on the chosen device; the docstrings list the limitations (Gaussian a-priori LLRs, long interleavers, no curve for decoders without soft output such as turbo and polar, early stopping).
|
|
37
|
+
- **Coding plots**: `plot_llr_distribution(llrs, bits)` draws the LLR densities given the transmitted bit and their consistency, `log p(L | 0) / p(L | 1)` against `L`, which shows LLRs from a wrong `noise_var` or demapper model; `plot_parity_check(code)` draws the nonzero entries and node degrees of a parity-check matrix; `plot_exit_chart(chart)` draws an `ExitChart` and marks where a closed tunnel stalls.
|
|
38
|
+
- INFO logging where it was missing: `decode` reports the blocks whose own check failed and the iterations used, `estimate_iq_imbalance` its estimate, and `SingleCarrierFrame.to_signal`, `save_npz` and `load_npz` what they built, saved or loaded. Notebook 00 shows how to turn it on.
|
|
39
|
+
- Notebook 06 checks LLR consistency; notebook 07 predicts the LDPC threshold with an EXIT chart and charts iterative demapping with Gray and natural labels.
|
|
40
|
+
|
|
41
|
+
### Development
|
|
42
|
+
|
|
43
|
+
- AGENTS.md states the logging levels: DEBUG for what a call is about to do, INFO for one summary per public call, WARNING for a returned but suspect result; value objects do not log, and `warnings.warn` is kept for `PrecisionWarning`.
|
|
6
44
|
|
|
7
45
|
## [3.1.3] - 2026-10-30
|
|
8
46
|
|
|
@@ -303,7 +341,12 @@ See the [GitHub release](https://github.com/lokgar/commkit/releases/tag/v1.1.0)
|
|
|
303
341
|
|
|
304
342
|
First release. See the [GitHub release](https://github.com/lokgar/commkit/releases/tag/v1.0.0).
|
|
305
343
|
|
|
306
|
-
[Unreleased]: https://github.com/lokgar/commkit/compare/v3.1
|
|
344
|
+
[Unreleased]: https://github.com/lokgar/commkit/compare/v3.2.1...HEAD
|
|
345
|
+
[3.2.1]: https://github.com/lokgar/commkit/compare/v3.2.0...v3.2.1
|
|
346
|
+
[3.2.0]: https://github.com/lokgar/commkit/compare/v3.1.3...v3.2.0
|
|
347
|
+
[3.1.3]: https://github.com/lokgar/commkit/compare/v3.1.2...v3.1.3
|
|
348
|
+
[3.1.2]: https://github.com/lokgar/commkit/compare/v3.1.1...v3.1.2
|
|
349
|
+
[3.1.1]: https://github.com/lokgar/commkit/compare/v3.1.0...v3.1.1
|
|
307
350
|
[3.1.0]: https://github.com/lokgar/commkit/compare/v3.0.0...v3.1.0
|
|
308
351
|
[3.0.0]: https://github.com/lokgar/commkit/compare/v2.3.0...v3.0.0
|
|
309
352
|
[2.3.0]: https://github.com/lokgar/commkit/compare/v2.2.0...v2.3.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: commkit
|
|
3
|
-
Version: 3.1
|
|
3
|
+
Version: 3.2.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
|
|
@@ -129,8 +129,8 @@ CommKit never picks a device for you: data stays where you put it, and each func
|
|
|
129
129
|
| [`commkit.math`](https://github.com/lokgar/commkit/blob/main/commkit/math.py) | `rms`, `normalize`, dB conversions. |
|
|
130
130
|
| [`commkit.smoothing`](https://github.com/lokgar/commkit/blob/main/commkit/smoothing.py) | Display and estimation smoothers (moving average, Savitzky-Golay, 2-D density). Signal-chain filters are in `filtering`. |
|
|
131
131
|
| [`commkit.io`](https://github.com/lokgar/commkit/blob/main/commkit/io.py) | `save_npz` / `load_npz` for Signals. |
|
|
132
|
-
| [`commkit.plotting`](https://github.com/lokgar/commkit/tree/main/commkit/plotting) | Constellations, eye diagrams, spectra, filter responses, equalizer convergence,
|
|
133
|
-
| [`commkit.coding`](https://github.com/lokgar/commkit/tree/main/commkit/coding) | Channel coding: `encode` / `decode` dispatched on a code object, `LinearBlockCode` (Hamming, extended and shortened), syndrome and maximum-likelihood decoding; `LDPCCode` (DVB-S2, the 802.3ca / 50G-PON code, any parity-check or quasi-cyclic matrix) with layered min-sum and sum-product decoding on the CPU (Numba) and GPU (CUDA); `ReedSolomonCode` (G.709, KR4, KP4, 10G-EPON, XGS-PON) and `BCHCode` with Berlekamp-Massey decoding; Chase-Pyndiah soft decoding; the G.709.2 `StaircaseCode` and `Concatenated` codes (400ZR C-FEC with its exact Hamming(128,119), 802.3dj); `PolarCode` (5G NR information set) with SC and CRC-aided SCL decoding; `ConvolutionalCode` (K = 7, terminated or tail-biting) with Viterbi and BCJR, `TurboCode` (LTE); `CRC` (NR, LTE, Ethernet), `Interleaver`, `Punctured` and `Shortened`; distribution matching (`CCDM`) and probabilistic amplitude shaping (`PAS`). Coded payloads through `generate(..., code=)`. |
|
|
132
|
+
| [`commkit.plotting`](https://github.com/lokgar/commkit/tree/main/commkit/plotting) | Constellations, eye diagrams, spectra, filter responses, equalizer convergence, synchronization and laser diagnostics that draw the estimates, and coding diagnostics: LLR distributions and their consistency, parity-check matrices, EXIT charts. Imported on first use. |
|
|
133
|
+
| [`commkit.coding`](https://github.com/lokgar/commkit/tree/main/commkit/coding) | Channel coding: `encode` / `decode` dispatched on a code object, `LinearBlockCode` (Hamming, extended and shortened), syndrome and maximum-likelihood decoding; `LDPCCode` (DVB-S2, the 802.3ca / 50G-PON code, any parity-check or quasi-cyclic matrix) with layered min-sum and sum-product decoding on the CPU (Numba) and GPU (CUDA); `ReedSolomonCode` (G.709, KR4, KP4, 10G-EPON, XGS-PON) and `BCHCode` with Berlekamp-Massey decoding; Chase-Pyndiah soft decoding; the G.709.2 `StaircaseCode` and `Concatenated` codes (400ZR C-FEC with its exact Hamming(128,119), 802.3dj); `PolarCode` (5G NR information set) with SC and CRC-aided SCL decoding; `ConvolutionalCode` (K = 7, terminated or tail-biting) with Viterbi and BCJR, `TurboCode` (LTE); `CRC` (NR, LTE, Ethernet), `Interleaver`, `Punctured` and `Shortened`; distribution matching (`CCDM`) and probabilistic amplitude shaping (`PAS`); EXIT charts of measured decoder and demapper curves, and of LDPC codes in closed form with their threshold. Coded payloads through `generate(..., code=)`. |
|
|
134
134
|
|
|
135
135
|
Importing `commkit` has no side effects: it configures no logging, Matplotlib or warning filters, and does not touch the GPU.
|
|
136
136
|
|
|
@@ -93,8 +93,8 @@ CommKit never picks a device for you: data stays where you put it, and each func
|
|
|
93
93
|
| [`commkit.math`](https://github.com/lokgar/commkit/blob/main/commkit/math.py) | `rms`, `normalize`, dB conversions. |
|
|
94
94
|
| [`commkit.smoothing`](https://github.com/lokgar/commkit/blob/main/commkit/smoothing.py) | Display and estimation smoothers (moving average, Savitzky-Golay, 2-D density). Signal-chain filters are in `filtering`. |
|
|
95
95
|
| [`commkit.io`](https://github.com/lokgar/commkit/blob/main/commkit/io.py) | `save_npz` / `load_npz` for Signals. |
|
|
96
|
-
| [`commkit.plotting`](https://github.com/lokgar/commkit/tree/main/commkit/plotting) | Constellations, eye diagrams, spectra, filter responses, equalizer convergence,
|
|
97
|
-
| [`commkit.coding`](https://github.com/lokgar/commkit/tree/main/commkit/coding) | Channel coding: `encode` / `decode` dispatched on a code object, `LinearBlockCode` (Hamming, extended and shortened), syndrome and maximum-likelihood decoding; `LDPCCode` (DVB-S2, the 802.3ca / 50G-PON code, any parity-check or quasi-cyclic matrix) with layered min-sum and sum-product decoding on the CPU (Numba) and GPU (CUDA); `ReedSolomonCode` (G.709, KR4, KP4, 10G-EPON, XGS-PON) and `BCHCode` with Berlekamp-Massey decoding; Chase-Pyndiah soft decoding; the G.709.2 `StaircaseCode` and `Concatenated` codes (400ZR C-FEC with its exact Hamming(128,119), 802.3dj); `PolarCode` (5G NR information set) with SC and CRC-aided SCL decoding; `ConvolutionalCode` (K = 7, terminated or tail-biting) with Viterbi and BCJR, `TurboCode` (LTE); `CRC` (NR, LTE, Ethernet), `Interleaver`, `Punctured` and `Shortened`; distribution matching (`CCDM`) and probabilistic amplitude shaping (`PAS`). Coded payloads through `generate(..., code=)`. |
|
|
96
|
+
| [`commkit.plotting`](https://github.com/lokgar/commkit/tree/main/commkit/plotting) | Constellations, eye diagrams, spectra, filter responses, equalizer convergence, synchronization and laser diagnostics that draw the estimates, and coding diagnostics: LLR distributions and their consistency, parity-check matrices, EXIT charts. Imported on first use. |
|
|
97
|
+
| [`commkit.coding`](https://github.com/lokgar/commkit/tree/main/commkit/coding) | Channel coding: `encode` / `decode` dispatched on a code object, `LinearBlockCode` (Hamming, extended and shortened), syndrome and maximum-likelihood decoding; `LDPCCode` (DVB-S2, the 802.3ca / 50G-PON code, any parity-check or quasi-cyclic matrix) with layered min-sum and sum-product decoding on the CPU (Numba) and GPU (CUDA); `ReedSolomonCode` (G.709, KR4, KP4, 10G-EPON, XGS-PON) and `BCHCode` with Berlekamp-Massey decoding; Chase-Pyndiah soft decoding; the G.709.2 `StaircaseCode` and `Concatenated` codes (400ZR C-FEC with its exact Hamming(128,119), 802.3dj); `PolarCode` (5G NR information set) with SC and CRC-aided SCL decoding; `ConvolutionalCode` (K = 7, terminated or tail-biting) with Viterbi and BCJR, `TurboCode` (LTE); `CRC` (NR, LTE, Ethernet), `Interleaver`, `Punctured` and `Shortened`; distribution matching (`CCDM`) and probabilistic amplitude shaping (`PAS`); EXIT charts of measured decoder and demapper curves, and of LDPC codes in closed form with their threshold. Coded payloads through `generate(..., code=)`. |
|
|
98
98
|
|
|
99
99
|
Importing `commkit` has no side effects: it configures no logging, Matplotlib or warning filters, and does not touch the GPU.
|
|
100
100
|
|
|
@@ -31,16 +31,18 @@ ROUNDS = dict(rounds=3, warmup_rounds=1, iterations=1)
|
|
|
31
31
|
N_SYM = 100_000
|
|
32
32
|
|
|
33
33
|
# Converging step sizes per (equalizer, block_size). RDE on 16-QAM from a
|
|
34
|
-
# cold start (no CMA pre-convergence) converges slowly
|
|
35
|
-
#
|
|
36
|
-
# not depend on
|
|
34
|
+
# cold start (no CMA pre-convergence) converges slowly at 2048: at 4e-4 it is
|
|
35
|
+
# error-free from about symbol 70 000, so its SER is scored after that (7e-4
|
|
36
|
+
# never settled, still 2-5 % SER at the end); its cost does not depend on
|
|
37
|
+
# the data.
|
|
37
38
|
STEP_SIZE = {
|
|
38
39
|
("cma", 256): 2e-4,
|
|
39
40
|
("cma", 2048): 3e-4,
|
|
40
41
|
("rde", 256): 3e-3,
|
|
41
|
-
("rde", 2048):
|
|
42
|
+
("rde", 2048): 4e-4,
|
|
42
43
|
}
|
|
43
|
-
|
|
44
|
+
SKIP = {("rde", 2048): 70_000}
|
|
45
|
+
MAX_SER = {"cma": 1e-2, "rde": 1e-2}
|
|
44
46
|
|
|
45
47
|
EQUALIZERS = [
|
|
46
48
|
("cma", block_cma),
|
|
@@ -66,7 +68,12 @@ def _bench_block_blind(benchmark, xp, sync, label, eq_fn, block_size):
|
|
|
66
68
|
|
|
67
69
|
r = benchmark.pedantic(run, **ROUNDS)
|
|
68
70
|
assert_converged(
|
|
69
|
-
r,
|
|
71
|
+
r,
|
|
72
|
+
syms,
|
|
73
|
+
Constellation.qam(16),
|
|
74
|
+
skip=SKIP.get((label, block_size), 20_000),
|
|
75
|
+
max_ser=MAX_SER[label],
|
|
76
|
+
blind=True,
|
|
70
77
|
)
|
|
71
78
|
|
|
72
79
|
|
|
@@ -26,16 +26,18 @@ LINK = dict(
|
|
|
26
26
|
)
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
# The plain/fused split exists on the GPU only; its CPU items are deselected.
|
|
30
|
+
PATHS = ["auto", pytest.param("plain", marks=pytest.mark.gpu_only)]
|
|
31
|
+
|
|
32
|
+
|
|
29
33
|
def _stages(backend_device, monkeypatch, path):
|
|
30
34
|
if path == "plain":
|
|
31
|
-
if backend_device != "gpu":
|
|
32
|
-
pytest.skip("the plain/fused split exists on the GPU only")
|
|
33
35
|
monkeypatch.setattr(
|
|
34
36
|
_fiber, "_make_ops", lambda x, xp, *args: _fiber._Ops(xp, x.dtype, *args)
|
|
35
37
|
)
|
|
36
38
|
|
|
37
39
|
|
|
38
|
-
@pytest.mark.parametrize("path",
|
|
40
|
+
@pytest.mark.parametrize("path", PATHS)
|
|
39
41
|
@pytest.mark.parametrize("n", [2**16, 2**20], ids=["N64k", "N1M"])
|
|
40
42
|
def bench_fiber_propagation(benchmark, backend_device, xp, sync, monkeypatch, n, path):
|
|
41
43
|
_stages(backend_device, monkeypatch, path)
|
|
@@ -49,7 +51,7 @@ def bench_fiber_propagation(benchmark, backend_device, xp, sync, monkeypatch, n,
|
|
|
49
51
|
benchmark.pedantic(run, **ROUNDS)
|
|
50
52
|
|
|
51
53
|
|
|
52
|
-
@pytest.mark.parametrize("path",
|
|
54
|
+
@pytest.mark.parametrize("path", PATHS)
|
|
53
55
|
@pytest.mark.parametrize("steps", [1, 10])
|
|
54
56
|
def bench_dbp(benchmark, backend_device, xp, sync, monkeypatch, steps, path):
|
|
55
57
|
_stages(backend_device, monkeypatch, path)
|
|
@@ -64,14 +64,22 @@ def assert_converged(result, syms, constellation, *, skip, max_ser, blind=False)
|
|
|
64
64
|
storms, divergence) instead of the operating point, so every equalizer
|
|
65
65
|
benchmark checks its symbol error rate after ``skip`` symbols. Blind
|
|
66
66
|
equalizers leave a phase ambiguity, which is resolved against the
|
|
67
|
-
reference first.
|
|
67
|
+
reference first, after the channel permutation of a butterfly.
|
|
68
68
|
"""
|
|
69
69
|
from commkit.backend import to_device
|
|
70
|
-
from commkit.recovery import resolve_phase_ambiguity
|
|
70
|
+
from commkit.recovery import resolve_channel_permutation, resolve_phase_ambiguity
|
|
71
71
|
|
|
72
72
|
y = to_device(result.y_hat, "cpu")[..., skip:]
|
|
73
73
|
ref = to_device(syms, "cpu")[..., skip : skip + y.shape[-1]]
|
|
74
74
|
if blind:
|
|
75
|
+
if y.ndim == 2:
|
|
76
|
+
y = resolve_channel_permutation(y, ref)
|
|
75
77
|
y = resolve_phase_ambiguity(y, ref, constellation=constellation)
|
|
76
|
-
|
|
78
|
+
# Symbol decisions, not bits (one wrong symbol is one error), and the
|
|
79
|
+
# worst channel, so one failed stream cannot hide behind a good one.
|
|
80
|
+
wrong = constellation._nearest(y.reshape(-1)) != constellation._nearest(
|
|
81
|
+
ref.reshape(-1)
|
|
82
|
+
)
|
|
83
|
+
per_channel = to_device(wrong, "cpu").reshape(y.shape).mean(axis=-1)
|
|
84
|
+
ser = float(per_channel.max())
|
|
77
85
|
assert ser <= max_ser, f"benchmark workload did not converge: SER {ser:.3g}"
|
|
@@ -56,6 +56,20 @@ def pytest_generate_tests(metafunc):
|
|
|
56
56
|
metafunc.parametrize("backend_device", params, indirect=True)
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
def pytest_collection_modifyitems(config, items):
|
|
60
|
+
"""Deselect, not skip, the CPU items of ``gpu_only`` parameters."""
|
|
61
|
+
cpu_only_gpu = [
|
|
62
|
+
item
|
|
63
|
+
for item in items
|
|
64
|
+
if item.get_closest_marker("gpu_only")
|
|
65
|
+
and getattr(item, "callspec", None) is not None
|
|
66
|
+
and item.callspec.params.get("backend_device") == "cpu"
|
|
67
|
+
]
|
|
68
|
+
if cpu_only_gpu:
|
|
69
|
+
items[:] = [item for item in items if item not in cpu_only_gpu]
|
|
70
|
+
config.hook.pytest_deselected(items=cpu_only_gpu)
|
|
71
|
+
|
|
72
|
+
|
|
59
73
|
@numba.njit(cache=True, parallel=True)
|
|
60
74
|
def _spin(x):
|
|
61
75
|
total = 0.0
|
|
@@ -23,13 +23,26 @@ Codes: :class:`LinearBlockCode` (Hamming), :class:`LDPCCode` (DVB-S2, PON),
|
|
|
23
23
|
:class:`Stages`, :class:`SC`, :class:`SCL`, :class:`Viterbi`, :class:`BCJR`,
|
|
24
24
|
:class:`TurboIterations`. Bit
|
|
25
25
|
permutations: :func:`interleave` and :func:`deinterleave` with an
|
|
26
|
-
:class:`Interleaver`.
|
|
26
|
+
:class:`Interleaver`. EXIT analysis: :func:`exit_chart` of two
|
|
27
|
+
:class:`ExitCurve` objects, from :func:`decoder_exit_curve` (any soft-output
|
|
28
|
+
decoder), :func:`demapper_exit_curve` (iterative demapping) or
|
|
29
|
+
:func:`ldpc_exit_curves` (LDPC nodes, closed form, with
|
|
30
|
+
:func:`ldpc_threshold`).
|
|
27
31
|
"""
|
|
28
32
|
|
|
29
33
|
from .algebraic import BCHCode, BerlekampMassey, ReedSolomonCode
|
|
30
34
|
from .base import DecodeResult, decode, encode
|
|
31
35
|
from .convolutional import BCJR, ConvolutionalCode, Viterbi
|
|
32
36
|
from .crc import CRC
|
|
37
|
+
from .exit import (
|
|
38
|
+
ExitChart,
|
|
39
|
+
ExitCurve,
|
|
40
|
+
decoder_exit_curve,
|
|
41
|
+
demapper_exit_curve,
|
|
42
|
+
exit_chart,
|
|
43
|
+
ldpc_exit_curves,
|
|
44
|
+
ldpc_threshold,
|
|
45
|
+
)
|
|
33
46
|
from .interleaving import Interleaver, deinterleave, interleave
|
|
34
47
|
from .ldpc import LDPCCode, MinSum, SumProduct
|
|
35
48
|
from .linear import Chase, LinearBlockCode, MaximumLikelihood, Syndrome
|
|
@@ -49,6 +62,8 @@ __all__ = [
|
|
|
49
62
|
"Concatenated",
|
|
50
63
|
"ConvolutionalCode",
|
|
51
64
|
"DecodeResult",
|
|
65
|
+
"ExitChart",
|
|
66
|
+
"ExitCurve",
|
|
52
67
|
"HardIterations",
|
|
53
68
|
"Interleaver",
|
|
54
69
|
"LDPCCode",
|
|
@@ -72,5 +87,10 @@ __all__ = [
|
|
|
72
87
|
"decode",
|
|
73
88
|
"deinterleave",
|
|
74
89
|
"encode",
|
|
90
|
+
"decoder_exit_curve",
|
|
91
|
+
"demapper_exit_curve",
|
|
92
|
+
"exit_chart",
|
|
75
93
|
"interleave",
|
|
94
|
+
"ldpc_exit_curves",
|
|
95
|
+
"ldpc_threshold",
|
|
76
96
|
]
|
|
@@ -17,6 +17,7 @@ along the last axis; a block never spans channels.
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
19
|
import dataclasses
|
|
20
|
+
import logging
|
|
20
21
|
from collections.abc import Callable
|
|
21
22
|
from dataclasses import dataclass
|
|
22
23
|
from typing import Any, NamedTuple
|
|
@@ -24,7 +25,7 @@ from typing import Any, NamedTuple
|
|
|
24
25
|
import numpy as np
|
|
25
26
|
|
|
26
27
|
from .._array import as_2d, restore_1d, validate_array, warn_if_double
|
|
27
|
-
from ..backend import ArrayType, get_array_module
|
|
28
|
+
from ..backend import ArrayType, get_array_module, to_device
|
|
28
29
|
from ..logger import logger
|
|
29
30
|
|
|
30
31
|
__all__ = ["DecodeResult", "decode", "encode"]
|
|
@@ -272,6 +273,26 @@ def encode(bits: ArrayType, *, code: Any) -> ArrayType:
|
|
|
272
273
|
return restore_1d(was_1d, out.reshape(x2.shape[0], num_blocks * code.n))
|
|
273
274
|
|
|
274
275
|
|
|
276
|
+
def _log_decode(res: _Decoded, code: Any, method: Any) -> None:
|
|
277
|
+
"""Log the decoder's own success check and iterations (INFO level)."""
|
|
278
|
+
# Two (R,) transfers, made only when INFO is enabled.
|
|
279
|
+
if not logger.isEnabledFor(logging.INFO) or res.valid.size == 0:
|
|
280
|
+
return
|
|
281
|
+
valid = to_device(res.valid, "cpu")
|
|
282
|
+
iterations = to_device(res.iterations, "cpu")
|
|
283
|
+
failed = int(valid.size - np.count_nonzero(valid))
|
|
284
|
+
most = int(iterations.max())
|
|
285
|
+
used = f", iterations mean {float(iterations.mean()):.1f}, max {most}"
|
|
286
|
+
logger.info(
|
|
287
|
+
"Decoded %d block(s) of %r with %r: %d failed the decoder's check%s.",
|
|
288
|
+
valid.size,
|
|
289
|
+
code,
|
|
290
|
+
method,
|
|
291
|
+
failed,
|
|
292
|
+
used if most > 1 else "",
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
|
|
275
296
|
def decode(llrs: ArrayType, *, code: Any, method: Any = None) -> DecodeResult:
|
|
276
297
|
"""Decode LLRs (or hard decisions) of a code's transmitted bits.
|
|
277
298
|
|
|
@@ -325,6 +346,7 @@ def decode(llrs: ArrayType, *, code: Any, method: Any = None) -> DecodeResult:
|
|
|
325
346
|
return None
|
|
326
347
|
return restore_1d(was_1d, a.reshape(x2.shape[0], num_blocks * size))
|
|
327
348
|
|
|
349
|
+
_log_decode(res, code, method)
|
|
328
350
|
return DecodeResult(
|
|
329
351
|
bits=shaped(res.bits, code.k),
|
|
330
352
|
llrs=shaped(res.llrs, code.n),
|