commkit 3.2.0__tar.gz → 3.3.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.0 → commkit-3.3.0}/AGENTS.md +18 -2
- {commkit-3.2.0 → commkit-3.3.0}/CHANGELOG.md +48 -1
- {commkit-3.2.0 → commkit-3.3.0}/PKG-INFO +29 -3
- {commkit-3.2.0 → commkit-3.3.0}/README.md +28 -2
- {commkit-3.2.0 → commkit-3.3.0}/benchmarks/bench_block_blind.py +13 -6
- {commkit-3.2.0 → commkit-3.3.0}/benchmarks/bench_fiber.py +6 -4
- {commkit-3.2.0 → commkit-3.3.0}/benchmarks/bench_filters.py +19 -1
- {commkit-3.2.0 → commkit-3.3.0}/benchmarks/benchutils.py +11 -3
- {commkit-3.2.0 → commkit-3.3.0}/benchmarks/conftest.py +14 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/__init__.py +1 -1
- {commkit-3.2.0 → commkit-3.3.0}/commkit/coding/linear.py +3 -1
- {commkit-3.2.0 → commkit-3.3.0}/commkit/equalization/_block/_blind.py +1 -1
- {commkit-3.2.0 → commkit-3.3.0}/commkit/equalization/_block/_engine.py +1 -1
- {commkit-3.2.0 → commkit-3.3.0}/commkit/equalization/_common.py +2 -2
- {commkit-3.2.0 → commkit-3.3.0}/commkit/equalization/_numba_loops.py +4 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/equalization/blind.py +4 -2
- {commkit-3.2.0 → commkit-3.3.0}/commkit/equalization/result.py +3 -1
- {commkit-3.2.0 → commkit-3.3.0}/commkit/equalization/sequential/_dd.py +20 -10
- {commkit-3.2.0 → commkit-3.3.0}/commkit/equalization/sequential/_setup.py +3 -1
- {commkit-3.2.0 → commkit-3.3.0}/commkit/filtering/__init__.py +8 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/filtering/linear.py +7 -5
- commkit-3.3.0/commkit/filtering/response.py +260 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/frequency.py +0 -4
- {commkit-3.2.0 → commkit-3.3.0}/commkit/plotting/__init__.py +4 -3
- {commkit-3.2.0 → commkit-3.3.0}/commkit/plotting/analysis.py +173 -111
- {commkit-3.2.0 → commkit-3.3.0}/commkit/plotting/coding.py +147 -75
- {commkit-3.2.0 → commkit-3.3.0}/commkit/plotting/constellation.py +11 -11
- commkit-3.3.0/commkit/plotting/equalizer.py +319 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/plotting/eye.py +22 -5
- commkit-3.3.0/commkit/plotting/filter_response.py +184 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/plotting/spectral.py +19 -4
- {commkit-3.2.0 → commkit-3.3.0}/commkit/plotting/sync.py +405 -254
- {commkit-3.2.0 → commkit-3.3.0}/commkit/plotting/theme.py +85 -12
- {commkit-3.2.0 → commkit-3.3.0}/commkit/plotting/waveform.py +32 -6
- {commkit-3.2.0 → commkit-3.3.0}/commkit/recovery/corrections.py +24 -6
- {commkit-3.2.0 → commkit-3.3.0}/commkit/recovery/pll.py +28 -3
- {commkit-3.2.0 → commkit-3.3.0}/commkit/recovery/tikhonov.py +38 -4
- {commkit-3.2.0 → commkit-3.3.0}/commkit/timing.py +3 -3
- {commkit-3.2.0 → commkit-3.3.0}/examples/01-constellations-and-bit-mapping.ipynb +10 -13
- {commkit-3.2.0 → commkit-3.3.0}/examples/02-spectral-analysis.ipynb +9 -3
- {commkit-3.2.0 → commkit-3.3.0}/examples/03-digital-filters.ipynb +61 -49
- {commkit-3.2.0 → commkit-3.3.0}/examples/04-pulse-shaping-and-matched-filtering.ipynb +25 -12
- {commkit-3.2.0 → commkit-3.3.0}/examples/05-multirate-and-resampling.ipynb +8 -13
- {commkit-3.2.0 → commkit-3.3.0}/examples/06-awgn-and-performance-metrics.ipynb +2 -2
- {commkit-3.2.0 → commkit-3.3.0}/examples/07-channel-coding.ipynb +5 -3
- {commkit-3.2.0 → commkit-3.3.0}/examples/08-probabilistic-shaping-and-coded-modulation.ipynb +25 -4
- {commkit-3.2.0 → commkit-3.3.0}/examples/09-laser-phase-noise.ipynb +6 -8
- {commkit-3.2.0 → commkit-3.3.0}/examples/10-front-end-impairments.ipynb +16 -8
- {commkit-3.2.0 → commkit-3.3.0}/examples/11-frame-synchronization-and-timing.ipynb +10 -3
- {commkit-3.2.0 → commkit-3.3.0}/examples/12-frequency-offset-estimation.ipynb +3 -1
- {commkit-3.2.0 → commkit-3.3.0}/examples/13-blind-carrier-phase-recovery.ipynb +11 -3
- {commkit-3.2.0 → commkit-3.3.0}/examples/14-pilot-aided-carrier-recovery.ipynb +11 -3
- {commkit-3.2.0 → commkit-3.3.0}/examples/15-linear-channel-equalization.ipynb +18 -9
- {commkit-3.2.0 → commkit-3.3.0}/examples/16-adaptive-equalization.ipynb +23 -11
- {commkit-3.2.0 → commkit-3.3.0}/examples/17-blind-equalization.ipynb +3 -3
- {commkit-3.2.0 → commkit-3.3.0}/examples/18-polarization-multiplexing.ipynb +15 -3
- {commkit-3.2.0 → commkit-3.3.0}/examples/19-chromatic-dispersion.ipynb +15 -3
- {commkit-3.2.0 → commkit-3.3.0}/examples/20-nonlinear-fiber-and-backpropagation.ipynb +1 -1
- {commkit-3.2.0 → commkit-3.3.0}/examples/21-fec-in-optical-standards.ipynb +17 -2
- {commkit-3.2.0 → commkit-3.3.0}/examples/22-coherent-transceiver-end-to-end.ipynb +3 -1
- {commkit-3.2.0 → commkit-3.3.0}/pyproject.toml +4 -2
- commkit-3.3.0/tests/analysis/test_allan.py +75 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/analysis/test_linewidth.py +3 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/coding/test_ldpc.py +24 -6
- {commkit-3.2.0 → commkit-3.3.0}/tests/coding/test_product.py +87 -20
- {commkit-3.2.0 → commkit-3.3.0}/tests/coding/test_ratematch.py +30 -9
- {commkit-3.2.0 → commkit-3.3.0}/tests/coding/test_shaping.py +12 -5
- {commkit-3.2.0 → commkit-3.3.0}/tests/coding/test_turbo.py +41 -17
- {commkit-3.2.0 → commkit-3.3.0}/tests/common/__init__.py +2 -2
- {commkit-3.2.0 → commkit-3.3.0}/tests/common/metrics.py +0 -27
- commkit-3.3.0/tests/common/scoring.py +129 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/common/signals.py +91 -13
- commkit-3.3.0/tests/common/theory.py +84 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/conftest.py +64 -7
- {commkit-3.2.0 → commkit-3.3.0}/tests/core/test_frame.py +11 -43
- {commkit-3.2.0 → commkit-3.3.0}/tests/core/test_signal.py +13 -34
- {commkit-3.2.0 → commkit-3.3.0}/tests/equalization/test_blind.py +99 -66
- {commkit-3.2.0 → commkit-3.3.0}/tests/equalization/test_block.py +177 -333
- {commkit-3.2.0 → commkit-3.3.0}/tests/equalization/test_cpr.py +241 -291
- {commkit-3.2.0 → commkit-3.3.0}/tests/equalization/test_equalizer_oracles.py +14 -11
- {commkit-3.2.0 → commkit-3.3.0}/tests/equalization/test_linear.py +65 -89
- commkit-3.3.0/tests/equalization/test_mimo.py +164 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/equalization/test_polarization.py +13 -0
- commkit-3.3.0/tests/equalization/test_sequential.py +662 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/equalization/test_state.py +2 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/equalization/test_winit.py +36 -151
- {commkit-3.2.0 → commkit-3.3.0}/tests/impairments/channel/test_channel_linear.py +68 -69
- {commkit-3.2.0 → commkit-3.3.0}/tests/impairments/channel/test_channel_nonlinear.py +2 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/impairments/test_frontend.py +0 -42
- {commkit-3.2.0 → commkit-3.3.0}/tests/impairments/test_noise.py +0 -7
- {commkit-3.2.0 → commkit-3.3.0}/tests/impairments/test_source.py +0 -12
- {commkit-3.2.0 → commkit-3.3.0}/tests/mapping/test_bits.py +0 -16
- {commkit-3.2.0 → commkit-3.3.0}/tests/plotting/test_plot_analysis.py +41 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/plotting/test_plot_coding.py +37 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/plotting/test_plot_constellation.py +23 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/plotting/test_plot_equalizer.py +73 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/plotting/test_plot_eye.py +30 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/plotting/test_plot_filter_response.py +52 -3
- {commkit-3.2.0 → commkit-3.3.0}/tests/plotting/test_plot_spectral.py +15 -1
- {commkit-3.2.0 → commkit-3.3.0}/tests/plotting/test_plot_sync.py +125 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/plotting/test_plot_waveform.py +16 -1
- {commkit-3.2.0 → commkit-3.3.0}/tests/plugins/transfer_trace.py +1 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/recovery/test_bps.py +35 -64
- {commkit-3.2.0 → commkit-3.3.0}/tests/recovery/test_corrections.py +34 -38
- {commkit-3.2.0 → commkit-3.3.0}/tests/recovery/test_pilots.py +14 -57
- {commkit-3.2.0 → commkit-3.3.0}/tests/recovery/test_pll.py +24 -47
- {commkit-3.2.0 → commkit-3.3.0}/tests/recovery/test_tikhonov.py +129 -106
- {commkit-3.2.0 → commkit-3.3.0}/tests/recovery/test_viterbi_viterbi.py +38 -85
- {commkit-3.2.0 → commkit-3.3.0}/tests/test_api_contracts.py +11 -0
- commkit-3.3.0/tests/test_filter_response.py +190 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/test_filtering.py +75 -37
- {commkit-3.2.0 → commkit-3.3.0}/tests/test_frequency.py +72 -68
- {commkit-3.2.0 → commkit-3.3.0}/tests/test_io.py +16 -4
- {commkit-3.2.0 → commkit-3.3.0}/tests/test_metrics.py +51 -34
- {commkit-3.2.0 → commkit-3.3.0}/tests/test_multirate.py +96 -36
- {commkit-3.2.0 → commkit-3.3.0}/tests/test_pulse_shaping.py +23 -32
- commkit-3.3.0/tests/test_receiver_chain.py +244 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/test_spectral.py +83 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/test_timing.py +54 -39
- {commkit-3.2.0 → commkit-3.3.0}/uv.lock +1 -1
- commkit-3.2.0/commkit/plotting/equalizer.py +0 -292
- commkit-3.2.0/commkit/plotting/filter_response.py +0 -216
- commkit-3.2.0/tests/analysis/test_allan.py +0 -34
- commkit-3.2.0/tests/equalization/test_mimo.py +0 -218
- commkit-3.2.0/tests/equalization/test_sequential.py +0 -1170
- {commkit-3.2.0 → commkit-3.3.0}/.gitattributes +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/.github/workflows/ci.yml +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/.github/workflows/publish.yml +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/.gitignore +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/.python-version +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/LICENSE +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0002_pre_v2.json +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0003_v2_0.json +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0004_fiber_dbp.json +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0005_gpu_decoder_path.json +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0006_perf_cleanup.json +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0007_v3_0.json +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0008_v3_1.json +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/benchmarks/bench_block_lms.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/benchmarks/bench_bps.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/benchmarks/bench_coding.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/benchmarks/bench_equalizers.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/benchmarks/bench_import.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/benchmarks/bench_llr.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/benchmarks/bench_mapping.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/benchmarks/bench_signal_pipeline.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/benchmarks/bench_sync_misc.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/benchmarks/record_baseline.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/benchmarks/workloads.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/_array.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/_circular.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/_constants.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/_cuda/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/_cuda/compiler.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/_cuda/graphs.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/_cuda/src/algebraic.cu +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/_cuda/src/bps_anchor.cu +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/_cuda/src/bps_min_d2.cu +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/_cuda/src/cs_block.cu +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/_cuda/src/iir.cu +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/_cuda/src/ldpc.cu +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/_cuda/src/llr.cu +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/_cuda/src/selftest.cu +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/_dispersion.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/_fft.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/_fiber.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/_overlap_save.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/_random.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/_sequences.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/analysis/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/analysis/_common.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/analysis/allan.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/analysis/drift.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/analysis/fm_noise.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/analysis/interferometry.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/analysis/linewidth/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/analysis/linewidth/dsh.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/analysis/linewidth/phase.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/analysis/linewidth/result.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/analysis/trajectory.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/backend.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/coding/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/coding/_galois.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/coding/_gf2.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/coding/_ldpc_tables.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/coding/_numba_loops.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/coding/algebraic.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/coding/base.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/coding/convolutional.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/coding/crc.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/coding/exit.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/coding/interleaving.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/coding/ldpc.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/coding/polar.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/coding/product.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/coding/ratematch.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/coding/shaping.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/coding/turbo.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/core/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/core/_signal_adapter.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/core/frame.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/core/generation.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/core/signal.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/equalization/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/equalization/_block/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/equalization/_block/_dd.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/equalization/_kernels_numba.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/equalization/linear.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/equalization/pilots.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/equalization/polarization.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/equalization/sequential/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/equalization/sequential/_blind.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/filtering/fiber.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/filtering/pulses.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/impairments/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/impairments/channel/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/impairments/channel/linear.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/impairments/channel/nonlinear.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/impairments/frontend.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/impairments/noise.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/impairments/source.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/io.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/logger.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/mapping/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/mapping/_numba_llr.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/mapping/bits.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/mapping/constellation.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/mapping/gray.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/mapping/llr.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/mapping/shaping.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/math.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/metrics.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/multirate.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/py.typed +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/recovery/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/recovery/_common.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/recovery/bps.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/recovery/carrier_phase.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/recovery/pilots.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/recovery/viterbi_viterbi.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/smoothing.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/commkit/spectral.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/examples/00-getting-started.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/examples/23-carrier-phase-characterization.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/examples/24-laser-linewidth-self-heterodyne.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/examples/25-laser-linewidth-short-delay-and-flicker.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/examples/26-laser-linewidth-self-homodyne-iq.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/examples/27-measurement-template-self-heterodyne.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/examples/28-measurement-template-self-homodyne-iq.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/examples/README.md +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/analysis/test_drift.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/analysis/test_fm_noise.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/analysis/test_interferometry.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/analysis/test_trajectory.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/coding/test_algebraic.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/coding/test_coding_base.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/coding/test_convolutional.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/coding/test_crc.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/coding/test_exit.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/coding/test_gf2.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/coding/test_interleaving.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/coding/test_ldpc_exit.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/coding/test_ldpc_kernel.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/coding/test_ldpc_tables.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/coding/test_linear_codes.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/coding/test_polar.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/common/coding.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/common/conversions.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/common/kernel_utils.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/common/memory.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/common/periodic.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/common/reference_impl.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/core/test_generation.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/core/test_psqam.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/core/test_signal_adapter.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/core/test_signal_mimo.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/equalization/test_bps_kernel.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/equalization/test_cs_kernel.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/mapping/test_constellation.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/mapping/test_gray.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/mapping/test_llr.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/plotting/test_plot_theme.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/plugins/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/plugins/precision_trace.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/recovery/test_joint.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/recovery/test_recovery_oracles.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/test_array.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/test_backend.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/test_cuda_infra.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/test_dispersion.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/test_examples.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/test_fiber.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/test_import_hygiene.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/test_logger.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/test_math.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/test_signal_pipeline.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/tests/test_smoothing.py +0 -0
- {commkit-3.2.0 → commkit-3.3.0}/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.
|
|
@@ -298,13 +299,24 @@ Docstrings describe the code as it is; a change in behavior updates them in the
|
|
|
298
299
|
|
|
299
300
|
## 7. Tests
|
|
300
301
|
|
|
301
|
-
- **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(...)`.
|
|
302
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.
|
|
303
304
|
- **Shared builders** live in `tests/common/` and use the public API.
|
|
304
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.
|
|
305
306
|
- Do not duplicate these checks in per-module tests.
|
|
306
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.
|
|
307
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.
|
|
308
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.
|
|
309
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.
|
|
310
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.
|
|
@@ -370,6 +382,10 @@ Steps, once CuPy 14.3 (with cp315 wheels for `cupy-cuda13x`) and a fixed `jupyte
|
|
|
370
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.
|
|
371
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.
|
|
372
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
|
+
|
|
373
389
|
### Nonlinear fiber channel: future features
|
|
374
390
|
|
|
375
391
|
Each is an addition that leaves the current API unchanged.
|
|
@@ -4,6 +4,51 @@ Notable changes to CommKit, newest first. Performance figures are from the refer
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [3.3.0] - 2026-11-03
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
### Changes that may need code updates
|
|
12
|
+
|
|
13
|
+
- `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.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- `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.
|
|
18
|
+
- 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.
|
|
19
|
+
- 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.
|
|
20
|
+
- 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.
|
|
21
|
+
|
|
22
|
+
### Development
|
|
23
|
+
|
|
24
|
+
- The transfer audit documents the Tikhonov smoother's host round trip, which it flagged as undocumented.
|
|
25
|
+
- `bench_filter_response` times responses on the DFT grid and on an arbitrary grid at 31, 257 and 4097 taps.
|
|
26
|
+
|
|
27
|
+
## [3.2.1] - 2026-11-02
|
|
28
|
+
|
|
29
|
+
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.
|
|
30
|
+
|
|
31
|
+
### Changes that alter results
|
|
32
|
+
|
|
33
|
+
- **`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).
|
|
34
|
+
- **`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.
|
|
35
|
+
- **`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.
|
|
36
|
+
- **`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`.
|
|
37
|
+
|
|
38
|
+
- **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.
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- **`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.
|
|
43
|
+
- 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.
|
|
44
|
+
- `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.
|
|
45
|
+
|
|
46
|
+
### Development
|
|
47
|
+
|
|
48
|
+
- 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.
|
|
49
|
+
- 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.
|
|
50
|
+
- `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.
|
|
51
|
+
|
|
7
52
|
## [3.2.0] - 2026-10-30
|
|
8
53
|
|
|
9
54
|
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.
|
|
@@ -318,7 +363,9 @@ See the [GitHub release](https://github.com/lokgar/commkit/releases/tag/v1.1.0)
|
|
|
318
363
|
|
|
319
364
|
First release. See the [GitHub release](https://github.com/lokgar/commkit/releases/tag/v1.0.0).
|
|
320
365
|
|
|
321
|
-
[Unreleased]: https://github.com/lokgar/commkit/compare/v3.
|
|
366
|
+
[Unreleased]: https://github.com/lokgar/commkit/compare/v3.3.0...HEAD
|
|
367
|
+
[3.3.0]: https://github.com/lokgar/commkit/compare/v3.2.1...v3.3.0
|
|
368
|
+
[3.2.1]: https://github.com/lokgar/commkit/compare/v3.2.0...v3.2.1
|
|
322
369
|
[3.2.0]: https://github.com/lokgar/commkit/compare/v3.1.3...v3.2.0
|
|
323
370
|
[3.1.3]: https://github.com/lokgar/commkit/compare/v3.1.2...v3.1.3
|
|
324
371
|
[3.1.2]: https://github.com/lokgar/commkit/compare/v3.1.1...v3.1.2
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: commkit
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.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`. |
|
|
@@ -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)
|
|
@@ -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)
|
|
@@ -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
|
|
@@ -247,7 +247,9 @@ class Chase:
|
|
|
247
247
|
code, ``Syndrome(max_weight=1)`` rejects test words with two errors.
|
|
248
248
|
beta : float, optional
|
|
249
249
|
Soft-output magnitude of bits without a competitor; ``None`` takes
|
|
250
|
-
the largest competitor magnitude of the same word
|
|
250
|
+
the largest competitor magnitude of the same word, or, when no
|
|
251
|
+
candidate of the word differs from the decision anywhere, the
|
|
252
|
+
word's largest input ``|LLR|``.
|
|
251
253
|
"""
|
|
252
254
|
|
|
253
255
|
test_positions: int = 4
|
|
@@ -74,7 +74,7 @@ def _block_fdaf_blind(
|
|
|
74
74
|
pilot positions overridden by the LMS residual. ``r2`` is the Godard radius
|
|
75
75
|
(CMA) and ``radii_np`` the unique ring radii (RDE), decided by their
|
|
76
76
|
squared midpoints (``_rde_rings``). ``step_size=None``
|
|
77
|
-
is
|
|
77
|
+
is the default of both, ``min(1e-3, 0.5 / block_size)``.
|
|
78
78
|
"""
|
|
79
79
|
use_pilots = pilot_ref is not None and pilot_mask is not None
|
|
80
80
|
equalizer = f"block_{kind}"
|
|
@@ -198,7 +198,7 @@ def _prepare_block(
|
|
|
198
198
|
|
|
199
199
|
samples, xp, _ = dispatch(samples)
|
|
200
200
|
if xp is np:
|
|
201
|
-
logger.
|
|
201
|
+
logger.info(
|
|
202
202
|
"%s is running on CPU (NumPy). For CPU workloads %s is typically "
|
|
203
203
|
"faster. Move samples to GPU (CuPy) to benefit from block-FFT "
|
|
204
204
|
"acceleration.",
|
|
@@ -328,7 +328,7 @@ def _unpack_result_numpy(
|
|
|
328
328
|
|
|
329
329
|
w_history = None
|
|
330
330
|
if store_weights:
|
|
331
|
-
w_history = xp.asarray(w_hist)
|
|
331
|
+
w_history = xp.asarray(w_hist if n_sym is None else w_hist[:n_sym])
|
|
332
332
|
if was_1d:
|
|
333
333
|
w_history = w_history[:, 0, 0, :]
|
|
334
334
|
|
|
@@ -375,7 +375,7 @@ def _validate_sps(sps: Any, num_taps: int) -> None:
|
|
|
375
375
|
"proportionally more taps.",
|
|
376
376
|
sps,
|
|
377
377
|
)
|
|
378
|
-
if num_taps < 2 * sps:
|
|
378
|
+
if sps > 1 and num_taps < 2 * sps: # fractionally spaced only
|
|
379
379
|
logger.warning(
|
|
380
380
|
"num_taps=%s is small for sps=%s. Recommend num_taps >= %s "
|
|
381
381
|
"for fractionally-spaced equalization.",
|
|
@@ -150,6 +150,10 @@ def _rls_update(W, P, x_bar, e, Px, xH_P, k_gain, lam_f64, leak_term, update):
|
|
|
150
150
|
denom = lam_f64
|
|
151
151
|
for jj in range(N):
|
|
152
152
|
denom = denom + (np.conj(x_bar[jj]) * Px[jj]).real
|
|
153
|
+
# A positive-definite P gives denom >= λ; anything else means P has lost
|
|
154
|
+
# it, and rls() reports the divergence.
|
|
155
|
+
if not denom > 0.0:
|
|
156
|
+
raise FloatingPointError("RLS inverse correlation matrix lost definiteness")
|
|
153
157
|
inv_denom = np.float64(1.0) / denom
|
|
154
158
|
for ii in range(N):
|
|
155
159
|
k_gain[ii] = Px[ii] * inv_denom
|
|
@@ -79,7 +79,7 @@ def block_cma(
|
|
|
79
79
|
*,
|
|
80
80
|
num_taps: int = 21,
|
|
81
81
|
sps: int | None = None,
|
|
82
|
-
step_size: float =
|
|
82
|
+
step_size: float | None = None,
|
|
83
83
|
block_size: int = 256,
|
|
84
84
|
constellation: Any = None,
|
|
85
85
|
initial_taps: ArrayType | None = None,
|
|
@@ -113,10 +113,12 @@ def block_cma(
|
|
|
113
113
|
sps : int, optional
|
|
114
114
|
Samples per symbol, an integer. Taken from the Signal; required for
|
|
115
115
|
array input. A value that disagrees with the Signal raises.
|
|
116
|
-
step_size : float,
|
|
116
|
+
step_size : float, optional
|
|
117
117
|
Step size on the scale of :func:`cma`. The weights are frozen
|
|
118
118
|
across each block, so the stability ceiling is about ``block_size``
|
|
119
119
|
times lower than per symbol; reduce it if the run raises divergence.
|
|
120
|
+
Defaults to ``min(1e-3, 0.5 / block_size)``, as for
|
|
121
|
+
:func:`block_rde`.
|
|
120
122
|
block_size : int, default 256
|
|
121
123
|
Output symbols per weight update.
|
|
122
124
|
constellation : Constellation, optional
|
|
@@ -170,7 +170,9 @@ class EqualizerResult:
|
|
|
170
170
|
error : ndarray
|
|
171
171
|
Error signal, ``(N_sym,)`` or ``(C, N_sym)``.
|
|
172
172
|
weights_history : ndarray or None
|
|
173
|
-
Tap weights over time, only with ``store_weights=True
|
|
173
|
+
Tap weights over time, only with ``store_weights=True``: one row per
|
|
174
|
+
symbol of ``y_hat`` (RLS's trimmed tail included), the taps that
|
|
175
|
+
produced it.
|
|
174
176
|
num_train_symbols : int
|
|
175
177
|
Training symbols consumed in the data-aided phase (LMS/RLS); skip
|
|
176
178
|
them before steady-state metrics such as EVM, SNR and BER.
|
|
@@ -367,13 +367,17 @@ def _check_rls_divergence(
|
|
|
367
367
|
actionable error instead of returning garbage weights.
|
|
368
368
|
"""
|
|
369
369
|
if not bool(xp.isfinite(weights).all()):
|
|
370
|
-
raise RuntimeError(
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
)
|
|
370
|
+
raise RuntimeError(_rls_divergence_message(forgetting_factor, regularization))
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _rls_divergence_message(forgetting_factor: float, regularization: float) -> str:
|
|
374
|
+
return (
|
|
375
|
+
f"RLS equalizer diverged (forgetting_factor={forgetting_factor}, "
|
|
376
|
+
f"regularization={regularization}). RLS requires a positive-definite "
|
|
377
|
+
"correlation matrix. "
|
|
378
|
+
"Try increasing 'regularization', raising 'forgetting_factor' toward 1, "
|
|
379
|
+
"or adding 'leakage' (e.g. 1e-4) to stabilize fractionally-spaced inputs."
|
|
380
|
+
)
|
|
377
381
|
|
|
378
382
|
|
|
379
383
|
def rls(
|
|
@@ -477,7 +481,8 @@ def rls(
|
|
|
477
481
|
Returns
|
|
478
482
|
-------
|
|
479
483
|
EqualizerResult
|
|
480
|
-
The fields described for :func:`lms`. ``y_hat``
|
|
484
|
+
The fields described for :func:`lms`. ``y_hat``, ``error``,
|
|
485
|
+
``weights_history`` and ``phase_trajectory`` lack the last
|
|
481
486
|
``num_taps // 2`` symbols (``tail_trim``), and ``signal`` carries the
|
|
482
487
|
reference cut to match.
|
|
483
488
|
|
|
@@ -565,7 +570,7 @@ def rls(
|
|
|
565
570
|
n_update_halt = max(0, run.n_sym - num_taps // 2)
|
|
566
571
|
tail_trim = num_taps // 2
|
|
567
572
|
if tail_trim > 0:
|
|
568
|
-
logger.
|
|
573
|
+
logger.info(
|
|
569
574
|
"RLS tail trim: last %s symbols removed from y_hat "
|
|
570
575
|
"(zero-padding contamination zone). result.signal carries the "
|
|
571
576
|
"trimmed reference; trim reference arrays to match: "
|
|
@@ -663,7 +668,12 @@ def rls(
|
|
|
663
668
|
|
|
664
669
|
# W and P freeze at n_update_halt, so the state resumes there at the latest.
|
|
665
670
|
resume = min(run.n_done, n_update_halt)
|
|
666
|
-
|
|
671
|
+
try:
|
|
672
|
+
snap = _run_resumable(run, resume, segment, snapshot)
|
|
673
|
+
except FloatingPointError as err: # raised by the kernel when P is no longer PD
|
|
674
|
+
raise RuntimeError(
|
|
675
|
+
_rls_divergence_message(forgetting_factor, regularization)
|
|
676
|
+
) from err
|
|
667
677
|
result = _assemble_sequential(
|
|
668
678
|
run,
|
|
669
679
|
equalizer="rls",
|
|
@@ -397,7 +397,9 @@ def _inline_cpr(cpr: Any, constellation: Any, function_name: str) -> _InlineCpr
|
|
|
397
397
|
joint=bool(cpr.joint_channels),
|
|
398
398
|
symmetry=symmetry,
|
|
399
399
|
cycle_slip=cycle_slip is not None,
|
|
400
|
-
history=slip._history_values(
|
|
400
|
+
history=slip._history_values( # per-symbol phase
|
|
401
|
+
1, cpr._slip_history if isinstance(cpr, PLL) else None
|
|
402
|
+
),
|
|
401
403
|
threshold=float(slip.threshold),
|
|
402
404
|
)
|
|
403
405
|
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
- Design: :func:`fir_taps` (window method) and the IIR designs in
|
|
7
7
|
second-order sections, :func:`butterworth_sos`, :func:`chebyshev1_sos`,
|
|
8
8
|
:func:`chebyshev2_sos`, :func:`elliptic_sos` and :func:`bessel_sos`.
|
|
9
|
+
- Responses: :func:`filter_response` (a :class:`FilterResponse`),
|
|
10
|
+
:func:`impulse_response` and :func:`step_response`, computed on the
|
|
11
|
+
coefficients' device.
|
|
9
12
|
- Application: :func:`fir_filter` (FFT convolution, optionally circular),
|
|
10
13
|
:func:`ols_fir_filter` (overlap-save, for long filters), :func:`iir_filter`
|
|
11
14
|
and :func:`matched_filter`.
|
|
@@ -40,8 +43,10 @@ from .pulses import (
|
|
|
40
43
|
Rect,
|
|
41
44
|
SmoothRect,
|
|
42
45
|
)
|
|
46
|
+
from .response import FilterResponse, filter_response, impulse_response, step_response
|
|
43
47
|
|
|
44
48
|
__all__ = [
|
|
49
|
+
"FilterResponse",
|
|
45
50
|
"Gaussian",
|
|
46
51
|
"Pulse",
|
|
47
52
|
"RC",
|
|
@@ -55,9 +60,12 @@ __all__ = [
|
|
|
55
60
|
"correct_chromatic_dispersion",
|
|
56
61
|
"correct_fiber_propagation",
|
|
57
62
|
"elliptic_sos",
|
|
63
|
+
"filter_response",
|
|
58
64
|
"fir_filter",
|
|
59
65
|
"fir_taps",
|
|
60
66
|
"iir_filter",
|
|
67
|
+
"impulse_response",
|
|
61
68
|
"matched_filter",
|
|
62
69
|
"ols_fir_filter",
|
|
70
|
+
"step_response",
|
|
63
71
|
]
|