commkit 3.2.1__tar.gz → 3.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {commkit-3.2.1 → commkit-3.3.0}/CHANGELOG.md +24 -1
- {commkit-3.2.1 → commkit-3.3.0}/PKG-INFO +29 -3
- {commkit-3.2.1 → commkit-3.3.0}/README.md +28 -2
- {commkit-3.2.1 → commkit-3.3.0}/benchmarks/bench_filters.py +19 -1
- {commkit-3.2.1 → commkit-3.3.0}/commkit/__init__.py +1 -1
- {commkit-3.2.1 → commkit-3.3.0}/commkit/filtering/__init__.py +8 -0
- commkit-3.3.0/commkit/filtering/response.py +260 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/plotting/__init__.py +4 -3
- {commkit-3.2.1 → commkit-3.3.0}/commkit/plotting/analysis.py +166 -101
- {commkit-3.2.1 → commkit-3.3.0}/commkit/plotting/coding.py +146 -73
- {commkit-3.2.1 → commkit-3.3.0}/commkit/plotting/constellation.py +11 -11
- commkit-3.3.0/commkit/plotting/equalizer.py +319 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/plotting/eye.py +21 -4
- commkit-3.3.0/commkit/plotting/filter_response.py +184 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/plotting/spectral.py +16 -1
- {commkit-3.2.1 → commkit-3.3.0}/commkit/plotting/sync.py +404 -253
- {commkit-3.2.1 → commkit-3.3.0}/commkit/plotting/theme.py +73 -2
- {commkit-3.2.1 → commkit-3.3.0}/commkit/plotting/waveform.py +31 -5
- {commkit-3.2.1 → commkit-3.3.0}/examples/01-constellations-and-bit-mapping.ipynb +10 -13
- {commkit-3.2.1 → commkit-3.3.0}/examples/02-spectral-analysis.ipynb +9 -3
- {commkit-3.2.1 → commkit-3.3.0}/examples/03-digital-filters.ipynb +61 -49
- {commkit-3.2.1 → commkit-3.3.0}/examples/04-pulse-shaping-and-matched-filtering.ipynb +25 -12
- {commkit-3.2.1 → commkit-3.3.0}/examples/05-multirate-and-resampling.ipynb +8 -13
- {commkit-3.2.1 → commkit-3.3.0}/examples/06-awgn-and-performance-metrics.ipynb +2 -2
- {commkit-3.2.1 → commkit-3.3.0}/examples/07-channel-coding.ipynb +5 -3
- {commkit-3.2.1 → commkit-3.3.0}/examples/08-probabilistic-shaping-and-coded-modulation.ipynb +25 -4
- {commkit-3.2.1 → commkit-3.3.0}/examples/09-laser-phase-noise.ipynb +6 -8
- {commkit-3.2.1 → commkit-3.3.0}/examples/10-front-end-impairments.ipynb +16 -8
- {commkit-3.2.1 → commkit-3.3.0}/examples/11-frame-synchronization-and-timing.ipynb +10 -3
- {commkit-3.2.1 → commkit-3.3.0}/examples/12-frequency-offset-estimation.ipynb +3 -1
- {commkit-3.2.1 → commkit-3.3.0}/examples/13-blind-carrier-phase-recovery.ipynb +11 -3
- {commkit-3.2.1 → commkit-3.3.0}/examples/14-pilot-aided-carrier-recovery.ipynb +11 -3
- {commkit-3.2.1 → commkit-3.3.0}/examples/15-linear-channel-equalization.ipynb +18 -9
- {commkit-3.2.1 → commkit-3.3.0}/examples/16-adaptive-equalization.ipynb +23 -11
- {commkit-3.2.1 → commkit-3.3.0}/examples/18-polarization-multiplexing.ipynb +15 -3
- {commkit-3.2.1 → commkit-3.3.0}/examples/19-chromatic-dispersion.ipynb +15 -3
- {commkit-3.2.1 → commkit-3.3.0}/examples/20-nonlinear-fiber-and-backpropagation.ipynb +1 -1
- {commkit-3.2.1 → commkit-3.3.0}/examples/21-fec-in-optical-standards.ipynb +17 -2
- {commkit-3.2.1 → commkit-3.3.0}/examples/22-coherent-transceiver-end-to-end.ipynb +3 -1
- {commkit-3.2.1 → commkit-3.3.0}/pyproject.toml +2 -2
- {commkit-3.2.1 → commkit-3.3.0}/tests/plotting/test_plot_analysis.py +40 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/plotting/test_plot_coding.py +37 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/plotting/test_plot_constellation.py +23 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/plotting/test_plot_equalizer.py +73 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/plotting/test_plot_eye.py +30 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/plotting/test_plot_filter_response.py +51 -4
- {commkit-3.2.1 → commkit-3.3.0}/tests/plotting/test_plot_spectral.py +11 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/plotting/test_plot_sync.py +123 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/plotting/test_plot_waveform.py +14 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/plugins/transfer_trace.py +1 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/test_api_contracts.py +4 -0
- commkit-3.3.0/tests/test_filter_response.py +190 -0
- {commkit-3.2.1 → commkit-3.3.0}/uv.lock +1 -1
- commkit-3.2.1/commkit/plotting/equalizer.py +0 -290
- commkit-3.2.1/commkit/plotting/filter_response.py +0 -217
- {commkit-3.2.1 → commkit-3.3.0}/.gitattributes +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/.github/workflows/ci.yml +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/.github/workflows/publish.yml +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/.gitignore +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/.python-version +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/AGENTS.md +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/LICENSE +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0002_pre_v2.json +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0003_v2_0.json +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0004_fiber_dbp.json +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0005_gpu_decoder_path.json +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0006_perf_cleanup.json +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0007_v3_0.json +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0008_v3_1.json +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/benchmarks/bench_block_blind.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/benchmarks/bench_block_lms.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/benchmarks/bench_bps.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/benchmarks/bench_coding.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/benchmarks/bench_equalizers.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/benchmarks/bench_fiber.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/benchmarks/bench_import.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/benchmarks/bench_llr.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/benchmarks/bench_mapping.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/benchmarks/bench_signal_pipeline.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/benchmarks/bench_sync_misc.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/benchmarks/benchutils.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/benchmarks/conftest.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/benchmarks/record_baseline.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/benchmarks/workloads.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/_array.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/_circular.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/_constants.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/_cuda/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/_cuda/compiler.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/_cuda/graphs.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/_cuda/src/algebraic.cu +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/_cuda/src/bps_anchor.cu +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/_cuda/src/bps_min_d2.cu +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/_cuda/src/cs_block.cu +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/_cuda/src/iir.cu +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/_cuda/src/ldpc.cu +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/_cuda/src/llr.cu +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/_cuda/src/selftest.cu +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/_dispersion.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/_fft.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/_fiber.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/_overlap_save.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/_random.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/_sequences.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/analysis/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/analysis/_common.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/analysis/allan.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/analysis/drift.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/analysis/fm_noise.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/analysis/interferometry.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/analysis/linewidth/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/analysis/linewidth/dsh.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/analysis/linewidth/phase.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/analysis/linewidth/result.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/analysis/trajectory.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/backend.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/coding/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/coding/_galois.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/coding/_gf2.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/coding/_ldpc_tables.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/coding/_numba_loops.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/coding/algebraic.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/coding/base.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/coding/convolutional.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/coding/crc.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/coding/exit.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/coding/interleaving.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/coding/ldpc.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/coding/linear.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/coding/polar.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/coding/product.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/coding/ratematch.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/coding/shaping.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/coding/turbo.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/core/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/core/_signal_adapter.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/core/frame.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/core/generation.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/core/signal.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/equalization/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/equalization/_block/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/equalization/_block/_blind.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/equalization/_block/_dd.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/equalization/_block/_engine.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/equalization/_common.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/equalization/_kernels_numba.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/equalization/_numba_loops.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/equalization/blind.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/equalization/linear.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/equalization/pilots.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/equalization/polarization.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/equalization/result.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/equalization/sequential/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/equalization/sequential/_blind.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/equalization/sequential/_dd.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/equalization/sequential/_setup.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/filtering/fiber.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/filtering/linear.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/filtering/pulses.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/frequency.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/impairments/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/impairments/channel/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/impairments/channel/linear.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/impairments/channel/nonlinear.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/impairments/frontend.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/impairments/noise.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/impairments/source.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/io.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/logger.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/mapping/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/mapping/_numba_llr.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/mapping/bits.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/mapping/constellation.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/mapping/gray.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/mapping/llr.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/mapping/shaping.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/math.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/metrics.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/multirate.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/py.typed +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/recovery/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/recovery/_common.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/recovery/bps.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/recovery/carrier_phase.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/recovery/corrections.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/recovery/pilots.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/recovery/pll.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/recovery/tikhonov.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/recovery/viterbi_viterbi.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/smoothing.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/spectral.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/commkit/timing.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/examples/00-getting-started.ipynb +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/examples/17-blind-equalization.ipynb +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/examples/23-carrier-phase-characterization.ipynb +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/examples/24-laser-linewidth-self-heterodyne.ipynb +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/examples/25-laser-linewidth-short-delay-and-flicker.ipynb +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/examples/26-laser-linewidth-self-homodyne-iq.ipynb +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/examples/27-measurement-template-self-heterodyne.ipynb +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/examples/28-measurement-template-self-homodyne-iq.ipynb +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/examples/README.md +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/analysis/test_allan.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/analysis/test_drift.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/analysis/test_fm_noise.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/analysis/test_interferometry.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/analysis/test_linewidth.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/analysis/test_trajectory.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/coding/test_algebraic.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/coding/test_coding_base.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/coding/test_convolutional.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/coding/test_crc.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/coding/test_exit.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/coding/test_gf2.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/coding/test_interleaving.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/coding/test_ldpc.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/coding/test_ldpc_exit.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/coding/test_ldpc_kernel.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/coding/test_ldpc_tables.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/coding/test_linear_codes.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/coding/test_polar.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/coding/test_product.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/coding/test_ratematch.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/coding/test_shaping.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/coding/test_turbo.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/common/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/common/coding.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/common/conversions.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/common/kernel_utils.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/common/memory.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/common/metrics.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/common/periodic.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/common/reference_impl.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/common/scoring.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/common/signals.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/common/theory.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/conftest.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/core/test_frame.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/core/test_generation.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/core/test_psqam.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/core/test_signal.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/core/test_signal_adapter.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/core/test_signal_mimo.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/equalization/test_blind.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/equalization/test_block.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/equalization/test_bps_kernel.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/equalization/test_cpr.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/equalization/test_cs_kernel.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/equalization/test_equalizer_oracles.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/equalization/test_linear.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/equalization/test_mimo.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/equalization/test_polarization.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/equalization/test_sequential.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/equalization/test_state.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/equalization/test_winit.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/impairments/channel/test_channel_linear.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/impairments/channel/test_channel_nonlinear.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/impairments/test_frontend.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/impairments/test_noise.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/impairments/test_source.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/mapping/test_bits.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/mapping/test_constellation.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/mapping/test_gray.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/mapping/test_llr.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/plotting/test_plot_theme.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/plugins/__init__.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/plugins/precision_trace.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/recovery/test_bps.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/recovery/test_corrections.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/recovery/test_joint.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/recovery/test_pilots.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/recovery/test_pll.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/recovery/test_recovery_oracles.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/recovery/test_tikhonov.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/recovery/test_viterbi_viterbi.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/test_array.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/test_backend.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/test_cuda_infra.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/test_dispersion.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/test_examples.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/test_fiber.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/test_filtering.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/test_frequency.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/test_import_hygiene.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/test_io.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/test_logger.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/test_math.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/test_metrics.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/test_multirate.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/test_pulse_shaping.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/test_receiver_chain.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/test_signal_pipeline.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/test_smoothing.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/test_spectral.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/test_timing.py +0 -0
- {commkit-3.2.1 → commkit-3.3.0}/tests/test_working_memory.py +0 -0
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
Notable changes to CommKit, newest first. Performance figures are from the reference machine (RTX 4070 Ti, Ryzen 7 7800X3D, WSL2) unless stated otherwise, as a ratio of new to old time (below 1 is faster).
|
|
4
4
|
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [3.3.0] - 2026-11-03
|
|
8
|
+
|
|
9
|
+
Numerical filter responses and plots built for comparisons. `filter_response`, `impulse_response` and `step_response` evaluate any FIR, SOS or `(b, a)` filter on its device; the multi-panel diagnostics draw one selected panel, and every plot can overlay several results on one axis. The example notebooks explain what each result shows.
|
|
10
|
+
|
|
11
|
+
### Changes that may need code updates
|
|
12
|
+
|
|
13
|
+
- `plot_filter_response` raises `ValueError` when `ax` holds other than four axes for the dashboard (or one for a selected `quantity=`), as the other multi-panel plots do; it used to log a warning and open a new figure. Pass the right number of axes, or none.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- `filter_response` (returning a `FilterResponse` of frequencies, complex response and group delay), `impulse_response` and `step_response` compute the responses of FIR taps, second-order sections or a `(b, a)` pair on the coefficients' device. The group delay of SOS filters is summed section by section, so high orders keep their accuracy; real IIR time responses run through `iir_filter`, in double-precision state whatever the coefficients' precision.
|
|
18
|
+
- Multi-panel diagnostics (`plot_filter_response`, `plot_equalizer_result`, `plot_zf_equalizer_response`, `plot_llr_distribution`, `plot_parity_check`, `plot_timing_correlation`, `plot_mm_autocorrelation`, `plot_frequency_offset_blockwise_result`, `plot_pilot_phase_estimate`, `plot_pilot_tone_phase_estimate`, `plot_pilot_tones_phase_estimate`) take `quantity=` to draw one panel on one axis.
|
|
19
|
+
- Comparisons on one axis: the single-panel plots and a selected `quantity=` take `label=`, `xlim=`, `ylim=` and `legend=` (`True` shows, `False` hides, `None` keeps the plot's default), and the plots with fits, guides or markers take `annotations=False` to draw only the measured curves of an overlay. `plot_eye_diagram(quantity="i" | "q")` draws one component; `plot_carrier_phase_characterization` accepts existing axes.
|
|
20
|
+
- The example notebooks explain what each receiver result shows, their subplot grids scale the theme's figure size, and notebooks 13 and 16 overlay filter, smoothing, shaping and equalizer comparisons on one axis.
|
|
21
|
+
|
|
22
|
+
### Development
|
|
23
|
+
|
|
24
|
+
- The transfer audit documents the Tikhonov smoother's host round trip, which it flagged as undocumented.
|
|
25
|
+
- `bench_filter_response` times responses on the DFT grid and on an arbitrary grid at 31, 257 and 4097 taps.
|
|
26
|
+
|
|
5
27
|
## [3.2.1] - 2026-11-02
|
|
6
28
|
|
|
7
29
|
Fixes found by a rework of the test suite, which now checks what each algorithm achieves against the transmitted truth and theory. Five numerical defaults or models were wrong and change results (PLL cycle-slip repair, Tikhonov smoothing, `block_cma`'s step, `ols_fir_filter`'s alignment of even taps, `MthPower` near 0 Hz); no signature changes. Plots take their sizes from the theme.
|
|
@@ -341,7 +363,8 @@ See the [GitHub release](https://github.com/lokgar/commkit/releases/tag/v1.1.0)
|
|
|
341
363
|
|
|
342
364
|
First release. See the [GitHub release](https://github.com/lokgar/commkit/releases/tag/v1.0.0).
|
|
343
365
|
|
|
344
|
-
[Unreleased]: https://github.com/lokgar/commkit/compare/v3.
|
|
366
|
+
[Unreleased]: https://github.com/lokgar/commkit/compare/v3.3.0...HEAD
|
|
367
|
+
[3.3.0]: https://github.com/lokgar/commkit/compare/v3.2.1...v3.3.0
|
|
345
368
|
[3.2.1]: https://github.com/lokgar/commkit/compare/v3.2.0...v3.2.1
|
|
346
369
|
[3.2.0]: https://github.com/lokgar/commkit/compare/v3.1.3...v3.2.0
|
|
347
370
|
[3.1.3]: https://github.com/lokgar/commkit/compare/v3.1.2...v3.1.3
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: commkit
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Summary: A Python library for high-performance digital communications research.
|
|
5
5
|
Project-URL: Homepage, https://github.com/lokgar/commkit
|
|
6
6
|
Project-URL: Repository, https://github.com/lokgar/commkit
|
|
@@ -49,7 +49,7 @@ CommKit covers the receiver chain of coherent and IM/DD links (waveform generati
|
|
|
49
49
|
|
|
50
50
|
## The model
|
|
51
51
|
|
|
52
|
-
Data is a NumPy or CuPy array, or a `Signal` that wraps the samples together with what is known about them. Algorithms are plain functions that take the data and return a new `Signal` or array, or a typed result such as `EqualizerResult` or an `*Estimate`. Plotting functions draw those results
|
|
52
|
+
Data is a NumPy or CuPy array, or a `Signal` that wraps the samples together with what is known about them. Algorithms are plain functions that take the data and return a new `Signal` or array, or a typed result such as `EqualizerResult` or an `*Estimate`. Plotting functions draw those results or call the public compute functions needed to produce them.
|
|
53
53
|
|
|
54
54
|
- **Things that describe** a modulation, a pulse or a sub-algorithm are small frozen objects written in the call: `lms(rx, cpr=BPS(test_phases=64))`. The same object works standalone: `correct_carrier_phase(y, BPS())`.
|
|
55
55
|
- **A `Signal` carries facts and ground truth.** Facts are `sampling_rate`, `symbol_rate`, `center_frequency` and `domain`; the carrier of an optical Signal gives its `center_wavelength_nm`, which dispersion, fiber propagation and OSNR noise read. The description is `constellation` and `pulse`. The ground truth is `reference`: the transmitted symbols, the bits on the air and the source bits before coding or distribution matching (the same array when there is neither). Functions take facts from the Signal and raise on a conflicting argument. Choices such as the decision constellation default to the Signal's and can be overridden.
|
|
@@ -87,6 +87,32 @@ res2 = ck.equalization.lms(rx, num_taps=21, step_size=1e-3, cpr=cpr, state=res.s
|
|
|
87
87
|
|
|
88
88
|
Without CuPy, pass `device="cpu"` (the default): the same code runs on the CPU.
|
|
89
89
|
|
|
90
|
+
## Comparing filter responses
|
|
91
|
+
|
|
92
|
+
Calculate a response on the coefficients' device, or overlay selected panels
|
|
93
|
+
of several filters:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
short = ck.filtering.fir_taps(sampling_rate=1e6, num_taps=21, cutoff=100e3)
|
|
97
|
+
long = ck.filtering.fir_taps(sampling_rate=1e6, num_taps=101, cutoff=100e3)
|
|
98
|
+
response = ck.filtering.filter_response(long, sampling_rate=1e6)
|
|
99
|
+
print(response.frequencies, response.response, response.group_delay)
|
|
100
|
+
|
|
101
|
+
ck.plotting.apply_default_theme()
|
|
102
|
+
_, ax = ck.plotting.plot_filter_response(
|
|
103
|
+
short, sampling_rate=1e6, quantity="magnitude", label="21 taps"
|
|
104
|
+
)
|
|
105
|
+
ck.plotting.plot_filter_response(
|
|
106
|
+
long, sampling_rate=1e6, quantity="magnitude", ax=ax,
|
|
107
|
+
label="101 taps", legend=True, show=True,
|
|
108
|
+
)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
See notebook 03 for filter and smoothing comparisons. The multi-panel
|
|
112
|
+
equalizer, timing, recovery and coding diagnostics take `quantity=` in the
|
|
113
|
+
same way: draw a second result on the returned axis with its own `label=`,
|
|
114
|
+
and `annotations=False` to leave out repeated fits and guides.
|
|
115
|
+
|
|
90
116
|
## CPU and GPU
|
|
91
117
|
|
|
92
118
|
CommKit never picks a device for you: data stays where you put it, and each function runs on the device of its input. A few habits get the speed out of a GPU:
|
|
@@ -116,7 +142,7 @@ CommKit never picks a device for you: data stays where you put it, and each func
|
|
|
116
142
|
| --- | --- |
|
|
117
143
|
| [`commkit.core`](https://github.com/lokgar/commkit/tree/main/commkit/core) | `Signal`, `Reference`, `generate` (uniform, shaped through a distribution matcher, or coded with `code=`), `SingleCarrierFrame` and `Preamble` (pilots, guard intervals, MIMO channels), `extract_payload`. |
|
|
118
144
|
| [`commkit.mapping`](https://github.com/lokgar/commkit/tree/main/commkit/mapping) | `Constellation` (QAM, PSK, PAM, arbitrary points; Gray labels; probabilistic shaping with `.shaped()`), bit mapping, hard demapping, max-log and exact LLRs. |
|
|
119
|
-
| [`commkit.filtering`](https://github.com/lokgar/commkit/tree/main/commkit/filtering) | Pulses (`RRC`, `RC`, `Gaussian`, `Rect`, `SmoothRect`), FIR and IIR design (Butterworth, Chebyshev I/II, elliptic, Bessel), `fir_filter`, `iir_filter`, `matched_filter`, overlap-save, chromatic-dispersion compensation and digital backpropagation (`correct_fiber_propagation`). |
|
|
145
|
+
| [`commkit.filtering`](https://github.com/lokgar/commkit/tree/main/commkit/filtering) | Pulses (`RRC`, `RC`, `Gaussian`, `Rect`, `SmoothRect`), FIR and IIR design (Butterworth, Chebyshev I/II, elliptic, Bessel), numerical filter responses, `fir_filter`, `iir_filter`, `matched_filter`, overlap-save, chromatic-dispersion compensation and digital backpropagation (`correct_fiber_propagation`). |
|
|
120
146
|
| [`commkit.multirate`](https://github.com/lokgar/commkit/blob/main/commkit/multirate.py) | `resample` (fractional), `decimate`, `upsample`, `decimate_to_symbol_rate`. |
|
|
121
147
|
| [`commkit.spectral`](https://github.com/lokgar/commkit/blob/main/commkit/spectral.py) | Welch PSD, spectrograms, frequency shifting, pilot tones. |
|
|
122
148
|
| [`commkit.impairments`](https://github.com/lokgar/commkit/tree/main/commkit/impairments) | AWGN at an Es/N0 (measured as energy per symbol) or of an absolute variance, ASE at an OSNR (`apply_ase`), phase noise, IQ imbalance (blind estimate; Löwdin and Gram-Schmidt correction), chromatic dispersion, PMD and polarization mixing, and nonlinear fiber propagation over amplified spans (split-step Kerr effect with dispersion, loss, PMD and ASE) of a `Fiber`. |
|
|
@@ -13,7 +13,7 @@ CommKit covers the receiver chain of coherent and IM/DD links (waveform generati
|
|
|
13
13
|
|
|
14
14
|
## The model
|
|
15
15
|
|
|
16
|
-
Data is a NumPy or CuPy array, or a `Signal` that wraps the samples together with what is known about them. Algorithms are plain functions that take the data and return a new `Signal` or array, or a typed result such as `EqualizerResult` or an `*Estimate`. Plotting functions draw those results
|
|
16
|
+
Data is a NumPy or CuPy array, or a `Signal` that wraps the samples together with what is known about them. Algorithms are plain functions that take the data and return a new `Signal` or array, or a typed result such as `EqualizerResult` or an `*Estimate`. Plotting functions draw those results or call the public compute functions needed to produce them.
|
|
17
17
|
|
|
18
18
|
- **Things that describe** a modulation, a pulse or a sub-algorithm are small frozen objects written in the call: `lms(rx, cpr=BPS(test_phases=64))`. The same object works standalone: `correct_carrier_phase(y, BPS())`.
|
|
19
19
|
- **A `Signal` carries facts and ground truth.** Facts are `sampling_rate`, `symbol_rate`, `center_frequency` and `domain`; the carrier of an optical Signal gives its `center_wavelength_nm`, which dispersion, fiber propagation and OSNR noise read. The description is `constellation` and `pulse`. The ground truth is `reference`: the transmitted symbols, the bits on the air and the source bits before coding or distribution matching (the same array when there is neither). Functions take facts from the Signal and raise on a conflicting argument. Choices such as the decision constellation default to the Signal's and can be overridden.
|
|
@@ -51,6 +51,32 @@ res2 = ck.equalization.lms(rx, num_taps=21, step_size=1e-3, cpr=cpr, state=res.s
|
|
|
51
51
|
|
|
52
52
|
Without CuPy, pass `device="cpu"` (the default): the same code runs on the CPU.
|
|
53
53
|
|
|
54
|
+
## Comparing filter responses
|
|
55
|
+
|
|
56
|
+
Calculate a response on the coefficients' device, or overlay selected panels
|
|
57
|
+
of several filters:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
short = ck.filtering.fir_taps(sampling_rate=1e6, num_taps=21, cutoff=100e3)
|
|
61
|
+
long = ck.filtering.fir_taps(sampling_rate=1e6, num_taps=101, cutoff=100e3)
|
|
62
|
+
response = ck.filtering.filter_response(long, sampling_rate=1e6)
|
|
63
|
+
print(response.frequencies, response.response, response.group_delay)
|
|
64
|
+
|
|
65
|
+
ck.plotting.apply_default_theme()
|
|
66
|
+
_, ax = ck.plotting.plot_filter_response(
|
|
67
|
+
short, sampling_rate=1e6, quantity="magnitude", label="21 taps"
|
|
68
|
+
)
|
|
69
|
+
ck.plotting.plot_filter_response(
|
|
70
|
+
long, sampling_rate=1e6, quantity="magnitude", ax=ax,
|
|
71
|
+
label="101 taps", legend=True, show=True,
|
|
72
|
+
)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
See notebook 03 for filter and smoothing comparisons. The multi-panel
|
|
76
|
+
equalizer, timing, recovery and coding diagnostics take `quantity=` in the
|
|
77
|
+
same way: draw a second result on the returned axis with its own `label=`,
|
|
78
|
+
and `annotations=False` to leave out repeated fits and guides.
|
|
79
|
+
|
|
54
80
|
## CPU and GPU
|
|
55
81
|
|
|
56
82
|
CommKit never picks a device for you: data stays where you put it, and each function runs on the device of its input. A few habits get the speed out of a GPU:
|
|
@@ -80,7 +106,7 @@ CommKit never picks a device for you: data stays where you put it, and each func
|
|
|
80
106
|
| --- | --- |
|
|
81
107
|
| [`commkit.core`](https://github.com/lokgar/commkit/tree/main/commkit/core) | `Signal`, `Reference`, `generate` (uniform, shaped through a distribution matcher, or coded with `code=`), `SingleCarrierFrame` and `Preamble` (pilots, guard intervals, MIMO channels), `extract_payload`. |
|
|
82
108
|
| [`commkit.mapping`](https://github.com/lokgar/commkit/tree/main/commkit/mapping) | `Constellation` (QAM, PSK, PAM, arbitrary points; Gray labels; probabilistic shaping with `.shaped()`), bit mapping, hard demapping, max-log and exact LLRs. |
|
|
83
|
-
| [`commkit.filtering`](https://github.com/lokgar/commkit/tree/main/commkit/filtering) | Pulses (`RRC`, `RC`, `Gaussian`, `Rect`, `SmoothRect`), FIR and IIR design (Butterworth, Chebyshev I/II, elliptic, Bessel), `fir_filter`, `iir_filter`, `matched_filter`, overlap-save, chromatic-dispersion compensation and digital backpropagation (`correct_fiber_propagation`). |
|
|
109
|
+
| [`commkit.filtering`](https://github.com/lokgar/commkit/tree/main/commkit/filtering) | Pulses (`RRC`, `RC`, `Gaussian`, `Rect`, `SmoothRect`), FIR and IIR design (Butterworth, Chebyshev I/II, elliptic, Bessel), numerical filter responses, `fir_filter`, `iir_filter`, `matched_filter`, overlap-save, chromatic-dispersion compensation and digital backpropagation (`correct_fiber_propagation`). |
|
|
84
110
|
| [`commkit.multirate`](https://github.com/lokgar/commkit/blob/main/commkit/multirate.py) | `resample` (fractional), `decimate`, `upsample`, `decimate_to_symbol_rate`. |
|
|
85
111
|
| [`commkit.spectral`](https://github.com/lokgar/commkit/blob/main/commkit/spectral.py) | Welch PSD, spectrograms, frequency shifting, pilot tones. |
|
|
86
112
|
| [`commkit.impairments`](https://github.com/lokgar/commkit/tree/main/commkit/impairments) | AWGN at an Es/N0 (measured as energy per symbol) or of an absolute variance, ASE at an OSNR (`apply_ase`), phase noise, IQ imbalance (blind estimate; Löwdin and Gram-Schmidt correction), chromatic dispersion, PMD and polarization mixing, and nonlinear fiber propagation over amplified spans (split-step Kerr effect with dispersion, loss, PMD and ASE) of a `Fiber`. |
|
|
@@ -4,12 +4,15 @@
|
|
|
4
4
|
(1% of the sampling rate, poles near z = 1) on dual-channel complex64; on the
|
|
5
5
|
GPU it times the double-precision ``cupyx.scipy.signal`` path.
|
|
6
6
|
``bench_moving_average_valid`` smooths a two-channel float32 phase record.
|
|
7
|
+
``bench_filter_response`` evaluates the response and group delay of
|
|
8
|
+
low-pass FIRs on the default 4096-point grid (FFTs) and on an arbitrary
|
|
9
|
+
grid of as many points (Horner evaluation).
|
|
7
10
|
"""
|
|
8
11
|
|
|
9
12
|
import pytest
|
|
10
13
|
from workloads import FS, bps_workload, phase_workload
|
|
11
14
|
|
|
12
|
-
from commkit.filtering import butterworth_sos, iir_filter
|
|
15
|
+
from commkit.filtering import butterworth_sos, filter_response, fir_taps, iir_filter
|
|
13
16
|
from commkit.smoothing import moving_average
|
|
14
17
|
|
|
15
18
|
ROUNDS = dict(rounds=3, warmup_rounds=1, iterations=1)
|
|
@@ -39,3 +42,18 @@ def bench_moving_average_valid(benchmark, backend_device, xp, sync, window):
|
|
|
39
42
|
return out
|
|
40
43
|
|
|
41
44
|
benchmark.pedantic(run, **ROUNDS)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest.mark.parametrize("grid", ["dft", "custom"])
|
|
48
|
+
@pytest.mark.parametrize("num_taps", [31, 257, 4097])
|
|
49
|
+
def bench_filter_response(benchmark, backend_device, xp, sync, num_taps, grid):
|
|
50
|
+
taps = fir_taps(sampling_rate=FS, num_taps=num_taps, cutoff=0.1 * FS)
|
|
51
|
+
taps = xp.asarray(taps, dtype=xp.float32)
|
|
52
|
+
frequencies = xp.linspace(-0.5, 0.5, 4096) if grid == "custom" else None
|
|
53
|
+
|
|
54
|
+
def run():
|
|
55
|
+
result = filter_response(taps, nfft=4096, frequencies=frequencies)
|
|
56
|
+
sync()
|
|
57
|
+
return result
|
|
58
|
+
|
|
59
|
+
benchmark.pedantic(run, **ROUNDS)
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
- Design: :func:`fir_taps` (window method) and the IIR designs in
|
|
7
7
|
second-order sections, :func:`butterworth_sos`, :func:`chebyshev1_sos`,
|
|
8
8
|
:func:`chebyshev2_sos`, :func:`elliptic_sos` and :func:`bessel_sos`.
|
|
9
|
+
- Responses: :func:`filter_response` (a :class:`FilterResponse`),
|
|
10
|
+
:func:`impulse_response` and :func:`step_response`, computed on the
|
|
11
|
+
coefficients' device.
|
|
9
12
|
- Application: :func:`fir_filter` (FFT convolution, optionally circular),
|
|
10
13
|
:func:`ols_fir_filter` (overlap-save, for long filters), :func:`iir_filter`
|
|
11
14
|
and :func:`matched_filter`.
|
|
@@ -40,8 +43,10 @@ from .pulses import (
|
|
|
40
43
|
Rect,
|
|
41
44
|
SmoothRect,
|
|
42
45
|
)
|
|
46
|
+
from .response import FilterResponse, filter_response, impulse_response, step_response
|
|
43
47
|
|
|
44
48
|
__all__ = [
|
|
49
|
+
"FilterResponse",
|
|
45
50
|
"Gaussian",
|
|
46
51
|
"Pulse",
|
|
47
52
|
"RC",
|
|
@@ -55,9 +60,12 @@ __all__ = [
|
|
|
55
60
|
"correct_chromatic_dispersion",
|
|
56
61
|
"correct_fiber_propagation",
|
|
57
62
|
"elliptic_sos",
|
|
63
|
+
"filter_response",
|
|
58
64
|
"fir_filter",
|
|
59
65
|
"fir_taps",
|
|
60
66
|
"iir_filter",
|
|
67
|
+
"impulse_response",
|
|
61
68
|
"matched_filter",
|
|
62
69
|
"ols_fir_filter",
|
|
70
|
+
"step_response",
|
|
63
71
|
]
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""Frequency, impulse and step responses of digital filters.
|
|
2
|
+
|
|
3
|
+
:func:`filter_response` (returning a :class:`FilterResponse`),
|
|
4
|
+
:func:`impulse_response` and :func:`step_response` accept FIR taps,
|
|
5
|
+
second-order sections, or a ``(b, a)`` transfer function, and compute on the
|
|
6
|
+
coefficients' device. Frequencies are in cycles per sample unless a sampling
|
|
7
|
+
rate in Hz is given.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
import scipy
|
|
15
|
+
|
|
16
|
+
from .. import _fft
|
|
17
|
+
from ..backend import ArrayType, dispatch, to_device
|
|
18
|
+
from ..math import _phasor
|
|
19
|
+
from .linear import iir_filter
|
|
20
|
+
|
|
21
|
+
__all__ = ["FilterResponse", "filter_response", "impulse_response", "step_response"]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class FilterResponse:
|
|
26
|
+
"""Frequency response and group delay of a digital filter.
|
|
27
|
+
|
|
28
|
+
Attributes
|
|
29
|
+
----------
|
|
30
|
+
frequencies : ndarray
|
|
31
|
+
Frequency grid in Hz, or cycles per sample without a sampling rate.
|
|
32
|
+
response : ndarray
|
|
33
|
+
Complex frequency response on the input device.
|
|
34
|
+
group_delay : ndarray
|
|
35
|
+
Group delay in seconds, or samples without a sampling rate. Undefined
|
|
36
|
+
values at response zeros are ``nan``.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
frequencies: ArrayType
|
|
40
|
+
response: ArrayType
|
|
41
|
+
group_delay: ArrayType
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _system(system: Any) -> tuple[str, ArrayType, ArrayType | None, Any, Any]:
|
|
45
|
+
"""Validate a filter representation and select its device."""
|
|
46
|
+
if isinstance(system, tuple):
|
|
47
|
+
if len(system) != 2:
|
|
48
|
+
raise ValueError("system must be FIR taps, SOS, or a (b, a) pair")
|
|
49
|
+
b, xp, sp = dispatch(system[0])
|
|
50
|
+
a, axp, _ = dispatch(system[1])
|
|
51
|
+
if xp is not axp:
|
|
52
|
+
raise ValueError("system coefficients must be on the same device")
|
|
53
|
+
if b.ndim != 1 or a.ndim != 1 or not b.size or not a.size:
|
|
54
|
+
raise ValueError("system coefficients b and a must be nonempty 1-D arrays")
|
|
55
|
+
return "ba", b, a, xp, sp
|
|
56
|
+
x, xp, sp = dispatch(system)
|
|
57
|
+
if x.ndim == 1 and x.size:
|
|
58
|
+
return "fir", x, None, xp, sp
|
|
59
|
+
if x.ndim == 2 and x.shape[0] and x.shape[1] == 6:
|
|
60
|
+
return "sos", x, None, xp, sp
|
|
61
|
+
raise ValueError("system must be nonempty FIR taps, SOS, or a (b, a) pair")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def filter_response(
|
|
65
|
+
system: Any,
|
|
66
|
+
*,
|
|
67
|
+
nfft: int = 2048,
|
|
68
|
+
sampling_rate: float | None = None,
|
|
69
|
+
frequencies: ArrayType | None = None,
|
|
70
|
+
) -> FilterResponse:
|
|
71
|
+
"""Calculate the frequency response and group delay of a digital filter.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
system : array_like or (b, a) tuple
|
|
76
|
+
FIR taps, second-order sections ``(S, 6)``, or transfer coefficients.
|
|
77
|
+
nfft : int, default 2048
|
|
78
|
+
Number of frequency points from DC to just below Nyquist when
|
|
79
|
+
``frequencies`` is not given.
|
|
80
|
+
sampling_rate : float, optional
|
|
81
|
+
Sampling rate in Hz. Without it, frequencies are cycles per sample
|
|
82
|
+
and group delays are samples; otherwise they are Hz and seconds.
|
|
83
|
+
frequencies : array_like, optional
|
|
84
|
+
Frequencies to evaluate, in Hz or cycles per sample when no sampling
|
|
85
|
+
rate is given. Must be one-dimensional and on the coefficients' device.
|
|
86
|
+
When omitted, a uniform grid of ``nfft`` points is used.
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
FilterResponse
|
|
91
|
+
Frequency grid, complex response and group delay on the input device.
|
|
92
|
+
|
|
93
|
+
Notes
|
|
94
|
+
-----
|
|
95
|
+
The group delay is the sum of the section delays for SOS filters. It is
|
|
96
|
+
undefined at a zero or pole and is returned as ``nan`` there. Values
|
|
97
|
+
indistinguishable from zero in the coefficient precision are also marked
|
|
98
|
+
undefined. Frequency grids and phase wrapping use float64; coefficient
|
|
99
|
+
arithmetic follows the coefficient dtype.
|
|
100
|
+
"""
|
|
101
|
+
if not isinstance(nfft, (int, np.integer)) or nfft <= 0:
|
|
102
|
+
raise ValueError("nfft must be a positive integer")
|
|
103
|
+
if sampling_rate is not None and (
|
|
104
|
+
not np.isfinite(sampling_rate) or sampling_rate <= 0
|
|
105
|
+
):
|
|
106
|
+
raise ValueError("sampling_rate must be positive and finite")
|
|
107
|
+
kind, b, a, xp, _ = _system(system)
|
|
108
|
+
scale = float(sampling_rate) if sampling_rate is not None else 1.0
|
|
109
|
+
if frequencies is None:
|
|
110
|
+
f = xp.arange(nfft, dtype=xp.float64) * (scale / (2 * nfft))
|
|
111
|
+
else:
|
|
112
|
+
f, fxp, _ = dispatch(frequencies)
|
|
113
|
+
if fxp is not xp:
|
|
114
|
+
raise ValueError("frequencies must be on the system's device")
|
|
115
|
+
if f.ndim != 1 or f.size == 0 or xp.iscomplexobj(f):
|
|
116
|
+
raise ValueError("frequencies must be a nonempty real 1-D array")
|
|
117
|
+
if not bool(xp.all(xp.isfinite(f))):
|
|
118
|
+
raise ValueError("frequencies must be finite")
|
|
119
|
+
f = f.astype(xp.float64, copy=False)
|
|
120
|
+
# Wrap before forming the phase: multiplying a large frequency by 2π
|
|
121
|
+
# first discards its fractional cycle, especially in single precision.
|
|
122
|
+
cycles = xp.remainder(f, scale) / scale
|
|
123
|
+
dtype = xp.result_type(b.dtype, a.dtype if a is not None else b.dtype, xp.complex64)
|
|
124
|
+
z = _phasor(-2 * np.pi * cycles, dtype, xp)
|
|
125
|
+
|
|
126
|
+
def evaluate(coeff: ArrayType) -> tuple[ArrayType, ArrayType]:
|
|
127
|
+
coeff = coeff.astype(dtype, copy=False)
|
|
128
|
+
weighted_coeff = coeff * xp.arange(coeff.size, dtype=coeff.real.dtype)
|
|
129
|
+
if coeff.size > 16 and frequencies is None:
|
|
130
|
+
# Frequencies lie on a 2*nfft DFT grid. Folding coefficients
|
|
131
|
+
# preserves their response there even when the filter is longer.
|
|
132
|
+
fft_size = 2 * nfft
|
|
133
|
+
if coeff.size > fft_size:
|
|
134
|
+
pad = (-coeff.size) % fft_size
|
|
135
|
+
coeff = xp.pad(coeff, (0, pad)).reshape(-1, fft_size).sum(axis=0)
|
|
136
|
+
weighted_coeff = (
|
|
137
|
+
xp.pad(weighted_coeff, (0, pad)).reshape(-1, fft_size).sum(axis=0)
|
|
138
|
+
)
|
|
139
|
+
value = _fft.fft(coeff, n=fft_size)[:nfft]
|
|
140
|
+
weighted = _fft.fft(weighted_coeff, n=fft_size)[:nfft]
|
|
141
|
+
else:
|
|
142
|
+
value = xp.polyval(coeff[::-1], z)
|
|
143
|
+
weighted = xp.polyval(weighted_coeff[::-1], z)
|
|
144
|
+
return value, weighted
|
|
145
|
+
|
|
146
|
+
response = xp.ones(f.size, dtype=dtype)
|
|
147
|
+
delay = xp.zeros(f.size, dtype=xp.empty((), dtype=dtype).real.dtype)
|
|
148
|
+
sections = (
|
|
149
|
+
((b[i, :3], b[i, 3:]) for i in range(b.shape[0]))
|
|
150
|
+
if kind == "sos"
|
|
151
|
+
else ((b, a),)
|
|
152
|
+
)
|
|
153
|
+
for num, den in sections:
|
|
154
|
+
h, hn = evaluate(num)
|
|
155
|
+
if den is None:
|
|
156
|
+
d = xp.ones_like(h)
|
|
157
|
+
dn = xp.zeros_like(h)
|
|
158
|
+
else:
|
|
159
|
+
d, dn = evaluate(den)
|
|
160
|
+
# Evaluating a mathematical zero on the unit circle can leave a
|
|
161
|
+
# rounding-sized residual (e.g. sin(π)); its delay is not meaningful.
|
|
162
|
+
eps = xp.finfo(delay.dtype).eps
|
|
163
|
+
h_floor = eps * xp.sum(xp.abs(num))
|
|
164
|
+
d_floor = eps * xp.sum(xp.abs(den)) if den is not None else 0.0
|
|
165
|
+
valid = (xp.abs(h) > h_floor) & (xp.abs(d) > d_floor)
|
|
166
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
167
|
+
response *= h / d
|
|
168
|
+
delay += xp.where(valid, xp.real(hn / h - dn / d), xp.nan)
|
|
169
|
+
return FilterResponse(f, response, delay / scale)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _time_response(system: Any, num_samples: int, step: bool) -> ArrayType:
|
|
173
|
+
"""Impulse (``step=False``) or unit-step response of ``num_samples``."""
|
|
174
|
+
if not isinstance(num_samples, (int, np.integer)) or num_samples <= 0:
|
|
175
|
+
raise ValueError("num_samples must be a positive integer")
|
|
176
|
+
kind, b, a, xp, sp = _system(system)
|
|
177
|
+
if kind == "ba" and a is not None and a.size == 1:
|
|
178
|
+
kind, b = "fir", b / a[0]
|
|
179
|
+
dtype = xp.result_type(b.dtype, a.dtype if a is not None else b.dtype, xp.float32)
|
|
180
|
+
if kind == "fir":
|
|
181
|
+
# The impulse response of an FIR filter is its taps, the step response
|
|
182
|
+
# their running sum: no recursion, so nothing to filter.
|
|
183
|
+
h = xp.zeros(num_samples, dtype=dtype)
|
|
184
|
+
count = min(num_samples, b.size)
|
|
185
|
+
h[:count] = b[:count]
|
|
186
|
+
return xp.cumsum(h) if step else h
|
|
187
|
+
if step:
|
|
188
|
+
x = xp.ones(num_samples, dtype=dtype)
|
|
189
|
+
else:
|
|
190
|
+
x = xp.zeros(num_samples, dtype=dtype)
|
|
191
|
+
x[0] = 1
|
|
192
|
+
if x.dtype.kind == "c":
|
|
193
|
+
# iir_filter takes real sections; complex coefficients run through
|
|
194
|
+
# SciPy's direct form in their own precision.
|
|
195
|
+
if kind == "sos":
|
|
196
|
+
return sp.signal.sosfilt(b, x)
|
|
197
|
+
return sp.signal.lfilter(b, a, x)
|
|
198
|
+
# The sections go to the host, as iir_filter takes them (its kernels and
|
|
199
|
+
# their state transitions read float64 host sections; tf2sos is SciPy's
|
|
200
|
+
# root finding): a few coefficients, while x stays on its device.
|
|
201
|
+
if kind == "sos":
|
|
202
|
+
sos = np.asarray(to_device(b, "cpu"), dtype=np.float64)
|
|
203
|
+
sos = sos / sos[:, 3:4]
|
|
204
|
+
else:
|
|
205
|
+
sos = scipy.signal.tf2sos(
|
|
206
|
+
np.asarray(to_device(b, "cpu"), dtype=np.float64),
|
|
207
|
+
np.asarray(to_device(a, "cpu"), dtype=np.float64),
|
|
208
|
+
)
|
|
209
|
+
# iir_filter keeps the sections and the state in double precision, which
|
|
210
|
+
# poles near z = 1 need, and the samples in their own precision.
|
|
211
|
+
return iir_filter(x, sos)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def impulse_response(system: Any, *, num_samples: int = 500) -> ArrayType:
|
|
215
|
+
"""Calculate a filter's causal impulse response.
|
|
216
|
+
|
|
217
|
+
Parameters
|
|
218
|
+
----------
|
|
219
|
+
system : array_like or (b, a) tuple
|
|
220
|
+
FIR taps, second-order sections, or transfer coefficients.
|
|
221
|
+
num_samples : int, default 500
|
|
222
|
+
Number of output samples, including zeros after an FIR response.
|
|
223
|
+
|
|
224
|
+
Returns
|
|
225
|
+
-------
|
|
226
|
+
ndarray
|
|
227
|
+
Impulse response, ``(num_samples,)``, on the coefficients' device.
|
|
228
|
+
|
|
229
|
+
Notes
|
|
230
|
+
-----
|
|
231
|
+
Real IIR filters run through :func:`iir_filter`, with the sections and
|
|
232
|
+
the state in double precision whatever the coefficients' precision (a
|
|
233
|
+
``(b, a)`` pair is first converted to sections); the response keeps the
|
|
234
|
+
coefficients' precision, at least single. Complex IIR coefficients run
|
|
235
|
+
through SciPy's direct form in their own precision.
|
|
236
|
+
"""
|
|
237
|
+
return _time_response(system, num_samples, False)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def step_response(system: Any, *, num_samples: int = 500) -> ArrayType:
|
|
241
|
+
"""Calculate a filter's causal unit-step response.
|
|
242
|
+
|
|
243
|
+
Parameters
|
|
244
|
+
----------
|
|
245
|
+
system : array_like or (b, a) tuple
|
|
246
|
+
FIR taps, second-order sections, or transfer coefficients.
|
|
247
|
+
num_samples : int, default 500
|
|
248
|
+
Number of output samples.
|
|
249
|
+
|
|
250
|
+
Returns
|
|
251
|
+
-------
|
|
252
|
+
ndarray
|
|
253
|
+
Step response, ``(num_samples,)``, on the coefficients' device.
|
|
254
|
+
|
|
255
|
+
Notes
|
|
256
|
+
-----
|
|
257
|
+
Computed as :func:`impulse_response` is; for FIR taps it is their running
|
|
258
|
+
sum.
|
|
259
|
+
"""
|
|
260
|
+
return _time_response(system, num_samples, True)
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Every public function but :func:`apply_default_theme` is a ``plot_*`` function
|
|
4
4
|
that draws a Signal, an array or a result object (an estimate, an
|
|
5
|
-
:class:`~commkit.equalization.EqualizerResult`, an analysis summary)
|
|
6
|
-
|
|
7
|
-
what is drawn is transferred.
|
|
5
|
+
:class:`~commkit.equalization.EqualizerResult`, an analysis summary). A plot
|
|
6
|
+
that needs a calculation calls the corresponding public compute function;
|
|
7
|
+
device arrays are reduced on the device and only what is drawn is transferred.
|
|
8
|
+
Tick labels use SI prefixes.
|
|
8
9
|
:func:`apply_default_theme` sets the library's Matplotlib style; importing the
|
|
9
10
|
package changes no Matplotlib settings.
|
|
10
11
|
|