commkit 1.0.0__tar.gz → 1.1.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-1.0.0 → commkit-1.1.0}/CLAUDE.md +191 -38
- {commkit-1.0.0 → commkit-1.1.0}/PKG-INFO +39 -19
- {commkit-1.0.0 → commkit-1.1.0}/README.md +33 -18
- {commkit-1.0.0 → commkit-1.1.0}/benchmarks/baselines/Linux-CPython-3.14-64bit/0001_commkit_baseline.json +774 -729
- {commkit-1.0.0 → commkit-1.1.0}/benchmarks/conftest.py +29 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/__init__.py +1 -1
- {commkit-1.0.0 → commkit-1.1.0}/commkit/analysis/__init__.py +4 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/analysis/_common.py +177 -17
- {commkit-1.0.0 → commkit-1.1.0}/commkit/analysis/allan.py +3 -3
- {commkit-1.0.0 → commkit-1.1.0}/commkit/analysis/drift.py +38 -26
- {commkit-1.0.0 → commkit-1.1.0}/commkit/analysis/interferometry.py +160 -99
- {commkit-1.0.0 → commkit-1.1.0}/commkit/analysis/linewidth.py +78 -86
- {commkit-1.0.0 → commkit-1.1.0}/commkit/analysis/trajectory.py +25 -22
- {commkit-1.0.0 → commkit-1.1.0}/commkit/backend.py +15 -3
- {commkit-1.0.0 → commkit-1.1.0}/commkit/core/frame.py +32 -28
- {commkit-1.0.0 → commkit-1.1.0}/commkit/core/generation.py +196 -7
- {commkit-1.0.0 → commkit-1.1.0}/commkit/core/signal.py +3 -1
- commkit-1.1.0/commkit/equalization/_block/__init__.py +36 -0
- commkit-1.1.0/commkit/equalization/_block/_blind.py +371 -0
- commkit-1.0.0/commkit/equalization/_block.py → commkit-1.1.0/commkit/equalization/_block/_dd.py +75 -768
- commkit-1.1.0/commkit/equalization/_block/_seqmode.py +421 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/equalization/_common.py +9 -27
- {commkit-1.0.0 → commkit-1.1.0}/commkit/equalization/blind.py +107 -8
- {commkit-1.0.0 → commkit-1.1.0}/commkit/equalization/linear.py +64 -26
- {commkit-1.0.0 → commkit-1.1.0}/commkit/equalization/polarization.py +134 -23
- {commkit-1.0.0 → commkit-1.1.0}/commkit/equalization/result.py +7 -5
- commkit-1.1.0/commkit/equalization/sequential/__init__.py +22 -0
- commkit-1.1.0/commkit/equalization/sequential/_blind.py +1089 -0
- commkit-1.0.0/commkit/equalization/sequential.py → commkit-1.1.0/commkit/equalization/sequential/_dd.py +174 -999
- {commkit-1.0.0 → commkit-1.1.0}/commkit/filtering.py +386 -254
- {commkit-1.0.0 → commkit-1.1.0}/commkit/frequency.py +374 -114
- commkit-1.1.0/commkit/helpers.py +1108 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/impairments/channel/linear.py +132 -54
- {commkit-1.0.0 → commkit-1.1.0}/commkit/impairments/frontend.py +74 -57
- {commkit-1.0.0 → commkit-1.1.0}/commkit/impairments/noise.py +41 -10
- {commkit-1.0.0 → commkit-1.1.0}/commkit/impairments/source.py +54 -14
- {commkit-1.0.0 → commkit-1.1.0}/commkit/mapping/__init__.py +13 -1
- {commkit-1.0.0 → commkit-1.1.0}/commkit/mapping/bits.py +62 -29
- {commkit-1.0.0 → commkit-1.1.0}/commkit/mapping/constellation.py +2 -8
- {commkit-1.0.0 → commkit-1.1.0}/commkit/mapping/gray.py +111 -1
- {commkit-1.0.0 → commkit-1.1.0}/commkit/mapping/llr.py +71 -22
- {commkit-1.0.0 → commkit-1.1.0}/commkit/mapping/shaping.py +39 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/metrics.py +197 -105
- {commkit-1.0.0 → commkit-1.1.0}/commkit/multirate.py +37 -79
- {commkit-1.0.0 → commkit-1.1.0}/commkit/plotting/__init__.py +1 -1
- {commkit-1.0.0 → commkit-1.1.0}/commkit/plotting/analysis.py +13 -49
- {commkit-1.0.0 → commkit-1.1.0}/commkit/plotting/constellation.py +5 -5
- {commkit-1.0.0 → commkit-1.1.0}/commkit/plotting/equalizer.py +7 -128
- {commkit-1.0.0 → commkit-1.1.0}/commkit/plotting/eye.py +3 -2
- commkit-1.1.0/commkit/plotting/filter_response.py +219 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/plotting/spectral.py +17 -69
- {commkit-1.0.0 → commkit-1.1.0}/commkit/plotting/sync.py +13 -46
- {commkit-1.0.0 → commkit-1.1.0}/commkit/plotting/theme.py +28 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/plotting/waveform.py +10 -35
- commkit-1.1.0/commkit/recovery/_common.py +119 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/recovery/bps.py +89 -50
- {commkit-1.0.0 → commkit-1.1.0}/commkit/recovery/corrections.py +393 -189
- {commkit-1.0.0 → commkit-1.1.0}/commkit/recovery/pilots.py +146 -83
- {commkit-1.0.0 → commkit-1.1.0}/commkit/recovery/pll.py +74 -40
- {commkit-1.0.0 → commkit-1.1.0}/commkit/recovery/tikhonov.py +84 -74
- {commkit-1.0.0 → commkit-1.1.0}/commkit/recovery/viterbi_viterbi.py +77 -89
- commkit-1.1.0/commkit/smoothing.py +130 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/spectral.py +88 -56
- {commkit-1.0.0 → commkit-1.1.0}/commkit/timing.py +113 -46
- {commkit-1.0.0 → commkit-1.1.0}/pyproject.toml +5 -2
- {commkit-1.0.0 → commkit-1.1.0}/tests/analysis/test_interferometry.py +31 -0
- commkit-1.1.0/tests/analysis/test_trajectory.py +97 -0
- commkit-1.1.0/tests/core/test_generation.py +58 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/core/test_psqam.py +31 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/equalization/test_blind.py +35 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/equalization/test_block.py +24 -2
- {commkit-1.0.0 → commkit-1.1.0}/tests/equalization/test_linear.py +53 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/equalization/test_polarization.py +124 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/equalization/test_sequential.py +69 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/equalization/test_sequential_jax.py +11 -7
- {commkit-1.0.0 → commkit-1.1.0}/tests/impairments/channel/test_channel_linear.py +48 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/impairments/test_frontend.py +45 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/impairments/test_noise.py +13 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/impairments/test_source.py +13 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/mapping/test_gray.py +79 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/mapping/test_llr.py +34 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/recovery/test_bps.py +30 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/recovery/test_corrections.py +182 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/recovery/test_pilots.py +52 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/recovery/test_pll.py +17 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/recovery/test_tikhonov.py +19 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/recovery/test_viterbi_viterbi.py +17 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/test_filtering.py +164 -51
- {commkit-1.0.0 → commkit-1.1.0}/tests/test_frequency.py +77 -0
- commkit-1.1.0/tests/test_helpers.py +587 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/test_multirate.py +0 -11
- {commkit-1.0.0 → commkit-1.1.0}/tests/test_plotting.py +51 -25
- commkit-1.1.0/tests/test_smoothing.py +54 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/test_timing.py +49 -1
- {commkit-1.0.0 → commkit-1.1.0}/uv.lock +9 -2
- commkit-1.0.0/commkit/helpers.py +0 -489
- commkit-1.0.0/tests/analysis/test_trajectory.py +0 -50
- commkit-1.0.0/tests/test_helpers.py +0 -225
- {commkit-1.0.0 → commkit-1.1.0}/.gitattributes +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/.github/workflows/ci.yml +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/.github/workflows/publish.yml +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/.gitignore +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/.python-version +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/LICENSE +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/benchmarks/bench_block_blind.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/benchmarks/bench_block_lms.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/benchmarks/bench_bps.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/benchmarks/bench_equalizers.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/benchmarks/bench_sync_misc.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/benchmarks/benchutils.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/benchmarks/workloads.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/_cuda/__init__.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/_cuda/compiler.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/_cuda/src/bps_min_d2.cu +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/_cuda/src/cs_block.cu +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/_cuda/src/selftest.cu +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/__init__.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/base.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/bch.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/convolutional.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/crc.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/galois.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/hamming.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/interleaving.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/ldpc.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/polar.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/ratematch.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/reed_solomon.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/coding/turbo.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/core/__init__.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/equalization/__init__.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/equalization/_kernels_jax.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/equalization/_kernels_numba.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/impairments/__init__.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/impairments/channel/__init__.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/impairments/channel/nonlinear.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/io.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/logger.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/py.typed +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/commkit/recovery/__init__.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/examples/carrier_phase_analysis.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/examples/laser_linewidth_dsh.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/examples/laser_linewidth_homodyne_iq.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/examples/measurement_laser_linewidth_dsh.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/examples/measurement_laser_linewidth_homodyne_iq.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/analysis/test_allan.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/analysis/test_drift.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/analysis/test_linewidth.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/conftest.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/core/test_frame.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/core/test_signal.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/core/test_signal_mimo.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/equalization/test_block_update.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/equalization/test_bps_kernel.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/equalization/test_cpr.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/equalization/test_cs_kernel.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/equalization/test_mimo.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/equalization/test_winit.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/mapping/test_bits.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/mapping/test_constellation.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/recovery/test_joint.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/test_backend.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/test_cuda_infra.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/test_io.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/test_logger.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/test_metrics.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/test_pulse_shaping.py +0 -0
- {commkit-1.0.0 → commkit-1.1.0}/tests/test_spectral.py +0 -0
|
@@ -78,7 +78,7 @@ uv run pytest benchmarks/ --benchmark-only --device=all \
|
|
|
78
78
|
--benchmark-compare=0001 --benchmark-storage=file://benchmarks/baselines
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
See **Section
|
|
81
|
+
See **Section 5 - Benchmark Suite** for what each file measures and how to read the results.
|
|
82
82
|
|
|
83
83
|
### Linting & Formatting
|
|
84
84
|
|
|
@@ -153,37 +153,7 @@ Since `bump-my-version` is defined in the project's development dependencies, al
|
|
|
153
153
|
|
|
154
154
|
---
|
|
155
155
|
|
|
156
|
-
## 3.
|
|
157
|
-
|
|
158
|
-
> `examples/` holds notebook-style scripts (jupytext `# %%` cell format, ready
|
|
159
|
-
> to convert to `.ipynb`) that walk through a full analysis chain end-to-end
|
|
160
|
-
> with the physics, method limitations, and interpretation of the results
|
|
161
|
-
> spelled out in markdown cells:
|
|
162
|
-
>
|
|
163
|
-
> * `examples/carrier_phase_analysis.py` - data-aided carrier-phase
|
|
164
|
-
> characterization of a coherent transmission (trajectory -> drift/PN split ->
|
|
165
|
-
> linewidth -> Allan -> dashboard).
|
|
166
|
-
> * `examples/laser_linewidth_dsh.py` - delayed self-heterodyne (AOM receiver)
|
|
167
|
-
> laser linewidth and FM-noise PSD estimation (all three estimators, both
|
|
168
|
-
> coherence regimes, flicker-noise effects).
|
|
169
|
-
> * `examples/laser_linewidth_homodyne_iq.py` - the decoherence interferometer
|
|
170
|
-
> with a 90°-hybrid IQ receiver (no AOM) as the main path: dark-capture DC
|
|
171
|
-
> calibration, GSOP, all three estimators at 0 Hz, discriminator Allan
|
|
172
|
-
> deviation.
|
|
173
|
-
> * `examples/measurement_laser_linewidth_dsh.py` /
|
|
174
|
-
> `examples/measurement_laser_linewidth_homodyne_iq.py` - **measurement
|
|
175
|
-
> templates**: edit the system-parameter cell, point `DATA_FILE` at a real
|
|
176
|
-
> capture, run. Include τ_d calibration from the notch comb (DSH) and a
|
|
177
|
-
> seeded synthetic demo fallback so they run end-to-end without data.
|
|
178
|
-
>
|
|
179
|
-
> Examples are **runners, not library code**: orchestration chains belong in
|
|
180
|
-
> `examples/` (or user projects), never as `commkit` functions. For
|
|
181
|
-
> everything not yet covered by an example, `tests/` is the most current usage
|
|
182
|
-
> reference for every public function.
|
|
183
|
-
|
|
184
|
-
---
|
|
185
|
-
|
|
186
|
-
## 4. DSP & Coding Guidelines
|
|
156
|
+
## 3. DSP & Coding Guidelines
|
|
187
157
|
|
|
188
158
|
### Multi-Backend Dispatching
|
|
189
159
|
|
|
@@ -264,6 +234,151 @@ Inside library code, **never extract a scalar from a possibly-GPU array inside a
|
|
|
264
234
|
* **SISO**: 1-D array: `(N_samples,)`
|
|
265
235
|
* **MIMO**: 2-D array: `(N_channels, N_samples)` - **time is always on the last axis**.
|
|
266
236
|
|
|
237
|
+
Never hand-roll the promote/squeeze idiom. `commkit/helpers.py` owns it, so the
|
|
238
|
+
validation is identical everywhere (0-d and 3-D inputs raise a shape error
|
|
239
|
+
naming the offending argument instead of silently reaching a confusing
|
|
240
|
+
broadcast failure downstream):
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
from commkit.helpers import as_2d, broadcast_channels, require_channels, restore_1d
|
|
244
|
+
|
|
245
|
+
def my_dsp_function(samples, ref_symbols):
|
|
246
|
+
x, xp, _ = dispatch(samples)
|
|
247
|
+
x2, was_1d = as_2d(x, name="samples") # (N,) -> (1, N)
|
|
248
|
+
ref = broadcast_channels(xp.asarray(ref_symbols), # (L,) / (1, L) -> (C, L)
|
|
249
|
+
x2.shape[0], xp, name="ref_symbols")
|
|
250
|
+
out = ... # channel-batched body
|
|
251
|
+
return restore_1d(was_1d, out) # squeeze back for SISO
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
* `as_2d` / `restore_1d` - the promote/squeeze pair. `restore_1d` takes several
|
|
255
|
+
outputs at once: `drift, pn = restore_1d(was_1d, drift, pn)`.
|
|
256
|
+
* `broadcast_channels` - a shared reference across channels, with the
|
|
257
|
+
channel-count check the bare `ref[None, :]` promotion lacks. Returns a
|
|
258
|
+
**read-only** broadcast view; `.copy()` before writing.
|
|
259
|
+
* `require_channels` - for entry points defined only at a fixed channel count
|
|
260
|
+
(dual-pol channel models); pass `description=` to keep domain wording in the
|
|
261
|
+
error.
|
|
262
|
+
* `to_report_scalar` - the reporting-layer counterpart: collapses a `(C,)`
|
|
263
|
+
metric to a Python float for SISO, transferring from device if needed.
|
|
264
|
+
* `linear_trend_slope` / `remove_linear_trend` - the per-channel least-squares
|
|
265
|
+
slope shared by the pilot-tone and DSH detrend stages; keeps `Σ(x-x̄)²` on
|
|
266
|
+
device rather than syncing it back as a float.
|
|
267
|
+
|
|
268
|
+
Note these are plain indexing/broadcast helpers, valid on NumPy, CuPy **and**
|
|
269
|
+
JAX arrays - unlike `dispatch`, which recognizes NumPy/CuPy only and will
|
|
270
|
+
silently pull a JAX array to the host.
|
|
271
|
+
|
|
272
|
+
### Signal-Awareness
|
|
273
|
+
|
|
274
|
+
Any new DSP function whose primary argument is genuinely **signal-representable
|
|
275
|
+
data** (raw IQ samples, or a field a `Signal` actually carries, like
|
|
276
|
+
`resolved_symbols`) should accept a `Signal` alongside a raw array, transparently:
|
|
277
|
+
array in → array out, `Signal` in → `Signal` out. Use the same idiom
|
|
278
|
+
everywhere, defined once in `commkit/helpers.py`:
|
|
279
|
+
|
|
280
|
+
```python
|
|
281
|
+
from commkit.helpers import unwrap_signal, rewrap_signal
|
|
282
|
+
|
|
283
|
+
def fir_filter(samples, taps, axis=-1):
|
|
284
|
+
x, sig = unwrap_signal(samples) # x: array; sig: Signal | None
|
|
285
|
+
if sig is not None:
|
|
286
|
+
return rewrap_signal(sig, fir_filter(x, taps, axis=-1))
|
|
287
|
+
... existing array-only body, unchanged ...
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
* `unwrap_signal(x, *, field="samples")` - returns `(array, signal_or_None)`.
|
|
291
|
+
Most functions read `.samples`; a few (hard-decision demapping, phase-rotation
|
|
292
|
+
correction on `resolved_symbols`) pass `field=` to read a different attribute.
|
|
293
|
+
* `rewrap_signal(sig, array, **metadata)` - `sig=None` passes `array` through
|
|
294
|
+
unchanged; otherwise returns `sig.copy()` with `.samples = array` and any
|
|
295
|
+
`**metadata` kwargs applied via `setattr` (e.g. `sampling_rate=sig.symbol_rate`
|
|
296
|
+
after decimating to symbol rate).
|
|
297
|
+
|
|
298
|
+
**Metadata priority: the `Signal`'s own value always wins.** When a function
|
|
299
|
+
takes both a `Signal` and a scalar metadata parameter that duplicates one of
|
|
300
|
+
its fields (`sampling_rate`, `sps`, `mod_scheme`, `mod_order`, `ps_pmf`,
|
|
301
|
+
...), the `Signal`'s field - never the supplied argument - is what the
|
|
302
|
+
Signal-branch call actually uses. A caller passing a conflicting value on a
|
|
303
|
+
`Signal` is a bug to catch, not a request to honor: silently letting the
|
|
304
|
+
supplied value win would let a signal at 2 GSa/s be equalized with a stale
|
|
305
|
+
`sps=2` nobody meant to apply to it.
|
|
306
|
+
|
|
307
|
+
There is no shared helper for this - both cases are short enough to write
|
|
308
|
+
inline in the `if sig is not None:` branch, and a generic helper covering
|
|
309
|
+
both the "always wins" and "falls back with a warning" behaviors in one call
|
|
310
|
+
signature ends up harder to read at the call site than the few extra lines.
|
|
311
|
+
|
|
312
|
+
* **Required fields** (`sampling_rate`, `symbol_rate`, and the derived `sps`)
|
|
313
|
+
are never `None` on a `Signal` - pydantic enforces it - so the Signal
|
|
314
|
+
branch references `sig.sampling_rate`/`sig.sps` directly and drops the
|
|
315
|
+
supplied argument entirely. There is no case where the Signal is missing
|
|
316
|
+
the field, but the caller can still pass a stale/conflicting value by
|
|
317
|
+
mistake, so warn whenever one was supplied at all:
|
|
318
|
+
|
|
319
|
+
```python
|
|
320
|
+
x, sig = unwrap_signal(samples)
|
|
321
|
+
if sig is not None:
|
|
322
|
+
# sig.sampling_rate is required, so it always wins over a supplied
|
|
323
|
+
# sampling_rate - see CLAUDE.md, "Signal-Awareness".
|
|
324
|
+
if sampling_rate is not None:
|
|
325
|
+
logger.warning(
|
|
326
|
+
"my_dsp_function(): ignoring supplied sampling_rate=%r for "
|
|
327
|
+
"Signal input; using the signal's own sampling_rate=%r instead.",
|
|
328
|
+
sampling_rate,
|
|
329
|
+
sig.sampling_rate,
|
|
330
|
+
)
|
|
331
|
+
return my_dsp_function(x, sig.sampling_rate, ...)
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
* **Optional fields** (`mod_scheme`, `mod_order`, `ps_pmf`, ...) can
|
|
335
|
+
legitimately be unset on a `Signal` (e.g. an unshaped QAM signal has
|
|
336
|
+
`ps_pmf=None`). Read the Signal's field first; fall back to the supplied
|
|
337
|
+
argument - logging a `logger.warning` - only when the Signal genuinely
|
|
338
|
+
lacks it *and* a fallback was supplied. Passing neither stays silent (the
|
|
339
|
+
common case), so the log isn't spammed on every call to an unshaped signal:
|
|
340
|
+
|
|
341
|
+
```python
|
|
342
|
+
mod = sig.mod_scheme
|
|
343
|
+
if mod is None:
|
|
344
|
+
mod = modulation
|
|
345
|
+
if mod is not None:
|
|
346
|
+
logger.warning(
|
|
347
|
+
"my_dsp_function(): Signal has no mod_scheme set; falling "
|
|
348
|
+
"back to supplied modulation=%r.", mod,
|
|
349
|
+
)
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
**Metadata rules for functions that change rate or domain:**
|
|
353
|
+
|
|
354
|
+
| Situation | What to pass `rewrap_signal` |
|
|
355
|
+
| --- | --- |
|
|
356
|
+
| Shape/rate unchanged (most `apply_*`/`correct_*`) | `rewrap_signal(sig, result)` - no metadata |
|
|
357
|
+
| Decimates to one sample/symbol (equalizers) | `sampling_rate=sig.symbol_rate` |
|
|
358
|
+
| Upsamples by an integer factor | `sampling_rate=sig.sampling_rate * factor` |
|
|
359
|
+
| Resamples to an explicit target rate | `sampling_rate=<the target rate param>` |
|
|
360
|
+
| Output field differs from the input field (`resolve_symbols`, `demap_symbols_hard`) | Skip `rewrap_signal`; `sig.copy()` + `setattr` by hand - the one sanctioned exception |
|
|
361
|
+
| Returns a scalar/dict/tuple in a different domain (frequency/tau/PSD bins, phase or frequency *estimates*) | `unwrap_signal` only, on the input - never wrap the output |
|
|
362
|
+
|
|
363
|
+
**Not every array parameter is signal-representable.** A function that
|
|
364
|
+
consumes a *derived* quantity - a phase or frequency trajectory, a
|
|
365
|
+
correlation array, PSD bins, an `EqualizerResult`, filter taps - one or more
|
|
366
|
+
stages downstream of the original capture has no sound field to unwrap from;
|
|
367
|
+
forcing Signal-awareness there is backwards, since there is no `Signal` yet
|
|
368
|
+
(or any more) to preserve metadata from. `smooth_phase_wiener`,
|
|
369
|
+
`estimate_fractional_delay`, `allan_deviation`, and every `plot_*` function
|
|
370
|
+
in `plotting/{analysis,equalizer,sync}.py` are examples: they take arrays a
|
|
371
|
+
`Signal` would never hold as `.samples`, so they stay plain-array functions.
|
|
372
|
+
Likewise, a function whose only job is to build the data a `Signal` gets
|
|
373
|
+
constructed *from* is a synthesis primitive, not a transform on existing
|
|
374
|
+
`Signal` data - same exclusion as `generate_*`. `core/generation.py`'s
|
|
375
|
+
`shape_pulse`/`expand` (TX symbol -> waveform) and
|
|
376
|
+
`equalization.build_pilot_ref` (sparse pilots -> dense equalizer reference)
|
|
377
|
+
are the current examples; note that `shape_pulse`/`expand` live in
|
|
378
|
+
`core/generation.py` rather than `filtering.py`/`multirate.py` for exactly
|
|
379
|
+
this reason - they build the samples a `Signal` gets constructed from, not
|
|
380
|
+
transform an existing one.
|
|
381
|
+
|
|
267
382
|
### Naming Conventions
|
|
268
383
|
|
|
269
384
|
* **Verb prefixes for processing functions.** Recovery/correction routines follow a
|
|
@@ -296,9 +411,38 @@ Inside library code, **never extract a scalar from a possibly-GPU array inside a
|
|
|
296
411
|
modules and `plotting`. Never add a bare-noun plot function that shadows a
|
|
297
412
|
compute function.
|
|
298
413
|
|
|
414
|
+
### Design-vs-Apply Separation
|
|
415
|
+
|
|
416
|
+
For any DSP building block with distinct "design" and "apply" phases - a
|
|
417
|
+
coefficient/parameter generator, and a step that consumes those coefficients
|
|
418
|
+
against data - keep the two as separate functions rather than one function
|
|
419
|
+
that does both internally. This lets a caller design once and apply many
|
|
420
|
+
times (e.g. reuse the same filter across a batch of signals) and keeps each
|
|
421
|
+
function's signature focused. Established examples in `filtering.py`:
|
|
422
|
+
|
|
423
|
+
* FIR taps generators (`rrc_taps`, `rc_taps`, `gaussian_taps`, `fir_taps`
|
|
424
|
+
(lowpass/highpass/bandpass/bandstop via `btype=`), ...) produce coefficient
|
|
425
|
+
arrays; `fir_filter(samples, taps)` applies them.
|
|
426
|
+
* IIR SOS generators (`butterworth_sos`, `chebyshev1_sos`, `chebyshev2_sos`,
|
|
427
|
+
`elliptic_sos`, `bessel_sos`) produce second-order-section coefficients;
|
|
428
|
+
`iir_filter(samples, sos)` applies them.
|
|
429
|
+
|
|
430
|
+
New DSP building blocks with a design/apply split should follow this same
|
|
431
|
+
two-function pattern rather than folding both steps into one.
|
|
432
|
+
|
|
433
|
+
`filtering.py` vs. `smoothing.py`: `filtering.py` holds real signal-chain
|
|
434
|
+
filters (the design/apply pairs above, matched filtering, Overlap-Save) with
|
|
435
|
+
an actual frequency response and causality claim. `smoothing.py` holds
|
|
436
|
+
diagnostic/plotting-only smoothers (`moving_average`, `savgol_smooth`,
|
|
437
|
+
`smooth_density_2d`) - non-causal, no frequency-response meaning, used only
|
|
438
|
+
to make a plotted curve or a robust estimate less noisy. A routine that
|
|
439
|
+
removes/passes a frequency band as part of the signal chain itself belongs in
|
|
440
|
+
`filtering.py`; a routine that only exists to smooth something for display or
|
|
441
|
+
a peak search belongs in `smoothing.py`.
|
|
442
|
+
|
|
299
443
|
---
|
|
300
444
|
|
|
301
|
-
##
|
|
445
|
+
## 4. Testing Conventions
|
|
302
446
|
|
|
303
447
|
* **Parametrization**: Test cases must utilize `backend_device` and `xp` fixtures from `conftest.py` to automatically validate code correctness on both CPU and GPU backends.
|
|
304
448
|
* **Assertions**: Standard `numpy.testing` assertions raise `TypeError` when evaluated on GPU arrays. Always use the `xpt` helper assertion module. Use `xp.asarray(expected)` to cast expectation variables to the active backend, and cast reductions to standard Python scalars before comparison:
|
|
@@ -318,10 +462,19 @@ Inside library code, **never extract a scalar from a possibly-GPU array inside a
|
|
|
318
462
|
`test_winit.py`, `test_linear.py` (zf/MMSE), `test_polarization.py`,
|
|
319
463
|
`test_blind.py` + `test_block_update.py` (block_cma/block_rde),
|
|
320
464
|
`test_block.py` (block_lms / FDAF), `test_cpr.py`, and the CUDA-kernel tests
|
|
321
|
-
`test_bps_kernel.py` / `test_cs_kernel.py`.
|
|
465
|
+
`test_bps_kernel.py` / `test_cs_kernel.py`. `sequential.py` and `_block.py`
|
|
466
|
+
are themselves subpackages internally (`sequential/_dd.py` + `_blind.py`;
|
|
467
|
+
`_block/_seqmode.py` + `_dd.py` + `_blind.py`) per the module-splitting
|
|
468
|
+
trigger below - the test files above are unaffected since they exercise
|
|
469
|
+
the public `commkit.equalization` surface, not these internal module
|
|
470
|
+
paths (a handful of `patch()`/`monkeypatch` targets in `test_block.py` /
|
|
471
|
+
`test_sequential_jax.py` do reach into the internal paths and were
|
|
472
|
+
updated when the split landed).
|
|
322
473
|
* `tests/recovery/` <-> `commkit/recovery/` - `test_viterbi_viterbi.py`,
|
|
323
474
|
`test_bps.py`, `test_pilots.py`, `test_tikhonov.py`, `test_pll.py`,
|
|
324
|
-
`test_corrections.py
|
|
475
|
+
`test_corrections.py` (also covers `recovery/_common.py`'s shared
|
|
476
|
+
`_vv_block_phase` block-phase estimator and `_log_phase_summary` CPR
|
|
477
|
+
diagnostic logger), plus `test_joint.py` for the cross-algorithm
|
|
325
478
|
joint-channel consistency checks.
|
|
326
479
|
* `tests/core/` <-> `commkit/core/` - `test_signal.py`, `test_signal_mimo.py`,
|
|
327
480
|
`test_frame.py` (`Preamble`/`SingleCarrierFrame`), `test_psqam.py` (generation).
|
|
@@ -336,8 +489,8 @@ Inside library code, **never extract a scalar from a possibly-GPU array inside a
|
|
|
336
489
|
* `tests/mapping/` <-> `commkit/mapping/` - `test_gray.py`, `test_bits.py`,
|
|
337
490
|
`test_llr.py`, `test_constellation.py` (the `Constellation` value object).
|
|
338
491
|
Probabilistic-shaping tests live in `tests/core/test_psqam.py`.
|
|
339
|
-
* Flat modules (`filtering`, `metrics`, `spectral`, `timing`, `frequency`,
|
|
340
|
-
keep a single top-level `tests/test_<module>.py`.
|
|
492
|
+
* Flat modules (`filtering`, `metrics`, `spectral`, `timing`, `frequency`,
|
|
493
|
+
`smoothing`, ...) keep a single top-level `tests/test_<module>.py`.
|
|
341
494
|
|
|
342
495
|
When a single module's test file grows unwieldy, split it by *concern* within
|
|
343
496
|
the same subpackage (e.g. sequential vs. JAX vs. MIMO) rather than letting one
|
|
@@ -355,7 +508,7 @@ Inside library code, **never extract a scalar from a possibly-GPU array inside a
|
|
|
355
508
|
|
|
356
509
|
---
|
|
357
510
|
|
|
358
|
-
##
|
|
511
|
+
## 5. Benchmark Suite
|
|
359
512
|
|
|
360
513
|
The `benchmarks/` directory tracks the performance of the GPU-relevant hot paths. Baselines are committed under `benchmarks/baselines/` so any optimization PR can be gated quantitatively (run -> compare -> quote the delta).
|
|
361
514
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: commkit
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: A Python library for high-performance digital communications research.
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -12,6 +12,11 @@ Requires-Dist: numpy>=2.4
|
|
|
12
12
|
Requires-Dist: pydantic>=2.13
|
|
13
13
|
Requires-Dist: pyyaml>=6.0.2
|
|
14
14
|
Requires-Dist: scipy>=1.17
|
|
15
|
+
Provides-Extra: full
|
|
16
|
+
Requires-Dist: cupy-cuda13x>=14.1; extra == 'full'
|
|
17
|
+
Requires-Dist: jax[cuda13]>=0.10; extra == 'full'
|
|
18
|
+
Requires-Dist: jupyter>=1.1.1; extra == 'full'
|
|
19
|
+
Requires-Dist: nvidia-curand>=10.4.1.34; extra == 'full'
|
|
15
20
|
Provides-Extra: gpu
|
|
16
21
|
Requires-Dist: cupy-cuda13x>=14.1; extra == 'gpu'
|
|
17
22
|
Requires-Dist: jax[cuda13]>=0.10; extra == 'gpu'
|
|
@@ -39,7 +44,7 @@ CommKit is a Python library for digital communications research that treats hard
|
|
|
39
44
|
|
|
40
45
|
- **One object, complete context:** Sampling rate, symbol rate, modulation format, and pulse shape travel with the signal through the processing pipeline.
|
|
41
46
|
- **Backend-transparent DSP:** `dispatch()` resolves NumPy, CuPy, or SciPy modules at runtime. The same code executes seamlessly on CPU or GPU.
|
|
42
|
-
- **
|
|
47
|
+
- **Functional pipelines:** DSP functions accept and return a `Signal` directly (`sig = fir_filter(sig, taps)`; `sig = resample(sig, sps_out=2)`), so pipelines compose without a monolithic `Signal` wrapper API - `sig.to("gpu")` moves data across backends, the rest is plain function composition.
|
|
43
48
|
- **JAX escape hatch:** Zero-copy DLPack export on GPU allows direct application of JAX transforms (gradients, `vmap`, `scan`) without leaving the research loop.
|
|
44
49
|
|
|
45
50
|
---
|
|
@@ -48,23 +53,24 @@ CommKit is a Python library for digital communications research that treats hard
|
|
|
48
53
|
|
|
49
54
|
| Module | Key Capabilities & Features |
|
|
50
55
|
| --- | --- |
|
|
51
|
-
| [`commkit.core`](commkit/core) | `Signal` container (IQ samples + metadata), `SingleCarrierFrame`, `Preamble`, and symbol/frame factories (PAM, PSK, QAM). |
|
|
56
|
+
| [`commkit.core`](commkit/core) | `Signal` container (IQ samples + metadata), `SingleCarrierFrame`, `Preamble`, and symbol/frame factories (PAM, PSK, QAM, PS-QAM). |
|
|
52
57
|
| [`commkit.backend`](commkit/backend.py) | Hardware abstraction layer (`dispatch`, `to_device`, `to_jax`, `from_jax`), placement management, and backend execution (NumPy, CuPy, JAX). |
|
|
53
|
-
| [`commkit.mapping`](commkit/mapping) | Gray-coded constellations, symbol mapping, hard demapping, soft LLR computation (max-log and exact log-sum-exp via JAX JIT), and
|
|
54
|
-
| [`commkit.filtering`](commkit/filtering.py) | Pulse shaping (RRC, RC, Gaussian, Smooth-Rectangle), tap generators,
|
|
58
|
+
| [`commkit.mapping`](commkit/mapping) | Gray-coded constellations, symbol mapping, hard demapping, soft LLR computation (max-log and exact log-sum-exp via JAX JIT), and probabilistic shaping (Maxwell-Boltzmann). |
|
|
59
|
+
| [`commkit.filtering`](commkit/filtering.py) | Pulse shaping (RRC, RC, Gaussian, Smooth-Rectangle), FIR tap generators, IIR SOS filter design (Butterworth, Chebyshev I/II, elliptic, Bessel) and application, matched filtering, and Overlap-Save. |
|
|
55
60
|
| [`commkit.multirate`](commkit/multirate.py) | Fractional and integer sample rate conversion (`resample`, `decimate`, `upsample`, `decimate_to_symbol_rate`). |
|
|
56
61
|
| [`commkit.timing`](commkit/timing.py) | Preamble generation (Barker, Zadoff-Chu), cross-correlation timing delay estimation, and frame alignment. |
|
|
57
|
-
| [`commkit.frequency`](commkit/frequency.py) | Carrier frequency offset estimation (FOE via M-th power, Mengali-Morelli,
|
|
58
|
-
| [`commkit.recovery`](commkit/recovery) | Carrier phase recovery (CPR via Viterbi-Viterbi, BPS, DD-PLL)
|
|
59
|
-
| [`commkit.equalization`](commkit/equalization) |
|
|
60
|
-
| [`commkit.impairments`](commkit/impairments) | Channel impairments simulation: AWGN (with SPS correction), PMD (differential group delay, Jones matrix), phase noise, IQ imbalance, and chromatic dispersion. |
|
|
61
|
-
| [`commkit.coding`](commkit/coding) |
|
|
62
|
-
| [`commkit.metrics`](commkit/metrics.py) | System performance evaluation: EVM
|
|
62
|
+
| [`commkit.frequency`](commkit/frequency.py) | Carrier frequency offset estimation (FOE via M-th power, Mengali-Morelli, pilot-symbol, bias-tone) and static/blockwise time-varying FOE correction. |
|
|
63
|
+
| [`commkit.recovery`](commkit/recovery) | Carrier phase recovery (CPR via Viterbi-Viterbi, BPS, DD-PLL, MAP Tikhonov-RTS, pilot-symbol/pilot-tone), cycle-slip detection/correction, and phase/channel-permutation ambiguity resolution. |
|
|
64
|
+
| [`commkit.equalization`](commkit/equalization) | Sequential (`lms`, `rls`, `cma`, `rde`) and frequency-domain block (`block_lms`, `block_cma`, `block_rde`) adaptive equalizers, `zf_equalizer`, butterfly MIMO topology support, and polarization-tone demultiplexing, with Numba JIT and JAX execution backends. |
|
|
65
|
+
| [`commkit.impairments`](commkit/impairments) | Channel impairments simulation: AWGN (with SPS correction), PMD (differential group delay, Jones matrix), phase noise, IQ imbalance (application + Löwdin/Gram-Schmidt compensation), and chromatic dispersion. |
|
|
66
|
+
| [`commkit.coding`](commkit/coding) | **Planned, not yet implemented** - scaffold-only placeholders reserving the layout for channel coding / FEC primitives (BCH, Convolutional, CRC, Galois field arithmetic, Hamming, Interleaving, LDPC, Polar, Rate matching, Reed-Solomon, Turbo codes). |
|
|
67
|
+
| [`commkit.metrics`](commkit/metrics.py) | System performance evaluation: EVM, SNR, BER, SER, and capacity metrics (GMI, MI) with PS-QAM support. |
|
|
63
68
|
| [`commkit.analysis`](commkit/analysis) | Laser phase and linewidth characterization: DSH, homodyne IQ, zero-phase drift detrending, AWGN-free lag-slope linewidth fit, Di Domenico $\beta$-separation line FWHM, and Allan deviation. |
|
|
64
|
-
| [`commkit.spectral`](commkit/spectral.py) | Welch PSD estimation and frequency shifting with bin-quantized mixing. |
|
|
69
|
+
| [`commkit.spectral`](commkit/spectral.py) | Welch PSD estimation, spectrograms, and frequency shifting with bin-quantized mixing. |
|
|
70
|
+
| [`commkit.smoothing`](commkit/smoothing.py) | Diagnostic/plotting-only smoothers (moving average, Savitzky-Golay, 2-D density smoothing) - not signal-chain filters; see `commkit.filtering` for those. |
|
|
65
71
|
| [`commkit.io`](commkit/io.py) | Signal persistence and disk serialization (`load_npz`, `save_npz`). |
|
|
66
|
-
| [`commkit.plotting`](commkit/plotting) | Visualization tools for constellations, eye diagrams, PSDs, time-domain signals, filter responses, and
|
|
67
|
-
| [`commkit.helpers`](commkit/helpers.py) | General DSP helpers: random bit/symbol generators, array normalization, RMS calculation, and SI prefix formatting. |
|
|
72
|
+
| [`commkit.plotting`](commkit/plotting) | Visualization tools for constellations, eye diagrams, PSDs/spectrograms, time-domain signals, filter responses, equalizer convergence, and sync/CPR diagnostics (timing correlation, FOE spectra, carrier-phase trajectories). |
|
|
73
|
+
| [`commkit.helpers`](commkit/helpers.py) | General DSP helpers: random bit/symbol generators, array normalization, RMS calculation, dB<->linear conversion, and SI prefix formatting. |
|
|
68
74
|
|
|
69
75
|
---
|
|
70
76
|
|
|
@@ -76,10 +82,10 @@ CommKit is a Python library for digital communications research that treats hard
|
|
|
76
82
|
|
|
77
83
|
```bash
|
|
78
84
|
# Using uv (Recommended)
|
|
79
|
-
uv pip install
|
|
85
|
+
uv pip install commkit
|
|
80
86
|
|
|
81
87
|
# Or with standard pip
|
|
82
|
-
pip install
|
|
88
|
+
pip install commkit
|
|
83
89
|
```
|
|
84
90
|
|
|
85
91
|
### GPU Support
|
|
@@ -88,10 +94,10 @@ To install with CUDA acceleration (includes JAX CUDA 13 and CuPy stacks):
|
|
|
88
94
|
|
|
89
95
|
```bash
|
|
90
96
|
# Using uv
|
|
91
|
-
uv pip install "commkit[gpu]
|
|
97
|
+
uv pip install "commkit[gpu]"
|
|
92
98
|
|
|
93
99
|
# Or with standard pip
|
|
94
|
-
pip install "commkit[gpu]
|
|
100
|
+
pip install "commkit[gpu]"
|
|
95
101
|
```
|
|
96
102
|
|
|
97
103
|
> [!NOTE]
|
|
@@ -104,6 +110,20 @@ pip install "commkit[gpu] @ git+https://github.com/lokgar/commkit.git"
|
|
|
104
110
|
>
|
|
105
111
|
> *(Assumes `commkit` is cloned in `$HOME/commkit`. If located elsewhere, replace `$HOME/commkit` with `<path-to-repo>`. `python3.*/` matches any Python version automatically).*
|
|
106
112
|
|
|
113
|
+
### Notebook Support
|
|
114
|
+
|
|
115
|
+
To run the example notebooks and enable the rich HTML `Signal.print_info()` table (falls back to plain text without it):
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Using uv
|
|
119
|
+
uv pip install "commkit[notebook]"
|
|
120
|
+
|
|
121
|
+
# Or with standard pip
|
|
122
|
+
pip install "commkit[notebook]"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Extras can be combined, e.g. `commkit[gpu,notebook]`, or install everything at once with `commkit[full]`.
|
|
126
|
+
|
|
107
127
|
### Development Installation
|
|
108
128
|
|
|
109
129
|
```bash
|
|
@@ -113,7 +133,7 @@ cd commkit
|
|
|
113
133
|
# Sync core environment
|
|
114
134
|
uv sync
|
|
115
135
|
|
|
116
|
-
# Sync environment with all extras (including GPU packages)
|
|
136
|
+
# Sync environment with all extras (including GPU and notebook packages)
|
|
117
137
|
uv sync --all-extras
|
|
118
138
|
```
|
|
119
139
|
|
|
@@ -17,7 +17,7 @@ CommKit is a Python library for digital communications research that treats hard
|
|
|
17
17
|
|
|
18
18
|
- **One object, complete context:** Sampling rate, symbol rate, modulation format, and pulse shape travel with the signal through the processing pipeline.
|
|
19
19
|
- **Backend-transparent DSP:** `dispatch()` resolves NumPy, CuPy, or SciPy modules at runtime. The same code executes seamlessly on CPU or GPU.
|
|
20
|
-
- **
|
|
20
|
+
- **Functional pipelines:** DSP functions accept and return a `Signal` directly (`sig = fir_filter(sig, taps)`; `sig = resample(sig, sps_out=2)`), so pipelines compose without a monolithic `Signal` wrapper API - `sig.to("gpu")` moves data across backends, the rest is plain function composition.
|
|
21
21
|
- **JAX escape hatch:** Zero-copy DLPack export on GPU allows direct application of JAX transforms (gradients, `vmap`, `scan`) without leaving the research loop.
|
|
22
22
|
|
|
23
23
|
---
|
|
@@ -26,23 +26,24 @@ CommKit is a Python library for digital communications research that treats hard
|
|
|
26
26
|
|
|
27
27
|
| Module | Key Capabilities & Features |
|
|
28
28
|
| --- | --- |
|
|
29
|
-
| [`commkit.core`](commkit/core) | `Signal` container (IQ samples + metadata), `SingleCarrierFrame`, `Preamble`, and symbol/frame factories (PAM, PSK, QAM). |
|
|
29
|
+
| [`commkit.core`](commkit/core) | `Signal` container (IQ samples + metadata), `SingleCarrierFrame`, `Preamble`, and symbol/frame factories (PAM, PSK, QAM, PS-QAM). |
|
|
30
30
|
| [`commkit.backend`](commkit/backend.py) | Hardware abstraction layer (`dispatch`, `to_device`, `to_jax`, `from_jax`), placement management, and backend execution (NumPy, CuPy, JAX). |
|
|
31
|
-
| [`commkit.mapping`](commkit/mapping) | Gray-coded constellations, symbol mapping, hard demapping, soft LLR computation (max-log and exact log-sum-exp via JAX JIT), and
|
|
32
|
-
| [`commkit.filtering`](commkit/filtering.py) | Pulse shaping (RRC, RC, Gaussian, Smooth-Rectangle), tap generators,
|
|
31
|
+
| [`commkit.mapping`](commkit/mapping) | Gray-coded constellations, symbol mapping, hard demapping, soft LLR computation (max-log and exact log-sum-exp via JAX JIT), and probabilistic shaping (Maxwell-Boltzmann). |
|
|
32
|
+
| [`commkit.filtering`](commkit/filtering.py) | Pulse shaping (RRC, RC, Gaussian, Smooth-Rectangle), FIR tap generators, IIR SOS filter design (Butterworth, Chebyshev I/II, elliptic, Bessel) and application, matched filtering, and Overlap-Save. |
|
|
33
33
|
| [`commkit.multirate`](commkit/multirate.py) | Fractional and integer sample rate conversion (`resample`, `decimate`, `upsample`, `decimate_to_symbol_rate`). |
|
|
34
34
|
| [`commkit.timing`](commkit/timing.py) | Preamble generation (Barker, Zadoff-Chu), cross-correlation timing delay estimation, and frame alignment. |
|
|
35
|
-
| [`commkit.frequency`](commkit/frequency.py) | Carrier frequency offset estimation (FOE via M-th power, Mengali-Morelli,
|
|
36
|
-
| [`commkit.recovery`](commkit/recovery) | Carrier phase recovery (CPR via Viterbi-Viterbi, BPS, DD-PLL)
|
|
37
|
-
| [`commkit.equalization`](commkit/equalization) |
|
|
38
|
-
| [`commkit.impairments`](commkit/impairments) | Channel impairments simulation: AWGN (with SPS correction), PMD (differential group delay, Jones matrix), phase noise, IQ imbalance, and chromatic dispersion. |
|
|
39
|
-
| [`commkit.coding`](commkit/coding) |
|
|
40
|
-
| [`commkit.metrics`](commkit/metrics.py) | System performance evaluation: EVM
|
|
35
|
+
| [`commkit.frequency`](commkit/frequency.py) | Carrier frequency offset estimation (FOE via M-th power, Mengali-Morelli, pilot-symbol, bias-tone) and static/blockwise time-varying FOE correction. |
|
|
36
|
+
| [`commkit.recovery`](commkit/recovery) | Carrier phase recovery (CPR via Viterbi-Viterbi, BPS, DD-PLL, MAP Tikhonov-RTS, pilot-symbol/pilot-tone), cycle-slip detection/correction, and phase/channel-permutation ambiguity resolution. |
|
|
37
|
+
| [`commkit.equalization`](commkit/equalization) | Sequential (`lms`, `rls`, `cma`, `rde`) and frequency-domain block (`block_lms`, `block_cma`, `block_rde`) adaptive equalizers, `zf_equalizer`, butterfly MIMO topology support, and polarization-tone demultiplexing, with Numba JIT and JAX execution backends. |
|
|
38
|
+
| [`commkit.impairments`](commkit/impairments) | Channel impairments simulation: AWGN (with SPS correction), PMD (differential group delay, Jones matrix), phase noise, IQ imbalance (application + Löwdin/Gram-Schmidt compensation), and chromatic dispersion. |
|
|
39
|
+
| [`commkit.coding`](commkit/coding) | **Planned, not yet implemented** - scaffold-only placeholders reserving the layout for channel coding / FEC primitives (BCH, Convolutional, CRC, Galois field arithmetic, Hamming, Interleaving, LDPC, Polar, Rate matching, Reed-Solomon, Turbo codes). |
|
|
40
|
+
| [`commkit.metrics`](commkit/metrics.py) | System performance evaluation: EVM, SNR, BER, SER, and capacity metrics (GMI, MI) with PS-QAM support. |
|
|
41
41
|
| [`commkit.analysis`](commkit/analysis) | Laser phase and linewidth characterization: DSH, homodyne IQ, zero-phase drift detrending, AWGN-free lag-slope linewidth fit, Di Domenico $\beta$-separation line FWHM, and Allan deviation. |
|
|
42
|
-
| [`commkit.spectral`](commkit/spectral.py) | Welch PSD estimation and frequency shifting with bin-quantized mixing. |
|
|
42
|
+
| [`commkit.spectral`](commkit/spectral.py) | Welch PSD estimation, spectrograms, and frequency shifting with bin-quantized mixing. |
|
|
43
|
+
| [`commkit.smoothing`](commkit/smoothing.py) | Diagnostic/plotting-only smoothers (moving average, Savitzky-Golay, 2-D density smoothing) - not signal-chain filters; see `commkit.filtering` for those. |
|
|
43
44
|
| [`commkit.io`](commkit/io.py) | Signal persistence and disk serialization (`load_npz`, `save_npz`). |
|
|
44
|
-
| [`commkit.plotting`](commkit/plotting) | Visualization tools for constellations, eye diagrams, PSDs, time-domain signals, filter responses, and
|
|
45
|
-
| [`commkit.helpers`](commkit/helpers.py) | General DSP helpers: random bit/symbol generators, array normalization, RMS calculation, and SI prefix formatting. |
|
|
45
|
+
| [`commkit.plotting`](commkit/plotting) | Visualization tools for constellations, eye diagrams, PSDs/spectrograms, time-domain signals, filter responses, equalizer convergence, and sync/CPR diagnostics (timing correlation, FOE spectra, carrier-phase trajectories). |
|
|
46
|
+
| [`commkit.helpers`](commkit/helpers.py) | General DSP helpers: random bit/symbol generators, array normalization, RMS calculation, dB<->linear conversion, and SI prefix formatting. |
|
|
46
47
|
|
|
47
48
|
---
|
|
48
49
|
|
|
@@ -54,10 +55,10 @@ CommKit is a Python library for digital communications research that treats hard
|
|
|
54
55
|
|
|
55
56
|
```bash
|
|
56
57
|
# Using uv (Recommended)
|
|
57
|
-
uv pip install
|
|
58
|
+
uv pip install commkit
|
|
58
59
|
|
|
59
60
|
# Or with standard pip
|
|
60
|
-
pip install
|
|
61
|
+
pip install commkit
|
|
61
62
|
```
|
|
62
63
|
|
|
63
64
|
### GPU Support
|
|
@@ -66,10 +67,10 @@ To install with CUDA acceleration (includes JAX CUDA 13 and CuPy stacks):
|
|
|
66
67
|
|
|
67
68
|
```bash
|
|
68
69
|
# Using uv
|
|
69
|
-
uv pip install "commkit[gpu]
|
|
70
|
+
uv pip install "commkit[gpu]"
|
|
70
71
|
|
|
71
72
|
# Or with standard pip
|
|
72
|
-
pip install "commkit[gpu]
|
|
73
|
+
pip install "commkit[gpu]"
|
|
73
74
|
```
|
|
74
75
|
|
|
75
76
|
> [!NOTE]
|
|
@@ -82,6 +83,20 @@ pip install "commkit[gpu] @ git+https://github.com/lokgar/commkit.git"
|
|
|
82
83
|
>
|
|
83
84
|
> *(Assumes `commkit` is cloned in `$HOME/commkit`. If located elsewhere, replace `$HOME/commkit` with `<path-to-repo>`. `python3.*/` matches any Python version automatically).*
|
|
84
85
|
|
|
86
|
+
### Notebook Support
|
|
87
|
+
|
|
88
|
+
To run the example notebooks and enable the rich HTML `Signal.print_info()` table (falls back to plain text without it):
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# Using uv
|
|
92
|
+
uv pip install "commkit[notebook]"
|
|
93
|
+
|
|
94
|
+
# Or with standard pip
|
|
95
|
+
pip install "commkit[notebook]"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Extras can be combined, e.g. `commkit[gpu,notebook]`, or install everything at once with `commkit[full]`.
|
|
99
|
+
|
|
85
100
|
### Development Installation
|
|
86
101
|
|
|
87
102
|
```bash
|
|
@@ -91,7 +106,7 @@ cd commkit
|
|
|
91
106
|
# Sync core environment
|
|
92
107
|
uv sync
|
|
93
108
|
|
|
94
|
-
# Sync environment with all extras (including GPU packages)
|
|
109
|
+
# Sync environment with all extras (including GPU and notebook packages)
|
|
95
110
|
uv sync --all-extras
|
|
96
111
|
```
|
|
97
112
|
|