commkit 3.2.0__tar.gz → 3.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {commkit-3.2.0 → commkit-3.2.1}/AGENTS.md +18 -2
- {commkit-3.2.0 → commkit-3.2.1}/CHANGELOG.md +26 -2
- {commkit-3.2.0 → commkit-3.2.1}/PKG-INFO +1 -1
- {commkit-3.2.0 → commkit-3.2.1}/benchmarks/bench_block_blind.py +13 -6
- {commkit-3.2.0 → commkit-3.2.1}/benchmarks/bench_fiber.py +6 -4
- {commkit-3.2.0 → commkit-3.2.1}/benchmarks/benchutils.py +11 -3
- {commkit-3.2.0 → commkit-3.2.1}/benchmarks/conftest.py +14 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/__init__.py +1 -1
- {commkit-3.2.0 → commkit-3.2.1}/commkit/coding/linear.py +3 -1
- {commkit-3.2.0 → commkit-3.2.1}/commkit/equalization/_block/_blind.py +1 -1
- {commkit-3.2.0 → commkit-3.2.1}/commkit/equalization/_block/_engine.py +1 -1
- {commkit-3.2.0 → commkit-3.2.1}/commkit/equalization/_common.py +2 -2
- {commkit-3.2.0 → commkit-3.2.1}/commkit/equalization/_numba_loops.py +4 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/equalization/blind.py +4 -2
- {commkit-3.2.0 → commkit-3.2.1}/commkit/equalization/result.py +3 -1
- {commkit-3.2.0 → commkit-3.2.1}/commkit/equalization/sequential/_dd.py +20 -10
- {commkit-3.2.0 → commkit-3.2.1}/commkit/equalization/sequential/_setup.py +3 -1
- {commkit-3.2.0 → commkit-3.2.1}/commkit/filtering/linear.py +7 -5
- {commkit-3.2.0 → commkit-3.2.1}/commkit/frequency.py +0 -4
- {commkit-3.2.0 → commkit-3.2.1}/commkit/plotting/analysis.py +8 -11
- {commkit-3.2.0 → commkit-3.2.1}/commkit/plotting/coding.py +3 -4
- {commkit-3.2.0 → commkit-3.2.1}/commkit/plotting/equalizer.py +2 -4
- {commkit-3.2.0 → commkit-3.2.1}/commkit/plotting/eye.py +1 -1
- {commkit-3.2.0 → commkit-3.2.1}/commkit/plotting/filter_response.py +4 -3
- {commkit-3.2.0 → commkit-3.2.1}/commkit/plotting/spectral.py +3 -3
- {commkit-3.2.0 → commkit-3.2.1}/commkit/plotting/sync.py +2 -2
- {commkit-3.2.0 → commkit-3.2.1}/commkit/plotting/theme.py +12 -10
- {commkit-3.2.0 → commkit-3.2.1}/commkit/plotting/waveform.py +1 -1
- {commkit-3.2.0 → commkit-3.2.1}/commkit/recovery/corrections.py +24 -6
- {commkit-3.2.0 → commkit-3.2.1}/commkit/recovery/pll.py +28 -3
- {commkit-3.2.0 → commkit-3.2.1}/commkit/recovery/tikhonov.py +38 -4
- {commkit-3.2.0 → commkit-3.2.1}/commkit/timing.py +3 -3
- {commkit-3.2.0 → commkit-3.2.1}/examples/17-blind-equalization.ipynb +3 -3
- {commkit-3.2.0 → commkit-3.2.1}/pyproject.toml +4 -2
- commkit-3.2.1/tests/analysis/test_allan.py +75 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/analysis/test_linewidth.py +3 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/coding/test_ldpc.py +24 -6
- {commkit-3.2.0 → commkit-3.2.1}/tests/coding/test_product.py +87 -20
- {commkit-3.2.0 → commkit-3.2.1}/tests/coding/test_ratematch.py +30 -9
- {commkit-3.2.0 → commkit-3.2.1}/tests/coding/test_shaping.py +12 -5
- {commkit-3.2.0 → commkit-3.2.1}/tests/coding/test_turbo.py +41 -17
- {commkit-3.2.0 → commkit-3.2.1}/tests/common/__init__.py +2 -2
- {commkit-3.2.0 → commkit-3.2.1}/tests/common/metrics.py +0 -27
- commkit-3.2.1/tests/common/scoring.py +129 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/common/signals.py +91 -13
- commkit-3.2.1/tests/common/theory.py +84 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/conftest.py +64 -7
- {commkit-3.2.0 → commkit-3.2.1}/tests/core/test_frame.py +11 -43
- {commkit-3.2.0 → commkit-3.2.1}/tests/core/test_signal.py +13 -34
- {commkit-3.2.0 → commkit-3.2.1}/tests/equalization/test_blind.py +99 -66
- {commkit-3.2.0 → commkit-3.2.1}/tests/equalization/test_block.py +177 -333
- {commkit-3.2.0 → commkit-3.2.1}/tests/equalization/test_cpr.py +241 -291
- {commkit-3.2.0 → commkit-3.2.1}/tests/equalization/test_equalizer_oracles.py +14 -11
- {commkit-3.2.0 → commkit-3.2.1}/tests/equalization/test_linear.py +65 -89
- commkit-3.2.1/tests/equalization/test_mimo.py +164 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/equalization/test_polarization.py +13 -0
- commkit-3.2.1/tests/equalization/test_sequential.py +662 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/equalization/test_state.py +2 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/equalization/test_winit.py +36 -151
- {commkit-3.2.0 → commkit-3.2.1}/tests/impairments/channel/test_channel_linear.py +68 -69
- {commkit-3.2.0 → commkit-3.2.1}/tests/impairments/channel/test_channel_nonlinear.py +2 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/impairments/test_frontend.py +0 -42
- {commkit-3.2.0 → commkit-3.2.1}/tests/impairments/test_noise.py +0 -7
- {commkit-3.2.0 → commkit-3.2.1}/tests/impairments/test_source.py +0 -12
- {commkit-3.2.0 → commkit-3.2.1}/tests/mapping/test_bits.py +0 -16
- {commkit-3.2.0 → commkit-3.2.1}/tests/plotting/test_plot_analysis.py +1 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/plotting/test_plot_filter_response.py +2 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/plotting/test_plot_spectral.py +4 -1
- {commkit-3.2.0 → commkit-3.2.1}/tests/plotting/test_plot_sync.py +2 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/plotting/test_plot_waveform.py +2 -1
- {commkit-3.2.0 → commkit-3.2.1}/tests/recovery/test_bps.py +35 -64
- {commkit-3.2.0 → commkit-3.2.1}/tests/recovery/test_corrections.py +34 -38
- {commkit-3.2.0 → commkit-3.2.1}/tests/recovery/test_pilots.py +14 -57
- {commkit-3.2.0 → commkit-3.2.1}/tests/recovery/test_pll.py +24 -47
- {commkit-3.2.0 → commkit-3.2.1}/tests/recovery/test_tikhonov.py +129 -106
- {commkit-3.2.0 → commkit-3.2.1}/tests/recovery/test_viterbi_viterbi.py +38 -85
- {commkit-3.2.0 → commkit-3.2.1}/tests/test_api_contracts.py +7 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/test_filtering.py +75 -37
- {commkit-3.2.0 → commkit-3.2.1}/tests/test_frequency.py +72 -68
- {commkit-3.2.0 → commkit-3.2.1}/tests/test_io.py +16 -4
- {commkit-3.2.0 → commkit-3.2.1}/tests/test_metrics.py +51 -34
- {commkit-3.2.0 → commkit-3.2.1}/tests/test_multirate.py +96 -36
- {commkit-3.2.0 → commkit-3.2.1}/tests/test_pulse_shaping.py +23 -32
- commkit-3.2.1/tests/test_receiver_chain.py +244 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/test_spectral.py +83 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/test_timing.py +54 -39
- {commkit-3.2.0 → commkit-3.2.1}/uv.lock +1 -1
- 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.2.1}/.gitattributes +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/.github/workflows/ci.yml +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/.github/workflows/publish.yml +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/.gitignore +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/.python-version +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/LICENSE +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/README.md +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/benchmarks/baselines/Linux-CPython-3.14-64bit/0002_pre_v2.json +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/benchmarks/baselines/Linux-CPython-3.14-64bit/0003_v2_0.json +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/benchmarks/baselines/Linux-CPython-3.14-64bit/0004_fiber_dbp.json +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/benchmarks/baselines/Linux-CPython-3.14-64bit/0005_gpu_decoder_path.json +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/benchmarks/baselines/Linux-CPython-3.14-64bit/0006_perf_cleanup.json +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/benchmarks/baselines/Linux-CPython-3.14-64bit/0007_v3_0.json +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/benchmarks/baselines/Linux-CPython-3.14-64bit/0008_v3_1.json +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/benchmarks/bench_block_lms.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/benchmarks/bench_bps.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/benchmarks/bench_coding.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/benchmarks/bench_equalizers.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/benchmarks/bench_filters.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/benchmarks/bench_import.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/benchmarks/bench_llr.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/benchmarks/bench_mapping.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/benchmarks/bench_signal_pipeline.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/benchmarks/bench_sync_misc.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/benchmarks/record_baseline.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/benchmarks/workloads.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/_array.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/_circular.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/_constants.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/_cuda/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/_cuda/compiler.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/_cuda/graphs.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/_cuda/src/algebraic.cu +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/_cuda/src/bps_anchor.cu +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/_cuda/src/bps_min_d2.cu +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/_cuda/src/cs_block.cu +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/_cuda/src/iir.cu +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/_cuda/src/ldpc.cu +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/_cuda/src/llr.cu +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/_cuda/src/selftest.cu +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/_dispersion.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/_fft.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/_fiber.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/_overlap_save.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/_random.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/_sequences.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/analysis/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/analysis/_common.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/analysis/allan.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/analysis/drift.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/analysis/fm_noise.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/analysis/interferometry.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/analysis/linewidth/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/analysis/linewidth/dsh.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/analysis/linewidth/phase.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/analysis/linewidth/result.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/analysis/trajectory.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/backend.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/coding/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/coding/_galois.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/coding/_gf2.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/coding/_ldpc_tables.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/coding/_numba_loops.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/coding/algebraic.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/coding/base.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/coding/convolutional.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/coding/crc.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/coding/exit.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/coding/interleaving.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/coding/ldpc.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/coding/polar.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/coding/product.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/coding/ratematch.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/coding/shaping.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/coding/turbo.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/core/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/core/_signal_adapter.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/core/frame.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/core/generation.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/core/signal.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/equalization/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/equalization/_block/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/equalization/_block/_dd.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/equalization/_kernels_numba.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/equalization/linear.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/equalization/pilots.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/equalization/polarization.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/equalization/sequential/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/equalization/sequential/_blind.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/filtering/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/filtering/fiber.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/filtering/pulses.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/impairments/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/impairments/channel/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/impairments/channel/linear.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/impairments/channel/nonlinear.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/impairments/frontend.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/impairments/noise.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/impairments/source.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/io.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/logger.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/mapping/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/mapping/_numba_llr.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/mapping/bits.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/mapping/constellation.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/mapping/gray.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/mapping/llr.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/mapping/shaping.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/math.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/metrics.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/multirate.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/plotting/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/plotting/constellation.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/py.typed +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/recovery/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/recovery/_common.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/recovery/bps.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/recovery/carrier_phase.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/recovery/pilots.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/recovery/viterbi_viterbi.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/smoothing.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/commkit/spectral.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/00-getting-started.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/01-constellations-and-bit-mapping.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/02-spectral-analysis.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/03-digital-filters.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/04-pulse-shaping-and-matched-filtering.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/05-multirate-and-resampling.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/06-awgn-and-performance-metrics.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/07-channel-coding.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/08-probabilistic-shaping-and-coded-modulation.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/09-laser-phase-noise.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/10-front-end-impairments.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/11-frame-synchronization-and-timing.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/12-frequency-offset-estimation.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/13-blind-carrier-phase-recovery.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/14-pilot-aided-carrier-recovery.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/15-linear-channel-equalization.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/16-adaptive-equalization.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/18-polarization-multiplexing.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/19-chromatic-dispersion.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/20-nonlinear-fiber-and-backpropagation.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/21-fec-in-optical-standards.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/22-coherent-transceiver-end-to-end.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/23-carrier-phase-characterization.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/24-laser-linewidth-self-heterodyne.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/25-laser-linewidth-short-delay-and-flicker.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/26-laser-linewidth-self-homodyne-iq.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/27-measurement-template-self-heterodyne.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/28-measurement-template-self-homodyne-iq.ipynb +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/examples/README.md +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/analysis/test_drift.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/analysis/test_fm_noise.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/analysis/test_interferometry.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/analysis/test_trajectory.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/coding/test_algebraic.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/coding/test_coding_base.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/coding/test_convolutional.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/coding/test_crc.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/coding/test_exit.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/coding/test_gf2.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/coding/test_interleaving.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/coding/test_ldpc_exit.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/coding/test_ldpc_kernel.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/coding/test_ldpc_tables.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/coding/test_linear_codes.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/coding/test_polar.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/common/coding.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/common/conversions.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/common/kernel_utils.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/common/memory.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/common/periodic.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/common/reference_impl.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/core/test_generation.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/core/test_psqam.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/core/test_signal_adapter.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/core/test_signal_mimo.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/equalization/test_bps_kernel.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/equalization/test_cs_kernel.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/mapping/test_constellation.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/mapping/test_gray.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/mapping/test_llr.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/plotting/test_plot_coding.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/plotting/test_plot_constellation.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/plotting/test_plot_equalizer.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/plotting/test_plot_eye.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/plotting/test_plot_theme.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/plugins/__init__.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/plugins/precision_trace.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/plugins/transfer_trace.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/recovery/test_joint.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/recovery/test_recovery_oracles.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/test_array.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/test_backend.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/test_cuda_infra.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/test_dispersion.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/test_examples.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/test_fiber.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/test_import_hygiene.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/test_logger.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/test_math.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/test_signal_pipeline.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/test_smoothing.py +0 -0
- {commkit-3.2.0 → commkit-3.2.1}/tests/test_working_memory.py +0 -0
|
@@ -33,6 +33,7 @@ uv sync --all-extras # environment incl. GPU packages
|
|
|
33
33
|
uv run nbstripout --install # once per clone: strip notebook outputs on commit
|
|
34
34
|
uv run pytest # CPU + GPU tests (default --device=all)
|
|
35
35
|
uv run pytest --device=cpu # what CI runs
|
|
36
|
+
uv run pytest --run-slow # also the long statistical validation (marker slow)
|
|
36
37
|
uv run pytest tests/equalization/ -k lms # subset
|
|
37
38
|
uv run pytest --cov=commkit # coverage
|
|
38
39
|
NUMBA_DISABLE_JIT=1 uv run pytest tests/equalization tests/recovery \
|
|
@@ -111,7 +112,7 @@ uv run python benchmarks/record_baseline.py NAME # record a new baseline
|
|
|
111
112
|
|
|
112
113
|
**Release:**
|
|
113
114
|
|
|
114
|
-
1. On `main`: the CPU and GPU suites pass with no failures or skips (`uv run pytest -rs
|
|
115
|
+
1. On `main`: the CPU and GPU suites pass with no failures or skips, slow validation included (`uv run pytest -rs --run-slow`; strict xfails, if any, are listed in §9), `uv build` succeeds and `uv run --with twine twine check dist/*` passes.
|
|
115
116
|
2. `CHANGELOG.md`: a `## [X.Y.Z] - YYYY-MM-DD` section for the release, written for users. Changes that may need code updates come first, with how to migrate, then Added, Performance (measured, as a ratio of new to old time), Fixed and Development. Add its compare link at the bottom, and commit it.
|
|
116
117
|
3. `uv run bump-my-version bump {patch|minor|major}`. This commits the new version and tags it `vX.Y.Z`.
|
|
117
118
|
4. `git push origin main --tags`. The ruleset on `main` requires pull requests; repository admins bypass it for these two commits.
|
|
@@ -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.
|
|
@@ -2,7 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
Notable changes to CommKit, newest first. Performance figures are from the reference machine (RTX 4070 Ti, Ryzen 7 7800X3D, WSL2) unless stated otherwise, as a ratio of new to old time (below 1 is faster).
|
|
4
4
|
|
|
5
|
-
## [
|
|
5
|
+
## [3.2.1] - 2026-11-02
|
|
6
|
+
|
|
7
|
+
Fixes found by a rework of the test suite, which now checks what each algorithm achieves against the transmitted truth and theory. Five numerical defaults or models were wrong and change results (PLL cycle-slip repair, Tikhonov smoothing, `block_cma`'s step, `ols_fir_filter`'s alignment of even taps, `MthPower` near 0 Hz); no signature changes. Plots take their sizes from the theme.
|
|
8
|
+
|
|
9
|
+
### Changes that alter results
|
|
10
|
+
|
|
11
|
+
- **`Tikhonov` uses the actual variance of a Viterbi-Viterbi block phase** as its observation variance, computed from the constellation and `snr_db`: the noise through the M-th power plus the data self-noise of QAM. It used `1/(M²·SNR·N)`, too small by `M²/2` for PSK and by three to four orders of magnitude for QAM, so the smoother followed the raw block phases: on 16-QAM it now lowers the Viterbi-Viterbi error (static phase, 30 dB, 16-symbol blocks: 0.101 rad before and after smoothing, now 0.048 rad).
|
|
12
|
+
- **`PLL(cycle_slip=CycleSlip())` defaults to a history of `8/μ` symbols** (twice the loop memory, between 10 and 640), standalone and inline in `lms` / `rls`; it used 640 symbols, as the block methods do. A PLL trajectory is smoothed by the loop, and the long linear fit lagged the phase noise enough to declare false slips: 16-QAM, `2π·Δν·T = 2π·1e-4`, 16 dB, `B·T = 2e-2`, four records of 2·10⁴ symbols: 10 rotation changes against the transmitted symbols with the repair, 3 without; now 3 with it. The repair does not undo the loop's own slips, which drift over about `1/(B·T)` symbols; the `CycleSlip` docstring says so. An explicit `history` is unchanged.
|
|
13
|
+
- **`block_cma` defaults to `step_size=min(1e-3, 0.5 / block_size)`**, as `block_rde` does; it used 2e-4. On QPSK, where CMA and RDE are the same algorithm, the old default left the worst channel at 2.3 times the AWGN symbol error rate over the second half of 8·10⁴ symbols (dual polarization through rotation and DGD, Es/N0 10 dB; now 1.3 times) and at 16 times on 2·10⁴ symbols (now 2 times); on 16-QAM at 18 dB the SER of 2·10⁴-symbol records falls from 0.13 to 0.008. Above 2500-symbol blocks the new default is the smaller step, inside the stability ceiling that 2e-4 crossed (16-QAM, 4096-symbol blocks: SER 0.02 before, 0.004 now). Pass `step_size=2e-4` for the previous behavior.
|
|
14
|
+
- **`ols_fir_filter` centres even-length taps on tap `(L - 1) // 2`**, as `fir_filter` (SciPy's `mode="same"`) does and as its docstring promised; it used `L // 2`, one sample later. Odd-length taps and `causal=True` are unchanged. The docstrings of both now state the centre tap; they said `L // 2`.
|
|
15
|
+
|
|
16
|
+
- **Plots take line widths, marker sizes and fonts from the theme.** The plot functions no longer set their own line widths and marker sizes, so `apply_default_theme` and the user's rcParams control them; the theme's text is larger (16 pt, legends 14 pt, titles 18 pt) with longer ticks. SI-prefix tick labels show the prefix only (`10 k`, not `10 kHz`), as the axis labels already carry the unit.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **`MthPower` estimates offsets near 0 Hz.** It zeroed bin 0 of the M-th power spectrum and, for constellations without a constant envelope, subtracted the mean of `x^M`, which for QAM is the tone itself: an offset with `|M Δf|` under half a bin was never found (QPSK, 4096 symbols at 1 MS/s: 0 Hz read as -82 kHz, 10 Hz as 30.5 Hz). Both steps are removed; at other offsets the estimates do not change, also with a DC offset of -20 dB in the input.
|
|
21
|
+
- Messages that report what the caller asked for log at INFO, not WARNING, so a WARNING from commkit always means a suspect result: the block equalizers' advice to move CPU data to the GPU, RLS's tail trim, `correct_timing(mode="slice")` shortening the record and the plotting theme's font fallback. The advice to use more taps for fractionally spaced equalization is no longer logged at `sps=1`, where it does not apply.
|
|
22
|
+
- `rls()`: `weights_history` has one row per returned symbol, so it matches `y_hat` after the tail trim (it kept the `num_taps // 2` trimmed rows). A run whose inverse correlation matrix loses positive definiteness (a forgetting factor far below 1) raises the documented `RuntimeError` instead of a bare `ZeroDivisionError` from the kernel, and the message now advises raising the forgetting factor toward 1, not lowering it.
|
|
23
|
+
|
|
24
|
+
### Development
|
|
25
|
+
|
|
26
|
+
- The test suite checks what the code achieves, not how it is written. Equalizers converge on RRC records through ISI, fractional delays, polarization rotation and DGD, scored against the transmitted symbols with only the legitimate rotation and permutation removed, and each "improves" test also runs a baseline that must fail; carrier recovery is scored against the true phase; estimators and metrics against Cramér–Rao bounds and AWGN theory; Chase and shortened-code decoding against brute force. New end-to-end receiver chains (coded single carrier, dual polarization, 80 km of fiber) each show that leaving out one correction breaks them. Monte Carlo tests state what their frame counts bound and gain `--run-slow` validation runs. A WARNING log from commkit fails the test. Tests that only repeated the contract registry's shape and device checks are removed.
|
|
27
|
+
- The library faults these tests found are fixed above: the PLL's cycle-slip repair adding slips, Tikhonov's noise model on QAM, the dead zone of `MthPower` around 0 Hz, the alignment of even-length taps (`ols_fir_filter` against `fir_filter`) and RLS's `weights_history` length.
|
|
28
|
+
- `benchutils.assert_converged` checks the symbol error rate of the worst channel (it compared bit errors before), and the 2048-tap block RDE benchmark uses a workload that converges.
|
|
6
29
|
|
|
7
30
|
## [3.2.0] - 2026-10-30
|
|
8
31
|
|
|
@@ -318,7 +341,8 @@ See the [GitHub release](https://github.com/lokgar/commkit/releases/tag/v1.1.0)
|
|
|
318
341
|
|
|
319
342
|
First release. See the [GitHub release](https://github.com/lokgar/commkit/releases/tag/v1.0.0).
|
|
320
343
|
|
|
321
|
-
[Unreleased]: https://github.com/lokgar/commkit/compare/v3.2.
|
|
344
|
+
[Unreleased]: https://github.com/lokgar/commkit/compare/v3.2.1...HEAD
|
|
345
|
+
[3.2.1]: https://github.com/lokgar/commkit/compare/v3.2.0...v3.2.1
|
|
322
346
|
[3.2.0]: https://github.com/lokgar/commkit/compare/v3.1.3...v3.2.0
|
|
323
347
|
[3.1.3]: https://github.com/lokgar/commkit/compare/v3.1.2...v3.1.3
|
|
324
348
|
[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.2.
|
|
3
|
+
Version: 3.2.1
|
|
4
4
|
Summary: A Python library for high-performance digital communications research.
|
|
5
5
|
Project-URL: Homepage, https://github.com/lokgar/commkit
|
|
6
6
|
Project-URL: Repository, https://github.com/lokgar/commkit
|
|
@@ -31,16 +31,18 @@ ROUNDS = dict(rounds=3, warmup_rounds=1, iterations=1)
|
|
|
31
31
|
N_SYM = 100_000
|
|
32
32
|
|
|
33
33
|
# Converging step sizes per (equalizer, block_size). RDE on 16-QAM from a
|
|
34
|
-
# cold start (no CMA pre-convergence) converges slowly
|
|
35
|
-
#
|
|
36
|
-
# not depend on
|
|
34
|
+
# cold start (no CMA pre-convergence) converges slowly at 2048: at 4e-4 it is
|
|
35
|
+
# error-free from about symbol 70 000, so its SER is scored after that (7e-4
|
|
36
|
+
# never settled, still 2-5 % SER at the end); its cost does not depend on
|
|
37
|
+
# the data.
|
|
37
38
|
STEP_SIZE = {
|
|
38
39
|
("cma", 256): 2e-4,
|
|
39
40
|
("cma", 2048): 3e-4,
|
|
40
41
|
("rde", 256): 3e-3,
|
|
41
|
-
("rde", 2048):
|
|
42
|
+
("rde", 2048): 4e-4,
|
|
42
43
|
}
|
|
43
|
-
|
|
44
|
+
SKIP = {("rde", 2048): 70_000}
|
|
45
|
+
MAX_SER = {"cma": 1e-2, "rde": 1e-2}
|
|
44
46
|
|
|
45
47
|
EQUALIZERS = [
|
|
46
48
|
("cma", block_cma),
|
|
@@ -66,7 +68,12 @@ def _bench_block_blind(benchmark, xp, sync, label, eq_fn, block_size):
|
|
|
66
68
|
|
|
67
69
|
r = benchmark.pedantic(run, **ROUNDS)
|
|
68
70
|
assert_converged(
|
|
69
|
-
r,
|
|
71
|
+
r,
|
|
72
|
+
syms,
|
|
73
|
+
Constellation.qam(16),
|
|
74
|
+
skip=SKIP.get((label, block_size), 20_000),
|
|
75
|
+
max_ser=MAX_SER[label],
|
|
76
|
+
blind=True,
|
|
70
77
|
)
|
|
71
78
|
|
|
72
79
|
|
|
@@ -26,16 +26,18 @@ LINK = dict(
|
|
|
26
26
|
)
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
# The plain/fused split exists on the GPU only; its CPU items are deselected.
|
|
30
|
+
PATHS = ["auto", pytest.param("plain", marks=pytest.mark.gpu_only)]
|
|
31
|
+
|
|
32
|
+
|
|
29
33
|
def _stages(backend_device, monkeypatch, path):
|
|
30
34
|
if path == "plain":
|
|
31
|
-
if backend_device != "gpu":
|
|
32
|
-
pytest.skip("the plain/fused split exists on the GPU only")
|
|
33
35
|
monkeypatch.setattr(
|
|
34
36
|
_fiber, "_make_ops", lambda x, xp, *args: _fiber._Ops(xp, x.dtype, *args)
|
|
35
37
|
)
|
|
36
38
|
|
|
37
39
|
|
|
38
|
-
@pytest.mark.parametrize("path",
|
|
40
|
+
@pytest.mark.parametrize("path", PATHS)
|
|
39
41
|
@pytest.mark.parametrize("n", [2**16, 2**20], ids=["N64k", "N1M"])
|
|
40
42
|
def bench_fiber_propagation(benchmark, backend_device, xp, sync, monkeypatch, n, path):
|
|
41
43
|
_stages(backend_device, monkeypatch, path)
|
|
@@ -49,7 +51,7 @@ def bench_fiber_propagation(benchmark, backend_device, xp, sync, monkeypatch, n,
|
|
|
49
51
|
benchmark.pedantic(run, **ROUNDS)
|
|
50
52
|
|
|
51
53
|
|
|
52
|
-
@pytest.mark.parametrize("path",
|
|
54
|
+
@pytest.mark.parametrize("path", PATHS)
|
|
53
55
|
@pytest.mark.parametrize("steps", [1, 10])
|
|
54
56
|
def bench_dbp(benchmark, backend_device, xp, sync, monkeypatch, steps, path):
|
|
55
57
|
_stages(backend_device, monkeypatch, path)
|
|
@@ -64,14 +64,22 @@ def assert_converged(result, syms, constellation, *, skip, max_ser, blind=False)
|
|
|
64
64
|
storms, divergence) instead of the operating point, so every equalizer
|
|
65
65
|
benchmark checks its symbol error rate after ``skip`` symbols. Blind
|
|
66
66
|
equalizers leave a phase ambiguity, which is resolved against the
|
|
67
|
-
reference first.
|
|
67
|
+
reference first, after the channel permutation of a butterfly.
|
|
68
68
|
"""
|
|
69
69
|
from commkit.backend import to_device
|
|
70
|
-
from commkit.recovery import resolve_phase_ambiguity
|
|
70
|
+
from commkit.recovery import resolve_channel_permutation, resolve_phase_ambiguity
|
|
71
71
|
|
|
72
72
|
y = to_device(result.y_hat, "cpu")[..., skip:]
|
|
73
73
|
ref = to_device(syms, "cpu")[..., skip : skip + y.shape[-1]]
|
|
74
74
|
if blind:
|
|
75
|
+
if y.ndim == 2:
|
|
76
|
+
y = resolve_channel_permutation(y, ref)
|
|
75
77
|
y = resolve_phase_ambiguity(y, ref, constellation=constellation)
|
|
76
|
-
|
|
78
|
+
# Symbol decisions, not bits (one wrong symbol is one error), and the
|
|
79
|
+
# worst channel, so one failed stream cannot hide behind a good one.
|
|
80
|
+
wrong = constellation._nearest(y.reshape(-1)) != constellation._nearest(
|
|
81
|
+
ref.reshape(-1)
|
|
82
|
+
)
|
|
83
|
+
per_channel = to_device(wrong, "cpu").reshape(y.shape).mean(axis=-1)
|
|
84
|
+
ser = float(per_channel.max())
|
|
77
85
|
assert ser <= max_ser, f"benchmark workload did not converge: SER {ser:.3g}"
|
|
@@ -56,6 +56,20 @@ def pytest_generate_tests(metafunc):
|
|
|
56
56
|
metafunc.parametrize("backend_device", params, indirect=True)
|
|
57
57
|
|
|
58
58
|
|
|
59
|
+
def pytest_collection_modifyitems(config, items):
|
|
60
|
+
"""Deselect, not skip, the CPU items of ``gpu_only`` parameters."""
|
|
61
|
+
cpu_only_gpu = [
|
|
62
|
+
item
|
|
63
|
+
for item in items
|
|
64
|
+
if item.get_closest_marker("gpu_only")
|
|
65
|
+
and getattr(item, "callspec", None) is not None
|
|
66
|
+
and item.callspec.params.get("backend_device") == "cpu"
|
|
67
|
+
]
|
|
68
|
+
if cpu_only_gpu:
|
|
69
|
+
items[:] = [item for item in items if item not in cpu_only_gpu]
|
|
70
|
+
config.hook.pytest_deselected(items=cpu_only_gpu)
|
|
71
|
+
|
|
72
|
+
|
|
59
73
|
@numba.njit(cache=True, parallel=True)
|
|
60
74
|
def _spin(x):
|
|
61
75
|
total = 0.0
|
|
@@ -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
|
|
|
@@ -387,8 +387,8 @@ def ols_fir_filter(
|
|
|
387
387
|
is at least ``L`` samples long.
|
|
388
388
|
causal : bool, default False
|
|
389
389
|
By default the output is aligned as :func:`fir_filter` aligns it
|
|
390
|
-
(SciPy's ``mode='same'``, centered on tap ``L // 2``):
|
|
391
|
-
``y[n] = sum_k x[n + L//2 - k]·taps[k]``, which removes the group
|
|
390
|
+
(SciPy's ``mode='same'``, centered on tap ``(L - 1) // 2``):
|
|
391
|
+
``y[n] = sum_k x[n + (L-1)//2 - k]·taps[k]``, which removes the group
|
|
392
392
|
delay of a symmetric filter before symbol sampling. With ``True``
|
|
393
393
|
the output is the causal convolution ``y[n] = sum_k x[n-k]·taps[k]``
|
|
394
394
|
(``numpy.convolve(x, taps, mode='full')[:N]``), for a raw causal
|
|
@@ -412,7 +412,7 @@ def ols_fir_filter(
|
|
|
412
412
|
A symmetric guard of ``nfft // 4`` samples is discarded from each
|
|
413
413
|
block edge, so ``nfft // 4 >= len(taps)`` must hold.
|
|
414
414
|
|
|
415
|
-
Without ``causal`` the input is padded at the end by ``L // 2`` zeros
|
|
415
|
+
Without ``causal`` the input is padded at the end by ``(L - 1) // 2`` zeros
|
|
416
416
|
and as many leading output samples are dropped, which costs at most one
|
|
417
417
|
extra block.
|
|
418
418
|
"""
|
|
@@ -425,7 +425,7 @@ def ols_fir_filter(
|
|
|
425
425
|
out_dtype = x.dtype # capture before any reshape
|
|
426
426
|
|
|
427
427
|
L = len(taps)
|
|
428
|
-
half = L // 2
|
|
428
|
+
half = (L - 1) // 2 # SciPy's mode='same' centre, as fir_filter
|
|
429
429
|
|
|
430
430
|
x, was_1d = as_2d(x, name="samples")
|
|
431
431
|
|
|
@@ -473,7 +473,9 @@ def fir_filter(samples: S, taps: ArrayType, *, periodic: bool = False) -> S:
|
|
|
473
473
|
"""Apply an FIR filter along the time (last) axis.
|
|
474
474
|
|
|
475
475
|
FFT convolution, ``mode="same"``: the output is centered on tap
|
|
476
|
-
``len(taps) // 2``,
|
|
476
|
+
``(len(taps) - 1) // 2``, ``y[n] = sum_k x[n + (L-1)//2 - k]·taps[k]``,
|
|
477
|
+
so a symmetric filter of odd length adds no delay (one of even length,
|
|
478
|
+
whose centre falls between two taps, half a sample). With
|
|
477
479
|
``periodic=True`` the convolution is circular; away from the record ends
|
|
478
480
|
both give the same samples.
|
|
479
481
|
|
|
@@ -476,9 +476,6 @@ def _mth_power(
|
|
|
476
476
|
# In the samples' precision: complex128 changed no estimate by more than
|
|
477
477
|
# 0.005 Hz (16-QAM, 2^16 symbols, 15 dB; the error itself is 726 Hz rms).
|
|
478
478
|
x_m = x**M
|
|
479
|
-
# Non-constant-envelope symbols leave an amplitude residual at DC.
|
|
480
|
-
if constellation is not None and np.ptp(np.abs(constellation.points)) > 1e-9:
|
|
481
|
-
x_m = x_m - xp.mean(x_m, axis=-1, keepdims=True)
|
|
482
479
|
|
|
483
480
|
X_m = _fft.fft(x_m, n=nfft, axis=-1) # (R, nfft)
|
|
484
481
|
freqs_np = np.fft.fftfreq(nfft, d=1.0 / fs) # reported on the host
|
|
@@ -494,7 +491,6 @@ def _mth_power(
|
|
|
494
491
|
freqs = xp.fft.fftfreq(nfft, d=1.0 / fs)
|
|
495
492
|
mask = (freqs >= tone_lo) & (freqs <= tone_hi)
|
|
496
493
|
mag = xp.where(mask[None, :], mag, xp.zeros_like(mag))
|
|
497
|
-
mag[:, 0] = 0.0 # Δf = 0 is degenerate; also any residual DC
|
|
498
494
|
|
|
499
495
|
if shared_rows:
|
|
500
496
|
k_peak = xp.broadcast_to(xp.argmax(xp.sum(mag, axis=0)), (R,))
|
|
@@ -92,8 +92,8 @@ def plot_frequency_drift(
|
|
|
92
92
|
axi.axhline(amp_ref, color="white", ls="--", label="±amplitude")
|
|
93
93
|
axi.axhline(-amp_ref, color="white", ls="--")
|
|
94
94
|
|
|
95
|
-
_set_eng_formatter(axi, "x"
|
|
96
|
-
_set_eng_formatter(axi, "y"
|
|
95
|
+
_set_eng_formatter(axi, "x")
|
|
96
|
+
_set_eng_formatter(axi, "y")
|
|
97
97
|
axi.set_xlabel("Time [s]")
|
|
98
98
|
axi.set_ylabel(r"$\Delta f$ [Hz]")
|
|
99
99
|
axi.set_title(title)
|
|
@@ -232,7 +232,7 @@ def plot_frequency_noise_psd(
|
|
|
232
232
|
model = getattr(estimate, "S_f_model", None)
|
|
233
233
|
if model is not None:
|
|
234
234
|
m_c = _as_channels(model)
|
|
235
|
-
axi.loglog(fp, m_c[0, pos], color="#ef476f",
|
|
235
|
+
axi.loglog(fp, m_c[0, pos], color="#ef476f", label="Fitted model")
|
|
236
236
|
|
|
237
237
|
used_label = "Fit bins" if model is not None else "Plateau bins (median)"
|
|
238
238
|
if used is not None:
|
|
@@ -246,7 +246,6 @@ def plot_frequency_noise_psd(
|
|
|
246
246
|
S_c[0, pos][sel],
|
|
247
247
|
".",
|
|
248
248
|
color="#06d6a0",
|
|
249
|
-
ms=3.0,
|
|
250
249
|
ls="none",
|
|
251
250
|
label=used_label,
|
|
252
251
|
)
|
|
@@ -282,7 +281,7 @@ def plot_frequency_noise_psd(
|
|
|
282
281
|
for edge in (lo, float(band[1])):
|
|
283
282
|
axi.axvline(edge, color="#06d6a0")
|
|
284
283
|
|
|
285
|
-
_set_eng_formatter(axi, "x"
|
|
284
|
+
_set_eng_formatter(axi, "x")
|
|
286
285
|
axi.set_xlabel("Frequency [Hz]")
|
|
287
286
|
axi.set_ylabel("$S_f$ [Hz²/Hz]")
|
|
288
287
|
axi.set_title(title)
|
|
@@ -338,7 +337,6 @@ def plot_allan_deviation(
|
|
|
338
337
|
tau,
|
|
339
338
|
adv[i],
|
|
340
339
|
"o-",
|
|
341
|
-
ms=3,
|
|
342
340
|
color=f"C{i}",
|
|
343
341
|
label=f"Pol {i}" if C > 1 else r"$\sigma_y(\tau)$",
|
|
344
342
|
)
|
|
@@ -356,8 +354,8 @@ def plot_allan_deviation(
|
|
|
356
354
|
label=r"$\tau^{-1/2}$ (white-FM)",
|
|
357
355
|
)
|
|
358
356
|
|
|
359
|
-
_set_eng_formatter(axi, "x"
|
|
360
|
-
_set_eng_formatter(axi, "y"
|
|
357
|
+
_set_eng_formatter(axi, "x")
|
|
358
|
+
_set_eng_formatter(axi, "y")
|
|
361
359
|
axi.set_xlabel(r"Averaging Time $\tau$ [s]")
|
|
362
360
|
axi.set_ylabel(r"Allan Deviation $\sigma_y(\tau)$ [Hz]")
|
|
363
361
|
axi.set_title(title)
|
|
@@ -414,7 +412,6 @@ def plot_increment_variance(
|
|
|
414
412
|
lag,
|
|
415
413
|
v[i],
|
|
416
414
|
"o",
|
|
417
|
-
ms=4,
|
|
418
415
|
color=f"C{i}",
|
|
419
416
|
label=f"Measured pol {i}" if C > 1 else "Measured",
|
|
420
417
|
)
|
|
@@ -437,7 +434,7 @@ def plot_increment_variance(
|
|
|
437
434
|
label=r"Intercept (noise $2\sigma_\phi^2$)",
|
|
438
435
|
)
|
|
439
436
|
|
|
440
|
-
_set_eng_formatter(axi, "x"
|
|
437
|
+
_set_eng_formatter(axi, "x")
|
|
441
438
|
axi.set_xlabel("Increment Lag [s]")
|
|
442
439
|
axi.set_ylabel("Variance [rad²]")
|
|
443
440
|
axi.set_title(title)
|
|
@@ -517,7 +514,7 @@ def plot_dsh_beat_psd(
|
|
|
517
514
|
label=f"$W_{{-{level_db:g}\\,dB}}$",
|
|
518
515
|
)
|
|
519
516
|
|
|
520
|
-
_set_eng_formatter(axi, "x"
|
|
517
|
+
_set_eng_formatter(axi, "x")
|
|
521
518
|
axi.set_xlabel(
|
|
522
519
|
"Frequency Offset from Beat Peak [Hz]"
|
|
523
520
|
if f_peak is not None
|
|
@@ -158,7 +158,7 @@ def plot_llr_distribution(
|
|
|
158
158
|
with np.errstate(divide="ignore"):
|
|
159
159
|
ratio = np.log(h0[j][keep] / h1[j][keep]) + np.log(n1[j] / n0[j])
|
|
160
160
|
label = "measured" if m == 1 else f"bit {j}"
|
|
161
|
-
ax_cons.plot(centers[keep], ratio, "o-",
|
|
161
|
+
ax_cons.plot(centers[keep], ratio, "o-", label=label)
|
|
162
162
|
if keep.any():
|
|
163
163
|
span = [min(span[0], centers[keep][0]), max(span[1], centers[keep][-1])]
|
|
164
164
|
if span[0] > span[1]:
|
|
@@ -257,7 +257,7 @@ def plot_parity_check(
|
|
|
257
257
|
ax_h.spy(h, markersize=size, aspect="auto", rasterized=True)
|
|
258
258
|
if punctured:
|
|
259
259
|
ax_h.axvspan(n - punctured - 0.5, n - 0.5, color="gray", alpha=0.3)
|
|
260
|
-
ax_h.text(n - punctured, -0.5, "punctured", va="bottom"
|
|
260
|
+
ax_h.text(n - punctured, -0.5, "punctured", va="bottom")
|
|
261
261
|
ax_h.tick_params(labeltop=False, labelbottom=True)
|
|
262
262
|
ax_h.grid(False)
|
|
263
263
|
ax_h.set_xlabel("Variable node (column)")
|
|
@@ -342,7 +342,6 @@ def plot_exit_chart(
|
|
|
342
342
|
chart.trajectory[:, 0],
|
|
343
343
|
chart.trajectory[:, 1],
|
|
344
344
|
color="white",
|
|
345
|
-
linewidth=0.8,
|
|
346
345
|
alpha=0.8,
|
|
347
346
|
label="trajectory",
|
|
348
347
|
)
|
|
@@ -358,5 +357,5 @@ def plot_exit_chart(
|
|
|
358
357
|
state = "open" if chart.converges else "closed"
|
|
359
358
|
title = f"EXIT chart: tunnel {state}, {chart.iterations} iterations"
|
|
360
359
|
axi.set_title(title)
|
|
361
|
-
axi.legend(loc="lower right"
|
|
360
|
+
axi.legend(loc="lower right")
|
|
362
361
|
return _finish((fig, axi), show)
|
|
@@ -130,7 +130,6 @@ def plot_equalizer_result(
|
|
|
130
130
|
tap_idx,
|
|
131
131
|
np.abs(weights[i, j]),
|
|
132
132
|
marker="o",
|
|
133
|
-
markersize=3,
|
|
134
133
|
label=label,
|
|
135
134
|
)
|
|
136
135
|
ax_taps.legend(ncol=2)
|
|
@@ -138,8 +137,7 @@ def plot_equalizer_result(
|
|
|
138
137
|
# SISO: (num_taps,) - stem plot
|
|
139
138
|
num_taps = weights.shape[0]
|
|
140
139
|
tap_idx = np.arange(num_taps) - (num_taps // 2)
|
|
141
|
-
|
|
142
|
-
markerline.set_markersize(4)
|
|
140
|
+
ax_taps.stem(tap_idx, np.abs(weights))
|
|
143
141
|
|
|
144
142
|
ax_taps.set_xlabel("Tap Index")
|
|
145
143
|
ax_taps.set_ylabel("|w|")
|
|
@@ -241,7 +239,7 @@ def plot_zf_equalizer_response(
|
|
|
241
239
|
axi.plot(f_sorted, 20 * np.log10(np.abs(data) + 1e-12), color=col)
|
|
242
240
|
prefix = f"{row_label} " if row_label else ""
|
|
243
241
|
axi.set_title(f"{prefix}{lbl}")
|
|
244
|
-
_set_eng_formatter(axi, "x"
|
|
242
|
+
_set_eng_formatter(axi, "x")
|
|
245
243
|
axi.set_xlabel("Frequency [Hz]")
|
|
246
244
|
axi.set_ylabel("Magnitude [dB]")
|
|
247
245
|
|
|
@@ -108,7 +108,7 @@ def _plot_eye_traces(
|
|
|
108
108
|
segments = np.empty((traces.shape[0], trace_len, 2))
|
|
109
109
|
segments[:, :, 0] = t
|
|
110
110
|
segments[:, :, 1] = traces
|
|
111
|
-
line_kwargs: dict[str, Any] = {"alpha": 0.2, "
|
|
111
|
+
line_kwargs: dict[str, Any] = {"alpha": 0.2, "color": "C0"}
|
|
112
112
|
line_kwargs.update(kwargs)
|
|
113
113
|
ax.add_collection(LineCollection(list(segments), **line_kwargs))
|
|
114
114
|
y_lo, y_hi = float(traces.min()), float(traces.max())
|