CommKit
Digital-communications research kit for Python, on CPU and GPU.
CommKit covers the receiver chain of coherent and IM/DD links (waveform generation, channel impairments, synchronization, carrier recovery, adaptive equalization, metrics, laser characterization and plotting) on NumPy or CuPy arrays. Computation runs on the device the data lives on.
The model
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.
- 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()). - A
Signalcarries facts and ground truth. Facts aresampling_rate,symbol_rate,center_frequencyanddomain; the carrier of an optical Signal gives itscenter_wavelength_nm, which dispersion, fiber propagation and OSNR noise read. The description isconstellationandpulse. The ground truth isreference: 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. - Results with several values are frozen dataclasses with named fields. Metrics return host floats: one per channel for
(C, N)input. - The device follows the data. Processing functions have no
backend=ordevice=argument. Only factories, which have no input data to follow, takedevice=:generate(..., device="gpu"). Move existing data withsig.to("gpu"). - Plots draw results.
plot_*functions take a Signal, an array or a result object; numerical code never imports plotting. - There is no pipeline object, receiver class or configuration file: the orchestration stays in your script.
Quickstart
import commkit as ck
from commkit import RRC, Constellation
from commkit.recovery import BPS, CycleSlip
tx = ck.generate(Constellation.qam(16), num_symbols=2**16, symbol_rate=32e9,
sps=2, pulse=RRC(rolloff=0.1), rng=1, device="gpu")
rx = ck.impairments.apply_phase_noise(tx, linewidth=100e3, rng=2)
rx = ck.impairments.apply_awgn(rx, esn0_db=18, rng=3) # on the GPU, like tx
rx = ck.filtering.matched_filter(rx) # pulse from rx
cpr = BPS(test_phases=64, cycle_slip=CycleSlip(history=100))
res = ck.equalization.lms(
rx, num_taps=21, step_size=1e-3,
training_symbols=rx.reference.symbols[..., :2000], cpr=cpr,
)
y = res.signal # 1-SPS Signal, reference aligned
print(f"EVM {ck.metrics.evm(y, num_skip_symbols=2000):.1f} %, "
f"BER {ck.metrics.ber(y, num_skip_symbols=2000):.1e}")
# EVM 12.7 %, BER 2.4e-04 (on the CPU; the GPU draws other noise)
# The next record continues from the converged taps and CPR state.
res2 = ck.equalization.lms(rx, num_taps=21, step_size=1e-3, cpr=cpr, state=res.state)
Without CuPy, pass device="cpu" (the default): the same code runs on the CPU.
Comparing filter responses
Calculate a response on the coefficients' device, or overlay selected panels of several filters:
short = ck.filtering.fir_taps(sampling_rate=1e6, num_taps=21, cutoff=100e3)
long = ck.filtering.fir_taps(sampling_rate=1e6, num_taps=101, cutoff=100e3)
response = ck.filtering.filter_response(long, sampling_rate=1e6)
print(response.frequencies, response.response, response.group_delay)
ck.plotting.apply_default_theme()
_, ax = ck.plotting.plot_filter_response(
short, sampling_rate=1e6, quantity="magnitude", label="21 taps"
)
ck.plotting.plot_filter_response(
long, sampling_rate=1e6, quantity="magnitude", ax=ax,
label="101 taps", legend=True, show=True,
)
See notebook 03 for filter and smoothing comparisons. The multi-panel
equalizer, timing, recovery and coding diagnostics take quantity= in the
same way: draw a second result on the returned axis with its own label=,
and annotations=False to leave out repeated fits and guides.
CPU and GPU
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:
-
Put data on the GPU once, at the source. Build it there with
generate(..., device="gpu"),frame.to_signal(..., device="gpu")orload_npz(path, device="gpu"); move a capture withsig.to("gpu"). Do not move data back and forth between stages. Building on the GPU is faster thangenerate(...).to("gpu"): about 26 ms instead of 370 ms for 4M symbols, because mapping and pulse shaping run there. -
You do not need to move results back. Metrics (
evm,ber, ...) and analysis summaries return host floats, and plots reduce on the device and transfer only what they draw. -
Know what runs where.
Work GPU input Generation, filters, resampling, spectra, impairments, BPS, LLRs, metrics, analysis runs on the GPU Frequency-domain equalizers block_lms,block_cma,block_rderuns on the GPU (CUDA graphs) Sequential equalizers lms,rls,cma,rde;PLL,Tikhonov, cycle-slip correctionruns on the CPU (Numba): one copy to the host and back, no speedup A sample-by-sample recursion has no parallel work per step. For long records on the GPU, prefer the block equalizers with
block_size=1024or more. Each block costs a fixed overhead: at 256 the GPU is about 2x faster than the CPU without carrier recovery and 5x with BPS, at 2048 about 4x and 13x. A larger block also lowers the stablestep_size. -
Short records do not pay off. Launch overhead dominates below roughly 10⁴-10⁵ samples, where the CPU is as fast.
-
Batch channels and records. Pass
(C, N)arrays rather than looping over channels in Python: one call does the work of C. -
Keep scalars on the device inside loops.
float(x),x.item()andif x > 0:on a CuPy array wait for the GPU and copy. Collect values in an array and transfer once. -
The first call compiles. Numba and CUDA kernels compile on first use and are cached on disk. Warm up once before timing anything.
-
Seeds and devices. The same
rnggives the same bits and symbols on both devices. Channel noise (AWGN, phase noise) is drawn on the device, so its realization differs between CPU and GPU while its statistics match. For the exact same phase-noise trajectory on both, draw it on the CPU withgenerate_phase_noise(...)and move it.
Modules
| Module | Contents |
|---|---|
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. |
commkit.mapping |
Constellation (QAM, PSK, PAM, arbitrary points; Gray labels; probabilistic shaping with .shaped()), bit mapping, hard demapping, max-log and exact LLRs. |
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). |
commkit.multirate |
resample (fractional), decimate, upsample, decimate_to_symbol_rate. |
commkit.spectral |
Welch PSD, spectrograms, frequency shifting, pilot tones. |
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. |
commkit.timing |
Barker and Zadoff-Chu sequences, estimate_timing / correct_timing, fractional delay estimation and correction. |
commkit.frequency |
estimate_frequency_offset / correct_frequency_offset with MthPower, MengaliMorelli, PilotSymbols and BiasTone; static and blockwise. |
commkit.recovery |
estimate_carrier_phase / correct_carrier_phase with ViterbiViterbi, BPS, PLL, Tikhonov, DataAided, PilotAided, PilotTone(s); cycle-slip correction; resolve_phase_ambiguity and resolve_channel_permutation. |
commkit.equalization |
Sequential lms, rls, cma, rde (Numba) and frequency-domain block_lms, block_cma, block_rde (CuPy, CUDA graphs), butterfly MIMO, inline carrier recovery (cpr=PLL() / BPS()), continuation with state=, zf_equalizer; pilot-aided CMA and RDE from a frame's pilots (EqualizerPilots); polarization demultiplexing on pilot tones (estimate_polarization_tones / correct_polarization). |
commkit.metrics |
evm, snr, ber, fer, ser, gmi, ngmi, mi, including shaped constellations; pre-FEC and post-FEC (ber(decoded, sig)); estimate_noise_variance for LLRs; host values per channel. |
commkit.analysis |
Laser and carrier-phase characterization: estimate_linewidth (increment slope, β-separation, delayed self-heterodyne FM-PSD / increment / Lorentzian), FM-noise PSDs, drift separation, Allan deviation. |
commkit.math |
rms, normalize, dB conversions. |
commkit.smoothing |
Display and estimation smoothers (moving average, Savitzky-Golay, 2-D density). Signal-chain filters are in filtering. |
commkit.io |
save_npz / load_npz for Signals. |
commkit.plotting |
Constellations, eye diagrams, spectra, filter responses, equalizer convergence, synchronization and laser diagnostics that draw the estimates, and coding diagnostics: LLR distributions and their consistency, parity-check matrices, EXIT charts. Imported on first use. |
commkit.coding |
Channel coding: encode / decode dispatched on a code object, LinearBlockCode (Hamming, extended and shortened), syndrome and maximum-likelihood decoding; LDPCCode (DVB-S2, the 802.3ca / 50G-PON code, any parity-check or quasi-cyclic matrix) with layered min-sum and sum-product decoding on the CPU (Numba) and GPU (CUDA); ReedSolomonCode (G.709, KR4, KP4, 10G-EPON, XGS-PON) and BCHCode with Berlekamp-Massey decoding; Chase-Pyndiah soft decoding; the G.709.2 StaircaseCode and Concatenated codes (400ZR C-FEC with its exact Hamming(128,119), 802.3dj); PolarCode (5G NR information set) with SC and CRC-aided SCL decoding; ConvolutionalCode (K = 7, terminated or tail-biting) with Viterbi and BCJR, TurboCode (LTE); CRC (NR, LTE, Ethernet), Interleaver, Punctured and Shortened; distribution matching (CCDM) and probabilistic amplitude shaping (PAS); EXIT charts of measured decoder and demapper curves, and of LDPC codes in closed form with their threshold. Coded payloads through generate(..., code=). |
Importing commkit has no side effects: it configures no logging, Matplotlib or warning filters, and does not touch the GPU.
Installation
Requires Python 3.12+.
pip install commkit # CPU
pip install "commkit[gpu]" # CuPy with the CUDA 13 toolkit libraries
pip install "commkit[notebook]" # to run the example notebooks
pip install "commkit[full]" # everything
With uv, use uv pip install in place of pip install. Extras combine, e.g. commkit[gpu,notebook].
Examples
examples/ is a course in 29 Jupyter notebooks, one topic each, in teaching order (install the notebook extra and run jupyter lab examples):
- 00-05, signals and DSP: the
Signal, constellations and bit mapping, spectra, filters, pulse shaping and matched filtering, resampling; - 06-10, channel, coding and performance: AWGN and the metrics (SNR, BER, LLRs, GMI), channel coding, probabilistic shaping and coded modulation, laser phase noise, front-end impairments;
- 11-18, receiver algorithms: frame synchronization, frequency-offset estimation, blind and pilot-aided carrier recovery, linear, adaptive and blind equalization, polarization demultiplexing;
- 19-22, optical fiber systems: chromatic dispersion, the nonlinear fiber with digital backpropagation, FEC in optical standards, and an end-to-end coherent transceiver;
- 23-28, characterization: carrier phase and laser linewidth measurements, with templates for real captures.
examples/README.md lists every notebook with its topics. The quickstart above appears cell by cell in 00-getting-started and 16-adaptive-equalization. The notebooks are committed without outputs and run in CI.
Development
git clone https://github.com/lokgar/commkit.git
cd commkit
uv sync --all-extras
uv run nbstripout --install # once per clone: notebooks are committed without outputs
uv run pytest # CPU and GPU tests
uv run pytest --device=cpu # what CI runs
uv run ruff check . && uv run mypy commkit/
Contributor and coding-agent guidance (architecture, API rules, numerics, performance and test conventions) is in AGENTS.md.
License
Metadata
Release files for commkit 3.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| commkit-3.4.0.tar.gz | 1.2 MB | With provenance | Details for commkit-3.4.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| commkit-3.4.0-py3-none-any.whl | Python 3 | none | any | Details for commkit-3.4.0-py3-none-any.whl |
Total release size: 1.8 MB
Release files / commkit-3.4.0.tar.gz
| Download URL | commkit-3.4.0.tar.gz |
|---|---|
| Size | 1.2 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0713f146f1a546d07dd15d0106cb6edbffb03946fa942a723800fd47925f3eaa
|
|
BLAKE2b-256 checksum How to use checksums |
1f6aa55b4024eb1e4257d4b058d9f07c64279aaf5ee11a9a68165f0aa1327345
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.
Transparency logRelease files / commkit-3.4.0-py3-none-any.whl
| Download URL | commkit-3.4.0-py3-none-any.whl |
|---|---|
| Size | 559.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fffbba0d5f9d2d0750226c5d27c0c5cf9208d2d9b65b8c311d15fd9b35244246
|
|
BLAKE2b-256 checksum How to use checksums |
80f1cf1b1db354a11489ddbe1d1bcf602cb845d9608f75798f9227b1f8d95b59
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.
Transparency logSecurity for commkit 3.4.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for commkit-3.4.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.