commkit 3.1.1__tar.gz → 3.1.2__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.1.1 → commkit-3.1.2}/CHANGELOG.md +14 -0
- {commkit-3.1.1 → commkit-3.1.2}/PKG-INFO +1 -1
- {commkit-3.1.1 → commkit-3.1.2}/commkit/__init__.py +1 -1
- commkit-3.1.2/commkit/impairments/noise.py +229 -0
- {commkit-3.1.1 → commkit-3.1.2}/pyproject.toml +2 -2
- {commkit-3.1.1 → commkit-3.1.2}/tests/impairments/test_noise.py +85 -1
- {commkit-3.1.1 → commkit-3.1.2}/uv.lock +1 -1
- commkit-3.1.1/commkit/impairments/noise.py +0 -184
- {commkit-3.1.1 → commkit-3.1.2}/.gitattributes +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/.github/workflows/ci.yml +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/.github/workflows/publish.yml +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/.gitignore +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/.python-version +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/AGENTS.md +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/LICENSE +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/README.md +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/benchmarks/baselines/Linux-CPython-3.14-64bit/0002_pre_v2.json +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/benchmarks/baselines/Linux-CPython-3.14-64bit/0003_v2_0.json +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/benchmarks/baselines/Linux-CPython-3.14-64bit/0004_fiber_dbp.json +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/benchmarks/baselines/Linux-CPython-3.14-64bit/0005_gpu_decoder_path.json +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/benchmarks/baselines/Linux-CPython-3.14-64bit/0006_perf_cleanup.json +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/benchmarks/baselines/Linux-CPython-3.14-64bit/0007_v3_0.json +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/benchmarks/baselines/Linux-CPython-3.14-64bit/0008_v3_1.json +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/benchmarks/bench_block_blind.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/benchmarks/bench_block_lms.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/benchmarks/bench_bps.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/benchmarks/bench_coding.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/benchmarks/bench_equalizers.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/benchmarks/bench_fiber.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/benchmarks/bench_filters.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/benchmarks/bench_import.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/benchmarks/bench_llr.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/benchmarks/bench_mapping.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/benchmarks/bench_signal_pipeline.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/benchmarks/bench_sync_misc.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/benchmarks/benchutils.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/benchmarks/conftest.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/benchmarks/record_baseline.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/benchmarks/workloads.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/_array.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/_circular.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/_constants.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/_cuda/__init__.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/_cuda/compiler.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/_cuda/graphs.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/_cuda/src/algebraic.cu +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/_cuda/src/bps_anchor.cu +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/_cuda/src/bps_min_d2.cu +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/_cuda/src/cs_block.cu +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/_cuda/src/iir.cu +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/_cuda/src/ldpc.cu +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/_cuda/src/llr.cu +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/_cuda/src/selftest.cu +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/_dispersion.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/_fft.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/_fiber.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/_overlap_save.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/_random.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/_sequences.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/analysis/__init__.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/analysis/_common.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/analysis/allan.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/analysis/drift.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/analysis/fm_noise.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/analysis/interferometry.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/analysis/linewidth/__init__.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/analysis/linewidth/dsh.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/analysis/linewidth/phase.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/analysis/linewidth/result.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/analysis/trajectory.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/backend.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/coding/__init__.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/coding/_galois.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/coding/_gf2.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/coding/_ldpc_tables.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/coding/_numba_loops.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/coding/algebraic.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/coding/base.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/coding/convolutional.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/coding/crc.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/coding/interleaving.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/coding/ldpc.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/coding/linear.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/coding/polar.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/coding/product.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/coding/ratematch.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/coding/shaping.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/coding/turbo.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/core/__init__.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/core/_signal_adapter.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/core/frame.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/core/generation.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/core/signal.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/equalization/__init__.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/equalization/_block/__init__.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/equalization/_block/_blind.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/equalization/_block/_dd.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/equalization/_block/_engine.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/equalization/_common.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/equalization/_kernels_numba.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/equalization/_numba_loops.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/equalization/blind.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/equalization/linear.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/equalization/pilots.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/equalization/polarization.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/equalization/result.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/equalization/sequential/__init__.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/equalization/sequential/_blind.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/equalization/sequential/_dd.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/equalization/sequential/_setup.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/filtering/__init__.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/filtering/fiber.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/filtering/linear.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/filtering/pulses.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/frequency.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/impairments/__init__.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/impairments/channel/__init__.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/impairments/channel/linear.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/impairments/channel/nonlinear.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/impairments/frontend.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/impairments/source.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/io.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/logger.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/mapping/__init__.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/mapping/_numba_llr.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/mapping/bits.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/mapping/constellation.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/mapping/gray.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/mapping/llr.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/mapping/shaping.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/math.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/metrics.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/multirate.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/plotting/__init__.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/plotting/analysis.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/plotting/constellation.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/plotting/equalizer.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/plotting/eye.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/plotting/filter_response.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/plotting/spectral.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/plotting/sync.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/plotting/theme.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/plotting/waveform.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/py.typed +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/recovery/__init__.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/recovery/_common.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/recovery/bps.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/recovery/carrier_phase.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/recovery/corrections.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/recovery/pilots.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/recovery/pll.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/recovery/tikhonov.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/recovery/viterbi_viterbi.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/smoothing.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/spectral.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/commkit/timing.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/00-getting-started.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/01-constellations-and-bit-mapping.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/02-spectral-analysis.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/03-digital-filters.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/04-pulse-shaping-and-matched-filtering.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/05-multirate-and-resampling.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/06-awgn-and-performance-metrics.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/07-channel-coding.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/08-probabilistic-shaping-and-coded-modulation.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/09-laser-phase-noise.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/10-front-end-impairments.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/11-frame-synchronization-and-timing.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/12-frequency-offset-estimation.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/13-blind-carrier-phase-recovery.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/14-pilot-aided-carrier-recovery.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/15-linear-channel-equalization.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/16-adaptive-equalization.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/17-blind-equalization.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/18-polarization-multiplexing.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/19-chromatic-dispersion.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/20-nonlinear-fiber-and-backpropagation.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/21-fec-in-optical-standards.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/22-coherent-transceiver-end-to-end.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/23-carrier-phase-characterization.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/24-laser-linewidth-self-heterodyne.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/25-laser-linewidth-short-delay-and-flicker.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/26-laser-linewidth-self-homodyne-iq.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/27-measurement-template-self-heterodyne.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/28-measurement-template-self-homodyne-iq.ipynb +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/examples/README.md +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/analysis/test_allan.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/analysis/test_drift.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/analysis/test_fm_noise.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/analysis/test_interferometry.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/analysis/test_linewidth.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/analysis/test_trajectory.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/coding/test_algebraic.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/coding/test_coding_base.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/coding/test_convolutional.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/coding/test_crc.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/coding/test_gf2.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/coding/test_interleaving.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/coding/test_ldpc.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/coding/test_ldpc_kernel.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/coding/test_ldpc_tables.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/coding/test_linear_codes.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/coding/test_polar.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/coding/test_product.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/coding/test_ratematch.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/coding/test_shaping.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/coding/test_turbo.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/common/__init__.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/common/coding.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/common/conversions.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/common/kernel_utils.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/common/memory.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/common/metrics.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/common/periodic.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/common/reference_impl.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/common/signals.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/conftest.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/core/test_frame.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/core/test_generation.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/core/test_psqam.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/core/test_signal.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/core/test_signal_adapter.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/core/test_signal_mimo.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/equalization/test_blind.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/equalization/test_block.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/equalization/test_bps_kernel.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/equalization/test_cpr.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/equalization/test_cs_kernel.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/equalization/test_equalizer_oracles.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/equalization/test_linear.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/equalization/test_mimo.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/equalization/test_polarization.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/equalization/test_sequential.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/equalization/test_state.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/equalization/test_winit.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/impairments/channel/test_channel_linear.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/impairments/channel/test_channel_nonlinear.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/impairments/test_frontend.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/impairments/test_source.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/mapping/test_bits.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/mapping/test_constellation.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/mapping/test_gray.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/mapping/test_llr.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/plotting/test_plot_analysis.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/plotting/test_plot_constellation.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/plotting/test_plot_equalizer.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/plotting/test_plot_eye.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/plotting/test_plot_filter_response.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/plotting/test_plot_spectral.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/plotting/test_plot_sync.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/plotting/test_plot_theme.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/plotting/test_plot_waveform.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/plugins/__init__.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/plugins/precision_trace.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/plugins/transfer_trace.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/recovery/test_bps.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/recovery/test_corrections.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/recovery/test_joint.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/recovery/test_pilots.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/recovery/test_pll.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/recovery/test_recovery_oracles.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/recovery/test_tikhonov.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/recovery/test_viterbi_viterbi.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/test_api_contracts.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/test_array.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/test_backend.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/test_cuda_infra.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/test_dispersion.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/test_examples.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/test_fiber.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/test_filtering.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/test_frequency.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/test_import_hygiene.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/test_io.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/test_logger.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/test_math.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/test_metrics.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/test_multirate.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/test_pulse_shaping.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/test_signal_pipeline.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/test_smoothing.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/test_spectral.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/test_timing.py +0 -0
- {commkit-3.1.1 → commkit-3.1.2}/tests/test_working_memory.py +0 -0
|
@@ -4,6 +4,20 @@ Notable changes to CommKit, newest first. Performance figures are from the refer
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [3.1.2] - 2026-10-29
|
|
8
|
+
|
|
9
|
+
`apply_awgn` without hidden assumptions about what a channel is: Es/N0 holds per channel, and the OSNR counts the ASE of the modes the medium guides instead of assuming the two polarizations of a single-mode fiber.
|
|
10
|
+
|
|
11
|
+
### Changes that alter results
|
|
12
|
+
|
|
13
|
+
- **`apply_awgn(esn0_db=)` gives every channel the requested Es/N0.** The default noise level comes from each channel's own mean power; it used to come from the mean over all channels, so with unequal channel powers a stronger channel got a better Es/N0 than requested and a weaker one a worse. Equal powers give the same result as before. Pass a scalar `signal_power` for the old behavior, one absolute noise level shared by every channel.
|
|
14
|
+
- **`apply_awgn(osnr_db=)` counts the ASE of `noise_modes` modes, `max(C, 2)` by default.** One or two channels are unchanged (both polarizations of a single-mode fiber). More channels, such as the modes of a few-mode fiber or the cores of a multicore fiber, each count as a mode; they used to share the ASE of two polarizations, which put C/2 times too much noise on each.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- `apply_awgn(noise_modes=)` for a medium that guides modes without signal, e.g. `noise_modes=6` for four channels in a three-mode fiber.
|
|
19
|
+
- `apply_awgn(signal_power=)` accepts one power per channel, `(C,)`.
|
|
20
|
+
|
|
7
21
|
## [3.1.1] - 2026-10-29
|
|
8
22
|
|
|
9
23
|
API consistency. One name per concept and a unit in every size, defaults that model the physics, physical units where the rest of the API has them, and features that use what the Signal and the frame already carry: the optical carrier for dispersion, fiber and OSNR noise, the frame's pilots for the pilot-aided algorithms, the code of the reference for post-FEC metrics. Most changes are renames that fail loudly with `TypeError`; the four default changes and the speed of light alter numbers and are listed first.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: commkit
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.2
|
|
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
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""Additive white Gaussian noise: :func:`apply_awgn`."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from .._random import RNG, as_generator, complex_normal, standard_normal
|
|
6
|
+
from ..backend import ArrayType, dispatch
|
|
7
|
+
from ..core._signal_adapter import S, adapt_signal
|
|
8
|
+
from ..logger import logger
|
|
9
|
+
from ..math import db_to_linear
|
|
10
|
+
|
|
11
|
+
__all__ = ["apply_awgn"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def apply_awgn(
|
|
15
|
+
samples: S,
|
|
16
|
+
*,
|
|
17
|
+
esn0_db: float | None = None,
|
|
18
|
+
osnr_db: float | None = None,
|
|
19
|
+
sps: float | None = None,
|
|
20
|
+
signal_power: float | ArrayType | None = None,
|
|
21
|
+
reference_bandwidth: float = 12.5e9,
|
|
22
|
+
noise_modes: int | None = None,
|
|
23
|
+
rng: RNG = None,
|
|
24
|
+
) -> S:
|
|
25
|
+
"""Add white Gaussian noise at an Es/N0, or at an OSNR for an optical field.
|
|
26
|
+
|
|
27
|
+
Powers are per sample and per channel, in the squared units of the
|
|
28
|
+
samples: ``P_c = mean(|x_c[n]|²)`` over the samples of channel ``c``.
|
|
29
|
+
A symbol lasts ``T = sps / f_s``, so its energy is
|
|
30
|
+
``Es_c = P_c · T = sps · P_c / f_s``; white noise of variance ``σ²``
|
|
31
|
+
per sample fills the band ``f_s`` with the density ``N0 = σ² / f_s``.
|
|
32
|
+
Hence ``Es/N0 = sps · P_c / σ²``: in sample units, ``Es`` is the power
|
|
33
|
+
summed over the ``sps`` samples of a symbol and ``N0`` the noise
|
|
34
|
+
variance of one sample.
|
|
35
|
+
|
|
36
|
+
With ``esn0_db`` every channel gets the requested Es/N0 on its own:
|
|
37
|
+
channel ``c`` gets noise of variance ``σ_c² = sps · P_c / (Es/N0)`` per
|
|
38
|
+
sample. That is the Es/N0 per symbol after a matched filter and
|
|
39
|
+
decimation, whatever the oversampling. A channel can be anything with
|
|
40
|
+
its own receiver noise: a polarization, an antenna, a wire pair, a lane.
|
|
41
|
+
|
|
42
|
+
With ``osnr_db`` (an optical Signal) the noise is amplified spontaneous
|
|
43
|
+
emission specified as optical links are: the total signal power of all
|
|
44
|
+
channels over the ASE power in ``reference_bandwidth``, summed over the
|
|
45
|
+
``M = noise_modes`` modes the medium guides (polarizations included).
|
|
46
|
+
ASE has the same density in every mode and is white over the simulated
|
|
47
|
+
band ``f_s``, so complex noise of variance ``σ²`` per mode puts
|
|
48
|
+
``M σ² B_ref / f_s`` into ``B_ref``; every channel therefore gets the
|
|
49
|
+
same
|
|
50
|
+
|
|
51
|
+
σ² = (Σ_c P_c) · f_s / (M · B_ref · OSNR),
|
|
52
|
+
|
|
53
|
+
which for equal powers gives ``Es/N0 = M B_ref / (C R_s) · OSNR`` per
|
|
54
|
+
channel, whatever the oversampling: the textbook
|
|
55
|
+
``2 B_ref / (p R_s) · OSNR`` for ``p`` polarizations of a single-mode
|
|
56
|
+
fiber.
|
|
57
|
+
|
|
58
|
+
Parameters
|
|
59
|
+
----------
|
|
60
|
+
samples : array_like or Signal
|
|
61
|
+
Input samples, time on the last axis, ``(N,)`` or ``(C, N)``; for
|
|
62
|
+
``osnr_db`` a complex optical Signal (``domain="OPT"``) with one
|
|
63
|
+
channel per guided mode it occupies (polarization, spatial mode or
|
|
64
|
+
core).
|
|
65
|
+
esn0_db : float, optional
|
|
66
|
+
Symbol energy over noise spectral density, Es/N0, in dB, for each
|
|
67
|
+
channel.
|
|
68
|
+
osnr_db : float, optional
|
|
69
|
+
Optical signal-to-noise ratio in dB in ``reference_bandwidth``,
|
|
70
|
+
instead of ``esn0_db``.
|
|
71
|
+
sps : float, optional
|
|
72
|
+
Samples per symbol, for ``esn0_db``. Taken from the Signal; required
|
|
73
|
+
for array input. A value that disagrees with the Signal raises.
|
|
74
|
+
signal_power : float or array_like, optional
|
|
75
|
+
The power ``P_c`` per sample of each channel, ``mean(|x_c|²)`` in
|
|
76
|
+
the squared units of the samples, that the Es/N0 or OSNR refers to:
|
|
77
|
+
``Es_c = sps · P_c``. A scalar is the power of *each* channel (not
|
|
78
|
+
their sum or a total), so every channel gets the same absolute
|
|
79
|
+
noise level, as identical receivers with one noise floor; ``(C,)``
|
|
80
|
+
gives one power per channel. Defaults to each channel's measured
|
|
81
|
+
mean power over the whole record, which counts any silent stretch.
|
|
82
|
+
Pass the nominal power instead when that is the reference: e.g.
|
|
83
|
+
``signal_power=1.0`` for unit-power symbols at one sample per
|
|
84
|
+
symbol sent in bursts separated by zero guard intervals, whose
|
|
85
|
+
measured mean falls below the power of the symbols. For a
|
|
86
|
+
pulse-shaped signal the per-sample power of unit-energy symbols is
|
|
87
|
+
``1 / sps`` (``signal_power=1 / sig.sps``). With ``osnr_db`` the
|
|
88
|
+
signal power is the sum over channels, ``C · signal_power`` for a
|
|
89
|
+
scalar.
|
|
90
|
+
reference_bandwidth : float, default 12.5e9
|
|
91
|
+
Noise reference bandwidth ``B_ref`` of ``osnr_db`` in Hz. The
|
|
92
|
+
standard reference is 0.1 nm of optical bandwidth, ``c · 0.1 nm /
|
|
93
|
+
λ²``: 12.5 GHz in the C band (12.48 GHz at 1550 nm), 17.5 GHz at
|
|
94
|
+
1310 nm. Outside the C band pass the value for the carrier, e.g.
|
|
95
|
+
``reference_bandwidth=17.5e9`` for an O-band OSNR in 0.1 nm.
|
|
96
|
+
noise_modes : int, optional
|
|
97
|
+
For ``osnr_db``: the number ``M`` of guided modes, polarizations
|
|
98
|
+
included, whose ASE the OSNR counts. Defaults to ``max(C, 2)``: both
|
|
99
|
+
polarizations of a single-mode fiber for one or two channels (an
|
|
100
|
+
OSNR meter's definition; a single-polarization signal receives the
|
|
101
|
+
half of the ASE in its own polarization), and one mode per channel
|
|
102
|
+
beyond that (few-mode or multicore fiber with every mode carrying
|
|
103
|
+
signal). Pass it when the medium guides modes that carry no signal,
|
|
104
|
+
e.g. ``noise_modes=6`` for 4 channels in a 3-mode fiber.
|
|
105
|
+
rng : int, numpy.random.Generator or None
|
|
106
|
+
Random source. The noise is drawn on the data's device: from this
|
|
107
|
+
Generator on the CPU, and on the GPU from a CuPy Generator seeded from
|
|
108
|
+
it, so a seed is reproducible per device.
|
|
109
|
+
|
|
110
|
+
Returns
|
|
111
|
+
-------
|
|
112
|
+
ndarray or Signal
|
|
113
|
+
The noisy samples, same shape, dtype and device as the input.
|
|
114
|
+
|
|
115
|
+
Raises
|
|
116
|
+
------
|
|
117
|
+
ValueError
|
|
118
|
+
If neither or both of ``esn0_db`` and ``osnr_db`` are given,
|
|
119
|
+
``osnr_db`` is given for anything but a complex optical Signal,
|
|
120
|
+
``reference_bandwidth`` is not positive, ``noise_modes`` is not an
|
|
121
|
+
integer of at least ``C``, or a per-channel ``signal_power`` does not
|
|
122
|
+
have ``C`` entries.
|
|
123
|
+
|
|
124
|
+
Notes
|
|
125
|
+
-----
|
|
126
|
+
Complex input gets circular complex noise, its variance split equally
|
|
127
|
+
between I and Q; real input gets real noise of the full variance. At
|
|
128
|
+
``sps = 1`` Es/N0 equals the per-sample SNR.
|
|
129
|
+
|
|
130
|
+
OSNR is a property of the whole optical field, so pass every channel in
|
|
131
|
+
one call: calling once per polarization of a dual-polarization signal
|
|
132
|
+
counts only that polarization's power and adds half the intended
|
|
133
|
+
noise. Modes left out of a simulation carry no noise, which matters
|
|
134
|
+
for a receiver without a polarizer (direct detection, where the
|
|
135
|
+
orthogonal polarization adds ASE-ASE beat noise); model such a link
|
|
136
|
+
with both polarizations.
|
|
137
|
+
|
|
138
|
+
Examples
|
|
139
|
+
--------
|
|
140
|
+
>>> sig = generate(Constellation.pam(4), 1000, symbol_rate=1e6, sps=4)
|
|
141
|
+
>>> noisy = apply_awgn(sig.samples, esn0_db=20, sps=4)
|
|
142
|
+
>>> noisy = apply_awgn(sig, esn0_db=20) # Signal input: sps taken from sig
|
|
143
|
+
>>> field = sig.replace(domain="OPT", center_frequency=193.4e12)
|
|
144
|
+
>>> noisy = apply_awgn(field, osnr_db=18) # in 12.5 GHz, both polarizations
|
|
145
|
+
"""
|
|
146
|
+
signal_adapter = adapt_signal(samples, function_name="apply_awgn()")
|
|
147
|
+
if (esn0_db is None) == (osnr_db is None):
|
|
148
|
+
raise ValueError("apply_awgn(): give one of esn0_db and osnr_db.")
|
|
149
|
+
x, xp, _ = dispatch(signal_adapter.array)
|
|
150
|
+
num_channels = 1 if x.ndim == 1 else x.shape[0]
|
|
151
|
+
power = _channel_power(x, xp, signal_power, num_channels)
|
|
152
|
+
|
|
153
|
+
if osnr_db is not None:
|
|
154
|
+
sig = signal_adapter.signal
|
|
155
|
+
if sig is None or sig.domain != "OPT" or not xp.iscomplexobj(x):
|
|
156
|
+
raise ValueError(
|
|
157
|
+
"apply_awgn(): osnr_db needs a complex optical Signal "
|
|
158
|
+
"(domain='OPT'), whose sampling rate sets the noise bandwidth."
|
|
159
|
+
)
|
|
160
|
+
if not reference_bandwidth > 0:
|
|
161
|
+
raise ValueError(
|
|
162
|
+
"apply_awgn(): reference_bandwidth must be positive (Hz), got "
|
|
163
|
+
f"{reference_bandwidth!r}."
|
|
164
|
+
)
|
|
165
|
+
if noise_modes is None:
|
|
166
|
+
noise_modes = max(num_channels, 2)
|
|
167
|
+
if (
|
|
168
|
+
isinstance(noise_modes, bool)
|
|
169
|
+
or not isinstance(noise_modes, int)
|
|
170
|
+
or noise_modes < num_channels
|
|
171
|
+
):
|
|
172
|
+
raise ValueError(
|
|
173
|
+
f"apply_awgn(): noise_modes must be an integer of at least the "
|
|
174
|
+
f"{num_channels} channels, got {noise_modes!r}."
|
|
175
|
+
)
|
|
176
|
+
logger.info(
|
|
177
|
+
"Adding ASE noise (OSNR target: %.2f dB, %d modes).", osnr_db, noise_modes
|
|
178
|
+
)
|
|
179
|
+
# The ASE density is the same in every mode: one variance for all
|
|
180
|
+
# channels, from their total power.
|
|
181
|
+
noise_power = (
|
|
182
|
+
xp.sum(xp.broadcast_to(power, (num_channels, 1)))
|
|
183
|
+
* sig.sampling_rate
|
|
184
|
+
/ (noise_modes * reference_bandwidth * db_to_linear(osnr_db, power=True))
|
|
185
|
+
)
|
|
186
|
+
return signal_adapter.wrap_samples(x + _noise(x, xp, noise_power, rng))
|
|
187
|
+
|
|
188
|
+
sps = signal_adapter.resolve_fact("sps", sps)
|
|
189
|
+
logger.info("Adding AWGN (Es/N0 target: %.2f dB).", esn0_db)
|
|
190
|
+
|
|
191
|
+
# Es/N0 to the noise variance per sample: Es = sps · P_c is the energy of
|
|
192
|
+
# a symbol over its sps samples; white noise of variance σ² over the band
|
|
193
|
+
# f_s = sps · R_s has N0 = σ² / f_s, so Es/N0 = P_c · sps / σ² per
|
|
194
|
+
# symbol, i.e. a per-sample SNR of Es/N0 / sps.
|
|
195
|
+
esn0_linear = max(db_to_linear(esn0_db, power=True), 1e-20)
|
|
196
|
+
noise_power = power * (sps / esn0_linear)
|
|
197
|
+
return signal_adapter.wrap_samples(x + _noise(x, xp, noise_power, rng))
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _channel_power(
|
|
201
|
+
x: ArrayType, xp: Any, signal_power: Any, num_channels: int
|
|
202
|
+
) -> ArrayType:
|
|
203
|
+
"""Signal power per channel, broadcastable against ``x``: measured,
|
|
204
|
+
``(C, 1)`` (``(1,)`` for 1-D input), or the given scalar or ``(C,)``."""
|
|
205
|
+
if signal_power is None:
|
|
206
|
+
return xp.mean(xp.abs(x) ** 2, axis=-1, keepdims=True)
|
|
207
|
+
power = xp.asarray(signal_power, dtype=xp.float64)
|
|
208
|
+
if power.ndim == 0:
|
|
209
|
+
return power
|
|
210
|
+
if power.shape != (num_channels,):
|
|
211
|
+
raise ValueError(
|
|
212
|
+
f"apply_awgn(): signal_power must be a scalar or have one entry per "
|
|
213
|
+
f"channel ({num_channels},), got shape {power.shape}."
|
|
214
|
+
)
|
|
215
|
+
return power[:, None] if x.ndim == 2 else power
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _noise(x: Any, xp: Any, noise_power: Any, rng: RNG) -> Any:
|
|
219
|
+
"""White Gaussian noise of ``noise_power`` per sample, shaped like ``x``.
|
|
220
|
+
|
|
221
|
+
Complex noise splits its power equally between I and Q.
|
|
222
|
+
"""
|
|
223
|
+
gen = as_generator(rng)
|
|
224
|
+
real_dtype = x.real.dtype if x.real.dtype.kind == "f" else xp.dtype(xp.float64)
|
|
225
|
+
if xp.iscomplexobj(x):
|
|
226
|
+
std = xp.sqrt(noise_power / 2).astype(real_dtype)
|
|
227
|
+
return complex_normal(gen, x.shape, std=std, dtype=real_dtype, xp=xp)
|
|
228
|
+
std = xp.sqrt(noise_power).astype(real_dtype)
|
|
229
|
+
return std * standard_normal(gen, x.shape, dtype=real_dtype, xp=xp)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "commkit"
|
|
3
|
-
version = "3.1.
|
|
3
|
+
version = "3.1.2"
|
|
4
4
|
description = "A Python library for high-performance digital communications research."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "Denis Fatkhiev" }]
|
|
@@ -86,7 +86,7 @@ markers = [
|
|
|
86
86
|
]
|
|
87
87
|
|
|
88
88
|
[tool.bumpversion]
|
|
89
|
-
current_version = "3.1.
|
|
89
|
+
current_version = "3.1.2"
|
|
90
90
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
|
91
91
|
serialize = ["{major}.{minor}.{patch}"]
|
|
92
92
|
search = "{current_version}"
|
|
@@ -37,7 +37,7 @@ class TestAddAWGN:
|
|
|
37
37
|
)
|
|
38
38
|
|
|
39
39
|
def test_awgn_signal_power_override(self, xp):
|
|
40
|
-
"""Explicit signal_power sets an absolute noise level (
|
|
40
|
+
"""Explicit signal_power sets an absolute noise level (silent capture)."""
|
|
41
41
|
dark = xp.zeros(20000, dtype=xp.complex128)
|
|
42
42
|
noisy = apply_awgn(dark, esn0_db=10.0, sps=1, signal_power=1.0, rng=3)
|
|
43
43
|
# Without the override the noise power would be 0; with it, 0.1.
|
|
@@ -158,3 +158,87 @@ class TestOSNR:
|
|
|
158
158
|
wide = apply_awgn(sig, osnr_db=15.0, reference_bandwidth=25e9, rng=1)
|
|
159
159
|
var_wide = float(xp.mean(xp.abs(wide.samples - x) ** 2))
|
|
160
160
|
assert var_wide == pytest.approx(var / 2, rel=0.02)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class TestPerChannel:
|
|
164
|
+
"""Es/N0 holds per channel; a scalar signal_power shares one noise floor."""
|
|
165
|
+
|
|
166
|
+
@staticmethod
|
|
167
|
+
def _noise_var(xp, noisy, x):
|
|
168
|
+
return xp.mean(xp.abs(noisy - x) ** 2, axis=-1)
|
|
169
|
+
|
|
170
|
+
def test_each_channel_gets_its_esn0(self, xp):
|
|
171
|
+
x = xp.ones((2, 1 << 16), dtype=xp.complex64)
|
|
172
|
+
x[1] *= 2.0 # 6 dB stronger
|
|
173
|
+
noisy = apply_awgn(x, esn0_db=10.0, sps=1, rng=1)
|
|
174
|
+
var = self._noise_var(xp, noisy, x)
|
|
175
|
+
assert xp.allclose(var, xp.asarray([0.1, 0.4]), rtol=0.03)
|
|
176
|
+
|
|
177
|
+
def test_scalar_signal_power_is_shared(self, xp):
|
|
178
|
+
x = xp.ones((2, 1 << 16), dtype=xp.complex64)
|
|
179
|
+
x[1] *= 2.0
|
|
180
|
+
noisy = apply_awgn(x, esn0_db=10.0, sps=1, signal_power=1.0, rng=1)
|
|
181
|
+
var = self._noise_var(xp, noisy, x)
|
|
182
|
+
assert xp.allclose(var, xp.asarray([0.1, 0.1]), rtol=0.03)
|
|
183
|
+
|
|
184
|
+
def test_per_channel_signal_power(self, xp):
|
|
185
|
+
x = xp.zeros((2, 1 << 16), dtype=xp.complex64)
|
|
186
|
+
noisy = apply_awgn(x, esn0_db=10.0, sps=2, signal_power=[1.0, 0.5], rng=1)
|
|
187
|
+
var = self._noise_var(xp, noisy, x)
|
|
188
|
+
assert noisy.dtype == xp.complex64
|
|
189
|
+
assert xp.allclose(var, xp.asarray([0.2, 0.1]), rtol=0.03)
|
|
190
|
+
with pytest.raises(ValueError, match="one entry per channel"):
|
|
191
|
+
apply_awgn(x, esn0_db=10.0, sps=1, signal_power=[1.0, 1.0, 1.0])
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class TestNoiseModes:
|
|
195
|
+
"""apply_awgn(osnr_db=, noise_modes=): ASE counted over the guided modes."""
|
|
196
|
+
|
|
197
|
+
RS = 32e9
|
|
198
|
+
|
|
199
|
+
def _field(self, xp, num_channels, sps=2):
|
|
200
|
+
x = xp.ones((num_channels, 1 << 16), dtype=xp.complex64)
|
|
201
|
+
return Signal(
|
|
202
|
+
samples=x,
|
|
203
|
+
sampling_rate=sps * self.RS,
|
|
204
|
+
symbol_rate=self.RS,
|
|
205
|
+
domain="OPT",
|
|
206
|
+
center_frequency=193.4e12,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
def _esn0(self, xp, sig, **kwargs):
|
|
210
|
+
noisy = apply_awgn(sig, osnr_db=20.0, rng=2, **kwargs)
|
|
211
|
+
var = float(xp.mean(xp.abs(noisy.samples - sig.samples) ** 2))
|
|
212
|
+
return sig.sps / var
|
|
213
|
+
|
|
214
|
+
def test_mode_division_default_counts_every_channel(self, xp):
|
|
215
|
+
"""Six channels in a 3-mode fiber: Es/N0 = 6 B_ref / (6 R_s) · OSNR,
|
|
216
|
+
the single-mode dual-polarization value."""
|
|
217
|
+
esn0 = self._esn0(xp, self._field(xp, 6))
|
|
218
|
+
assert esn0 == pytest.approx(100 * 12.5e9 / self.RS, rel=0.02)
|
|
219
|
+
assert self._esn0(xp, self._field(xp, 2)) == pytest.approx(esn0, rel=0.02)
|
|
220
|
+
|
|
221
|
+
def test_unexcited_modes_still_carry_ase(self, xp):
|
|
222
|
+
"""4 of 6 modes carry signal: the ASE of all 6 counts, so each
|
|
223
|
+
channel sees 6/4 of the default's Es/N0."""
|
|
224
|
+
sig = self._field(xp, 4)
|
|
225
|
+
assert self._esn0(xp, sig, noise_modes=6) == pytest.approx(
|
|
226
|
+
1.5 * self._esn0(xp, sig), rel=0.02
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
def test_unequal_powers_share_one_ase_level(self, xp):
|
|
230
|
+
sig = self._field(xp, 2)
|
|
231
|
+
x = sig.samples.copy()
|
|
232
|
+
x[1] *= 2.0
|
|
233
|
+
noisy = apply_awgn(sig.replace(samples=x), osnr_db=20.0, rng=2)
|
|
234
|
+
var = xp.mean(xp.abs(noisy.samples - x) ** 2, axis=-1)
|
|
235
|
+
assert float(var[1]) == pytest.approx(float(var[0]), rel=0.03)
|
|
236
|
+
# Total power 1 + 4 = 5 over two modes: 5 · f_s / (2 B_ref OSNR).
|
|
237
|
+
expected = 5 * 2 * self.RS / (2 * 12.5e9 * 100)
|
|
238
|
+
assert float(var[0]) == pytest.approx(expected, rel=0.03)
|
|
239
|
+
|
|
240
|
+
def test_noise_modes_validated(self, xp):
|
|
241
|
+
sig = self._field(xp, 4)
|
|
242
|
+
for bad in (3, 0, 4.0, True):
|
|
243
|
+
with pytest.raises(ValueError, match="noise_modes"):
|
|
244
|
+
apply_awgn(sig, osnr_db=20.0, noise_modes=bad)
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
"""Additive white Gaussian noise: :func:`apply_awgn`."""
|
|
2
|
-
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
|
-
from .._random import RNG, as_generator, complex_normal, standard_normal
|
|
6
|
-
from ..backend import dispatch
|
|
7
|
-
from ..core._signal_adapter import S, adapt_signal
|
|
8
|
-
from ..logger import logger
|
|
9
|
-
from ..math import db_to_linear
|
|
10
|
-
|
|
11
|
-
__all__ = ["apply_awgn"]
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def apply_awgn(
|
|
15
|
-
samples: S,
|
|
16
|
-
*,
|
|
17
|
-
esn0_db: float | None = None,
|
|
18
|
-
osnr_db: float | None = None,
|
|
19
|
-
sps: float | None = None,
|
|
20
|
-
signal_power: float | None = None,
|
|
21
|
-
reference_bandwidth: float = 12.5e9,
|
|
22
|
-
rng: RNG = None,
|
|
23
|
-
) -> S:
|
|
24
|
-
"""Add white Gaussian noise at an Es/N0, or at an OSNR for an optical field.
|
|
25
|
-
|
|
26
|
-
With ``esn0_db`` the noise variance per sample is
|
|
27
|
-
``signal_power · sps / (Es/N0)``: with ``Es = sps · signal_power``, that
|
|
28
|
-
is ``Es/N0`` per symbol after a matched filter and decimation, whatever
|
|
29
|
-
the oversampling.
|
|
30
|
-
|
|
31
|
-
With ``osnr_db`` (an optical Signal) the noise is amplified spontaneous
|
|
32
|
-
emission specified as optical links are: the total signal power over the
|
|
33
|
-
ASE power in ``reference_bandwidth``, counted in both polarizations. ASE
|
|
34
|
-
is white over the simulated band ``f_s``, so complex noise of variance
|
|
35
|
-
``σ²`` per polarization has the density ``σ²/f_s`` and puts
|
|
36
|
-
``2 σ² B_ref / f_s`` into ``B_ref`` over both polarizations; each
|
|
37
|
-
channel therefore gets
|
|
38
|
-
|
|
39
|
-
σ² = C · signal_power · f_s / (2 · B_ref · OSNR),
|
|
40
|
-
|
|
41
|
-
which gives the textbook ``Es/N0 = 2 B_ref / (p R_s) · OSNR`` for ``p``
|
|
42
|
-
polarizations, whatever the oversampling. A single-polarization Signal
|
|
43
|
-
(``C = 1``) still counts the ASE of both polarizations, as an OSNR meter
|
|
44
|
-
does, and receives the half that falls into its own: the noise a
|
|
45
|
-
single-polarization coherent receiver sees, 3 dB less than a
|
|
46
|
-
dual-polarization signal of the same total power at the same OSNR.
|
|
47
|
-
|
|
48
|
-
OSNR is a property of the whole optical field, so pass every
|
|
49
|
-
polarization in one call. Calling once per polarization of a
|
|
50
|
-
dual-polarization signal counts only that polarization's power and
|
|
51
|
-
adds half the intended noise. The orthogonal polarization's ASE is not
|
|
52
|
-
simulated for ``C = 1``, which matters for a receiver without a
|
|
53
|
-
polarizer (direct detection, where it adds ASE-ASE beat noise); model
|
|
54
|
-
such a link with both polarizations.
|
|
55
|
-
|
|
56
|
-
Parameters
|
|
57
|
-
----------
|
|
58
|
-
samples : array_like or Signal
|
|
59
|
-
Input samples, time on the last axis; for ``osnr_db`` a complex
|
|
60
|
-
optical Signal (``domain="OPT"``), ``(N,)`` or ``(C, N)`` with one
|
|
61
|
-
channel per polarization.
|
|
62
|
-
esn0_db : float, optional
|
|
63
|
-
Symbol energy over noise spectral density, Es/N0, in dB.
|
|
64
|
-
osnr_db : float, optional
|
|
65
|
-
Optical signal-to-noise ratio in dB in ``reference_bandwidth``,
|
|
66
|
-
instead of ``esn0_db``.
|
|
67
|
-
sps : float, optional
|
|
68
|
-
Samples per symbol, for ``esn0_db``. Taken from the Signal; required
|
|
69
|
-
for array input. A value that disagrees with the Signal raises.
|
|
70
|
-
signal_power : float, optional
|
|
71
|
-
Signal power per channel for the noise scaling. Defaults to the mean
|
|
72
|
-
power of ``samples`` over all channels, which counts any silent
|
|
73
|
-
stretch. Pass the nominal power instead when that is the reference:
|
|
74
|
-
e.g. ``signal_power=1.0`` for a unit-power constellation sent in
|
|
75
|
-
bursts separated by zero guard intervals, whose measured mean falls
|
|
76
|
-
below the power of the symbols.
|
|
77
|
-
reference_bandwidth : float, default 12.5e9
|
|
78
|
-
Noise reference bandwidth ``B_ref`` of ``osnr_db`` in Hz. The
|
|
79
|
-
standard reference is 0.1 nm of optical bandwidth, ``c · 0.1 nm /
|
|
80
|
-
λ²``: 12.5 GHz in the C band (12.48 GHz at 1550 nm), 17.5 GHz at
|
|
81
|
-
1310 nm. Outside the C band pass the value for the carrier, e.g.
|
|
82
|
-
``reference_bandwidth=17.5e9`` for an O-band OSNR in 0.1 nm.
|
|
83
|
-
rng : int, numpy.random.Generator or None
|
|
84
|
-
Random source. The noise is drawn on the data's device: from this
|
|
85
|
-
Generator on the CPU, and on the GPU from a CuPy Generator seeded from
|
|
86
|
-
it, so a seed is reproducible per device.
|
|
87
|
-
|
|
88
|
-
Returns
|
|
89
|
-
-------
|
|
90
|
-
ndarray or Signal
|
|
91
|
-
The noisy samples, same shape, dtype and device as the input.
|
|
92
|
-
|
|
93
|
-
Raises
|
|
94
|
-
------
|
|
95
|
-
ValueError
|
|
96
|
-
If neither or both of ``esn0_db`` and ``osnr_db`` are given,
|
|
97
|
-
``osnr_db`` is given for anything but a complex optical Signal, or
|
|
98
|
-
``reference_bandwidth`` is not positive.
|
|
99
|
-
|
|
100
|
-
Notes
|
|
101
|
-
-----
|
|
102
|
-
Complex input gets circular complex noise, its variance split equally
|
|
103
|
-
between I and Q; real input gets real noise of the full variance. At
|
|
104
|
-
``sps = 1`` Es/N0 equals the per-sample SNR.
|
|
105
|
-
|
|
106
|
-
Examples
|
|
107
|
-
--------
|
|
108
|
-
>>> sig = generate(Constellation.pam(4), 1000, symbol_rate=1e6, sps=4)
|
|
109
|
-
>>> noisy = apply_awgn(sig.samples, esn0_db=20, sps=4)
|
|
110
|
-
>>> noisy = apply_awgn(sig, esn0_db=20) # Signal input: sps taken from sig
|
|
111
|
-
>>> field = sig.replace(domain="OPT", center_frequency=193.4e12)
|
|
112
|
-
>>> noisy = apply_awgn(field, osnr_db=18) # in 12.5 GHz, both polarizations
|
|
113
|
-
"""
|
|
114
|
-
signal_adapter = adapt_signal(samples, function_name="apply_awgn()")
|
|
115
|
-
if (esn0_db is None) == (osnr_db is None):
|
|
116
|
-
raise ValueError("apply_awgn(): give one of esn0_db and osnr_db.")
|
|
117
|
-
x, xp, _ = dispatch(signal_adapter.array)
|
|
118
|
-
if signal_power is None:
|
|
119
|
-
signal_power = xp.mean(xp.abs(x) ** 2)
|
|
120
|
-
if osnr_db is not None:
|
|
121
|
-
sig = signal_adapter.signal
|
|
122
|
-
if sig is None or sig.domain != "OPT" or not xp.iscomplexobj(x):
|
|
123
|
-
raise ValueError(
|
|
124
|
-
"apply_awgn(): osnr_db needs a complex optical Signal "
|
|
125
|
-
"(domain='OPT'), whose sampling rate sets the noise bandwidth."
|
|
126
|
-
)
|
|
127
|
-
if not reference_bandwidth > 0:
|
|
128
|
-
raise ValueError(
|
|
129
|
-
"apply_awgn(): reference_bandwidth must be positive (Hz), got "
|
|
130
|
-
f"{reference_bandwidth!r}."
|
|
131
|
-
)
|
|
132
|
-
logger.info("Adding ASE noise (OSNR target: %.2f dB).", osnr_db)
|
|
133
|
-
num_channels = 1 if x.ndim == 1 else x.shape[0]
|
|
134
|
-
total_power = num_channels * signal_power
|
|
135
|
-
noise_power = (
|
|
136
|
-
total_power
|
|
137
|
-
* sig.sampling_rate
|
|
138
|
-
/ (2.0 * reference_bandwidth * db_to_linear(osnr_db, power=True))
|
|
139
|
-
)
|
|
140
|
-
return signal_adapter.wrap_samples(x + _noise(x, xp, noise_power, rng))
|
|
141
|
-
|
|
142
|
-
sps = signal_adapter.resolve_fact("sps", sps)
|
|
143
|
-
logger.info("Adding AWGN (Es/N0 target: %.2f dB).", esn0_db)
|
|
144
|
-
|
|
145
|
-
# === Es/N0 to sample-level SNR conversion ===
|
|
146
|
-
#
|
|
147
|
-
# Es/N0 = Symbol Energy / Noise Spectral Density
|
|
148
|
-
#
|
|
149
|
-
# For oversampled signals:
|
|
150
|
-
# - Symbol energy Es = sps * (average sample power) [sum over sps samples]
|
|
151
|
-
# - Noise power in full bandwidth = N0 * fs = N0 * (sps * symbol_rate)
|
|
152
|
-
# - Noise power per sample = N0 * symbol_rate
|
|
153
|
-
#
|
|
154
|
-
# Therefore:
|
|
155
|
-
# Es/N0 = (sps * P_sample) / (N0 * symbol_rate)
|
|
156
|
-
# Sample_SNR = P_sample / P_noise_per_sample = P_sample / (N0 * symbol_rate)
|
|
157
|
-
#
|
|
158
|
-
# Relationship: Sample_SNR = Es/N0 / sps
|
|
159
|
-
#
|
|
160
|
-
# Or equivalently: P_noise = P_signal * sps / Es_N0_linear
|
|
161
|
-
|
|
162
|
-
esn0_linear = db_to_linear(esn0_db, power=True)
|
|
163
|
-
|
|
164
|
-
# Noise power accounting for oversampling
|
|
165
|
-
if esn0_linear <= 1e-20:
|
|
166
|
-
noise_power = signal_power * sps / 1e-20
|
|
167
|
-
else:
|
|
168
|
-
noise_power = signal_power * sps / esn0_linear
|
|
169
|
-
|
|
170
|
-
return signal_adapter.wrap_samples(x + _noise(x, xp, noise_power, rng))
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
def _noise(x: Any, xp: Any, noise_power: Any, rng: RNG) -> Any:
|
|
174
|
-
"""White Gaussian noise of ``noise_power`` per sample, shaped like ``x``.
|
|
175
|
-
|
|
176
|
-
Complex noise splits its power equally between I and Q.
|
|
177
|
-
"""
|
|
178
|
-
gen = as_generator(rng)
|
|
179
|
-
real_dtype = x.real.dtype if x.real.dtype.kind == "f" else xp.dtype(xp.float64)
|
|
180
|
-
if xp.iscomplexobj(x):
|
|
181
|
-
std = xp.sqrt(noise_power / 2).astype(real_dtype)
|
|
182
|
-
return complex_normal(gen, x.shape, std=std, dtype=real_dtype, xp=xp)
|
|
183
|
-
std = xp.sqrt(noise_power).astype(real_dtype)
|
|
184
|
-
return std * standard_normal(gen, x.shape, dtype=real_dtype, xp=xp)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{commkit-3.1.1 → commkit-3.1.2}/benchmarks/baselines/Linux-CPython-3.14-64bit/0002_pre_v2.json
RENAMED
|
File without changes
|
{commkit-3.1.1 → commkit-3.1.2}/benchmarks/baselines/Linux-CPython-3.14-64bit/0003_v2_0.json
RENAMED
|
File without changes
|
{commkit-3.1.1 → commkit-3.1.2}/benchmarks/baselines/Linux-CPython-3.14-64bit/0004_fiber_dbp.json
RENAMED
|
File without changes
|
|
File without changes
|
{commkit-3.1.1 → commkit-3.1.2}/benchmarks/baselines/Linux-CPython-3.14-64bit/0006_perf_cleanup.json
RENAMED
|
File without changes
|
{commkit-3.1.1 → commkit-3.1.2}/benchmarks/baselines/Linux-CPython-3.14-64bit/0007_v3_0.json
RENAMED
|
File without changes
|
{commkit-3.1.1 → commkit-3.1.2}/benchmarks/baselines/Linux-CPython-3.14-64bit/0008_v3_1.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|