commkit 3.2.1__tar.gz → 3.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {commkit-3.2.1 → commkit-3.4.0}/.github/workflows/ci.yml +1 -0
- commkit-3.4.0/AGENTS.md +330 -0
- {commkit-3.2.1 → commkit-3.4.0}/CHANGELOG.md +57 -1
- {commkit-3.2.1 → commkit-3.4.0}/PKG-INFO +29 -3
- {commkit-3.2.1 → commkit-3.4.0}/README.md +28 -2
- {commkit-3.2.1 → commkit-3.4.0}/benchmarks/bench_filters.py +19 -1
- {commkit-3.2.1 → commkit-3.4.0}/commkit/__init__.py +1 -1
- {commkit-3.2.1 → commkit-3.4.0}/commkit/coding/__init__.py +5 -1
- {commkit-3.2.1 → commkit-3.4.0}/commkit/coding/_numba_loops.py +73 -5
- {commkit-3.2.1 → commkit-3.4.0}/commkit/coding/algebraic.py +2 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/coding/base.py +65 -11
- {commkit-3.2.1 → commkit-3.4.0}/commkit/coding/convolutional.py +1 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/coding/crc.py +1 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/coding/exit.py +10 -9
- {commkit-3.2.1 → commkit-3.4.0}/commkit/coding/ldpc.py +1 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/coding/linear.py +134 -16
- commkit-3.4.0/commkit/coding/performance.py +369 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/coding/polar.py +1 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/coding/product.py +10 -2
- {commkit-3.2.1 → commkit-3.4.0}/commkit/coding/ratematch.py +2 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/coding/shaping.py +2 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/coding/turbo.py +8 -3
- {commkit-3.2.1 → commkit-3.4.0}/commkit/filtering/__init__.py +8 -0
- commkit-3.4.0/commkit/filtering/response.py +260 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/impairments/noise.py +65 -28
- {commkit-3.2.1 → commkit-3.4.0}/commkit/mapping/constellation.py +10 -3
- {commkit-3.2.1 → commkit-3.4.0}/commkit/mapping/llr.py +43 -6
- commkit-3.4.0/commkit/metrics/__init__.py +125 -0
- commkit-3.4.0/commkit/metrics/_common.py +180 -0
- commkit-3.4.0/commkit/metrics/error_rates.py +241 -0
- commkit-3.4.0/commkit/metrics/information.py +354 -0
- commkit-3.4.0/commkit/metrics/quality.py +248 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/plotting/__init__.py +13 -6
- {commkit-3.2.1 → commkit-3.4.0}/commkit/plotting/analysis.py +166 -101
- commkit-3.4.0/commkit/plotting/coding.py +649 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/plotting/constellation.py +11 -11
- commkit-3.4.0/commkit/plotting/equalizer.py +319 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/plotting/eye.py +21 -4
- commkit-3.4.0/commkit/plotting/filter_response.py +184 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/plotting/spectral.py +16 -1
- {commkit-3.2.1 → commkit-3.4.0}/commkit/plotting/sync.py +404 -253
- {commkit-3.2.1 → commkit-3.4.0}/commkit/plotting/theme.py +73 -2
- {commkit-3.2.1 → commkit-3.4.0}/commkit/plotting/waveform.py +31 -5
- {commkit-3.2.1 → commkit-3.4.0}/examples/00-getting-started.ipynb +87 -19
- {commkit-3.2.1 → commkit-3.4.0}/examples/01-constellations-and-bit-mapping.ipynb +87 -19
- {commkit-3.2.1 → commkit-3.4.0}/examples/02-spectral-analysis.ipynb +51 -14
- {commkit-3.2.1 → commkit-3.4.0}/examples/03-digital-filters.ipynb +116 -68
- {commkit-3.2.1 → commkit-3.4.0}/examples/04-pulse-shaping-and-matched-filtering.ipynb +95 -24
- {commkit-3.2.1 → commkit-3.4.0}/examples/05-multirate-and-resampling.ipynb +66 -21
- {commkit-3.2.1 → commkit-3.4.0}/examples/06-awgn-and-performance-metrics.ipynb +201 -27
- {commkit-3.2.1 → commkit-3.4.0}/examples/07-channel-coding.ipynb +224 -78
- {commkit-3.2.1 → commkit-3.4.0}/examples/08-probabilistic-shaping-and-coded-modulation.ipynb +68 -23
- {commkit-3.2.1 → commkit-3.4.0}/examples/09-laser-phase-noise.ipynb +37 -23
- {commkit-3.2.1 → commkit-3.4.0}/examples/10-front-end-impairments.ipynb +54 -8
- {commkit-3.2.1 → commkit-3.4.0}/examples/11-frame-synchronization-and-timing.ipynb +56 -3
- {commkit-3.2.1 → commkit-3.4.0}/examples/12-frequency-offset-estimation.ipynb +61 -9
- {commkit-3.2.1 → commkit-3.4.0}/examples/13-blind-carrier-phase-recovery.ipynb +91 -16
- {commkit-3.2.1 → commkit-3.4.0}/examples/14-pilot-aided-carrier-recovery.ipynb +22 -4
- {commkit-3.2.1 → commkit-3.4.0}/examples/15-linear-channel-equalization.ipynb +63 -17
- {commkit-3.2.1 → commkit-3.4.0}/examples/16-adaptive-equalization.ipynb +41 -33
- {commkit-3.2.1 → commkit-3.4.0}/examples/17-blind-equalization.ipynb +50 -7
- {commkit-3.2.1 → commkit-3.4.0}/examples/18-polarization-multiplexing.ipynb +48 -11
- {commkit-3.2.1 → commkit-3.4.0}/examples/19-chromatic-dispersion.ipynb +21 -3
- {commkit-3.2.1 → commkit-3.4.0}/examples/20-nonlinear-fiber-and-backpropagation.ipynb +59 -1
- {commkit-3.2.1 → commkit-3.4.0}/examples/21-fec-in-optical-standards.ipynb +80 -28
- {commkit-3.2.1 → commkit-3.4.0}/examples/22-coherent-transceiver-end-to-end.ipynb +16 -11
- {commkit-3.2.1 → commkit-3.4.0}/examples/24-laser-linewidth-self-heterodyne.ipynb +3 -3
- {commkit-3.2.1 → commkit-3.4.0}/examples/25-laser-linewidth-short-delay-and-flicker.ipynb +3 -3
- {commkit-3.2.1 → commkit-3.4.0}/examples/26-laser-linewidth-self-homodyne-iq.ipynb +19 -18
- {commkit-3.2.1 → commkit-3.4.0}/examples/27-measurement-template-self-heterodyne.ipynb +1 -1
- {commkit-3.2.1 → commkit-3.4.0}/examples/28-measurement-template-self-homodyne-iq.ipynb +12 -12
- {commkit-3.2.1 → commkit-3.4.0}/examples/README.md +3 -3
- {commkit-3.2.1 → commkit-3.4.0}/pyproject.toml +2 -2
- commkit-3.4.0/scripts/sanitize.sh +76 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/coding/test_coding_base.py +97 -1
- {commkit-3.2.1 → commkit-3.4.0}/tests/coding/test_exit.py +9 -2
- {commkit-3.2.1 → commkit-3.4.0}/tests/coding/test_linear_codes.py +53 -0
- commkit-3.4.0/tests/coding/test_performance.py +214 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/coding/test_product.py +37 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/coding/test_turbo.py +47 -1
- {commkit-3.2.1 → commkit-3.4.0}/tests/common/theory.py +17 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/impairments/test_noise.py +48 -2
- {commkit-3.2.1 → commkit-3.4.0}/tests/mapping/test_llr.py +78 -7
- commkit-3.4.0/tests/metrics/test_common.py +169 -0
- commkit-3.4.0/tests/metrics/test_error_rates.py +233 -0
- commkit-3.4.0/tests/metrics/test_information.py +283 -0
- commkit-3.4.0/tests/metrics/test_quality.py +185 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/plotting/test_plot_analysis.py +40 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/plotting/test_plot_coding.py +150 -1
- {commkit-3.2.1 → commkit-3.4.0}/tests/plotting/test_plot_constellation.py +23 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/plotting/test_plot_equalizer.py +73 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/plotting/test_plot_eye.py +30 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/plotting/test_plot_filter_response.py +51 -4
- {commkit-3.2.1 → commkit-3.4.0}/tests/plotting/test_plot_spectral.py +11 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/plotting/test_plot_sync.py +123 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/plotting/test_plot_waveform.py +14 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/plugins/transfer_trace.py +7 -2
- {commkit-3.2.1 → commkit-3.4.0}/tests/test_api_contracts.py +16 -9
- commkit-3.4.0/tests/test_filter_response.py +190 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/test_working_memory.py +2 -1
- {commkit-3.2.1 → commkit-3.4.0}/uv.lock +1 -1
- commkit-3.2.1/AGENTS.md +0 -398
- commkit-3.2.1/commkit/metrics.py +0 -991
- commkit-3.2.1/commkit/plotting/coding.py +0 -361
- commkit-3.2.1/commkit/plotting/equalizer.py +0 -290
- commkit-3.2.1/commkit/plotting/filter_response.py +0 -217
- commkit-3.2.1/tests/test_metrics.py +0 -839
- {commkit-3.2.1 → commkit-3.4.0}/.gitattributes +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/.github/workflows/publish.yml +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/.gitignore +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/.python-version +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/LICENSE +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0002_pre_v2.json +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0003_v2_0.json +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0004_fiber_dbp.json +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0005_gpu_decoder_path.json +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0006_perf_cleanup.json +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0007_v3_0.json +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0008_v3_1.json +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/benchmarks/bench_block_blind.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/benchmarks/bench_block_lms.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/benchmarks/bench_bps.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/benchmarks/bench_coding.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/benchmarks/bench_equalizers.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/benchmarks/bench_fiber.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/benchmarks/bench_import.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/benchmarks/bench_llr.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/benchmarks/bench_mapping.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/benchmarks/bench_signal_pipeline.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/benchmarks/bench_sync_misc.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/benchmarks/benchutils.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/benchmarks/conftest.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/benchmarks/record_baseline.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/benchmarks/workloads.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/_array.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/_circular.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/_constants.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/_cuda/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/_cuda/compiler.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/_cuda/graphs.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/_cuda/src/algebraic.cu +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/_cuda/src/bps_anchor.cu +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/_cuda/src/bps_min_d2.cu +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/_cuda/src/cs_block.cu +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/_cuda/src/iir.cu +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/_cuda/src/ldpc.cu +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/_cuda/src/llr.cu +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/_cuda/src/selftest.cu +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/_dispersion.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/_fft.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/_fiber.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/_overlap_save.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/_random.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/_sequences.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/analysis/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/analysis/_common.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/analysis/allan.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/analysis/drift.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/analysis/fm_noise.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/analysis/interferometry.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/analysis/linewidth/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/analysis/linewidth/dsh.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/analysis/linewidth/phase.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/analysis/linewidth/result.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/analysis/trajectory.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/backend.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/coding/_galois.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/coding/_gf2.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/coding/_ldpc_tables.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/coding/interleaving.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/core/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/core/_signal_adapter.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/core/frame.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/core/generation.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/core/signal.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/equalization/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/equalization/_block/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/equalization/_block/_blind.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/equalization/_block/_dd.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/equalization/_block/_engine.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/equalization/_common.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/equalization/_kernels_numba.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/equalization/_numba_loops.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/equalization/blind.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/equalization/linear.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/equalization/pilots.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/equalization/polarization.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/equalization/result.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/equalization/sequential/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/equalization/sequential/_blind.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/equalization/sequential/_dd.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/equalization/sequential/_setup.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/filtering/fiber.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/filtering/linear.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/filtering/pulses.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/frequency.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/impairments/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/impairments/channel/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/impairments/channel/linear.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/impairments/channel/nonlinear.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/impairments/frontend.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/impairments/source.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/io.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/logger.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/mapping/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/mapping/_numba_llr.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/mapping/bits.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/mapping/gray.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/mapping/shaping.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/math.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/multirate.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/py.typed +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/recovery/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/recovery/_common.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/recovery/bps.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/recovery/carrier_phase.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/recovery/corrections.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/recovery/pilots.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/recovery/pll.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/recovery/tikhonov.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/recovery/viterbi_viterbi.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/smoothing.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/spectral.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/commkit/timing.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/examples/23-carrier-phase-characterization.ipynb +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/analysis/test_allan.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/analysis/test_drift.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/analysis/test_fm_noise.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/analysis/test_interferometry.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/analysis/test_linewidth.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/analysis/test_trajectory.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/coding/test_algebraic.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/coding/test_convolutional.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/coding/test_crc.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/coding/test_gf2.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/coding/test_interleaving.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/coding/test_ldpc.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/coding/test_ldpc_exit.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/coding/test_ldpc_kernel.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/coding/test_ldpc_tables.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/coding/test_polar.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/coding/test_ratematch.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/coding/test_shaping.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/common/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/common/coding.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/common/conversions.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/common/kernel_utils.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/common/memory.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/common/metrics.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/common/periodic.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/common/reference_impl.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/common/scoring.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/common/signals.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/conftest.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/core/test_frame.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/core/test_generation.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/core/test_psqam.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/core/test_signal.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/core/test_signal_adapter.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/core/test_signal_mimo.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/equalization/test_blind.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/equalization/test_block.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/equalization/test_bps_kernel.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/equalization/test_cpr.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/equalization/test_cs_kernel.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/equalization/test_equalizer_oracles.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/equalization/test_linear.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/equalization/test_mimo.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/equalization/test_polarization.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/equalization/test_sequential.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/equalization/test_state.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/equalization/test_winit.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/impairments/channel/test_channel_linear.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/impairments/channel/test_channel_nonlinear.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/impairments/test_frontend.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/impairments/test_source.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/mapping/test_bits.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/mapping/test_constellation.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/mapping/test_gray.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/plotting/test_plot_theme.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/plugins/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/plugins/precision_trace.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/recovery/test_bps.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/recovery/test_corrections.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/recovery/test_joint.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/recovery/test_pilots.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/recovery/test_pll.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/recovery/test_recovery_oracles.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/recovery/test_tikhonov.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/recovery/test_viterbi_viterbi.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/test_array.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/test_backend.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/test_cuda_infra.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/test_dispersion.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/test_examples.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/test_fiber.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/test_filtering.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/test_frequency.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/test_import_hygiene.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/test_io.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/test_logger.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/test_math.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/test_multirate.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/test_pulse_shaping.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/test_receiver_chain.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/test_signal_pipeline.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/test_smoothing.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/test_spectral.py +0 -0
- {commkit-3.2.1 → commkit-3.4.0}/tests/test_timing.py +0 -0
|
@@ -119,6 +119,7 @@ jobs:
|
|
|
119
119
|
tests/coding/test_polar.py::TestDecoding::test_crc_failure_keeps_the_best_path
|
|
120
120
|
tests/coding/test_convolutional.py tests/coding/test_turbo.py::TestEncoding
|
|
121
121
|
tests/coding/test_turbo.py::TestDecoding::test_noiseless
|
|
122
|
+
tests/coding/test_linear_codes.py::TestMaximumLikelihood
|
|
122
123
|
-k "not waterfall and not threshold"
|
|
123
124
|
--device=cpu --cov=commkit --cov-report=
|
|
124
125
|
|
commkit-3.4.0/AGENTS.md
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Guide for anyone, human or coding agent, changing the **CommKit** repository.
|
|
4
|
+
|
|
5
|
+
The rules below describe the code as it is. New code follows them; a change that breaks one changes this file in the same commit.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. What CommKit is
|
|
10
|
+
|
|
11
|
+
A Python library for digital-communications research: simulation, receiver DSP, metrics, plotting and experimental captures, on NumPy (CPU) and CuPy (GPU) data. It is **not differentiable**. A separate JAX library, *commax*, will hold the blocks that must sit inside a gradient. commax depends on commkit, never the reverse, and takes its conventions (`Constellation`, bit labels, pulse taps, frame layouts) from commkit.
|
|
12
|
+
|
|
13
|
+
Users are researchers and engineers. Design for them:
|
|
14
|
+
|
|
15
|
+
- **Algorithms are plain functions.** Data is a `Signal` or an array. Things that *describe* a modulation, a pulse or an embedded sub-algorithm are small frozen value objects written inline in the call:
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
res = lms(rx, num_taps=21, step_size=1e-3, cpr=BPS(test_phases=64))
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- **Everything is expressed in Python.** No configuration files, registries, global switches or multi-step setup.
|
|
22
|
+
- **We deliberately do not build** pipeline or receiver objects, DSP methods on `Signal`, string shorthands for constellations, or public "config objects per algorithm".
|
|
23
|
+
- **Scope is the reference model and its inverse**, not a catalogue of research variants: the fiber channel and standard digital backpropagation (`correct_fiber_propagation`, with `steps_per_span` and `nonlinear_scale`), like chromatic dispersion and its compensation. Reduced-complexity nonlinearity compensation (filtered or enhanced split step, perturbation-based) is left to user code built on these; learned DBP, which needs gradients, belongs in commax.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 2. Commands
|
|
28
|
+
|
|
29
|
+
Always use `uv`. Python 3.12+.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
uv sync --all-extras # environment incl. GPU packages
|
|
33
|
+
uv run nbstripout --install # once per clone: strip notebook outputs on commit
|
|
34
|
+
uv run pytest # CPU + GPU tests (default --device=all)
|
|
35
|
+
uv run pytest --device=cpu # what CI runs
|
|
36
|
+
uv run pytest --run-slow # also the long statistical validation (marker slow)
|
|
37
|
+
uv run pytest tests/equalization/ -k lms # subset
|
|
38
|
+
uv run pytest --cov=commkit # coverage
|
|
39
|
+
uv run pytest --device=cpu -p tests.plugins.precision_trace \
|
|
40
|
+
--ignore=tests/test_examples.py # precision audit (§5); the suite passes under it
|
|
41
|
+
uv run pytest --device=gpu -p tests.plugins.transfer_trace \
|
|
42
|
+
--ignore=tests/test_examples.py # transfer audit (§6); fails on an undocumented transfer
|
|
43
|
+
scripts/sanitize.sh # kernels under compute-sanitizer (§6); setup in the script
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Numba kernel coverage runs the kernel tests with `NUMBA_DISABLE_JIT=1` and reports only the kernel modules; the test selection and the `--include` list are in the `kernel-coverage` job of `.github/workflows/ci.yml`.
|
|
47
|
+
|
|
48
|
+
**Commit gate.** CI fails on any of these, so run all of them before every commit, with `ruff format` last:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
uv run ruff check . --fix
|
|
52
|
+
uv run mypy commkit/
|
|
53
|
+
uv run pytest
|
|
54
|
+
uv run ruff format .
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Benchmarks** are explicit-only; `uv run pytest` never collects them. A commit that changes a public signature smoke-runs them once, untimed, so their call sites cannot rot:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
uv run pytest benchmarks/ --benchmark-disable --device=all
|
|
61
|
+
uv run pytest benchmarks/bench_bps.py --benchmark-only --device=all # timed
|
|
62
|
+
uv run pytest benchmarks/ --benchmark-only --device=all \
|
|
63
|
+
--benchmark-compare=0008 --benchmark-storage=file://benchmarks/baselines
|
|
64
|
+
uv run python benchmarks/record_baseline.py NAME # record a new baseline (§8)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 3. Architecture
|
|
70
|
+
|
|
71
|
+
Dependencies point downward only:
|
|
72
|
+
|
|
73
|
+
| Layer | Modules | May import |
|
|
74
|
+
| --- | --- | --- |
|
|
75
|
+
| Infrastructure | `backend`, `logger`, `_cuda` | NumPy, CuPy |
|
|
76
|
+
| Array and math helpers | `_array` (shape and validation), `math` (power, normalization, dB, linear trend) | infrastructure |
|
|
77
|
+
| Value objects | `mapping.Constellation`, pulse classes, `recovery` CPR configs, `coding` (code objects with their `encode` / `decode`; array-only, never imports `core`) | layers above |
|
|
78
|
+
| Core | `core` (`Signal`, frames, generation), `io` | layers above |
|
|
79
|
+
| DSP | `mapping`, `filtering`, `multirate`, `spectral`, `smoothing`, `impairments`, `timing`, `frequency`, `recovery`, `equalization`, `metrics`, `analysis` | layers above, and lower DSP modules |
|
|
80
|
+
| Presentation | `plotting` | everything; **nothing imports plotting** (no `debug_plot`: plot functions consume results or recompute through public functions) |
|
|
81
|
+
|
|
82
|
+
- **Responsibilities:** `filtering` holds signal-chain filters with a real frequency response; `smoothing` display and estimation smoothers only; `analysis` laser and phase characterization; `metrics` is the reporting layer.
|
|
83
|
+
- **Codes are storable:** `io` stores a code as its class and constructor fields, so those fields are numbers, strings, tuples, host arrays, constellations or other codes.
|
|
84
|
+
- **Splitting modules:** promote a flat module to a package only when it exceeds about 1000 lines *and* has two or more separable concerns. Never create a `utils/` package or one-function files.
|
|
85
|
+
- **One documented home per public name.** A package `__init__` re-exports its subpackages' public names, and users and documentation import them from the package (`commkit.impairments.Fiber`), not from the subpackage that defines them.
|
|
86
|
+
- **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`).
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 4. API rules
|
|
91
|
+
|
|
92
|
+
### Signal
|
|
93
|
+
|
|
94
|
+
- `Signal` holds:
|
|
95
|
+
- samples: NumPy or CuPy, `(N,)` or `(C, N)`, time on the last axis;
|
|
96
|
+
- the **facts** `sampling_rate`, `symbol_rate`, `center_frequency` and `domain` (`"DIG"`, `"RF"` or `"OPT"`; only RF and optical signals have a carrier, and an optical carrier gives the derived fact `center_wavelength_nm`);
|
|
97
|
+
- the **description** `constellation` and `pulse`;
|
|
98
|
+
- the ground truth `reference`: transmitted symbols, the bits on the air `bits`, the source bits `info_bits` (the same array when nothing is coded or matched) and the `code` between them, plus the `frame` layout.
|
|
99
|
+
- It never holds pipeline results.
|
|
100
|
+
- Construction does no hidden work: no mapping, no normalization, no device move, no shape guessing. Factories (`generate`, frames) do that work.
|
|
101
|
+
- `sig.to("gpu")` and `sig.replace(...)` return new Signals sharing the arrays; `clone()` copies.
|
|
102
|
+
- **Alignment invariant:** reference symbol *k* corresponds to symbol period *k* of the samples. A function that drops or shifts symbol periods slices the reference to match. Metrics raise on mismatch; they never guess.
|
|
103
|
+
|
|
104
|
+
### Function signatures
|
|
105
|
+
|
|
106
|
+
- `f(data, other_data, *, params)`: everything after the data arguments is keyword-only. A function without data arguments may keep a single primary parameter positional (`barker_sequence(13)`); with several, all are keyword-only. The contract registry records each function's data-argument count.
|
|
107
|
+
- **Signal awareness.** A function whose main argument is waveform data (or a field a Signal carries) accepts an array or a Signal: it unwraps once with `adapt_signal()` (`core/_signal_adapter.py`), works on arrays, and returns through `wrap_samples()`, never by re-entering the public function. Functions on *derived* quantities (phase trajectories, correlations, taps, PSD bins, results) and synthesis primitives stay array-only.
|
|
108
|
+
- **Types:** a transform is `def f(samples: S, ...) -> S` with the TypeVar `S` from `core/_signal_adapter.py`. Do not reassign `samples`; name the unwrapped array `x`.
|
|
109
|
+
- **Facts versus choices.** Facts (`sampling_rate`, `symbol_rate`, hence `sps`) come from the Signal; a conflicting argument raises `ValueError`, an equal one is fine, and a missing fact for array input raises. Choices (decision `constellation`, `noise_var`, `pulse`) default to the Signal's value; an explicit argument wins. Neither case warns.
|
|
110
|
+
- Integer-SPS algorithms validate with `require_integer_sps()` before any cast; never truncate 1.5 SPS to 1.
|
|
111
|
+
- **Shapes:** SISO is `(N,)`, MIMO `(C, N)`. Use `as_2d`, `restore_1d`, `broadcast_channels` and `require_channels`; never hand-roll the promote/squeeze idiom.
|
|
112
|
+
- **Value objects are frozen dataclasses** with read-only NumPy arrays:
|
|
113
|
+
- `Constellation`: `.qam(M)`, `.psk(M)`, `.pam(M, unipolar=)`, or arbitrary `points`; `.shaped(nu=|entropy=)` keeps unit power. Functions accept the object only, never strings.
|
|
114
|
+
- Pulses: `RRC`, `RC`, `Gaussian`, `Rect`, `SmoothRect`. A raw taps array is accepted wherever a pulse is.
|
|
115
|
+
- Algorithm objects, one per method (`BPS`, `PLL`, `ViterbiViterbi`, `MthPower`, `CycleSlip`, ...), used standalone (`correct_carrier_phase(y, BPS())`) and nested (`lms(..., cpr=BPS())`). Each lives next to its kernel and validates its parameters on construction. Class names are unique across the package.
|
|
116
|
+
- **Design and apply are separate functions**: `RRC(...).taps(sps)` and `fir_filter`, `butterworth_sos` and `iir_filter`.
|
|
117
|
+
- **Randomness:** `rng: int | np.random.Generator | None` (SciPy SPEC 7); never a global RNG.
|
|
118
|
+
- What is transmitted (bits, symbols, pilots) and the few random parameters of a realization (the PMD waveplates of `Fiber`) are drawn on the host, so a seed gives the same payload and fiber on every device.
|
|
119
|
+
- What the channel adds (AWGN, phase noise, ASE) is drawn on the data's device through `_random.standard_normal` / `complex_normal`; realizations differ between devices, statistics agree.
|
|
120
|
+
- A seed is reproducible within a library version only. Tests and baselines assert statistics, never exact realizations.
|
|
121
|
+
- **Device follows the data.** No `backend=` arguments, no global switches. Unsupported array types (JAX, PyTorch) raise `TypeError`.
|
|
122
|
+
- Only factories with no input data to follow take `device: str = "cpu"` (never "GPU if available"): `generate`, `generate_phase_noise`, `Preamble`/`SingleCarrierFrame.to_signal`, `load_npz`, `decoder_exit_curve`, `demapper_exit_curve`, `error_rate_curve`. They validate it with `require_device()` (in `coding`, by the same check in `coding.exit`), draw randomness as above and do the vectorized work on `device`.
|
|
123
|
+
|
|
124
|
+
### Returns
|
|
125
|
+
|
|
126
|
+
- **Transforms:** array in gives array out on the same device; Signal in gives a new Signal (with `sampling_rate` updated when the rate changes).
|
|
127
|
+
- **Estimators** reduce over the time axis: `(N,)` gives 0-d, `(C, N)` gives `(C,)`, on the input device.
|
|
128
|
+
- **Metrics** (`evm`, `snr`, `ber`, `fer`, `ser`, `gmi`, `ngmi`, `mi`) and **analysis summaries** (`estimate_linewidth`, `allan_deviation`, the `frequency_drift` statistics, which fit plot-sized reductions) return host values: a `float` for 1-D input, `np.ndarray (C,)` for 2-D. Sample-rate outputs (trajectories, PSDs, `dsh_phase`) stay on the input device.
|
|
129
|
+
- Metrics **raise** on empty selections, never return `None` or `0.0` for "nothing measured", and never silently align, rotate or permute against the reference.
|
|
130
|
+
- **More than one value** is a frozen dataclass with named fields. No dicts, no tuples longer than two.
|
|
131
|
+
- **Equalizers** return `EqualizerResult`: `y_hat` is always an array, `signal` the 1-SPS Signal for Signal input, `state` the continuation state (`state=result.state`), and diagnostics as data.
|
|
132
|
+
|
|
133
|
+
### Naming
|
|
134
|
+
|
|
135
|
+
| Prefix | Meaning |
|
|
136
|
+
| --- | --- |
|
|
137
|
+
| `apply_*` | add an impairment or apply a model |
|
|
138
|
+
| `estimate_<quantity>(x, method)` | measure, never change the data; returns a frozen `<Quantity>Estimate` (value plus diagnostics) |
|
|
139
|
+
| `correct_<quantity>(x, how)` | remove the impairment; returns only the corrected data. `how` is an estimate or an algorithm object (which estimates first) |
|
|
140
|
+
| `resolve_*` | pick among discrete candidates (π/2 rotation, channel permutation); nothing else |
|
|
141
|
+
| `generate` | synthesis from parameters, takes `rng` |
|
|
142
|
+
| `encode` / `decode` | information bits to a code's transmitted bits, and LLRs (or hard decisions) back to a `DecodeResult`; dispatched on the code object, the decoder chosen by a `method` object |
|
|
143
|
+
| `plot_*` | every public plotting function (only `apply_default_theme` is exempt) |
|
|
144
|
+
|
|
145
|
+
- 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_*`.
|
|
146
|
+
- Computations use plain nouns (`allan_deviation`).
|
|
147
|
+
- **Glossary**, one name each:
|
|
148
|
+
- `num_taps`, `step_size`, `sps`, `constellation`, `pulse`, `rng`, `num_symbols`, `num_channels` (never streams), `sampling_rate`, `symbol_rate`, `noise_var`, `nfft`, `regularization`, `max_iterations`.
|
|
149
|
+
- Noise has three names, each with one meaning: `noise_var` is the variance E|n|² of the noise in the samples a function receives (measured by `estimate_noise_variance`); `snr_db` a power ratio in those samples (measured by `snr`); `esn0_db` the channel's textbook Es/N0, independent of the dtype (real samples carry the in-phase half of the noise, `N0/2`). A function taking `noise_var` for a demapping model (`compute_llr`, `mi`, `gmi`, `ngmi`) uses the likelihood of complex or real noise after the samples' dtype and also accepts `esn0_db`, so users never convert by hand.
|
|
150
|
+
- A size names its unit: `num_symbols`, `num_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.
|
|
151
|
+
- A seeded part of a factory takes `<part>_rng` (`payload_rng`), with the semantics of `rng`.
|
|
152
|
+
- `method=` strings name only a numerical flavor of one algorithm (`method="maxlog" | "exact"`); algorithms are objects.
|
|
153
|
+
- Values are in SI units without a suffix (`linewidth` in Hz, `drift_rate` in rad/s, `taus` in s). A result holding one quantity in two units suffixes the SI one (`lags` in samples, `lag_s`). A conventional non-SI unit goes into the name: `_db`, `_dbm`, `_deg`, `_nm`, `_km`, `_ps_nm_km`. Normalized quantities say so (`normalized_bandwidth`). No second name for one quantity in another unit (no `center_frequency_ghz`); `center_wavelength_nm` is a different, derived quantity.
|
|
154
|
+
|
|
155
|
+
### Docstrings
|
|
156
|
+
|
|
157
|
+
Docstrings describe the code as it is; a change in behavior updates them in the same commit. NumPy format (numpydoc), sections in its order: Parameters, Returns, Raises, Warns, See Also, Notes, References, Examples.
|
|
158
|
+
|
|
159
|
+
- **Summary line** on the line of the opening quotes, one line, ending with a period. Functions: imperative mood (`Apply ...`), or a noun phrase for a function named after what it returns (`evm`, `welch_psd`). Classes and modules: a noun phrase.
|
|
160
|
+
- **Module docstrings** name what the module holds (`:func:`, `:class:`), then the conventions particular to it (a sign convention, a scaling, a file layout). No marketing adjectives, no history of earlier layouts, no restatement of this file. A private module's summary ends with "(private)" and the body names its users.
|
|
161
|
+
- **Types:** inputs are `array_like`, or `array_like or Signal` where Signal-aware; returned arrays and fields are `ndarray` (on the input's device), `ndarray or Signal` for transforms, `numpy.ndarray` where always on the host. Shapes go in the description in double backticks: ``(C, N)``.
|
|
162
|
+
- **Plots** return `(fig, ax) or None`, multi-panel plots `(fig, axes) or None` (`None` with `show=True`).
|
|
163
|
+
- **Defaults:** `name : type, default value`; `name : type, optional` when the default is `None`, with the description saying what `None` does.
|
|
164
|
+
- Units go in the description; inline code in double backticks; library names through `:func:` / `:class:` / `:mod:` / `:meth:`.
|
|
165
|
+
|
|
166
|
+
### Errors and logging
|
|
167
|
+
|
|
168
|
+
- Wrong input raises, with a message naming the argument.
|
|
169
|
+
- **No fallbacks for a broken environment.** Code never catches a failure of CuPy, a CUDA kernel (compile, load, launch), a graph capture or another dependency to run a different implementation; the failure raises to the caller. A path kept "just in case" is code no test or user runs. (Dispatch on the *input* is allowed; see §6 Kernel policy.)
|
|
170
|
+
- `logger` is for diagnostics only. Per-channel logging that needs a device-to-host transfer is wrapped in `if logger.isEnabledFor(logging.INFO):`.
|
|
171
|
+
- **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 found, the parameters applied, an equalizer's exit MSE, a decoder's failed checks, what `generate` or `io` produced). `WARNING` flags a returned but suspect result (no convergence, a fit outside its valid range, near-zero power, a code with 4-cycles). Value objects and pure helpers do not log.
|
|
172
|
+
- `warnings.warn` is used only for `PrecisionWarning` (§5). The library never configures handlers, Matplotlib or warning filters at import time.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 5. Numerics
|
|
177
|
+
|
|
178
|
+
- **Storage dtypes:** `complex64` for IQ samples, `float32` for real signals; `generate`, frames and `load_npz` produce them, so single precision is the default path.
|
|
179
|
+
- **Precision contract.** Every function computes in a predictable precision, and none promotes single-precision data on its own:
|
|
180
|
+
- **Transforms keep the input's precision** throughout (filters, impairments, the fiber solver and DBP, resampling, delays, carrier-phase correction): complex64 in and out, complex128 in and out.
|
|
181
|
+
- **Fixed-precision blocks** compute in single precision whatever the input: the equalizers in complex64, `compute_llr` / `Constellation.llr` in float32. Double input emits `commkit.PrecisionWarning` (`_array.warn_if_double`, attributed to the caller's line). The test suite turns it into an error (`pytest_configure` in `tests/conftest.py`; not pyproject's `filterwarnings`, which imports commkit before coverage starts), so a library path feeding double data to these blocks fails CI. Training and reference symbols are not checked.
|
|
182
|
+
- **Deliberate float64 / complex128**, documented where it happens, only on phases, small arrays or accumulators, never on sample-rate data that is then cast back:
|
|
183
|
+
- phases before `xp.unwrap()` (float32 rounding causes spurious quadrant slips), phase trajectories and ramps (wrapped in float64, rotated in the data's precision), frequency grids and estimates;
|
|
184
|
+
- recursion state: PLL, Tikhonov/Kalman block transitions, the IIR filter's sections and state (poles near z = 1);
|
|
185
|
+
- RLS keeps `P`, the gain `k` and the regressor buffers in complex128 (in single precision `P` loses positive definiteness and the filter diverges);
|
|
186
|
+
- LMS/CMA/RDE accumulate dot products and gradient updates in complex128 around complex64 inputs and weights; the block equalizers stay complex64, as their tap sums run inside the FFTs;
|
|
187
|
+
- sums: metrics compute per element in the input's precision and accumulate in float64.
|
|
188
|
+
- 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.
|
|
189
|
+
- **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 to complex128. Combine single-precision data only with Python scalars or arrays cast to its precision. Frequency responses (dispersion, delays, Jones matrices) are computed at float64 phase accuracy and cast to the spectrum's precision before the multiply; rotate with `math._derotate`.
|
|
190
|
+
- **Audit with `tests/plugins/precision_trace.py`** after touching numerics. It lists every float64/complex128 array of sample-rate size that a NumPy path creates from single-precision, integer or bool data or from scratch, with its source line; output dtypes alone hide a compute-in-double-and-cast-back. Every reported site must be one of the deliberate uses above.
|
|
191
|
+
- **Normalization invariant:** at `sps` samples per symbol, `E[|x|²] = 1/sps`; at symbol rate, `E[|x|²] = 1`. Any rate-changing block applies the exact gain correction.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## 6. Performance
|
|
196
|
+
|
|
197
|
+
| Algorithm structure | Implementation | Device |
|
|
198
|
+
| --- | --- | --- |
|
|
199
|
+
| Vectorized: filters, resampling, spectra, impairments, metrics, frequency-domain equalizers | NumPy/CuPy via `x, xp, sp = dispatch(samples)` | follows the input |
|
|
200
|
+
| Per-symbol searches over a small table: LLRs (all points, or per axis for separable QAM; max-log without a prior in closed form), hard decisions without a square lattice | CPU: Numba `parallel=True` over blocks of symbols (LLRs), chunked NumPy (decisions). GPU: one thread per symbol (`llr`, `llr_separable`, `llr_separable_maxlog`, `bps_min_d2`'s nearest-point mode); decisions on double input or above 1024 points use the chunked search on both devices; LLRs above 16 bits per symbol raise on the GPU | follows the input |
|
|
201
|
+
| Sequential recursions: LMS/RLS/CMA/RDE, PLL, Tikhonov, cycle slips, the Mengali-Morelli bootstrap, per-codeword decoders on trellises and trees (CCDM, polar SC/SCL, Viterbi, BCJR, turbo, recursive systematic encoding) | Numba `@njit(cache=True, fastmath=True, nogil=True)`, independent rows in `prange`. The equalizers' kernel with inline carrier recovery compiles strict (unwrap, running sums) and calls fast-math tap and distance loops as separate functions (an `inline="always"` helper takes its caller's flags) | CPU; GPU input does one round trip |
|
|
202
|
+
| Linear recursions: IIR filtering | CPU: Numba without `fastmath`, so double input matches SciPy bit for bit. GPU: `iir` kernel: each block's state from a zero start in parallel, states carried across blocks in two levels (groups, then across groups), blocks filtered again, in double precision | follows the input |
|
|
203
|
+
| Binary linear algebra of codes: encoding, syndromes, ML-decoding correlations on the GPU, Chase's test patterns | one float32 GEMM per chunk (`coding._gf2.matmul_mod2`), exact below an inner dimension of 2^24, parity in place (CPU) or by one elementwise kernel writing int8 (GPU); small tables cached per code and device (`backend._device_tables`) | follows the input |
|
|
204
|
+
| Algebraic decoding (BCH, Reed-Solomon): Berlekamp-Massey, Chien search to the locator's degree, Forney | CPU: Numba `parallel=True` over codewords. GPU: `algebraic_correct`, one thread per codeword in integer table arithmetic, so both agree exactly (a lockstep CuPy version was 12x slower) | follows the input |
|
|
205
|
+
| Layered LDPC belief propagation (min-sum, sum-product) | CPU: Numba `parallel=True` over codewords, all iterations inside, no fast-math. GPU: `ldpc_layered`, one block per codeword, rows of a layer in parallel, early stop per codeword; same row order, so min-sum agrees bit for bit (a CuPy version was 7-23x slower) | follows the input |
|
|
206
|
+
| Maximum-likelihood decoding: the correlation with all `2^k` codewords, the decision and the per-bit maxima of the max-log soft output | CPU: one Numba pass, `parallel=True` over rows, branch-free selects under fast-math without the no-NaN and no-Inf flags (a GEMM then a separate pass ran 3x slower, its threads competing with BLAS's). GPU: the GEMM per chunk, the per-bit maxima by a CuPy `ReductionKernel` that broadcasts over the codewords instead of building `(rows, codewords, n)`. `soft=False` skips the maxima on both | follows the input |
|
|
207
|
+
| Monte Carlo analysis (EXIT curves, error-rate curves) | the existing encoders, decoders and demapper and vectorized NumPy/CuPy, through the private block functions (no INFO line per batch); closed-form parts are host NumPy on small grids. No kernel of its own | follows `device` |
|
|
208
|
+
| Elementwise chains between multi-core FFTs (fiber solver's Kerr and PMD stages) | NumPy: a Numba `parallel=True` pass competes with `scipy.fft`'s threads and measured slower end to end; adopt one only on a paired end-to-end A/B | CPU |
|
|
209
|
+
| Other hot spots limited by intermediates, launches or round trips | CUDA C++ via CuPy `RawModule` (`commkit/_cuda/src`) | GPU |
|
|
210
|
+
|
|
211
|
+
**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 `cache=True` works across processes. A kernel defined inside a function that calls another kernel defined there recompiles in every process (Numba's cache key misses the captured dispatcher); a getter may build a closure that captures only constants (`_get_numba_sos`).
|
|
212
|
+
|
|
213
|
+
**Host-sync hygiene:**
|
|
214
|
+
|
|
215
|
+
- Never extract scalars from a possibly-GPU array inside a loop (`float(x[c])`, `.item()`). Compute the vector on the device, transfer it once with `to_device(v, "cpu")`, then loop on the host.
|
|
216
|
+
- Reduce on the device before transferring, including for plotting; prefer on-device gathers (`xp.take_along_axis`); chunk `(N, M)` intermediates over N with an on-device accumulator.
|
|
217
|
+
- Build grids, windows and index tables on the data's device (`xp.fft.fftfreq`, `xp.hanning`, `xp.arange`).
|
|
218
|
+
- Every FFT goes through `commkit._fft` (`fft`, `ifft`, `rfft`, `irfft`), never `np.fft` / `xp.fft` (frequency grids excepted): on the CPU it runs multi-threaded `scipy.fft` and keeps complex64 in single precision (NumPy 2 computes it in double); on the GPU it is `cupy.fft`.
|
|
219
|
+
- Pass whole records to compiled code. No Python loops over symbols or small blocks, except inside a CUDA graph.
|
|
220
|
+
- Repeated GPU work with fixed buffers (the block equalizers' blocks, the fiber solver's spans) is replayed from CUDA graphs through `_cuda.graphs.GraphRunner`: warm once, capture on the second run, replay; a failed capture raises. Per-replay offsets come from a device index the graph advances, so the host launches one graph per block. Its stream is reused per device and thread, because CuPy's pool caches freed blocks per stream.
|
|
221
|
+
- **Audit with `tests/plugins/transfer_trace.py`** after touching a GPU path; the contract registry cannot see a silent round trip through the host. The plugin records every sample-rate host-device copy during a `--device=gpu` run and fails on a site missing from its documented list (the sequential recursions' round trip, equalizer continuation state, the reporting layer, explicit device moves, the transmitted payload, host values such as reference symbols or SciPy windows moved to the device). A new site is fixed or added to the list with its reason.
|
|
222
|
+
|
|
223
|
+
**Kernel policy:**
|
|
224
|
+
|
|
225
|
+
- A `.cu` kernel needs a benchmark showing that CuPy is limited by intermediates, launch count or round trips. It optimizes a CPU reference (Numba or NumPy) and is tested against it with `--device=all`.
|
|
226
|
+
- `_cuda.get_kernel` returns the launch wrapper or raises (compile, load, bad specialization); it is called only on CuPy input.
|
|
227
|
+
- Wrappers check dtype, contiguity, shape and size before launch and never launch a zero-size grid: empty input returns an empty result, or raises where the caller never has one (`cs_block`, `bps_anchor`). Grid dimensions y and z stop at 65535; a kernel mapping channels or rows there walks the rest in a grid-stride loop (`bps_min_d2`).
|
|
228
|
+
- **Dispatch on input limits:** a kernel's documented limits (dtype, number of points, phases, channels) may route to the general implementation where it exists anyway (the CPU's), saying so where it happens. A GPU-only path serving only inputs beyond a kernel's limit is not written; such input raises (`compute_llr` above 16 bits per symbol).
|
|
229
|
+
- `float32` with explicit literals. `--use_fast_math` only per kernel (through `options`), after an accuracy check.
|
|
230
|
+
- Short elementwise chains use `ElementwiseKernel` (named `commkit_*`) or `cupy.fuse`.
|
|
231
|
+
- Sequential recursions over a whole record are never ported to the GPU (IIR, above, is the linear exception). A short per-block scan may be, as one launch, when it replaces a host round trip per block (`cs_block`, which speculates its slip decisions in parallel; `bps_anchor`); keep its parallel part parallel, as float64 runs at 1/64 of float32 on consumer GPUs.
|
|
232
|
+
- A new or changed kernel passes `scripts/sanitize.sh` (memcheck, racecheck, synccheck, initcheck) before release; add its tests and kernel-name prefix to the script.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## 7. Tests
|
|
237
|
+
|
|
238
|
+
- **Fixtures:** use `backend_device`, `xp` and `xpt` from `tests/conftest.py`. Assert with `xpt.assert_allclose(...)` (`numpy.testing` raises on CuPy arrays); cast expectations with `xp.asarray` and reductions with `float(...)`.
|
|
239
|
+
- **No configuration skips.** The GPU legs skip only when CuPy is not installed, and fail instead under an explicit `--device=gpu`; a broken CuPy fails under any setting. Tests outside the selected run are deselected at collection (`gpu_only`, `cpu_only`, `slow` without `--run-slow`), so a run with CuPy reports no skips. A new test never calls `pytest.skip` for a configuration.
|
|
240
|
+
- **Layout mirrors the source:** `tests/<subpackage>/test_<module>.py`, `tests/test_<module>.py` for flat modules. Basenames are globally unique (prepend import mode), hence `channel/test_channel_linear.py`. Split a large file by concern. Shared builders live in `tests/common/` and use the public API.
|
|
241
|
+
- **Contract registry** (`tests/test_api_contracts.py`): every public function has a row, checked generically for array and Signal in/out, device preserved, input not mutated, keyword-only parameters, `TypeError` on unsupported arrays, fact conflicts and return rules. Do not repeat these checks per module.
|
|
242
|
+
- **Oracles:** sequential kernels are tested against plain-Python references in `tests/common/reference_impl.py`, CUDA kernels against their CPU reference. Kernel tests skip only on the CPU parametrization (`skip_unless_gpu`), so a kernel that fails to compile fails its tests.
|
|
243
|
+
- **Mathematical meaning:** test documented definitions (a pulse width, a dispersion sign, noise-variance scaling) against independently derived expectations, not only shapes and energies.
|
|
244
|
+
- **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:
|
|
245
|
+
- **Score against the truth**: the transmitted reference or the true phase, delay or offset, never the receiver's own decisions or a sibling estimate; constellation symmetry hides a wrong quadrant, a slip or a duplicated stream. Helpers are in `tests/common/scoring.py` (`ser`, `blind_ser`, `phase_rms`, `block_rotations`, `stream_correlation`).
|
|
246
|
+
- **Resolve only the legitimate ambiguities**, each once over the whole scored interval, never per segment: 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.
|
|
247
|
+
- **A baseline must fail**: the same test without the block it credits (step size 0, `cpr=None`, the corrector off) asserts failure.
|
|
248
|
+
- **Tolerances are derived** (AWGN theory, a Cramér–Rao bound, a Wiener or loop-noise prediction, a binomial interval), and the docstring states the derivation and the measured value. `tests/common/theory.py` holds the closed forms, in NumPy and SciPy only.
|
|
249
|
+
- **Real channels**: convergence tests use `make_channel_signal` (RRC pulses, ISI, fractional delay, polarization rotation, DGD, AWGN). The repeat-upsampled builders serve plumbing tests only.
|
|
250
|
+
- **Break the code to check the test**: monkeypatch a plausible wrong result (zeros, a factor of two, the feature off) and confirm the test fails.
|
|
251
|
+
- A strengthened test that exposes a library fault is marked `pytest.mark.xfail(strict=True, reason=...)` with the measured numbers and listed in §10; the fix lands in its own commit, which removes the mark.
|
|
252
|
+
- **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 uses a smoke size; a `slow` parametrization repeats it with enough trials to mean something.
|
|
253
|
+
- **End-to-end chains** in `tests/test_receiver_chain.py` run several impairments and their receiver, scored against the source bits or AWGN theory, each with one correction left out as a failing baseline.
|
|
254
|
+
- **Warnings are failures**: a WARNING from the `commkit` logger fails the test unless it is marked `allow_warnings` (it provokes a suspect result on purpose) or asserts the log through `caplog`. A WARNING that reports what the caller asked for is a log-level bug, fixed in the library.
|
|
255
|
+
- **API changes never change expected values.** A signature change only changes how tests call the code; numerical changes land in their own commit with independent validation.
|
|
256
|
+
- **Examples** are Jupyter notebooks in `examples/`, committed without outputs (the `nbstripout` filter, §2). `tests/test_examples.py` executes each one, so an API change updates the notebooks in the same commit.
|
|
257
|
+
- They form a course: one topic per notebook, `NN-lowercase-words.ipynb`, in teaching order (signals and DSP, channel and metrics, coding, receiver algorithms, fiber systems, characterization). Each opens with what it covers and links the notebooks it builds on, and ends with a link to the next. A new notebook takes its place in the order and updates `examples/README.md` and its neighbors' links.
|
|
258
|
+
- Every claim in the text matches what the cells print: run the notebook and read its numbers before committing. Keep each under about 20 s on the CPU.
|
|
259
|
+
- Plots of hand-computed arrays go through `to_device(x, "cpu")`, so the notebooks run unchanged on the GPU.
|
|
260
|
+
- **Coverage** must not drop below the CI floors: 78 % for the package, 85 % for the Numba kernel modules (the `NUMBA_DISABLE_JIT=1` job, §2).
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## 8. Benchmarks
|
|
265
|
+
|
|
266
|
+
`benchmarks/` tracks the GPU-relevant hot paths. Baselines are committed under `benchmarks/baselines/`. The reference is `0008` (`v3_1`, the 3.1 state) on the reference machine: RTX 4070 Ti, Ryzen 7 7800X3D, WSL2. Earlier baselines lack later benchmark files; `0002` ran the CPR equalizer and Viterbi-Viterbi benchmarks on workloads that did not converge, so compare those only from `0003` on.
|
|
267
|
+
|
|
268
|
+
- **Workloads must converge**, or the benchmark times a failure mode (slip storms, divergence). Every equalizer benchmark asserts its symbol error rate with `benchutils.assert_converged`.
|
|
269
|
+
- **Workloads** come from `benchmarks/workloads.py` with fixed seeds; never generate data inline.
|
|
270
|
+
- **Baselines** are recorded with `benchmarks/record_baseline.py`: each file in its own process, best of `--passes` (default three; raise it when small benchmarks keep outliers, as `0008` did with ten). Never record from a single `pytest benchmarks/` run: small GPU benchmarks after the large equalizer workloads ran several times slower than alone.
|
|
271
|
+
- **IDs** are `[cpu]` or `[gpu]`, meaning the input device.
|
|
272
|
+
- **Timing:** timed bodies end with the `sync` fixture, or GPU timings measure launches. Each benchmark has one warmup round (compilation, pool growth), and a session fixture warms Numba's thread pool before the first.
|
|
273
|
+
- **Tools:** `benchmarks/benchutils.py` provides `CudaEventTimer` and `nvtx_range` for `nsys`. Logging is set to WARNING in `benchmarks/conftest.py`.
|
|
274
|
+
- **Trust deltas, not single runs.** Swings of ±20-40 % have been seen under load. Re-run a suspected regression in isolation, or do a paired A/B of at least 7 repetitions, alternating old and new. When even unchanged benchmarks move, time the changed part with `CudaEventTimer` and quote it against the step it sits in.
|
|
275
|
+
- A commit touching a hot path quotes its delta against `0008`. Tolerance is 5 % on CPU and 10 % on GPU.
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## 9. Release
|
|
280
|
+
|
|
281
|
+
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 §10); `scripts/sanitize.sh` is clean if a kernel changed; `uv build` succeeds and `uv run --with twine twine check dist/*` passes.
|
|
282
|
+
2. `CHANGELOG.md`: a `## [X.Y.Z] - YYYY-MM-DD` section 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.
|
|
283
|
+
3. `uv run bump-my-version bump {patch|minor|major}` commits the new version and tags `vX.Y.Z`.
|
|
284
|
+
4. `git push origin main --tags`. The ruleset on `main` requires pull requests; repository admins bypass it for these two commits.
|
|
285
|
+
5. `gh release create vX.Y.Z --title vX.Y.Z --notes-file notes.md` with the changelog section as notes. Publishing the release runs `.github/workflows/publish.yml`, which uploads to PyPI through Trusted Publishing; a pushed tag alone publishes nothing.
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## 10. Roadmap and open items
|
|
290
|
+
|
|
291
|
+
What is not done yet, in suggested order. Unlike the sections above, this is a to-do list: remove an item in the commit that resolves it, and add one when a known gap is left open.
|
|
292
|
+
|
|
293
|
+
### Switch to Python 3.15 (blocked)
|
|
294
|
+
|
|
295
|
+
Goal: require Python 3.15 only, mainly for explicit lazy imports (PEP 810). State checked on 2026-10-23 (uv 0.13.5, CPython 3.15.1):
|
|
296
|
+
|
|
297
|
+
- **Blocker: CuPy has no 3.15 wheels.** `cupy-cuda13x` 14.2.0 ships cp310-cp314 only; support is tracked in cupy/cupy#10259, milestone v14.3.0.
|
|
298
|
+
- **Blocker: `jupyter_client` fails to import on 3.15**: `jsonutil.py` calls `strptime` with a day and no year, which 3.15 rejects (cpython#70647), so `tests/test_examples.py` fails at collection. Wait for a fixed release; do not work around it in commkit.
|
|
299
|
+
- **Ready:** wheels on 3.15 exist for NumPy 2.5.4, SciPy 1.18.1, Numba 0.68.0, Matplotlib 3.11.2, mypy 2.4.0 and the dev tools. The CPU suite has not been run on 3.15.
|
|
300
|
+
|
|
301
|
+
Steps, each its own commit (the version switch first, then each refactor):
|
|
302
|
+
|
|
303
|
+
1. **Validate unchanged code** in a 3.15 venv: full CPU+GPU suite with notebooks (`-rs`), benchmark smoke run, kernel-coverage job, precision audit.
|
|
304
|
+
2. **Switch the floor:** `requires-python = ">=3.15"`, classifiers, ruff `target-version`, dependency minimums to the first 3.15 wheels (`cupy-cuda13x[ctk]>=14.3`, `numba>=0.68`, `numpy>=2.5`, `scipy>=1.18`; recheck each); `ci.yml` matrix and jobs and `publish.yml` on 3.15 (keep `locked` and `lowest-direct`); `uv lock`; README and §2. Record a 3.15 baseline, compare it with `0008` by a paired A/B on unchanged code, and make it the reference.
|
|
305
|
+
3. **Lazy imports.** `lazy` is module-level only (not in functions, classes or `try`, not with `*`); confirm on the interpreter.
|
|
306
|
+
- Move the function-local imports (`import cupy as cp`, `import numba`, `from . import compiler` in `_cuda`, cycle-breaking imports of `Constellation`, `Signal`, `normalize`) to `lazy import` / `lazy from`; find them with `grep -rnE '^\s{4,}(import|from) ' commkit`.
|
|
307
|
+
- CuPy stays optional: every use stays behind `is_cupy_available` / `dispatch`; decide per site whether `backend.py`'s probes stay eager inside `try`.
|
|
308
|
+
- Check that a lazy `numba` does not move the import cost to an odd first call, and turn `TYPE_CHECKING` blocks into `lazy from` where the name is useful at run time.
|
|
309
|
+
- Measure with `benchmarks/bench_import.py`; keep `tests/test_import_hygiene.py` passing and extend it to assert that `import commkit` loads neither CuPy nor Matplotlib, if that becomes true.
|
|
310
|
+
4. **Other cleanups** (verify each against What's New in 3.14 and 3.15): drop `from __future__ import annotations` (PEP 649/749; check nothing reads `__annotations__` as strings); check `open()` without `encoding=` under default UTF-8 mode (PEP 686); grep for `strptime` with a day and no year; try the sampling profiler (PEP 799) on the CPU hot paths and mention it in §8 if useful.
|
|
311
|
+
|
|
312
|
+
### Channel coding
|
|
313
|
+
|
|
314
|
+
1. **oFEC** (OpenROADM, ITU-T G.709.3) is not implemented: its block structure is specified in OpenROADM MSA documents that were not available in a form to implement from. It would build on `BCHCode` (extended BCH(256, 239)), soft-output `Chase` and the staircase decoder's windowed iteration.
|
|
315
|
+
2. **NR rate matching for polar codes** (sub-block interleaving, 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.
|
|
316
|
+
|
|
317
|
+
### Test suite
|
|
318
|
+
|
|
319
|
+
- **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 specifications, each with a provenance header (document, version, table). ETSI's download pages refuse scripted access; the documents have to be supplied.
|
|
320
|
+
|
|
321
|
+
### Nonlinear fiber channel
|
|
322
|
+
|
|
323
|
+
Each is an addition that leaves the current API unchanged.
|
|
324
|
+
|
|
325
|
+
- **Batched realizations**: a leading axis `(B, 2, N)` (launch-power sweeps, Monte Carlo over seeds) so cuFFT batches them and short records fill the GPU.
|
|
326
|
+
- **Adaptive step rules**: a `step_rule=` object (maximum nonlinear phase per step, local-error method) next to the equal `steps_per_span`, which measured 3x more accurate than logarithmic steps on a 32 GBd, 80 km span.
|
|
327
|
+
- **WDM helpers**: combining frequency-shifted channels into one wideband field and selecting one after propagation; the solver already handles the wide field (with `dispersion_slope_ps_nm2_km` set).
|
|
328
|
+
- **Distributed (Raman) amplification and per-span fiber types** in one call (chaining calls already works: every call speaks launch-relative units).
|
|
329
|
+
- **cuFFT callbacks** fusing the linear stage into the transforms (CuPy's support is experimental and compiles with nvcc at run time).
|
|
330
|
+
- **Self-steepening and Raman scattering** for ultra-wideband fields.
|
|
@@ -2,6 +2,60 @@
|
|
|
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
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [3.4.0] - 2026-11-03
|
|
8
|
+
|
|
9
|
+
Error-rate curves for every code, decoders that say what they checked, and one noise convention for real and complex samples.
|
|
10
|
+
|
|
11
|
+
### Changes that may need code updates
|
|
12
|
+
|
|
13
|
+
- Real-valued samples (PAM, intensity modulation) now follow one noise convention, so their LLRs and information rates are right. Complex samples are unaffected.
|
|
14
|
+
- `apply_awgn(esn0_db=)` adds half the noise variance to real samples: the in-phase half of the channel's noise. A constellation's error rates at an Es/N0 no longer depend on whether its samples are real or complex, and BPSK has `BER = Q(sqrt(2 Es/N0))`. For a power ratio in the samples, as measurement code means it, use the new `apply_awgn(snr_db=)`; it adds what `esn0_db` used to add to real samples at one sample per symbol.
|
|
15
|
+
- `compute_llr`, `Constellation.llr`, `mi`, `gmi` and `ngmi` use the likelihood of real noise for real symbols. With the `noise_var` that `estimate_noise_variance` measures, real-symbol LLRs used to be twice too confident.
|
|
16
|
+
- `DecodeResult` has a new field, `checked`: whether `valid` comes from a check that can fail. Code that builds a `DecodeResult` itself must pass it. For decoders without a check, `valid` stays `True` everywhere, as before.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- `coding.error_rate_curve(code, constellation=, esn0_db=, ...)` measures a code's BER, FER and pre-FEC BER over AWGN by Monte Carlo. Each point stops after `min_block_errors` block errors or `max_blocks` blocks. It returns an `ErrorRateCurve` of counts, which you can also build from your own simulation loop.
|
|
21
|
+
- `plotting.plot_error_rate(curves)`: BER and FER against Es/N0 or Eb/N0, with 95 % intervals on the FER and the pre-FEC BER for reference, plus a panel splitting failures into detected and undetected ones.
|
|
22
|
+
- Soft output from more decoders: `TurboIterations` (a-posteriori LLRs of the last iteration), `MaximumLikelihood` (max-log) and `Concatenated` (the inner decoder's LLRs). `decoder_exit_curve` now covers turbo and maximum-likelihood decoding.
|
|
23
|
+
- `Chase(..., soft=False)` and `MaximumLikelihood(soft=False)` skip the soft output when only the decisions matter (`llrs` is `None`, the bits are the same).
|
|
24
|
+
- `compute_llr`, `mi`, `gmi` and `ngmi` take `esn0_db=` instead of `noise_var`, and convert it for real or complex symbols.
|
|
25
|
+
- `plot_llr_distribution(res, sig)` takes a `DecodeResult` and the transmitted Signal or Reference, as `ber` does.
|
|
26
|
+
- The example notebooks draw with the library's plot functions wherever one exists, and show spectra, constellations and eye diagrams before and after each block. Notebook 06 explains Es/N0, SNR and noise variance for real and complex samples; notebook 07 measures error-rate curves and the decoding outcome, compares the polar, convolutional and turbo codes against Eb/N0 and the turbo code at three block sizes; notebook 21 charts the optical standards' thresholds against their overhead.
|
|
27
|
+
|
|
28
|
+
### Performance
|
|
29
|
+
|
|
30
|
+
- `MaximumLikelihood` searches the codewords in one Numba pass on the CPU. For 16384 Hamming(16, 11) words (paired A/B against 3.3.0, 7 runs), decisions alone (`soft=False`) take 0.62x the time on the CPU and 1.0x on the GPU; with the new soft output, 1.55x and 4.3x.
|
|
31
|
+
- `Chase(soft=False)`: 0.44x the time of the default on the CPU and 0.74x on the GPU (400ZR Hamming(128, 119), 4 test positions, 4096 words).
|
|
32
|
+
- Turbo decoding is unchanged (1.00), now with its soft output.
|
|
33
|
+
|
|
34
|
+
### Development
|
|
35
|
+
|
|
36
|
+
- `metrics` is a package (`quality`, `error_rates`, `information`); every name is still imported from `commkit.metrics`.
|
|
37
|
+
- The transfer audit lists the payload moves of the EXIT and error-rate curves, which it flagged as undocumented; the kernel-coverage job runs the maximum-likelihood tests.
|
|
38
|
+
|
|
39
|
+
## [3.3.0] - 2026-11-03
|
|
40
|
+
|
|
41
|
+
Numerical filter responses and plots built for comparisons. `filter_response`, `impulse_response` and `step_response` evaluate any FIR, SOS or `(b, a)` filter on its device; the multi-panel diagnostics draw one selected panel, and every plot can overlay several results on one axis. The example notebooks explain what each result shows.
|
|
42
|
+
|
|
43
|
+
### Changes that may need code updates
|
|
44
|
+
|
|
45
|
+
- `plot_filter_response` raises `ValueError` when `ax` holds other than four axes for the dashboard (or one for a selected `quantity=`), as the other multi-panel plots do; it used to log a warning and open a new figure. Pass the right number of axes, or none.
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- `filter_response` (returning a `FilterResponse` of frequencies, complex response and group delay), `impulse_response` and `step_response` compute the responses of FIR taps, second-order sections or a `(b, a)` pair on the coefficients' device. The group delay of SOS filters is summed section by section, so high orders keep their accuracy; real IIR time responses run through `iir_filter`, in double-precision state whatever the coefficients' precision.
|
|
50
|
+
- Multi-panel diagnostics (`plot_filter_response`, `plot_equalizer_result`, `plot_zf_equalizer_response`, `plot_llr_distribution`, `plot_parity_check`, `plot_timing_correlation`, `plot_mm_autocorrelation`, `plot_frequency_offset_blockwise_result`, `plot_pilot_phase_estimate`, `plot_pilot_tone_phase_estimate`, `plot_pilot_tones_phase_estimate`) take `quantity=` to draw one panel on one axis.
|
|
51
|
+
- Comparisons on one axis: the single-panel plots and a selected `quantity=` take `label=`, `xlim=`, `ylim=` and `legend=` (`True` shows, `False` hides, `None` keeps the plot's default), and the plots with fits, guides or markers take `annotations=False` to draw only the measured curves of an overlay. `plot_eye_diagram(quantity="i" | "q")` draws one component; `plot_carrier_phase_characterization` accepts existing axes.
|
|
52
|
+
- The example notebooks explain what each receiver result shows, their subplot grids scale the theme's figure size, and notebooks 13 and 16 overlay filter, smoothing, shaping and equalizer comparisons on one axis.
|
|
53
|
+
|
|
54
|
+
### Development
|
|
55
|
+
|
|
56
|
+
- The transfer audit documents the Tikhonov smoother's host round trip, which it flagged as undocumented.
|
|
57
|
+
- `bench_filter_response` times responses on the DFT grid and on an arbitrary grid at 31, 257 and 4097 taps.
|
|
58
|
+
|
|
5
59
|
## [3.2.1] - 2026-11-02
|
|
6
60
|
|
|
7
61
|
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.
|
|
@@ -341,7 +395,9 @@ See the [GitHub release](https://github.com/lokgar/commkit/releases/tag/v1.1.0)
|
|
|
341
395
|
|
|
342
396
|
First release. See the [GitHub release](https://github.com/lokgar/commkit/releases/tag/v1.0.0).
|
|
343
397
|
|
|
344
|
-
[Unreleased]: https://github.com/lokgar/commkit/compare/v3.
|
|
398
|
+
[Unreleased]: https://github.com/lokgar/commkit/compare/v3.4.0...HEAD
|
|
399
|
+
[3.4.0]: https://github.com/lokgar/commkit/compare/v3.3.0...v3.4.0
|
|
400
|
+
[3.3.0]: https://github.com/lokgar/commkit/compare/v3.2.1...v3.3.0
|
|
345
401
|
[3.2.1]: https://github.com/lokgar/commkit/compare/v3.2.0...v3.2.1
|
|
346
402
|
[3.2.0]: https://github.com/lokgar/commkit/compare/v3.1.3...v3.2.0
|
|
347
403
|
[3.1.3]: https://github.com/lokgar/commkit/compare/v3.1.2...v3.1.3
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: commkit
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.0
|
|
4
4
|
Summary: A Python library for high-performance digital communications research.
|
|
5
5
|
Project-URL: Homepage, https://github.com/lokgar/commkit
|
|
6
6
|
Project-URL: Repository, https://github.com/lokgar/commkit
|
|
@@ -49,7 +49,7 @@ CommKit covers the receiver chain of coherent and IM/DD links (waveform generati
|
|
|
49
49
|
|
|
50
50
|
## The model
|
|
51
51
|
|
|
52
|
-
Data is a NumPy or CuPy array, or a `Signal` that wraps the samples together with what is known about them. Algorithms are plain functions that take the data and return a new `Signal` or array, or a typed result such as `EqualizerResult` or an `*Estimate`. Plotting functions draw those results
|
|
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 or call the public compute functions needed to produce 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
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.
|
|
@@ -87,6 +87,32 @@ res2 = ck.equalization.lms(rx, num_taps=21, step_size=1e-3, cpr=cpr, state=res.s
|
|
|
87
87
|
|
|
88
88
|
Without CuPy, pass `device="cpu"` (the default): the same code runs on the CPU.
|
|
89
89
|
|
|
90
|
+
## Comparing filter responses
|
|
91
|
+
|
|
92
|
+
Calculate a response on the coefficients' device, or overlay selected panels
|
|
93
|
+
of several filters:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
short = ck.filtering.fir_taps(sampling_rate=1e6, num_taps=21, cutoff=100e3)
|
|
97
|
+
long = ck.filtering.fir_taps(sampling_rate=1e6, num_taps=101, cutoff=100e3)
|
|
98
|
+
response = ck.filtering.filter_response(long, sampling_rate=1e6)
|
|
99
|
+
print(response.frequencies, response.response, response.group_delay)
|
|
100
|
+
|
|
101
|
+
ck.plotting.apply_default_theme()
|
|
102
|
+
_, ax = ck.plotting.plot_filter_response(
|
|
103
|
+
short, sampling_rate=1e6, quantity="magnitude", label="21 taps"
|
|
104
|
+
)
|
|
105
|
+
ck.plotting.plot_filter_response(
|
|
106
|
+
long, sampling_rate=1e6, quantity="magnitude", ax=ax,
|
|
107
|
+
label="101 taps", legend=True, show=True,
|
|
108
|
+
)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
See notebook 03 for filter and smoothing comparisons. The multi-panel
|
|
112
|
+
equalizer, timing, recovery and coding diagnostics take `quantity=` in the
|
|
113
|
+
same way: draw a second result on the returned axis with its own `label=`,
|
|
114
|
+
and `annotations=False` to leave out repeated fits and guides.
|
|
115
|
+
|
|
90
116
|
## CPU and GPU
|
|
91
117
|
|
|
92
118
|
CommKit never picks a device for you: data stays where you put it, and each function runs on the device of its input. A few habits get the speed out of a GPU:
|
|
@@ -116,7 +142,7 @@ CommKit never picks a device for you: data stays where you put it, and each func
|
|
|
116
142
|
| --- | --- |
|
|
117
143
|
| [`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
144
|
| [`commkit.mapping`](https://github.com/lokgar/commkit/tree/main/commkit/mapping) | `Constellation` (QAM, PSK, PAM, arbitrary points; Gray labels; probabilistic shaping with `.shaped()`), bit mapping, hard demapping, max-log and exact LLRs. |
|
|
119
|
-
| [`commkit.filtering`](https://github.com/lokgar/commkit/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`). |
|
|
145
|
+
| [`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), numerical filter responses, `fir_filter`, `iir_filter`, `matched_filter`, overlap-save, chromatic-dispersion compensation and digital backpropagation (`correct_fiber_propagation`). |
|
|
120
146
|
| [`commkit.multirate`](https://github.com/lokgar/commkit/blob/main/commkit/multirate.py) | `resample` (fractional), `decimate`, `upsample`, `decimate_to_symbol_rate`. |
|
|
121
147
|
| [`commkit.spectral`](https://github.com/lokgar/commkit/blob/main/commkit/spectral.py) | Welch PSD, spectrograms, frequency shifting, pilot tones. |
|
|
122
148
|
| [`commkit.impairments`](https://github.com/lokgar/commkit/tree/main/commkit/impairments) | AWGN at an Es/N0 (measured as energy per symbol) or of an absolute variance, ASE at an OSNR (`apply_ase`), 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`. |
|
|
@@ -13,7 +13,7 @@ CommKit covers the receiver chain of coherent and IM/DD links (waveform generati
|
|
|
13
13
|
|
|
14
14
|
## The model
|
|
15
15
|
|
|
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
|
|
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 or call the public compute functions needed to produce 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
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.
|
|
@@ -51,6 +51,32 @@ res2 = ck.equalization.lms(rx, num_taps=21, step_size=1e-3, cpr=cpr, state=res.s
|
|
|
51
51
|
|
|
52
52
|
Without CuPy, pass `device="cpu"` (the default): the same code runs on the CPU.
|
|
53
53
|
|
|
54
|
+
## Comparing filter responses
|
|
55
|
+
|
|
56
|
+
Calculate a response on the coefficients' device, or overlay selected panels
|
|
57
|
+
of several filters:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
short = ck.filtering.fir_taps(sampling_rate=1e6, num_taps=21, cutoff=100e3)
|
|
61
|
+
long = ck.filtering.fir_taps(sampling_rate=1e6, num_taps=101, cutoff=100e3)
|
|
62
|
+
response = ck.filtering.filter_response(long, sampling_rate=1e6)
|
|
63
|
+
print(response.frequencies, response.response, response.group_delay)
|
|
64
|
+
|
|
65
|
+
ck.plotting.apply_default_theme()
|
|
66
|
+
_, ax = ck.plotting.plot_filter_response(
|
|
67
|
+
short, sampling_rate=1e6, quantity="magnitude", label="21 taps"
|
|
68
|
+
)
|
|
69
|
+
ck.plotting.plot_filter_response(
|
|
70
|
+
long, sampling_rate=1e6, quantity="magnitude", ax=ax,
|
|
71
|
+
label="101 taps", legend=True, show=True,
|
|
72
|
+
)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
See notebook 03 for filter and smoothing comparisons. The multi-panel
|
|
76
|
+
equalizer, timing, recovery and coding diagnostics take `quantity=` in the
|
|
77
|
+
same way: draw a second result on the returned axis with its own `label=`,
|
|
78
|
+
and `annotations=False` to leave out repeated fits and guides.
|
|
79
|
+
|
|
54
80
|
## CPU and GPU
|
|
55
81
|
|
|
56
82
|
CommKit never picks a device for you: data stays where you put it, and each function runs on the device of its input. A few habits get the speed out of a GPU:
|
|
@@ -80,7 +106,7 @@ CommKit never picks a device for you: data stays where you put it, and each func
|
|
|
80
106
|
| --- | --- |
|
|
81
107
|
| [`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
108
|
| [`commkit.mapping`](https://github.com/lokgar/commkit/tree/main/commkit/mapping) | `Constellation` (QAM, PSK, PAM, arbitrary points; Gray labels; probabilistic shaping with `.shaped()`), bit mapping, hard demapping, max-log and exact LLRs. |
|
|
83
|
-
| [`commkit.filtering`](https://github.com/lokgar/commkit/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`). |
|
|
109
|
+
| [`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), numerical filter responses, `fir_filter`, `iir_filter`, `matched_filter`, overlap-save, chromatic-dispersion compensation and digital backpropagation (`correct_fiber_propagation`). |
|
|
84
110
|
| [`commkit.multirate`](https://github.com/lokgar/commkit/blob/main/commkit/multirate.py) | `resample` (fractional), `decimate`, `upsample`, `decimate_to_symbol_rate`. |
|
|
85
111
|
| [`commkit.spectral`](https://github.com/lokgar/commkit/blob/main/commkit/spectral.py) | Welch PSD, spectrograms, frequency shifting, pilot tones. |
|
|
86
112
|
| [`commkit.impairments`](https://github.com/lokgar/commkit/tree/main/commkit/impairments) | AWGN at an Es/N0 (measured as energy per symbol) or of an absolute variance, ASE at an OSNR (`apply_ase`), 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`. |
|
|
@@ -4,12 +4,15 @@
|
|
|
4
4
|
(1% of the sampling rate, poles near z = 1) on dual-channel complex64; on the
|
|
5
5
|
GPU it times the double-precision ``cupyx.scipy.signal`` path.
|
|
6
6
|
``bench_moving_average_valid`` smooths a two-channel float32 phase record.
|
|
7
|
+
``bench_filter_response`` evaluates the response and group delay of
|
|
8
|
+
low-pass FIRs on the default 4096-point grid (FFTs) and on an arbitrary
|
|
9
|
+
grid of as many points (Horner evaluation).
|
|
7
10
|
"""
|
|
8
11
|
|
|
9
12
|
import pytest
|
|
10
13
|
from workloads import FS, bps_workload, phase_workload
|
|
11
14
|
|
|
12
|
-
from commkit.filtering import butterworth_sos, iir_filter
|
|
15
|
+
from commkit.filtering import butterworth_sos, filter_response, fir_taps, iir_filter
|
|
13
16
|
from commkit.smoothing import moving_average
|
|
14
17
|
|
|
15
18
|
ROUNDS = dict(rounds=3, warmup_rounds=1, iterations=1)
|
|
@@ -39,3 +42,18 @@ def bench_moving_average_valid(benchmark, backend_device, xp, sync, window):
|
|
|
39
42
|
return out
|
|
40
43
|
|
|
41
44
|
benchmark.pedantic(run, **ROUNDS)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest.mark.parametrize("grid", ["dft", "custom"])
|
|
48
|
+
@pytest.mark.parametrize("num_taps", [31, 257, 4097])
|
|
49
|
+
def bench_filter_response(benchmark, backend_device, xp, sync, num_taps, grid):
|
|
50
|
+
taps = fir_taps(sampling_rate=FS, num_taps=num_taps, cutoff=0.1 * FS)
|
|
51
|
+
taps = xp.asarray(taps, dtype=xp.float32)
|
|
52
|
+
frequencies = xp.linspace(-0.5, 0.5, 4096) if grid == "custom" else None
|
|
53
|
+
|
|
54
|
+
def run():
|
|
55
|
+
result = filter_response(taps, nfft=4096, frequencies=frequencies)
|
|
56
|
+
sync()
|
|
57
|
+
return result
|
|
58
|
+
|
|
59
|
+
benchmark.pedantic(run, **ROUNDS)
|