commkit 3.6.0__tar.gz → 3.7.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.6.0 → commkit-3.7.0}/.github/workflows/ci.yml +3 -1
- {commkit-3.6.0 → commkit-3.7.0}/AGENTS.md +7 -4
- {commkit-3.6.0 → commkit-3.7.0}/CHANGELOG.md +45 -1
- {commkit-3.6.0 → commkit-3.7.0}/PKG-INFO +2 -2
- {commkit-3.6.0 → commkit-3.7.0}/README.md +1 -1
- {commkit-3.6.0 → commkit-3.7.0}/benchmarks/bench_block_blind.py +9 -9
- {commkit-3.6.0 → commkit-3.7.0}/benchmarks/bench_block_lms.py +4 -3
- commkit-3.7.0/benchmarks/bench_gardner.py +36 -0
- {commkit-3.6.0 → commkit-3.7.0}/benchmarks/workloads.py +29 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/__init__.py +1 -1
- {commkit-3.6.0 → commkit-3.7.0}/commkit/_constants.py +1 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/_dispersion.py +2 -3
- {commkit-3.6.0 → commkit-3.7.0}/commkit/_fiber.py +1 -1
- {commkit-3.6.0 → commkit-3.7.0}/commkit/analysis/_common.py +1 -1
- {commkit-3.6.0 → commkit-3.7.0}/commkit/coding/exit.py +0 -6
- {commkit-3.6.0 → commkit-3.7.0}/commkit/coding/linear.py +1 -2
- {commkit-3.6.0 → commkit-3.7.0}/commkit/coding/polar.py +2 -2
- {commkit-3.6.0 → commkit-3.7.0}/commkit/coding/shaping.py +4 -7
- {commkit-3.6.0 → commkit-3.7.0}/commkit/equalization/_block/_blind.py +6 -6
- {commkit-3.6.0 → commkit-3.7.0}/commkit/equalization/_block/_dd.py +25 -15
- {commkit-3.6.0 → commkit-3.7.0}/commkit/equalization/_block/_engine.py +0 -1
- {commkit-3.6.0 → commkit-3.7.0}/commkit/equalization/_common.py +19 -17
- {commkit-3.6.0 → commkit-3.7.0}/commkit/equalization/_sequential/_dd.py +40 -8
- {commkit-3.6.0 → commkit-3.7.0}/commkit/equalization/_sequential/_setup.py +0 -1
- {commkit-3.6.0 → commkit-3.7.0}/commkit/equalization/linear.py +3 -3
- {commkit-3.6.0 → commkit-3.7.0}/commkit/equalization/result.py +82 -10
- {commkit-3.6.0 → commkit-3.7.0}/commkit/filtering/fiber.py +1 -10
- {commkit-3.6.0 → commkit-3.7.0}/commkit/frequency.py +1 -1
- {commkit-3.6.0 → commkit-3.7.0}/commkit/impairments/__init__.py +25 -6
- {commkit-3.6.0 → commkit-3.7.0}/commkit/impairments/frontend.py +307 -5
- commkit-3.7.0/commkit/impairments/noise.py +685 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/impairments/source.py +69 -4
- {commkit-3.6.0 → commkit-3.7.0}/commkit/mapping/constellation.py +6 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/metrics/__init__.py +10 -2
- commkit-3.7.0/commkit/metrics/awgn.py +515 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/multirate.py +126 -3
- {commkit-3.6.0 → commkit-3.7.0}/commkit/plotting/analysis.py +25 -2
- {commkit-3.6.0 → commkit-3.7.0}/commkit/plotting/spectral.py +3 -1
- {commkit-3.6.0 → commkit-3.7.0}/commkit/spectral.py +4 -1
- commkit-3.7.0/commkit/timing/__init__.py +47 -0
- commkit-3.7.0/commkit/timing/_numba_loops.py +185 -0
- commkit-3.7.0/commkit/timing/clock_recovery.py +481 -0
- commkit-3.6.0/commkit/timing.py → commkit-3.7.0/commkit/timing/synchronization.py +10 -10
- {commkit-3.6.0 → commkit-3.7.0}/examples/00-getting-started.ipynb +2 -2
- {commkit-3.6.0 → commkit-3.7.0}/examples/01-constellations-and-bit-mapping.ipynb +1 -1
- {commkit-3.6.0 → commkit-3.7.0}/examples/02-spectral-analysis.ipynb +2 -2
- {commkit-3.6.0 → commkit-3.7.0}/examples/06-awgn-and-performance-metrics.ipynb +45 -28
- {commkit-3.6.0 → commkit-3.7.0}/examples/07-channel-coding.ipynb +2 -2
- {commkit-3.6.0 → commkit-3.7.0}/examples/09-laser-phase-noise.ipynb +2 -2
- {commkit-3.6.0 → commkit-3.7.0}/examples/10-front-end-impairments.ipynb +163 -11
- {commkit-3.6.0 → commkit-3.7.0}/examples/11-frame-synchronization-and-timing.ipynb +2 -2
- commkit-3.7.0/examples/12-sampling-clock-recovery.ipynb +316 -0
- commkit-3.6.0/examples/12-frequency-offset-estimation.ipynb → commkit-3.7.0/examples/13-frequency-offset-estimation.ipynb +3 -3
- commkit-3.6.0/examples/13-blind-carrier-phase-recovery.ipynb → commkit-3.7.0/examples/14-blind-carrier-phase-recovery.ipynb +3 -3
- commkit-3.6.0/examples/14-pilot-aided-carrier-recovery.ipynb → commkit-3.7.0/examples/15-pilot-aided-carrier-recovery.ipynb +5 -5
- commkit-3.6.0/examples/15-linear-channel-equalization.ipynb → commkit-3.7.0/examples/16-linear-channel-equalization.ipynb +3 -3
- commkit-3.6.0/examples/16-adaptive-equalization.ipynb → commkit-3.7.0/examples/17-adaptive-equalization.ipynb +8 -8
- commkit-3.6.0/examples/17-blind-equalization.ipynb → commkit-3.7.0/examples/18-blind-equalization.ipynb +7 -7
- commkit-3.6.0/examples/18-polarization-multiplexing.ipynb → commkit-3.7.0/examples/19-polarization-multiplexing.ipynb +5 -5
- commkit-3.6.0/examples/19-chromatic-dispersion.ipynb → commkit-3.7.0/examples/20-chromatic-dispersion.ipynb +4 -4
- commkit-3.6.0/examples/20-nonlinear-fiber-and-backpropagation.ipynb → commkit-3.7.0/examples/21-nonlinear-fiber-and-backpropagation.ipynb +4 -4
- commkit-3.7.0/examples/22-direct-and-self-coherent-detection.ipynb +278 -0
- commkit-3.6.0/examples/21-fec-in-optical-standards.ipynb → commkit-3.7.0/examples/23-fec-in-optical-standards.ipynb +2 -2
- commkit-3.6.0/examples/22-coherent-transceiver-end-to-end.ipynb → commkit-3.7.0/examples/24-coherent-transceiver-end-to-end.ipynb +5 -6
- commkit-3.6.0/examples/23-carrier-phase-characterization.ipynb → commkit-3.7.0/examples/25-carrier-phase-characterization.ipynb +3 -3
- commkit-3.6.0/examples/24-laser-linewidth-self-heterodyne.ipynb → commkit-3.7.0/examples/26-laser-linewidth-self-heterodyne.ipynb +10 -10
- commkit-3.6.0/examples/25-laser-linewidth-short-delay-and-flicker.ipynb → commkit-3.7.0/examples/27-laser-linewidth-short-delay-and-flicker.ipynb +5 -5
- commkit-3.6.0/examples/26-laser-linewidth-self-homodyne-iq.ipynb → commkit-3.7.0/examples/28-laser-linewidth-self-homodyne-iq.ipynb +5 -5
- commkit-3.6.0/examples/27-measurement-template-self-heterodyne.ipynb → commkit-3.7.0/examples/29-measurement-template-self-heterodyne.ipynb +2 -2
- commkit-3.6.0/examples/28-measurement-template-self-homodyne-iq.ipynb → commkit-3.7.0/examples/30-measurement-template-self-homodyne-iq.ipynb +2 -2
- {commkit-3.6.0 → commkit-3.7.0}/examples/README.md +19 -17
- {commkit-3.6.0 → commkit-3.7.0}/pyproject.toml +2 -2
- {commkit-3.6.0 → commkit-3.7.0}/tests/coding/test_exit.py +1 -1
- {commkit-3.6.0 → commkit-3.7.0}/tests/coding/test_shaping.py +1 -2
- {commkit-3.6.0 → commkit-3.7.0}/tests/common/reference_impl.py +97 -3
- {commkit-3.6.0 → commkit-3.7.0}/tests/common/signals.py +9 -0
- commkit-3.7.0/tests/common/theory.py +246 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/equalization/test_blind.py +10 -7
- {commkit-3.6.0 → commkit-3.7.0}/tests/equalization/test_block.py +8 -5
- commkit-3.7.0/tests/equalization/test_cold_start.py +104 -0
- commkit-3.7.0/tests/equalization/test_result.py +67 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/equalization/test_sequential.py +9 -9
- {commkit-3.6.0 → commkit-3.7.0}/tests/impairments/test_fiber_channel_nonlinear.py +94 -2
- commkit-3.7.0/tests/impairments/test_frontend.py +593 -0
- commkit-3.7.0/tests/impairments/test_noise.py +702 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/impairments/test_source.py +45 -1
- commkit-3.7.0/tests/metrics/test_awgn.py +188 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/plotting/test_plot_analysis.py +11 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/plugins/transfer_trace.py +4 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/test_api_contracts.py +71 -7
- {commkit-3.6.0 → commkit-3.7.0}/tests/test_filtering.py +4 -4
- {commkit-3.6.0 → commkit-3.7.0}/tests/test_multirate.py +12 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/test_receiver_chain.py +82 -5
- commkit-3.7.0/tests/timing/test_clock_recovery.py +225 -0
- commkit-3.6.0/tests/test_timing.py → commkit-3.7.0/tests/timing/test_synchronization.py +3 -2
- {commkit-3.6.0 → commkit-3.7.0}/uv.lock +1 -1
- commkit-3.6.0/commkit/impairments/noise.py +0 -320
- commkit-3.6.0/tests/common/theory.py +0 -101
- commkit-3.6.0/tests/impairments/test_frontend.py +0 -271
- commkit-3.6.0/tests/impairments/test_noise.py +0 -338
- {commkit-3.6.0 → commkit-3.7.0}/.gitattributes +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/.github/icon.svg +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/.github/logo.svg +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/.github/workflows/publish.yml +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/.gitignore +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/.python-version +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/LICENSE +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0002_pre_v2.json +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0003_v2_0.json +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0004_fiber_dbp.json +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0005_gpu_decoder_path.json +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0006_perf_cleanup.json +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0007_v3_0.json +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0008_v3_1.json +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/benchmarks/bench_bps.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/benchmarks/bench_coding.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/benchmarks/bench_equalizers.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/benchmarks/bench_fiber.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/benchmarks/bench_filters.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/benchmarks/bench_import.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/benchmarks/bench_llr.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/benchmarks/bench_mapping.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/benchmarks/bench_signal_pipeline.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/benchmarks/bench_sync_misc.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/benchmarks/benchutils.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/benchmarks/conftest.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/benchmarks/record_baseline.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/_array.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/_circular.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/_cuda/__init__.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/_cuda/compiler.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/_cuda/graphs.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/_cuda/src/algebraic.cu +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/_cuda/src/bps_anchor.cu +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/_cuda/src/bps_min_d2.cu +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/_cuda/src/cs_block.cu +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/_cuda/src/iir.cu +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/_cuda/src/ldpc.cu +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/_cuda/src/llr.cu +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/_cuda/src/selftest.cu +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/_fft.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/_overlap_save.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/_random.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/_sequences.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/analysis/__init__.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/analysis/allan.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/analysis/drift.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/analysis/fm_noise.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/analysis/interferometry.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/analysis/linewidth/__init__.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/analysis/linewidth/dsh.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/analysis/linewidth/phase.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/analysis/linewidth/result.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/analysis/trajectory.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/backend.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/coding/__init__.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/coding/_galois.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/coding/_gf2.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/coding/_ldpc_tables.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/coding/_numba_loops.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/coding/algebraic.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/coding/base.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/coding/convolutional.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/coding/crc.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/coding/interleaving.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/coding/ldpc.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/coding/performance.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/coding/product.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/coding/ratematch.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/coding/turbo.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/core/__init__.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/core/_signal_adapter.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/core/frame.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/core/generation.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/core/preamble.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/core/signal.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/equalization/__init__.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/equalization/_block/__init__.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/equalization/_numba_loops.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/equalization/_sequential/__init__.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/equalization/_sequential/_blind.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/equalization/pilots.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/equalization/polarization.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/filtering/__init__.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/filtering/linear.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/filtering/pulses.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/filtering/response.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/impairments/fiber_channel.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/io.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/logger.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/mapping/__init__.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/mapping/_numba_llr.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/mapping/bits.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/mapping/gray.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/mapping/llr.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/mapping/shaping.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/math.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/metrics/_common.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/metrics/error_rates.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/metrics/information.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/metrics/quality.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/plotting/__init__.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/plotting/coding.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/plotting/constellation.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/plotting/equalizer.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/plotting/eye.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/plotting/filter_response.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/plotting/frequency.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/plotting/recovery.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/plotting/theme.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/plotting/timing.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/plotting/waveform.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/py.typed +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/recovery/__init__.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/recovery/_common.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/recovery/bps.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/recovery/carrier_phase.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/recovery/corrections.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/recovery/pilots.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/recovery/pll.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/recovery/tikhonov.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/recovery/viterbi_viterbi.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/commkit/smoothing.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/examples/03-digital-filters.ipynb +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/examples/04-pulse-shaping-and-matched-filtering.ipynb +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/examples/05-multirate-and-resampling.ipynb +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/examples/08-probabilistic-shaping-and-coded-modulation.ipynb +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/scripts/sanitize.sh +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/analysis/test_allan.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/analysis/test_drift.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/analysis/test_fm_noise.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/analysis/test_interferometry.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/analysis/test_linewidth.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/analysis/test_trajectory.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/coding/test_algebraic.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/coding/test_coding_base.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/coding/test_convolutional.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/coding/test_crc.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/coding/test_gf2.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/coding/test_interleaving.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/coding/test_ldpc.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/coding/test_ldpc_exit.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/coding/test_ldpc_kernel.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/coding/test_ldpc_tables.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/coding/test_linear_codes.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/coding/test_performance.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/coding/test_polar.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/coding/test_product.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/coding/test_ratematch.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/coding/test_turbo.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/common/__init__.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/common/coding.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/common/conversions.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/common/kernel_utils.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/common/memory.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/common/metrics.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/common/periodic.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/common/scoring.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/conftest.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/core/test_frame.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/core/test_generation.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/core/test_preamble.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/core/test_psqam.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/core/test_signal.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/core/test_signal_adapter.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/core/test_signal_mimo.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/equalization/test_bps_kernel.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/equalization/test_cpr.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/equalization/test_cs_kernel.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/equalization/test_equalizer_oracles.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/equalization/test_linear.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/equalization/test_mimo.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/equalization/test_polarization.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/equalization/test_state.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/equalization/test_winit.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/impairments/test_fiber_channel_linear.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/mapping/test_bits.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/mapping/test_constellation.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/mapping/test_gray.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/mapping/test_llr.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/metrics/test_common.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/metrics/test_error_rates.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/metrics/test_information.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/metrics/test_quality.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/plotting/test_plot_coding.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/plotting/test_plot_constellation.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/plotting/test_plot_equalizer.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/plotting/test_plot_eye.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/plotting/test_plot_filter_response.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/plotting/test_plot_frequency.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/plotting/test_plot_recovery.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/plotting/test_plot_spectral.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/plotting/test_plot_theme.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/plotting/test_plot_timing.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/plotting/test_plot_waveform.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/plugins/__init__.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/plugins/precision_trace.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/recovery/test_bps.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/recovery/test_corrections.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/recovery/test_joint.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/recovery/test_pilots.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/recovery/test_pll.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/recovery/test_recovery_oracles.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/recovery/test_tikhonov.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/recovery/test_viterbi_viterbi.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/test_array.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/test_backend.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/test_cuda_infra.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/test_dispersion.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/test_examples.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/test_fiber.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/test_filter_response.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/test_frequency.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/test_import_hygiene.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/test_io.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/test_logger.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/test_math.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/test_pulse_shaping.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/test_signal_pipeline.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/test_smoothing.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/test_spectral.py +0 -0
- {commkit-3.6.0 → commkit-3.7.0}/tests/test_working_memory.py +0 -0
|
@@ -120,13 +120,15 @@ jobs:
|
|
|
120
120
|
tests/coding/test_convolutional.py tests/coding/test_turbo.py::TestEncoding
|
|
121
121
|
tests/coding/test_turbo.py::TestDecoding::test_noiseless
|
|
122
122
|
tests/coding/test_linear_codes.py::TestMaximumLikelihood
|
|
123
|
+
tests/timing/test_clock_recovery.py::TestEstimate::test_matches_oracle
|
|
124
|
+
tests/timing/test_clock_recovery.py::TestValidation
|
|
123
125
|
-k "not waterfall and not threshold"
|
|
124
126
|
--device=cpu --cov=commkit --cov-report=
|
|
125
127
|
|
|
126
128
|
- name: Kernel coverage report
|
|
127
129
|
run: >-
|
|
128
130
|
uv run coverage report --show-missing --fail-under=85
|
|
129
|
-
--include="commkit/equalization/_numba_loops.py,commkit/equalization/_sequential/*,commkit/frequency.py,commkit/recovery/*,commkit/coding/ldpc.py,commkit/coding/shaping.py,commkit/coding/polar.py,commkit/coding/convolutional.py,commkit/coding/turbo.py"
|
|
131
|
+
--include="commkit/equalization/_numba_loops.py,commkit/equalization/_sequential/*,commkit/frequency.py,commkit/recovery/*,commkit/coding/ldpc.py,commkit/coding/shaping.py,commkit/coding/polar.py,commkit/coding/convolutional.py,commkit/coding/turbo.py,commkit/timing/_numba_loops.py"
|
|
130
132
|
|
|
131
133
|
# GPU legs (--device=gpu / --device=all) require CuPy + CUDA and are run
|
|
132
134
|
# manually or on a self-hosted runner; they are intentionally not part of
|
|
@@ -54,6 +54,8 @@ uv run pytest
|
|
|
54
54
|
uv run ruff format .
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
A commit that changes only text that cannot change behaviour (docstrings, comments, Markdown) runs `ruff check` and `ruff format` alone. The full gate applies again as soon as code, tests or notebook cells change, or an edited docstring holds an example that runs.
|
|
58
|
+
|
|
57
59
|
**Benchmarks** are explicit-only; `uv run pytest` never collects them. A commit that changes a public signature smoke-runs them once, untimed, so their call sites cannot rot:
|
|
58
60
|
|
|
59
61
|
```bash
|
|
@@ -101,6 +103,7 @@ Dependencies point downward only:
|
|
|
101
103
|
- Construction does no hidden work: no mapping, no normalization, no device move, no shape guessing. Factories (`generate`, frames) do that work.
|
|
102
104
|
- `sig.to("gpu")` and `sig.replace(...)` return new Signals sharing the arrays; `clone()` copies.
|
|
103
105
|
- **Alignment invariant:** reference symbol *k* corresponds to symbol period *k* of the samples. A function that drops or shifts symbol periods slices the reference to match. Metrics raise on mismatch; they never guess.
|
|
106
|
+
- **Absolute power.** Samples are dimensionless, and a Signal holds no power. Impairments never rescale the signal component: noise and distortion are added in the samples' units. A function whose model depends on absolute power takes `<where>_power_dbm` (`launch_power_dbm`, `received_power_dbm`, `lo_power_dbm`), the mean power its input stands for, summed over channels, and returns samples in the input's units (a square-law detector: the input's power units). A PSD is in the samples' units per Hz, not dBm/Hz.
|
|
104
107
|
|
|
105
108
|
### Function signatures
|
|
106
109
|
|
|
@@ -153,7 +156,7 @@ Dependencies point downward only:
|
|
|
153
156
|
- `method=` strings name only a numerical flavor of one algorithm (`method="maxlog" | "exact"`); algorithms are objects.
|
|
154
157
|
- Output scaling has three names: `normalize` (bool) measures the power and rescales to the library's convention; `correct_power` (bool) applies the exact, known gain of a rate change without measuring; `<thing>_normalization` (a string) chooses how coefficients are scaled (`taps_normalization="unit_energy" | "unity_gain"`).
|
|
155
158
|
- A search window is `search_range`, a `(start, stop)` pair in the axis's unit, or `search_band`, a half-width in Hz around a nominal frequency.
|
|
156
|
-
- Values are in SI units without a suffix (`linewidth` in Hz, `drift_rate` in rad/s, `taus` in s). A result holding one quantity in two units suffixes the SI one (`lags` in samples, `lag_s`). A conventional non-SI unit goes into the name: `_db`, `_dbm`, `_deg`, `_nm`, `_km`, `_ps_nm_km`. Normalized quantities say so (`normalized_bandwidth`). No second name for one quantity in another unit (no `center_frequency_ghz`); `center_wavelength_nm` is a different, derived quantity.
|
|
159
|
+
- Values are in SI units without a suffix (`linewidth` in Hz, `drift_rate` in rad/s, `taus` in s). A result holding one quantity in two units suffixes the SI one (`lags` in samples, `lag_s`). A conventional non-SI unit goes into the name: `_db`, `_dbm`, `_deg`, `_nm`, `_km`, `_ps_nm_km`, `_ppm`, `_db_hz`. Normalized quantities say so (`normalized_bandwidth`). No second name for one quantity in another unit (no `center_frequency_ghz`); `center_wavelength_nm` is a different, derived quantity.
|
|
157
160
|
|
|
158
161
|
### Docstrings
|
|
159
162
|
|
|
@@ -184,7 +187,7 @@ Docstrings describe the code as it is; a change in behavior updates them in the
|
|
|
184
187
|
- **Transforms keep the input's precision** throughout (filters, impairments, the fiber solver and DBP, resampling, delays, carrier-phase correction): complex64 in and out, complex128 in and out.
|
|
185
188
|
- **Fixed-precision blocks** compute in single precision whatever the input: the equalizers in complex64, `compute_llr` / `Constellation.llr` in float32. Double input emits `commkit.PrecisionWarning` (`_array.warn_if_double`, attributed to the caller's line). The test suite turns it into an error (`pytest_configure` in `tests/conftest.py`; not pyproject's `filterwarnings`, which imports commkit before coverage starts), so a library path feeding double data to these blocks fails CI. Training and reference symbols are not checked.
|
|
186
189
|
- **Deliberate float64 / complex128**, documented where it happens, only on phases, small arrays or accumulators, never on sample-rate data that is then cast back:
|
|
187
|
-
- phases before `xp.unwrap()` (float32 rounding causes spurious quadrant slips), phase trajectories and ramps (wrapped in float64, rotated in the data's precision), frequency grids and estimates;
|
|
190
|
+
- phases before `xp.unwrap()` (float32 rounding causes spurious quadrant slips), phase trajectories and ramps (wrapped in float64, rotated in the data's precision), the strobe positions of clock recovery (symbol rate; the sampling instants built from them are an integer index and a fraction in the data's precision), frequency grids and estimates;
|
|
188
191
|
- recursion state: PLL, Tikhonov/Kalman block transitions, the IIR filter's sections and state (poles near z = 1);
|
|
189
192
|
- RLS keeps `P`, the gain `k` and the regressor buffers in complex128 (in single precision `P` loses positive definiteness and the filter diverges);
|
|
190
193
|
- LMS/CMA/RDE accumulate dot products and gradient updates in complex128 around complex64 inputs and weights; the block equalizers stay complex64, as their tap sums run inside the FFTs;
|
|
@@ -202,7 +205,7 @@ Docstrings describe the code as it is; a change in behavior updates them in the
|
|
|
202
205
|
| --- | --- | --- |
|
|
203
206
|
| Vectorized: filters, resampling, spectra, impairments, metrics, frequency-domain equalizers | NumPy/CuPy via `x, xp, sp = dispatch(samples)` | follows the input |
|
|
204
207
|
| Per-symbol searches over a small table: LLRs (all points, or per axis for separable QAM; max-log without a prior in closed form), hard decisions without a square lattice | CPU: Numba `parallel=True` over blocks of symbols (LLRs), chunked NumPy (decisions). GPU: one thread per symbol (`llr`, `llr_separable`, `llr_separable_maxlog`, `bps_min_d2`'s nearest-point mode); decisions on double input or above 1024 points use the chunked search on both devices; LLRs above 16 bits per symbol raise on the GPU | follows the input |
|
|
205
|
-
| Sequential recursions: LMS/RLS/CMA/RDE, PLL, Tikhonov, cycle slips, the Mengali-Morelli bootstrap, per-codeword decoders on trellises and trees (CCDM, polar SC/SCL, Viterbi, BCJR, turbo, recursive systematic encoding) | Numba `@njit(cache=True, fastmath=True, nogil=True)`, independent rows in `prange`. The equalizers' kernel with inline carrier recovery compiles strict (unwrap, running sums) and calls fast-math tap and distance loops as separate functions (an `inline="always"` helper takes its caller's flags) | CPU; GPU input does one round trip |
|
|
208
|
+
| Sequential recursions: LMS/RLS/CMA/RDE, PLL, Gardner clock recovery, Tikhonov, cycle slips, the Mengali-Morelli bootstrap, per-codeword decoders on trellises and trees (CCDM, polar SC/SCL, Viterbi, BCJR, turbo, recursive systematic encoding) | Numba `@njit(cache=True, fastmath=True, nogil=True)`, independent rows in `prange`. The equalizers' kernel with inline carrier recovery compiles strict (unwrap, running sums) and calls fast-math tap and distance loops as separate functions (an `inline="always"` helper takes its caller's flags) | CPU; GPU input does one round trip |
|
|
206
209
|
| Linear recursions: IIR filtering | CPU: Numba without `fastmath`, so double input matches SciPy bit for bit. GPU: `iir` kernel: each block's state from a zero start in parallel, states carried across blocks in two levels (groups, then across groups), blocks filtered again, in double precision | follows the input |
|
|
207
210
|
| Binary linear algebra of codes: encoding, syndromes, ML-decoding correlations on the GPU, Chase's test patterns | one float32 GEMM per chunk (`coding._gf2.matmul_mod2`), exact below an inner dimension of 2^24, parity in place (CPU) or by one elementwise kernel writing int8 (GPU); small tables cached per code and device (`backend._device_tables`) | follows the input |
|
|
208
211
|
| Algebraic decoding (BCH, Reed-Solomon): Berlekamp-Massey, Chien search to the locator's degree, Forney | CPU: Numba `parallel=True` over codewords. GPU: `algebraic_correct`, one thread per codeword in integer table arithmetic, so both agree exactly | follows the input |
|
|
@@ -212,7 +215,7 @@ Docstrings describe the code as it is; a change in behavior updates them in the
|
|
|
212
215
|
| Elementwise chains between multi-core FFTs (fiber solver's Kerr and PMD stages) | NumPy: a Numba `parallel=True` pass competes with `scipy.fft`'s threads; adopt one only on a paired end-to-end A/B | CPU |
|
|
213
216
|
| Other hot spots limited by intermediates, launches or round trips | CUDA C++ via CuPy `RawModule` (`commkit/_cuda/src`) | GPU |
|
|
214
217
|
|
|
215
|
-
**Numba kernels live at module level** in a private module (`equalization/_numba_loops.py`, `coding/_numba_loops.py`, `mapping/_numba_llr.py`) that a getter `_get_numba_<name>()`, defined in the module that calls the kernel, imports on first use, so `import commkit` does not load Numba and `cache=True` works across processes. A kernel defined inside a function that calls another kernel defined there recompiles in every process (Numba's cache key misses the captured dispatcher); a getter may build a closure that captures only constants (`_get_numba_sos`).
|
|
218
|
+
**Numba kernels live at module level** in a private module (`equalization/_numba_loops.py`, `coding/_numba_loops.py`, `mapping/_numba_llr.py`, `timing/_numba_loops.py`) that a getter `_get_numba_<name>()`, defined in the module that calls the kernel, imports on first use, so `import commkit` does not load Numba and `cache=True` works across processes. A kernel defined inside a function that calls another kernel defined there recompiles in every process (Numba's cache key misses the captured dispatcher); a getter may build a closure that captures only constants (`_get_numba_sos`).
|
|
216
219
|
|
|
217
220
|
**Host-sync hygiene:**
|
|
218
221
|
|
|
@@ -4,6 +4,48 @@ Notable changes to CommKit, newest first. Performance figures are from the refer
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [3.7.0] - 2026-11-10
|
|
8
|
+
|
|
9
|
+
The receiver's analog front end and the detector, at absolute power. The ADC clock, its jitter and quantizer, IQ skew and the photodiode join the impairments, with Gardner clock recovery as the inverse of the clock; coherent and square-law detection turn received and LO powers into the noise they give. Closed-form AWGN error rates and information rates sit next to the measured ones. Blind decision-directed equalizers no longer lock falsely on unipolar PAM and dense QAM, because their input scaling hands the slicer the right gain.
|
|
10
|
+
|
|
11
|
+
### Changes that may need code updates
|
|
12
|
+
|
|
13
|
+
- The adaptive equalizers (`lms`, `rls`, `cma`, `rde`, `block_lms`, `block_cma`, `block_rde`, `apply_taps(normalize=True)`) divide each input channel by its RMS instead of `rms * sqrt(sps)`. The old scale applied the transmit convention `E|x|^2 = 1/sps` at the slicer and handed it a gain of `1/sqrt(sps)` (0.71 at 2 sps), below the decision margins of unipolar PAM-4 (0.83) and 64-QAM (0.86), where blind decision-directed adaptation settled at a shrunk false solution (BER 0.25 and 0.20). After a matched filter the symbol instants now reach the slicer within `beta/4` of unit power at any timing phase, and blind adaptation reaches the trained BER. Consequences:
|
|
14
|
+
- an explicit `step_size` of the sequential equalizers acts `sps` times stronger (the regressor power is 1): halve it at 2 sps for the former behaviour;
|
|
15
|
+
- the block equalizers' default steps are divided by `sps`, so the defaults behave as before; explicit block steps act `sps` times stronger, and their stability ceilings read `step_size * block_size * sps`;
|
|
16
|
+
- `EqualizerResult.input_norm_factor` and `EqualizerState.input_norm_factor` are the plain per-channel RMS; the docstring gives the exact rebuild of `y_hat` from the raw samples, the taps and this factor.
|
|
17
|
+
- `commkit.timing` is a package (`synchronization`, `clock_recovery`). Import from `commkit.timing` and nothing changes.
|
|
18
|
+
- The example notebooks are renumbered: the new 12 (sampling-clock recovery) and 22 (direct and self-coherent detection) move the former 12-28 to 13-30.
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- `timing.estimate_sampling_clock_offset` and `timing.correct_sampling_clock_offset` with `timing.Gardner(normalized_bandwidth=, proportional_gain=, integral_gain=, joint_channels=, method="classical" | "power")`: Gardner clock recovery in one Numba loop. The offset in ppm comes from the slope of the tracked timing trajectory (`SamplingClockOffsetEstimate`); the correction resamples along the strobes, or by a constant factor from an earlier capture's estimate. An open-loop fit of the S-curve sets the gains from the loop bandwidth and starts the loop at the right phase; the power detector keeps its gain at small rolloffs. Measured within 0.06 ppm on 2^17 symbols at 20 dB; after recovery the SNR is the AWGN's plus the jitter's ISI, as predicted.
|
|
23
|
+
- `impairments.apply_sampling_clock_offset(offset_ppm=)`: an ADC clock off its nominal rate, by band-limited resampling (48-tap windowed sinc, error below -119 dB up to 0.4 cycles per sample).
|
|
24
|
+
- `impairments.apply_sampling_jitter(jitter=)` and `impairments.apply_quantization(num_bits=, full_scale=, enob=)`: sampling at jittered instants (SNR `-20 log10(2 pi f sigma)` of a tone) and a mid-rise quantizer with clipping and the converter's lumped noise.
|
|
25
|
+
- `apply_iq_imbalance(skew=)`: a time skew of Q against I, the frequency-dependent part of IQ imbalance (image `tan^2(pi f tau)`), which the memoryless corrections cannot remove.
|
|
26
|
+
- `metrics.awgn_ser`, `awgn_ber`, `awgn_mi`, `awgn_gmi(constellation, *, esn0_db)`: closed-form performance over AWGN in the conventions of the measured metrics. Error rates are exact for one-dimensional and separable constellations (PAM bipolar and unipolar, square QAM including shaped, BPSK, QPSK) and union bounds otherwise; MI and GMI by Gauss-Hermite quadrature, converged to 3e-7 bit.
|
|
27
|
+
- `impairments.apply_coherent_detection(received_power_dbm=, lo_power_dbm=, responsivity=, thermal_noise_density=, cmrr_db=, lo_rin_db_hz=, balanced=)`: the shot and thermal noise of a phase-diversity receiver from physical parameters (shot-noise limit `Es/N0 = eta n_p`), the common-mode leak of the signal's own power and of the LO's RIN through a finite CMRR, and single-ended detection.
|
|
28
|
+
- `impairments.apply_photodetection(received_power_dbm=, ...)`: the square law in the input's power units with signal-dependent shot noise, thermal noise, dark current, saturation and `sum_channels`; real output, and a Signal becomes digital baseband. `impairments.apply_intensity_noise(rin_db_hz=)`: laser RIN as a real amplitude factor.
|
|
29
|
+
- A WARNING when the power of an equalizer's decided symbols differs from the decision constellation's by more than 1.5 dB: a false lock, or a constellation that does not match the signal.
|
|
30
|
+
- Notebooks: 12 sampling-clock recovery, 22 direct and self-coherent detection; notebook 10 covers IQ skew and the ADC, notebook 06 uses the closed forms.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- `upsample`, `decimate` and `resample` promoted float32 to float64 (complex64 to complex128) on the GPU, because `cupyx` designs its polyphase filter in float64; the GPU now applies the CPU's filter in the data's precision.
|
|
35
|
+
- Equalizer docstrings: `weights_history` row `n` of the sequential equalizers holds the taps after the update at symbol `n`; an explicit `constellation` applies to the call only and the returned Signal keeps the input's description; real input is equalized as complex64. `Constellation.pam` states the unipolar energy penalty `2(2M - 1)/(M + 1)` (3.01 dB for OOK, 4.47 dB for PAM-4).
|
|
36
|
+
|
|
37
|
+
### Development
|
|
38
|
+
|
|
39
|
+
- `AGENTS.md`: the absolute-power rule (samples are dimensionless, a Signal holds no power, physical functions take `<where>_power_dbm` and return samples in the input's units), the `_ppm` and `_db_hz` suffixes, text-only commits skip mypy and pytest.
|
|
40
|
+
- Tests: a GN-model oracle for the split-step solver (first-order NLI with the coherent span factor, within 0.5 dB and the `P^3` law), the scaling chain of every equalizer rebuilt from raw samples, an IM-DD receiver chain with blind LMS on unipolar PAM-4, a plain-Python oracle for the Gardner loop.
|
|
41
|
+
- `benchmarks/bench_gardner.py` (no baseline recorded yet).
|
|
42
|
+
|
|
43
|
+
## [3.6.1] - 2026-11-05
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- `plot_frequency_noise_psd` shades the white-FM plateau: the fit bins where the fitted model stays within 10 % of the floor `Δν/π`, where white FM dominates and pins the linewidth down. It is a visual check only; `Δν` still comes from the model fitted over every bin in `used`, not from an average over the plateau.
|
|
48
|
+
|
|
7
49
|
## [3.6.0] - 2026-11-05
|
|
8
50
|
|
|
9
51
|
Chromatic dispersion for real captures. The compensation takes the link's accumulated dispersion and works as a receiver does, an FIR filter (or overlap-save blocks for backpropagation) in bounded memory, with the exact whole-record path as an option that is valid on captures too; three estimators measure an unknown dispersion, blind or from known symbols. The laser-characterization chain and its notebooks were corrected wherever a plot or a number did not mean what it said, method names that were generic now say what they do, and plot axes carry their SI prefix in the label.
|
|
@@ -461,7 +503,9 @@ See the [GitHub release](https://github.com/lokgar/commkit/releases/tag/v1.1.0)
|
|
|
461
503
|
|
|
462
504
|
First release. See the [GitHub release](https://github.com/lokgar/commkit/releases/tag/v1.0.0).
|
|
463
505
|
|
|
464
|
-
[Unreleased]: https://github.com/lokgar/commkit/compare/v3.
|
|
506
|
+
[Unreleased]: https://github.com/lokgar/commkit/compare/v3.7.0...HEAD
|
|
507
|
+
[3.7.0]: https://github.com/lokgar/commkit/compare/v3.6.1...v3.7.0
|
|
508
|
+
[3.6.1]: https://github.com/lokgar/commkit/compare/v3.6.0...v3.6.1
|
|
465
509
|
[3.6.0]: https://github.com/lokgar/commkit/compare/v3.5.0...v3.6.0
|
|
466
510
|
[3.5.0]: https://github.com/lokgar/commkit/compare/v3.4.0...v3.5.0
|
|
467
511
|
[3.4.0]: https://github.com/lokgar/commkit/compare/v3.3.0...v3.4.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: commkit
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.7.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
|
|
@@ -146,7 +146,7 @@ CommKit never picks a device for you: data stays where you put it, and each func
|
|
|
146
146
|
| [`commkit.multirate`](https://github.com/lokgar/commkit/blob/main/commkit/multirate.py) | `resample` (fractional), `decimate`, `upsample`, `decimate_to_symbol_rate`. |
|
|
147
147
|
| [`commkit.spectral`](https://github.com/lokgar/commkit/blob/main/commkit/spectral.py) | Welch PSD, spectrograms, frequency shifting, pilot tones. |
|
|
148
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`. |
|
|
149
|
-
| [`commkit.timing`](https://github.com/lokgar/commkit/
|
|
149
|
+
| [`commkit.timing`](https://github.com/lokgar/commkit/tree/main/commkit/timing) | Barker and Zadoff-Chu sequences, `estimate_timing` / `correct_timing`, fractional delay estimation and correction. |
|
|
150
150
|
| [`commkit.frequency`](https://github.com/lokgar/commkit/blob/main/commkit/frequency.py) | `estimate_frequency_offset` / `correct_frequency_offset` with `MthPower`, `MengaliMorelli`, `PilotSlope` and `SpectralPeak`; static and blockwise. |
|
|
151
151
|
| [`commkit.recovery`](https://github.com/lokgar/commkit/tree/main/commkit/recovery) | `estimate_carrier_phase` / `correct_carrier_phase` with `ViterbiViterbi`, `BPS`, `PLL`, `Tikhonov`, `ConstantPhase`, `PilotInterpolation`, `PilotTone(s)`; cycle-slip correction; `resolve_phase_ambiguity` and `resolve_channel_permutation`. |
|
|
152
152
|
| [`commkit.equalization`](https://github.com/lokgar/commkit/tree/main/commkit/equalization) | Sequential `lms`, `rls`, `cma`, `rde` (Numba) and frequency-domain `block_lms`, `block_cma`, `block_rde` (CuPy, CUDA graphs), butterfly MIMO, inline carrier recovery (`cpr=PLL()` / `BPS()`), continuation with `state=`, `zf_equalizer`; pilot-aided CMA and RDE from a frame's pilots (`EqualizerPilots`); polarization demultiplexing on pilot tones (`estimate_polarization_tones` / `correct_polarization`). |
|
|
@@ -110,7 +110,7 @@ CommKit never picks a device for you: data stays where you put it, and each func
|
|
|
110
110
|
| [`commkit.multirate`](https://github.com/lokgar/commkit/blob/main/commkit/multirate.py) | `resample` (fractional), `decimate`, `upsample`, `decimate_to_symbol_rate`. |
|
|
111
111
|
| [`commkit.spectral`](https://github.com/lokgar/commkit/blob/main/commkit/spectral.py) | Welch PSD, spectrograms, frequency shifting, pilot tones. |
|
|
112
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`. |
|
|
113
|
-
| [`commkit.timing`](https://github.com/lokgar/commkit/
|
|
113
|
+
| [`commkit.timing`](https://github.com/lokgar/commkit/tree/main/commkit/timing) | Barker and Zadoff-Chu sequences, `estimate_timing` / `correct_timing`, fractional delay estimation and correction. |
|
|
114
114
|
| [`commkit.frequency`](https://github.com/lokgar/commkit/blob/main/commkit/frequency.py) | `estimate_frequency_offset` / `correct_frequency_offset` with `MthPower`, `MengaliMorelli`, `PilotSlope` and `SpectralPeak`; static and blockwise. |
|
|
115
115
|
| [`commkit.recovery`](https://github.com/lokgar/commkit/tree/main/commkit/recovery) | `estimate_carrier_phase` / `correct_carrier_phase` with `ViterbiViterbi`, `BPS`, `PLL`, `Tikhonov`, `ConstantPhase`, `PilotInterpolation`, `PilotTone(s)`; cycle-slip correction; `resolve_phase_ambiguity` and `resolve_channel_permutation`. |
|
|
116
116
|
| [`commkit.equalization`](https://github.com/lokgar/commkit/tree/main/commkit/equalization) | Sequential `lms`, `rls`, `cma`, `rde` (Numba) and frequency-domain `block_lms`, `block_cma`, `block_rde` (CuPy, CUDA graphs), butterfly MIMO, inline carrier recovery (`cpr=PLL()` / `BPS()`), continuation with `state=`, `zf_equalizer`; pilot-aided CMA and RDE from a frame's pilots (`EqualizerPilots`); polarization demultiplexing on pilot tones (`estimate_polarization_tones` / `correct_polarization`). |
|
|
@@ -30,16 +30,16 @@ from commkit.mapping import Constellation
|
|
|
30
30
|
ROUNDS = dict(rounds=3, warmup_rounds=1, iterations=1)
|
|
31
31
|
N_SYM = 100_000
|
|
32
32
|
|
|
33
|
-
# Converging step sizes per (equalizer, block_size)
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
# the data.
|
|
33
|
+
# Converging step sizes per (equalizer, block_size), on input normalized to
|
|
34
|
+
# unit mean power at 2 sps. RDE on 16-QAM from a cold start (no CMA
|
|
35
|
+
# pre-convergence) converges slowly at 2048: at 2e-4 it is error-free from
|
|
36
|
+
# about symbol 70 000, so its SER is scored after that (3.5e-4 never
|
|
37
|
+
# settled, still 2-5 % SER at the end); its cost does not depend on the data.
|
|
38
38
|
STEP_SIZE = {
|
|
39
|
-
("cma", 256):
|
|
40
|
-
("cma", 2048):
|
|
41
|
-
("rde", 256):
|
|
42
|
-
("rde", 2048):
|
|
39
|
+
("cma", 256): 1e-4,
|
|
40
|
+
("cma", 2048): 1.5e-4,
|
|
41
|
+
("rde", 256): 1.5e-3,
|
|
42
|
+
("rde", 2048): 2e-4,
|
|
43
43
|
}
|
|
44
44
|
SKIP = {("rde", 2048): 70_000}
|
|
45
45
|
MAX_SER = {"cma": 1e-2, "rde": 1e-2}
|
|
@@ -54,9 +54,10 @@ CPR_CONFIGS = [
|
|
|
54
54
|
]
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
# Converging step sizes for the 30-degree mixing of the workload
|
|
58
|
-
# update's stability
|
|
59
|
-
|
|
57
|
+
# Converging step sizes for the 30-degree mixing of the workload, on input
|
|
58
|
+
# normalized to unit mean power at 2 sps: the block update's stability
|
|
59
|
+
# ceiling falls with block_size.
|
|
60
|
+
STEP_SIZE = {256: 1.5e-3, 2048: 2.5e-4}
|
|
60
61
|
|
|
61
62
|
|
|
62
63
|
def _bench_block_lms(benchmark, xp, sync, cpr_kwargs, block_size, n_train=None):
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Benchmarks: Gardner clock recovery (CPU Numba loop; GPU input makes one
|
|
2
|
+
round trip and resamples on the device)."""
|
|
3
|
+
|
|
4
|
+
from types import SimpleNamespace
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
from benchutils import assert_converged
|
|
8
|
+
from workloads import clock_offset_workload
|
|
9
|
+
|
|
10
|
+
from commkit.mapping import Constellation
|
|
11
|
+
from commkit.multirate import decimate_to_symbol_rate
|
|
12
|
+
from commkit.timing import Gardner, correct_sampling_clock_offset
|
|
13
|
+
|
|
14
|
+
ROUNDS = dict(rounds=3, warmup_rounds=1, iterations=1)
|
|
15
|
+
N_SYM = 2**18
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.mark.parametrize("method", ["classical", "power"])
|
|
19
|
+
def bench_correct_sampling_clock_offset(benchmark, backend_device, xp, sync, method):
|
|
20
|
+
samples, symbols = clock_offset_workload(N_SYM)
|
|
21
|
+
x = xp.asarray(samples)
|
|
22
|
+
|
|
23
|
+
def run():
|
|
24
|
+
out = correct_sampling_clock_offset(x, Gardner(method=method), sps=2)
|
|
25
|
+
sync()
|
|
26
|
+
return out
|
|
27
|
+
|
|
28
|
+
y = benchmark.pedantic(run, **ROUNDS)
|
|
29
|
+
sym = decimate_to_symbol_rate(y, sps=2)
|
|
30
|
+
assert_converged(
|
|
31
|
+
SimpleNamespace(y_hat=sym),
|
|
32
|
+
symbols,
|
|
33
|
+
Constellation.qam(16),
|
|
34
|
+
skip=1000,
|
|
35
|
+
max_ser=1e-3,
|
|
36
|
+
)
|
|
@@ -168,3 +168,32 @@ def coded_workload(code, num_codewords: int, esn0_db: float, seed: int = 0):
|
|
|
168
168
|
rx = apply_awgn(sig, esn0_db=esn0_db, rng=seed + 1)
|
|
169
169
|
llrs = compute_llr(rx, noise_var=10 ** (-esn0_db / 10))
|
|
170
170
|
return np.asarray(llrs, dtype=np.float32), sig.reference.info_bits
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def clock_offset_workload(
|
|
174
|
+
n_sym: int, num_ch: int = 2, ppm: float = 20.0, seed: int = 0
|
|
175
|
+
):
|
|
176
|
+
"""Matched-filtered dual-pol 16-QAM at 2 SPS (RRC 0.1, 20 dB Es/N0, a
|
|
177
|
+
0.37-sample delay) sampled by a clock ``ppm`` fast, for the clock-recovery
|
|
178
|
+
benches. Returns ``(samples (num_ch, ~2 n_sym) complex64, symbols)``."""
|
|
179
|
+
from commkit import RRC
|
|
180
|
+
from commkit.core import generate
|
|
181
|
+
from commkit.filtering import matched_filter
|
|
182
|
+
from commkit.impairments import apply_sampling_clock_offset
|
|
183
|
+
from commkit.timing import fft_fractional_delay
|
|
184
|
+
|
|
185
|
+
tx = generate(
|
|
186
|
+
Constellation.qam(16),
|
|
187
|
+
num_symbols=n_sym,
|
|
188
|
+
symbol_rate=FS,
|
|
189
|
+
sps=2,
|
|
190
|
+
pulse=RRC(rolloff=0.1),
|
|
191
|
+
num_channels=num_ch,
|
|
192
|
+
rng=seed,
|
|
193
|
+
)
|
|
194
|
+
rx = apply_awgn(tx, esn0_db=20.0, rng=seed + 1)
|
|
195
|
+
rx = apply_sampling_clock_offset(
|
|
196
|
+
fft_fractional_delay(rx, delay=0.37), offset_ppm=ppm
|
|
197
|
+
)
|
|
198
|
+
mf = matched_filter(rx)
|
|
199
|
+
return np.asarray(mf.samples, dtype=np.complex64), np.asarray(tx.reference.symbols)
|
|
@@ -110,7 +110,7 @@ def spread_samples(
|
|
|
110
110
|
|
|
111
111
|
``τ(ω) = β₂ L ω`` over ``|f| <= bandwidth / 2`` spans
|
|
112
112
|
``2π |β₂ L| · bandwidth`` seconds; over the whole sampled band
|
|
113
|
-
(``bandwidth = fs``) this is
|
|
113
|
+
(``bandwidth = fs``) this is the FIR length that covers the whole spread.
|
|
114
114
|
"""
|
|
115
115
|
return 2.0 * np.pi * abs(beta2_length) * bandwidth * sampling_rate
|
|
116
116
|
|
|
@@ -143,8 +143,7 @@ def compensation_taps(
|
|
|
143
143
|
|
|
144
144
|
Minimizes ``∫_{|θ|<W} |Σ h[n] e^{-jθn} - exp(j a θ²)|² dθ`` with
|
|
145
145
|
``θ = ω / fs``, ``W = π · bandwidth / fs`` and ``a = β₂ L fs² / 2``, for
|
|
146
|
-
``n`` centred on tap ``(num_taps - 1) // 2``
|
|
147
|
-
Lightw. Technol. 32, 2014): ``Q h = p`` with the Toeplitz Gram matrix
|
|
146
|
+
``n`` centred on tap ``(num_taps - 1) // 2``: ``Q h = p`` with the Toeplitz Gram matrix
|
|
148
147
|
``Q[m, k] = (W/π) sinc(W (m-k) / π)`` and ``p[m]`` the band integral of
|
|
149
148
|
the response, exact through Fresnel integrals. Over the whole band
|
|
150
149
|
(``W = π``) ``Q`` is the identity and ``h = p``, the truncated
|
|
@@ -15,7 +15,7 @@ fast birefringence of the fiber into ``8/9 · γ · (|x|² + |y|²)``. In
|
|
|
15
15
|
CommKit's sign convention (``_dispersion``: propagation multiplies a spectrum
|
|
16
16
|
by ``exp(-j β₂ ω² L / 2)``) the field obeys
|
|
17
17
|
|
|
18
|
-
dA/dz = -α/2 A
|
|
18
|
+
dA/dz = -α/2 A + j β₂/2 ∂²A/∂t² + β₃/6 ∂³A/∂t³ - j γ |A|² A,
|
|
19
19
|
|
|
20
20
|
the complex conjugate of the textbook (Agrawal) form, so the Kerr phase is
|
|
21
21
|
``exp(-j γ |A|² L)``. Matching the dispersion sign is what keeps solitons
|
|
@@ -21,7 +21,7 @@ __all__ = [
|
|
|
21
21
|
"_whittle_fit",
|
|
22
22
|
]
|
|
23
23
|
|
|
24
|
-
# β-separation-line slope
|
|
24
|
+
# β-separation-line slope: S_f(f) = (8 ln2 / π²) · f
|
|
25
25
|
_BETA_SLOPE = 8.0 * np.log(2.0) / (np.pi**2)
|
|
26
26
|
# FWHM linewidth from the integrated FM-noise area above the β-line:
|
|
27
27
|
# Δν = sqrt(8 ln2 · A)
|
|
@@ -655,12 +655,6 @@ def ldpc_exit_curves(
|
|
|
655
655
|
code run so close together that the closed-form fits of ``J``
|
|
656
656
|
(within 1e-3) move its threshold by 0.02 bit.
|
|
657
657
|
|
|
658
|
-
References
|
|
659
|
-
----------
|
|
660
|
-
S. ten Brink, G. Kramer, A. Ashikhmin, "Design of low-density
|
|
661
|
-
parity-check codes for modulation and detection," IEEE Trans. Commun.
|
|
662
|
-
52(4), 2004.
|
|
663
|
-
|
|
664
658
|
Examples
|
|
665
659
|
--------
|
|
666
660
|
>>> code = LDPCCode.dvbs2("1/2", "short")
|
|
@@ -236,8 +236,7 @@ class Chase:
|
|
|
236
236
|
``sum_i (1 - 2 c_i) LLR_i`` is the decision. The soft output of bit
|
|
237
237
|
``i`` is half the metric difference between the best candidates with
|
|
238
238
|
``c_i = 0`` and ``c_i = 1`` (max-log), or, when no candidate disagrees
|
|
239
|
-
with the decision there, ``beta`` with the decision's sign
|
|
240
|
-
1998). For codes with a hard decoder: linear block codes and BCH codes.
|
|
239
|
+
with the decision there, ``beta`` with the decision's sign. For codes with a hard decoder: linear block codes and BCH codes.
|
|
241
240
|
|
|
242
241
|
Parameters
|
|
243
242
|
----------
|
|
@@ -10,8 +10,8 @@ list decoding (CA-SCL). Encoding is ``n`` butterfly passes on the data's
|
|
|
10
10
|
device.
|
|
11
11
|
|
|
12
12
|
Decoding is successive cancellation (:class:`SC`) or its list version
|
|
13
|
-
(:class:`SCL`), with min-sum check-node updates and
|
|
14
|
-
|
|
13
|
+
(:class:`SCL`), with min-sum check-node updates and an LLR-based path
|
|
14
|
+
metric: sequential per codeword, so Numba over
|
|
15
15
|
codewords on the CPU, and GPU input makes one round trip. LLRs and partial
|
|
16
16
|
sums are kept level by level in buffers of ``2N`` (level ``s`` at offsets
|
|
17
17
|
``2^s ... 2^(s+1) - 1``), so a list path is copied in ``O(N)``.
|
|
@@ -7,10 +7,8 @@ target distribution, invertibly. Both classes are code objects, so
|
|
|
7
7
|
(Maxwell-Boltzmann priors, ``Constellation.shaped``) is in
|
|
8
8
|
:mod:`commkit.mapping.shaping`.
|
|
9
9
|
|
|
10
|
-
:class:`CCDM` (constant-composition distribution matching
|
|
11
|
-
|
|
12
|
-
fixed: the composition that best approximates the constellation's pmf
|
|
13
|
-
(Böcherer and Geiger, 2016). Each block of bits selects a point of
|
|
10
|
+
:class:`CCDM` (constant-composition distribution matching) maps ``k`` bits to ``num_symbols`` points whose counts are
|
|
11
|
+
fixed: the composition that best approximates the constellation's pmf. Each block of bits selects a point of
|
|
14
12
|
``[0, 1)``; the sequences of that composition partition ``[0, 1)`` by
|
|
15
13
|
arithmetic coding over the remaining counts, and the matcher outputs the
|
|
16
14
|
sequence whose interval holds the point. The coder is a 64-bit range coder:
|
|
@@ -24,8 +22,7 @@ sequential recursion per block: Numba over blocks on the CPU; GPU input to
|
|
|
24
22
|
:func:`~commkit.coding.decode` makes one round trip, and ``generate`` runs
|
|
25
23
|
it on the host before moving the payload.
|
|
26
24
|
|
|
27
|
-
:class:`PAS` (probabilistic amplitude shaping
|
|
28
|
-
Schulte, 2015) combines a CCDM over the amplitudes of one real dimension
|
|
25
|
+
:class:`PAS` (probabilistic amplitude shaping) combines a CCDM over the amplitudes of one real dimension
|
|
29
26
|
with a systematic FEC code, whose parity bits supply the signs. It needs
|
|
30
27
|
labels with a sign bit per real dimension and the same amplitude label for
|
|
31
28
|
``±a``: Gray QAM and bipolar PAM as the factories build them.
|
|
@@ -202,7 +199,7 @@ class CCDM(_Code):
|
|
|
202
199
|
|
|
203
200
|
|
|
204
201
|
def _composition(pmf: np.ndarray, n: int) -> np.ndarray:
|
|
205
|
-
"""KL-optimal counts ``c`` (sum ``n``) for ``pmf
|
|
202
|
+
"""KL-optimal counts ``c`` (sum ``n``) for ``pmf``."""
|
|
206
203
|
p = np.asarray(pmf, dtype=np.float64)
|
|
207
204
|
c = np.floor(n * p).astype(np.int64)
|
|
208
205
|
for _ in range(n - int(c.sum())):
|
|
@@ -86,7 +86,7 @@ def _block_fdaf_blind(
|
|
|
86
86
|
pilot positions overridden by the LMS residual. ``r2`` is the Godard radius
|
|
87
87
|
(CMA) and ``radii_np`` the unique ring radii (RDE), decided by their
|
|
88
88
|
squared midpoints (``_rde_rings``). ``step_size=None``
|
|
89
|
-
is the default of both, ``min(1e-3, 0.5 / block_size)``.
|
|
89
|
+
is the default of both, ``min(1e-3, 0.5 / block_size) / sps``.
|
|
90
90
|
"""
|
|
91
91
|
use_pilots = pilot_ref is not None and pilot_mask is not None
|
|
92
92
|
equalizer = f"block_{kind}"
|
|
@@ -105,7 +105,7 @@ def _block_fdaf_blind(
|
|
|
105
105
|
pilot_gain_db=pilot_gain_db,
|
|
106
106
|
)
|
|
107
107
|
xp, C, n_sym, sps = run.xp, run.C, run.n_sym, run.sps
|
|
108
|
-
mu = min(1e-3, 0.5 / run.block_size) if step_size is None else step_size
|
|
108
|
+
mu = min(1e-3, 0.5 / run.block_size) / sps if step_size is None else step_size
|
|
109
109
|
logger.info(
|
|
110
110
|
"%s: C=%s, num_taps=%s, sps=%s, block_size=%s, fftsize=%s, mu=%s, "
|
|
111
111
|
"n_sym=%s, pilot_aided=%s",
|
|
@@ -312,7 +312,7 @@ def block_cma(
|
|
|
312
312
|
Step size on the scale of :func:`cma`. The weights are frozen
|
|
313
313
|
across each block, so the stability ceiling is about ``block_size``
|
|
314
314
|
times lower than per symbol; reduce it if the run raises divergence.
|
|
315
|
-
Defaults to ``min(1e-3, 0.5 / block_size)``, as for
|
|
315
|
+
Defaults to ``min(1e-3, 0.5 / block_size) / sps``, as for
|
|
316
316
|
:func:`block_rde`.
|
|
317
317
|
block_size : int, default 256
|
|
318
318
|
Output symbols per weight update.
|
|
@@ -396,9 +396,9 @@ def block_rde(
|
|
|
396
396
|
step_size : float, optional
|
|
397
397
|
Step size on the scale of :func:`rde`, with the about ``block_size``
|
|
398
398
|
times lower stability ceiling of frozen-block adaptation. Defaults
|
|
399
|
-
to ``min(1e-3, 0.5 / block_size)``: the per-symbol step, capped
|
|
400
|
-
half the ceiling (``step_size * block_size`` below about 1
|
|
401
|
-
blocks.
|
|
399
|
+
to ``min(1e-3, 0.5 / block_size) / sps``: the per-symbol step, capped
|
|
400
|
+
at half the ceiling (``step_size * block_size * sps`` below about 1,
|
|
401
|
+
on input normalized to unit mean power) for large blocks.
|
|
402
402
|
block_size : int, default 256
|
|
403
403
|
Output symbols per weight update.
|
|
404
404
|
constellation : Constellation, optional
|
|
@@ -23,6 +23,7 @@ from ..result import (
|
|
|
23
23
|
EqualizerResult,
|
|
24
24
|
EqualizerState,
|
|
25
25
|
_attach_equalized_signal,
|
|
26
|
+
_check_decision_power,
|
|
26
27
|
_log_equalizer_exit,
|
|
27
28
|
)
|
|
28
29
|
from ._engine import (
|
|
@@ -386,12 +387,13 @@ def block_lms(
|
|
|
386
387
|
``block_size``.
|
|
387
388
|
|
|
388
389
|
Because the weights are frozen across the block, the stability
|
|
389
|
-
ceiling falls as ``1/block_size``:
|
|
390
|
-
about 1.5 (whatever
|
|
391
|
-
``
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
390
|
+
ceiling falls as ``1/block_size``: on input normalized to unit mean
|
|
391
|
+
power, ``μ·block_size·sps`` must stay below about 1.5 (whatever
|
|
392
|
+
``num_taps`` and the channel count). The default
|
|
393
|
+
``min(0.5 / block_size, 8e-3) / sps`` sits at a quarter of that
|
|
394
|
+
ceiling, where convergence takes a few blocks and the steady-state
|
|
395
|
+
MSE is within a few tenths of a dB of the best step; for
|
|
396
|
+
``block_size`` below 64 the cap limits the per-symbol misadjustment.
|
|
395
397
|
block_size : int, default 256
|
|
396
398
|
Output symbols per weight update. Larger blocks use the GPU better
|
|
397
399
|
but adapt more slowly. Independent of the BPS window
|
|
@@ -399,7 +401,14 @@ def block_lms(
|
|
|
399
401
|
constellation : Constellation, optional
|
|
400
402
|
Decision constellation, at unit power (a shaped constellation brings
|
|
401
403
|
its pmf). Defaults to the Signal's ``constellation``; required for
|
|
402
|
-
array input.
|
|
404
|
+
array input. A WARNING is logged when the decided symbols of the
|
|
405
|
+
last half of the decision-directed output lack its power by more
|
|
406
|
+
than 1.5 dB: a lock at a wrong gain, or a constellation that does
|
|
407
|
+
not match the signal. An explicit choice applies to this call
|
|
408
|
+
only: ``signal`` of the result keeps the input Signal's
|
|
409
|
+
``constellation``, which describes its reference symbols, so pass
|
|
410
|
+
the same choice to the metrics, or set it on the Signal with
|
|
411
|
+
``replace`` (with matching reference symbols) before equalizing.
|
|
403
412
|
store_weights : bool, default False
|
|
404
413
|
Record the weights at the start of every block in
|
|
405
414
|
``weights_history``.
|
|
@@ -465,7 +474,7 @@ def block_lms(
|
|
|
465
474
|
**Stability.** ``step_size`` multiplies the gradient summed (not
|
|
466
475
|
averaged) over the block. That keeps ``μ`` on the scale of :func:`lms`
|
|
467
476
|
(the same ``μ`` converges at the same speed per symbol), but it makes the
|
|
468
|
-
stability ceiling, ``μ·block_size`` below about 1.5, about
|
|
477
|
+
stability ceiling, ``μ·block_size·sps`` below about 1.5, about
|
|
469
478
|
``block_size`` times lower than for per-symbol LMS, because the weights
|
|
470
479
|
are frozen across the block. Above the ceiling the run diverges (NaN
|
|
471
480
|
weights, detected at the end, raise) or stays bounded without
|
|
@@ -553,7 +562,7 @@ def block_lms(
|
|
|
553
562
|
xp, C, n_sym, sps, block_size = run.xp, run.C, run.n_sym, run.sps, run.block_size
|
|
554
563
|
num_taps = run.num_taps
|
|
555
564
|
training = run.training
|
|
556
|
-
mu = min(0.5 / block_size, 8e-3) if step_size is None else step_size
|
|
565
|
+
mu = min(0.5 / block_size, 8e-3) / sps if step_size is None else step_size
|
|
557
566
|
|
|
558
567
|
if constellation is None:
|
|
559
568
|
raise ValueError(
|
|
@@ -757,18 +766,19 @@ def block_lms(
|
|
|
757
766
|
raise RuntimeError(
|
|
758
767
|
f"block_lms diverged (step_size={mu}, block_size={block_size}). "
|
|
759
768
|
f"Because the weights are frozen across the block, step_size * "
|
|
760
|
-
f"block_size must stay below about 1.5; the default "
|
|
761
|
-
f"min(0.5 / block_size, 8e-3)
|
|
762
|
-
f"sits at a quarter of that
|
|
769
|
+
f"block_size * sps must stay below about 1.5; the default "
|
|
770
|
+
f"min(0.5 / block_size, 8e-3) / sps = "
|
|
771
|
+
f"{min(0.5 / block_size, 8e-3) / sps:.2e} sits at a quarter of that "
|
|
772
|
+
f"ceiling."
|
|
763
773
|
)
|
|
764
774
|
weights, carrier = snap[0]
|
|
765
775
|
state_out = _block_state(
|
|
766
776
|
run, equalizer="block_lms", cpr=cpr, weights=weights, carrier=carrier
|
|
767
777
|
)
|
|
768
778
|
result = _assemble_block(run, y_all, e_all, w_hist, phi_all, n_train, state_out)
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
)
|
|
779
|
+
result = _log_equalizer_exit(result, name="Block-LMS")
|
|
780
|
+
result = _check_decision_power(result, constellation, "block_lms()")
|
|
781
|
+
return _attach_equalized_signal(result, sig, state)
|
|
772
782
|
|
|
773
783
|
|
|
774
784
|
def _block_bps(
|