commkit 1.0.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.
Files changed (155) hide show
  1. commkit-1.0.0/.gitattributes +1 -0
  2. commkit-1.0.0/.github/workflows/ci.yml +75 -0
  3. commkit-1.0.0/.github/workflows/publish.yml +40 -0
  4. commkit-1.0.0/.gitignore +31 -0
  5. commkit-1.0.0/.python-version +1 -0
  6. commkit-1.0.0/CLAUDE.md +386 -0
  7. commkit-1.0.0/LICENSE +21 -0
  8. commkit-1.0.0/PKG-INFO +145 -0
  9. commkit-1.0.0/README.md +123 -0
  10. commkit-1.0.0/benchmarks/baselines/Linux-CPython-3.14-64bit/0001_commkit_baseline.json +2375 -0
  11. commkit-1.0.0/benchmarks/bench_block_blind.py +63 -0
  12. commkit-1.0.0/benchmarks/bench_block_lms.py +85 -0
  13. commkit-1.0.0/benchmarks/bench_bps.py +49 -0
  14. commkit-1.0.0/benchmarks/bench_equalizers.py +174 -0
  15. commkit-1.0.0/benchmarks/bench_sync_misc.py +50 -0
  16. commkit-1.0.0/benchmarks/benchutils.py +57 -0
  17. commkit-1.0.0/benchmarks/conftest.py +102 -0
  18. commkit-1.0.0/benchmarks/workloads.py +87 -0
  19. commkit-1.0.0/commkit/__init__.py +74 -0
  20. commkit-1.0.0/commkit/_cuda/__init__.py +321 -0
  21. commkit-1.0.0/commkit/_cuda/compiler.py +88 -0
  22. commkit-1.0.0/commkit/_cuda/src/bps_min_d2.cu +104 -0
  23. commkit-1.0.0/commkit/_cuda/src/cs_block.cu +119 -0
  24. commkit-1.0.0/commkit/_cuda/src/selftest.cu +14 -0
  25. commkit-1.0.0/commkit/analysis/__init__.py +55 -0
  26. commkit-1.0.0/commkit/analysis/_common.py +236 -0
  27. commkit-1.0.0/commkit/analysis/allan.py +108 -0
  28. commkit-1.0.0/commkit/analysis/drift.py +213 -0
  29. commkit-1.0.0/commkit/analysis/interferometry.py +887 -0
  30. commkit-1.0.0/commkit/analysis/linewidth.py +480 -0
  31. commkit-1.0.0/commkit/analysis/trajectory.py +91 -0
  32. commkit-1.0.0/commkit/backend.py +507 -0
  33. commkit-1.0.0/commkit/coding/__init__.py +23 -0
  34. commkit-1.0.0/commkit/coding/base.py +17 -0
  35. commkit-1.0.0/commkit/coding/bch.py +6 -0
  36. commkit-1.0.0/commkit/coding/convolutional.py +7 -0
  37. commkit-1.0.0/commkit/coding/crc.py +7 -0
  38. commkit-1.0.0/commkit/coding/galois.py +8 -0
  39. commkit-1.0.0/commkit/coding/hamming.py +6 -0
  40. commkit-1.0.0/commkit/coding/interleaving.py +7 -0
  41. commkit-1.0.0/commkit/coding/ldpc.py +8 -0
  42. commkit-1.0.0/commkit/coding/polar.py +8 -0
  43. commkit-1.0.0/commkit/coding/ratematch.py +6 -0
  44. commkit-1.0.0/commkit/coding/reed_solomon.py +6 -0
  45. commkit-1.0.0/commkit/coding/turbo.py +8 -0
  46. commkit-1.0.0/commkit/core/__init__.py +32 -0
  47. commkit-1.0.0/commkit/core/frame.py +992 -0
  48. commkit-1.0.0/commkit/core/generation.py +581 -0
  49. commkit-1.0.0/commkit/core/signal.py +725 -0
  50. commkit-1.0.0/commkit/equalization/__init__.py +49 -0
  51. commkit-1.0.0/commkit/equalization/_block.py +1855 -0
  52. commkit-1.0.0/commkit/equalization/_common.py +606 -0
  53. commkit-1.0.0/commkit/equalization/_kernels_jax.py +1720 -0
  54. commkit-1.0.0/commkit/equalization/_kernels_numba.py +1704 -0
  55. commkit-1.0.0/commkit/equalization/blind.py +223 -0
  56. commkit-1.0.0/commkit/equalization/linear.py +365 -0
  57. commkit-1.0.0/commkit/equalization/polarization.py +790 -0
  58. commkit-1.0.0/commkit/equalization/result.py +191 -0
  59. commkit-1.0.0/commkit/equalization/sequential.py +2805 -0
  60. commkit-1.0.0/commkit/filtering.py +1120 -0
  61. commkit-1.0.0/commkit/frequency.py +1191 -0
  62. commkit-1.0.0/commkit/helpers.py +489 -0
  63. commkit-1.0.0/commkit/impairments/__init__.py +43 -0
  64. commkit-1.0.0/commkit/impairments/channel/__init__.py +20 -0
  65. commkit-1.0.0/commkit/impairments/channel/linear.py +310 -0
  66. commkit-1.0.0/commkit/impairments/channel/nonlinear.py +11 -0
  67. commkit-1.0.0/commkit/impairments/frontend.py +229 -0
  68. commkit-1.0.0/commkit/impairments/noise.py +105 -0
  69. commkit-1.0.0/commkit/impairments/source.py +219 -0
  70. commkit-1.0.0/commkit/io.py +308 -0
  71. commkit-1.0.0/commkit/logger.py +103 -0
  72. commkit-1.0.0/commkit/mapping/__init__.py +46 -0
  73. commkit-1.0.0/commkit/mapping/bits.py +240 -0
  74. commkit-1.0.0/commkit/mapping/constellation.py +153 -0
  75. commkit-1.0.0/commkit/mapping/gray.py +429 -0
  76. commkit-1.0.0/commkit/mapping/llr.py +253 -0
  77. commkit-1.0.0/commkit/mapping/shaping.py +218 -0
  78. commkit-1.0.0/commkit/metrics.py +949 -0
  79. commkit-1.0.0/commkit/multirate.py +476 -0
  80. commkit-1.0.0/commkit/plotting/__init__.py +78 -0
  81. commkit-1.0.0/commkit/plotting/analysis.py +627 -0
  82. commkit-1.0.0/commkit/plotting/constellation.py +483 -0
  83. commkit-1.0.0/commkit/plotting/equalizer.py +390 -0
  84. commkit-1.0.0/commkit/plotting/eye.py +388 -0
  85. commkit-1.0.0/commkit/plotting/spectral.py +575 -0
  86. commkit-1.0.0/commkit/plotting/sync.py +953 -0
  87. commkit-1.0.0/commkit/plotting/theme.py +203 -0
  88. commkit-1.0.0/commkit/plotting/waveform.py +200 -0
  89. commkit-1.0.0/commkit/py.typed +0 -0
  90. commkit-1.0.0/commkit/recovery/__init__.py +51 -0
  91. commkit-1.0.0/commkit/recovery/bps.py +337 -0
  92. commkit-1.0.0/commkit/recovery/corrections.py +751 -0
  93. commkit-1.0.0/commkit/recovery/pilots.py +803 -0
  94. commkit-1.0.0/commkit/recovery/pll.py +482 -0
  95. commkit-1.0.0/commkit/recovery/tikhonov.py +424 -0
  96. commkit-1.0.0/commkit/recovery/viterbi_viterbi.py +227 -0
  97. commkit-1.0.0/commkit/spectral.py +560 -0
  98. commkit-1.0.0/commkit/timing.py +841 -0
  99. commkit-1.0.0/examples/carrier_phase_analysis.py +378 -0
  100. commkit-1.0.0/examples/laser_linewidth_dsh.py +498 -0
  101. commkit-1.0.0/examples/laser_linewidth_homodyne_iq.py +358 -0
  102. commkit-1.0.0/examples/measurement_laser_linewidth_dsh.py +206 -0
  103. commkit-1.0.0/examples/measurement_laser_linewidth_homodyne_iq.py +212 -0
  104. commkit-1.0.0/pyproject.toml +133 -0
  105. commkit-1.0.0/tests/analysis/test_allan.py +25 -0
  106. commkit-1.0.0/tests/analysis/test_drift.py +44 -0
  107. commkit-1.0.0/tests/analysis/test_interferometry.py +258 -0
  108. commkit-1.0.0/tests/analysis/test_linewidth.py +135 -0
  109. commkit-1.0.0/tests/analysis/test_trajectory.py +50 -0
  110. commkit-1.0.0/tests/conftest.py +135 -0
  111. commkit-1.0.0/tests/core/test_frame.py +484 -0
  112. commkit-1.0.0/tests/core/test_psqam.py +314 -0
  113. commkit-1.0.0/tests/core/test_signal.py +904 -0
  114. commkit-1.0.0/tests/core/test_signal_mimo.py +197 -0
  115. commkit-1.0.0/tests/equalization/test_blind.py +202 -0
  116. commkit-1.0.0/tests/equalization/test_block.py +1032 -0
  117. commkit-1.0.0/tests/equalization/test_block_update.py +373 -0
  118. commkit-1.0.0/tests/equalization/test_bps_kernel.py +252 -0
  119. commkit-1.0.0/tests/equalization/test_cpr.py +961 -0
  120. commkit-1.0.0/tests/equalization/test_cs_kernel.py +173 -0
  121. commkit-1.0.0/tests/equalization/test_linear.py +260 -0
  122. commkit-1.0.0/tests/equalization/test_mimo.py +241 -0
  123. commkit-1.0.0/tests/equalization/test_polarization.py +300 -0
  124. commkit-1.0.0/tests/equalization/test_sequential.py +1166 -0
  125. commkit-1.0.0/tests/equalization/test_sequential_jax.py +556 -0
  126. commkit-1.0.0/tests/equalization/test_winit.py +407 -0
  127. commkit-1.0.0/tests/impairments/channel/test_channel_linear.py +265 -0
  128. commkit-1.0.0/tests/impairments/test_frontend.py +185 -0
  129. commkit-1.0.0/tests/impairments/test_noise.py +58 -0
  130. commkit-1.0.0/tests/impairments/test_source.py +146 -0
  131. commkit-1.0.0/tests/mapping/test_bits.py +159 -0
  132. commkit-1.0.0/tests/mapping/test_constellation.py +68 -0
  133. commkit-1.0.0/tests/mapping/test_gray.py +108 -0
  134. commkit-1.0.0/tests/mapping/test_llr.py +296 -0
  135. commkit-1.0.0/tests/recovery/test_bps.py +139 -0
  136. commkit-1.0.0/tests/recovery/test_corrections.py +352 -0
  137. commkit-1.0.0/tests/recovery/test_joint.py +94 -0
  138. commkit-1.0.0/tests/recovery/test_pilots.py +600 -0
  139. commkit-1.0.0/tests/recovery/test_pll.py +205 -0
  140. commkit-1.0.0/tests/recovery/test_tikhonov.py +276 -0
  141. commkit-1.0.0/tests/recovery/test_viterbi_viterbi.py +185 -0
  142. commkit-1.0.0/tests/test_backend.py +285 -0
  143. commkit-1.0.0/tests/test_cuda_infra.py +106 -0
  144. commkit-1.0.0/tests/test_filtering.py +429 -0
  145. commkit-1.0.0/tests/test_frequency.py +799 -0
  146. commkit-1.0.0/tests/test_helpers.py +225 -0
  147. commkit-1.0.0/tests/test_io.py +410 -0
  148. commkit-1.0.0/tests/test_logger.py +11 -0
  149. commkit-1.0.0/tests/test_metrics.py +572 -0
  150. commkit-1.0.0/tests/test_multirate.py +122 -0
  151. commkit-1.0.0/tests/test_plotting.py +898 -0
  152. commkit-1.0.0/tests/test_pulse_shaping.py +154 -0
  153. commkit-1.0.0/tests/test_spectral.py +345 -0
  154. commkit-1.0.0/tests/test_timing.py +985 -0
  155. commkit-1.0.0/uv.lock +3139 -0
@@ -0,0 +1 @@
1
+ *.ipynb filter=nbstripout
@@ -0,0 +1,75 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ # Cancel superseded runs on the same ref.
10
+ concurrency:
11
+ group: ${{ github.workflow }}-${{ github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ jobs:
15
+ static-analysis:
16
+ name: Static Analysis (Lint & Type Check)
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - name: Install uv
22
+ uses: astral-sh/setup-uv@v5
23
+ with:
24
+ enable-cache: true
25
+
26
+ - name: Set up Python
27
+ run: uv python install 3.12
28
+
29
+ - name: Sync environment
30
+ run: uv sync
31
+
32
+ - name: Ruff format check
33
+ run: uv run ruff format --check .
34
+
35
+ - name: Ruff lint
36
+ run: uv run ruff check .
37
+
38
+ - name: Type check
39
+ run: uv run mypy commkit/
40
+
41
+ test:
42
+ name: Test (Python ${{ matrix.python-version }} | ${{ matrix.resolution }})
43
+ runs-on: ubuntu-latest
44
+ strategy:
45
+ fail-fast: false
46
+ matrix:
47
+ python-version: ["3.12", "3.13", "3.14"]
48
+ resolution: ["locked", "lowest-direct"]
49
+
50
+ steps:
51
+ - uses: actions/checkout@v4
52
+
53
+ - name: Install uv
54
+ uses: astral-sh/setup-uv@v5
55
+ with:
56
+ enable-cache: true
57
+
58
+ - name: Set up Python ${{ matrix.python-version }}
59
+ run: uv python install ${{ matrix.python-version }}
60
+
61
+ - name: Install dependencies (locked)
62
+ if: matrix.resolution == 'locked'
63
+ run: uv sync --python ${{ matrix.python-version }}
64
+
65
+ - name: Install dependencies (lowest bounds)
66
+ if: matrix.resolution == 'lowest-direct'
67
+ # Ignore lockfile, install absolute minimums allowed by pyproject.toml
68
+ run: uv sync --python ${{ matrix.python-version }} --resolution lowest-direct
69
+
70
+ - name: Run tests (CPU) with coverage
71
+ run: uv run pytest --device=cpu --cov=commkit --cov-report=term-missing
72
+
73
+ # GPU legs (--device=gpu / --device=all) require CuPy + CUDA and are run
74
+ # manually or on a self-hosted runner; they are intentionally not part of
75
+ # this hosted CPU workflow.
@@ -0,0 +1,40 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ # Allows you to run this workflow manually from the Actions tab
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ pypi-publish:
11
+ name: Build and publish Python package
12
+ runs-on: ubuntu-latest
13
+
14
+ # REQUIRED for PyPI Trusted Publishing (OIDC)
15
+ permissions:
16
+ id-token: write
17
+ contents: read
18
+
19
+ steps:
20
+ - name: Checkout repository
21
+ uses: actions/checkout@v4
22
+
23
+ - name: Install uv
24
+ uses: astral-sh/setup-uv@v3
25
+ with:
26
+ version: "latest"
27
+
28
+ - name: Set up Python
29
+ uses: actions/setup-python@v5
30
+ with:
31
+ python-version: "3.12"
32
+
33
+ - name: Build package
34
+ run: uv build
35
+
36
+ - name: Publish package distributions to PyPI
37
+ uses: pypa/gh-action-pypi-publish@release/v1
38
+ with:
39
+ # This action automatically finds the built wheel/sdist in the dist/ folder
40
+ packages-dir: dist/
@@ -0,0 +1,31 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ .pytest_cache/
4
+ *.py[oc]
5
+ build/
6
+ dist/
7
+ wheels/
8
+ *.egg-info
9
+ .ruff_cache
10
+ .benchmarks
11
+
12
+ # Virtual environments
13
+ .venv
14
+
15
+ # mypy
16
+ .mypy_cache/
17
+
18
+ .vscode/
19
+
20
+ tmp/
21
+
22
+ # DSP captures / large binaries
23
+ *.npy
24
+ *.npz
25
+ *.mat
26
+ *.wav
27
+ *.bin
28
+ *.h5
29
+ *.hdf5
30
+ *.pdf
31
+
@@ -0,0 +1 @@
1
+ 3.14
@@ -0,0 +1,386 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance and reference commands for developers and AI agents (such as Claude Code) working in the **CommKit** repository.
4
+
5
+ ---
6
+
7
+ ## 1. Project Overview
8
+
9
+ CommKit is a Python library for high-performance digital communications research. It provides a unified `Signal` abstraction over CPU (NumPy), GPU (CuPy), and JAX backends, with automatic runtime dispatch based on where data resides.
10
+
11
+ ---
12
+
13
+ ## 2. Command Reference
14
+
15
+ All environment and script executions **must** use `uv` (Astral's Python package manager). Python 3.12+ is required.
16
+
17
+ ### Environment Management
18
+
19
+ ```bash
20
+ # Sync environment (core dependencies only)
21
+ uv sync
22
+
23
+ # Sync environment with all extras (includes local GPU development packages)
24
+ uv sync --all-extras
25
+
26
+ # Re-resolve and upgrade all packages in the lockfile to their latest matched versions
27
+ uv lock --upgrade
28
+
29
+ # Add a dependency / dev dependency
30
+ uv add <package>
31
+ uv add --dev <package>
32
+
33
+ # Run a Python script or console command
34
+ uv run <script.py>
35
+ ```
36
+
37
+ ### Testing (pytest)
38
+
39
+ ```bash
40
+ # Run tests on all backends - CPU and GPU (project default via addopts)
41
+ uv run pytest
42
+
43
+ # Run tests on CPU only
44
+ uv run pytest --device=cpu
45
+
46
+ # Run tests on GPU only (requires CuPy + CUDA)
47
+ uv run pytest --device=gpu
48
+
49
+ # Run a single test file (or a whole mirrored subpackage)
50
+ uv run pytest tests/core/test_signal.py
51
+ uv run pytest tests/equalization/
52
+
53
+ # Run a specific test case
54
+ uv run pytest tests/core/test_signal.py::test_signal_initialization -v
55
+
56
+ # Run with test coverage
57
+ uv run pytest --cov=commkit
58
+ ```
59
+
60
+ ### Benchmarking (pytest-benchmark)
61
+
62
+ The `benchmarks/` suite is **explicit-only**: the default `uv run pytest` collects `tests/` only (`testpaths` in `pyproject.toml`), so benchmarks never slow down a normal test run.
63
+
64
+ ```bash
65
+ # Run the full benchmark suite (CPU and GPU)
66
+ uv run pytest benchmarks/ --benchmark-only --device=all
67
+
68
+ # Run one benchmark file / one benchmark
69
+ uv run pytest benchmarks/bench_bps.py --benchmark-only --device=gpu
70
+ uv run pytest "benchmarks/bench_equalizers.py::bench_lms" --benchmark-only --device=all
71
+
72
+ # Save a named baseline (commit the JSON under benchmarks/baselines/)
73
+ uv run pytest benchmarks/ --benchmark-only --device=all \
74
+ --benchmark-save=<label> --benchmark-storage=file://benchmarks/baselines
75
+
76
+ # Compare the current code against a saved baseline (e.g. 0001_main)
77
+ uv run pytest benchmarks/ --benchmark-only --device=all \
78
+ --benchmark-compare=0001 --benchmark-storage=file://benchmarks/baselines
79
+ ```
80
+
81
+ See **Section 6 - Benchmark Suite** for what each file measures and how to read the results.
82
+
83
+ ### Linting & Formatting
84
+
85
+ ```bash
86
+ # Run Ruff linter
87
+ uv run ruff check .
88
+
89
+ # Format code with Ruff
90
+ uv run ruff format .
91
+
92
+ # Run static type checking
93
+ uv run mypy commkit/
94
+ ```
95
+
96
+ > **CI gates on `ruff format --check .`, `ruff check .`, *and*
97
+ > `mypy commkit/` (see `.github/workflows/ci.yml`) - not just the linter.**
98
+ > `ruff check` and `ruff format` are independent: the linter passing does
99
+ > **not** mean the file is formatted, and neither implies the type check
100
+ > passes. Before committing/pushing **any** edited or newly added file -
101
+ > especially code appended programmatically (e.g. `cat >>`), which bypasses
102
+ > editor auto-format - run the same gate CI runs:
103
+ >
104
+ > ```bash
105
+ > uv run ruff format . # apply formatting (or `--check .` to verify only)
106
+ > uv run ruff check . --fix # lint + autofix
107
+ > uv run mypy commkit/ # static type check (library only, like CI)
108
+ > uv run pytest # CPU+GPU tests (CI runs --device=cpu)
109
+ > ```
110
+ >
111
+ > A formatting-only diff (line wrapping, trailing commas) still fails CI - run
112
+ > `ruff format` last so nothing slips through.
113
+
114
+ ### Version Management & Release Workflow
115
+
116
+ Since `bump-my-version` is defined in the project's development dependencies, always use `uv run bump-my-version` to run it directly from your local virtual environment (it is faster and avoids re-downloading packages compared to `uvx`).
117
+
118
+ #### Release Step-by-Step Guide
119
+
120
+ 1. **Verify tests & package build correctness**:
121
+ Ensure all tests are passing and the library compiles cleanly:
122
+
123
+ ```bash
124
+ uv run pytest
125
+ uv build
126
+ ```
127
+
128
+ 2. **Commit your active code changes**:
129
+ Make sure all your actual development features or bugfixes are committed in Git first:
130
+
131
+ ```bash
132
+ git add .
133
+ git commit -m "feat: add digital transceiver enhancement"
134
+ ```
135
+
136
+ 3. **Bump the version locally**:
137
+ Because `[tool.bumpversion]` in `pyproject.toml` is configured with `commit = true` and `tag = true` by default, running the bump command **automatically** updates version strings, commits the version changes to Git, and generates the Git release tag in a single atomic transaction:
138
+
139
+ ```bash
140
+ uv run bump-my-version bump patch # e.g., 3.4.1 -> 3.4.2
141
+ uv run bump-my-version bump minor # e.g., 3.4.1 -> 3.5.0
142
+ uv run bump-my-version bump major # e.g., 3.4.1 -> 4.0.0
143
+ ```
144
+
145
+ 4. **Push the release commits and tags to GitHub**:
146
+ Push the feature commits, version bump commit, and release tags to your origin repository:
147
+
148
+ ```bash
149
+ git push origin main --tags
150
+ ```
151
+
152
+ *Note: You only need to run `uv sync --all-extras` during release preparation if you explicitly added or modified package dependencies in `pyproject.toml`.*
153
+
154
+ ---
155
+
156
+ ## 3. Reference Implementation Files
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
187
+
188
+ ### Multi-Backend Dispatching
189
+
190
+ Always utilize the `backend.dispatch(samples)` helper in DSP functions. It dynamically returns the raw device array, the corresponding array module (`xp`), and the signal processing module (`sp`) based on the location of the data:
191
+
192
+ ```python
193
+ from commkit.backend import dispatch
194
+
195
+ def my_dsp_function(samples):
196
+ x, xp, sp = dispatch(samples)
197
+ # xp is numpy or cupy; sp is scipy or cupyx.scipy
198
+ return xp.fft.fft(x) # transparent CPU/GPU execution
199
+ ```
200
+
201
+ ### Data Types & Precision
202
+
203
+ To maximize GPU throughput and minimize memory footprint, CommKit utilizes mixed-precision layouts. Adhere strictly to the following dtype conventions:
204
+
205
+ * **Default Storage**: Use `complex64` (`np.complex64` / `cp.complex64`) for raw IQ samples and `float32` (`np.float32` / `cp.float32`) for real-valued signals.
206
+ * **Filter Dot-Product Accumulators**: In sequential adaptive filtering loops (LMS, CMA), inputs and weights are stored in `complex64` to save bandwidth. However, inside the hot loop, intermediate multiply-accumulate operations (dot-products for `y_out` and gradient weight adjustments) **must** promote variables to double precision (`float64` / `complex128`) to prevent catastrophic round-off cancellation over long symbol durations.
207
+ * **RLS Matrix Inversions**: The Recursive Least Squares (RLS) algorithm is highly sensitive to numerical accumulation. The inverse correlation matrix $P$, Kalman gain vector $k$, and regressor buffers must be maintained in **double precision** (`complex128` / `float64`) throughout the sequential loop. Updating $P$ in single-precision `complex64` quickly leads to loss of its positive-definite Hermitian properties, resulting in catastrophic filter divergence.
208
+ * **JAX TensorFloat-32 (TF32) Mitigation**: On modern NVIDIA GPUs (Ampere+), JAX defaults to TensorFloat-32 (TF32) for fast matrix multiplications, which truncates the mantissa from 23 bits (FP32) to 10 bits. For sequential gradient-accumulation loops like LMS/RLS weight updates, TF32 truncation causes severe drift. You **must** explicitly specify `Precision.HIGHEST` for JAX matrix products in equalizers to force true FP32.
209
+ * **Phase Unwrapping & Kalman Smoothers (CPR)**: In carrier phase recovery (e.g., Viterbi-Viterbi, BPS, pilot-aided), phase angle arrays must be promoted to double precision (`float64`) before calling `xp.unwrap()`. Unwrapping is extremely sensitive to $\pm\pi/M$ boundaries; single-precision `float32` rounding error can trigger spurious quadrant wrap-around slips. Tikhonov Kalman smoothers must also compute block transitions in `float64` to prevent underflow in small noise covariance variables.
210
+
211
+ ### Signal Normalisation Invariant
212
+
213
+ To prevent scaling issues across cascaded DSP operations, CommKit maintains a strict power normalisation invariant:
214
+
215
+ * **Symbol power representation**: A signal oversampled at `sps` has average sample energy `E[|x|²] = 1 / sps`.
216
+ * **Symbol-rate representation**: A signal at `sps=1` has average sample energy `E[|x|²] = 1`.
217
+ * Any new DSP blocks (e.g. filters, upsamplers, decimators) that alter the rate **must** apply the exact deterministic gain corrections (e.g., `sps_before / sps_after` scaling) to preserve this invariant.
218
+
219
+ ### Reproducibility & Randomness
220
+
221
+ Never call a module-level global RNG (`np.random.normal`, `xp.random.rand`, ...)
222
+ directly inside library code. Every function performing stochastic modeling
223
+ (noise injection, impairments, `generate_*`) must accept an optional
224
+ `seed: int | None = None` parameter and use one of two generation patterns,
225
+ chosen by data volume:
226
+
227
+ 1. **Device-identical (preferred)** - generate with NumPy's `default_rng` on
228
+ the CPU and transfer with `to_device(...)`, so a given seed produces
229
+ bit-identical data on CPU and GPU (`generate_bits`, `generate_phase_noise`).
230
+ Use for bit/symbol sequences and trajectories whose size is modest.
231
+
232
+ 2. **On-device** - only when the noise is as large as the signal itself and a
233
+ host round-trip would dominate (`apply_awgn`):
234
+
235
+ ```python
236
+ rng = xp.random.RandomState(seed) if seed is not None else xp.random
237
+ noise = rng.normal(0, std, samples.shape)
238
+ ```
239
+
240
+ Streams then differ between CPU and GPU for the same seed.
241
+
242
+ **Seed-stability policy:** a seed guarantees reproducibility *within* a
243
+ library version only - RNG internals may improve between versions (e.g.
244
+ `apply_phase_noise` moved from backend `RandomState` to pattern 1 in the
245
+ `generate_*` refactor). Never build tests or stored baselines on exact noise
246
+ realizations; assert statistics instead.
247
+
248
+ ### Performance JIT Compilation
249
+
250
+ * **Numba**: Use `@numba.njit(cache=True, fastmath=True, nogil=True)` for serial loops (like sequential LMS/RLS adaptive updates on CPU). Keep kernels compiled lazily and cached. For single-stream sequential equalization, Numba on CPU is the **fastest existing backend** - measured 150-200x faster than the per-symbol JAX scan on GPU (see `benchmarks/`).
251
+ * **JAX**: `jax.lax.scan` compiles sequential weight updates, but per-symbol scans on GPU are dominated by per-step XLA overhead and are slow for single streams. Reserve the JAX path for differentiability or batched workloads; GPU-side throughput improvements should use block/chunked formulations (`update_mode='block'`, the `block_lms` FDAF engine, and the `block_cma`/`block_rde` siblings).
252
+
253
+ ### Host-Device Synchronization Hygiene
254
+
255
+ Inside library code, **never extract a scalar from a possibly-GPU array inside a loop** (`float(x[ch])`, `int(x[ch])`, `.item()`) - each extraction forces a full GPU pipeline flush. Instead:
256
+
257
+ * Compute the full per-channel vector on device, transfer it **once** with `to_device(vec, "cpu")`, then loop over the host copy.
258
+ * Prefer on-device gathers (`xp.take_along_axis`) over Python list comprehensions with indexed scalars.
259
+ * Per-channel **diagnostic logging** must be gated: wrap the transfer + loop in `if logger.isEnabledFor(logging.INFO):` so disabled logging costs zero syncs (see `metrics.py` for the canonical pattern).
260
+ * Bound large broadcast intermediates: distance-matrix style `(N, M)` allocations should be chunked over N with an on-device accumulator (see `metrics.mi`).
261
+
262
+ ### Array Shapes
263
+
264
+ * **SISO**: 1-D array: `(N_samples,)`
265
+ * **MIMO**: 2-D array: `(N_channels, N_samples)` - **time is always on the last axis**.
266
+
267
+ ### Naming Conventions
268
+
269
+ * **Verb prefixes for processing functions.** Recovery/correction routines follow a
270
+ fixed verb vocabulary so the call site reads as a pipeline:
271
+ * `estimate_*` - measure an impairment without altering the signal
272
+ (returns the estimate, e.g. `estimate_carrier_frequency_offset`).
273
+ * `correct_*` - apply a (possibly externally supplied) correction
274
+ (e.g. `correct_carrier_phase`, `correct_cycle_slips`).
275
+ * `recover_*` - the combined estimate-then-correct convenience entry point
276
+ (e.g. `recover_carrier_phase_bps`).
277
+ * `resolve_*` - disambiguate a discrete/structural unknown
278
+ (e.g. `resolve_phase_ambiguity`, `resolve_channel_permutation`).
279
+ * **`generate_*` for synthesis.** Any function that synthesizes a new signal,
280
+ sequence, or noise process from *parameters* rather than from an input array
281
+ (stochastic, accepts `seed`) takes a `generate_` prefix: `generate_qam`,
282
+ `generate_psk`, `generate_pam`, `generate_psqam`, `generate_bits`,
283
+ `generate_symbols`, `generate_phase_noise`, plus the generic
284
+ `generate(modulation=...)` engine. Deterministic transforms of existing
285
+ arrays keep the `apply_*` / compute-noun conventions (e.g.
286
+ `analysis.dsh_beat(phi, ...)` is a compute function - no randomness, no
287
+ seed - even though it synthesizes a waveform from a phase trajectory).
288
+ * **Compute vs. plot.** A computation keeps the plain noun
289
+ (`analysis.carrier_phase_trajectory`, `analysis.allan_deviation`,
290
+ `spectral.spectrogram`). **Every** public function in `plotting` takes a
291
+ `plot_` prefix (`plot_constellation`, `plot_eye_diagram`, `plot_psd`,
292
+ `plot_carrier_phase_trajectory`, `plot_allan_deviation`, `plot_spectrogram`,
293
+ ...) - the only exception is the non-plot theme helper `apply_default_theme`.
294
+ This makes the layer obvious at the call site and removes every same-name
295
+ collision between the `analysis`/`spectral`/`timing`/`frequency` compute
296
+ modules and `plotting`. Never add a bare-noun plot function that shadows a
297
+ compute function.
298
+
299
+ ---
300
+
301
+ ## 5. Testing Conventions
302
+
303
+ * **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
+ * **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:
305
+
306
+ ```python
307
+ from commkit.testing import xpt
308
+ # ...
309
+ xpt.assert_allclose(result, expected, rtol=1e-5)
310
+ assert float(xp.mean(xp.abs(result))) > 0.0
311
+ ```
312
+
313
+ * **Layout mirrors the source tree.** Tests for a subpackage live in the
314
+ matching test subpackage and, as far as practical, **one test file maps to one
315
+ source module**:
316
+ * `tests/equalization/` <-> `commkit/equalization/` - `test_sequential.py`
317
+ (lms/rls/cma/rde, Numba), `test_sequential_jax.py`, `test_mimo.py`,
318
+ `test_winit.py`, `test_linear.py` (zf/MMSE), `test_polarization.py`,
319
+ `test_blind.py` + `test_block_update.py` (block_cma/block_rde),
320
+ `test_block.py` (block_lms / FDAF), `test_cpr.py`, and the CUDA-kernel tests
321
+ `test_bps_kernel.py` / `test_cs_kernel.py`.
322
+ * `tests/recovery/` <-> `commkit/recovery/` - `test_viterbi_viterbi.py`,
323
+ `test_bps.py`, `test_pilots.py`, `test_tikhonov.py`, `test_pll.py`,
324
+ `test_corrections.py`, plus `test_joint.py` for the cross-algorithm
325
+ joint-channel consistency checks.
326
+ * `tests/core/` <-> `commkit/core/` - `test_signal.py`, `test_signal_mimo.py`,
327
+ `test_frame.py` (`Preamble`/`SingleCarrierFrame`), `test_psqam.py` (generation).
328
+ * `tests/analysis/` <-> `commkit/analysis/` - `test_trajectory.py`,
329
+ `test_drift.py`, `test_linewidth.py`, `test_allan.py`,
330
+ `test_interferometry.py` (DSH laser characterization).
331
+ * `tests/impairments/` <-> `commkit/impairments/` - `test_noise.py`,
332
+ `test_source.py`, `test_frontend.py`, and `channel/test_channel_linear.py`
333
+ (the file basename is `test_channel_linear` rather than `test_linear`
334
+ because pytest's default prepend import-mode requires globally-unique test
335
+ basenames and `tests/equalization/test_linear.py` already exists).
336
+ * `tests/mapping/` <-> `commkit/mapping/` - `test_gray.py`, `test_bits.py`,
337
+ `test_llr.py`, `test_constellation.py` (the `Constellation` value object).
338
+ 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`.
341
+
342
+ When a single module's test file grows unwieldy, split it by *concern* within
343
+ the same subpackage (e.g. sequential vs. JAX vs. MIMO) rather than letting one
344
+ multi-thousand-line file accumulate.
345
+
346
+ * **Module-splitting trigger (when to promote a flat module to a package).**
347
+ Split a flat module into a subpackage when it crosses **~1,000 LOC** *and*
348
+ contains **≥2 clearly separable concerns** (distinct physical/mathematical
349
+ domains, or estimate-vs-correct workflows) that don't share much state. Size
350
+ alone is not the trigger - cohesive large modules can stay flat - and neither
351
+ is multiple concerns in a small file. When splitting, the package
352
+ `__init__.py` **must re-export exactly today's public names** so the import
353
+ surface (`from commkit.X import Y`) stays byte-identical; internals move,
354
+ user imports don't break. Mirror the split in `tests/` per the rule above.
355
+
356
+ ---
357
+
358
+ ## 6. Benchmark Suite
359
+
360
+ 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
+
362
+ ### What each file measures
363
+
364
+ | File | Functions | What the numbers show |
365
+ | --- | --- | --- |
366
+ | `bench_bps.py` | `recover_carrier_phase_bps` | Square-QAM O(1) fast path vs. the non-square `(CHUNK, B, M)` distance-tensor path (16-QAM vs. 128-cross). Includes the GPU-only gate workload `128cross / N=1e6 / C=2` for the fused-kernel work. |
367
+ | `bench_block_lms.py` | `block_lms` | Frequency-domain equalizer with CPR off / `bps` / `bps + cycle-slip`. Three legs: `bench_block_lms` (block_size=256, fully trained) is the launch-overhead-bound eager stress case; `bench_block_lms_large` (block_size=2048) is the recommended large-block operating point and guards block-size-scaling regressions; `bench_block_lms_dd` (block_size=256, short training prefix) is the realistic decision-directed steady state and the only leg that exercises the CUDA-graph path (graph captures full DD blocks only) - a graph regression / silent fallback shows up here as a jump back toward the eager ~800 ms. |
368
+ | `bench_equalizers.py` | `lms`, `cma`, `rls` | Sequential equalizers across `numba` and `jax` backends, 50k symbols (20k for RLS, symbol-spaced). |
369
+ | `bench_sync_misc.py` | Viterbi-Viterbi + cycle slips, `resolve_phase_ambiguity`, `evm` | Host-sync hygiene targets in recovery/metrics. |
370
+
371
+ ### How to read the IDs
372
+
373
+ Benchmark IDs encode `[<input-device>-<equalizer-backend>]`:
374
+
375
+ * `[cpu-numba]` - NumPy input, Numba CPU loop (the reference).
376
+ * `[gpu-numba]` - **CuPy input** with `backend='numba'`: measures the documented D2H -> CPU loop -> H2D round trip (the Δ vs. `[cpu-numba]` is the transfer + sync cost, ~1-3 ms per 50k symbols).
377
+ * `[gpu-jax]` - JAX `lax.scan` running on the GPU.
378
+
379
+ ### Methodology rules
380
+
381
+ * Timed bodies must end with the `sync` fixture call - GPU wall time without a stream sync measures kernel *launches*, not execution.
382
+ * Every benchmark does one warmup round so Numba/NVRTC/XLA compilation and CuPy pool growth are excluded.
383
+ * Workloads come from `benchmarks/workloads.py` with **fixed seeds** - never inline ad-hoc signal generation, or baselines stop being comparable.
384
+ * `benchmarks/benchutils.py` provides `CudaEventTimer` (pure device time) and `nvtx_range` (annotate stages for `nsys profile` - used to count D2H transfers per function).
385
+ * **Trust deltas, not single runs**: the default 3 rounds are noisy (±20-40% has been observed under ambient load). Before believing a regression, re-run the benchmark in isolation or do a controlled A/B against the prior revision (`git checkout <rev> -- <file>` + a fixed-seed timing script with ≥7 repetitions).
386
+ * Library logging is set to WARNING in `benchmarks/conftest.py` - benchmark numbers exclude diagnostic-logging costs by design (and INFO-gated diagnostics are skipped entirely).
commkit-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 lokgar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction acting including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom it is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.