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.
- commkit-1.0.0/.gitattributes +1 -0
- commkit-1.0.0/.github/workflows/ci.yml +75 -0
- commkit-1.0.0/.github/workflows/publish.yml +40 -0
- commkit-1.0.0/.gitignore +31 -0
- commkit-1.0.0/.python-version +1 -0
- commkit-1.0.0/CLAUDE.md +386 -0
- commkit-1.0.0/LICENSE +21 -0
- commkit-1.0.0/PKG-INFO +145 -0
- commkit-1.0.0/README.md +123 -0
- commkit-1.0.0/benchmarks/baselines/Linux-CPython-3.14-64bit/0001_commkit_baseline.json +2375 -0
- commkit-1.0.0/benchmarks/bench_block_blind.py +63 -0
- commkit-1.0.0/benchmarks/bench_block_lms.py +85 -0
- commkit-1.0.0/benchmarks/bench_bps.py +49 -0
- commkit-1.0.0/benchmarks/bench_equalizers.py +174 -0
- commkit-1.0.0/benchmarks/bench_sync_misc.py +50 -0
- commkit-1.0.0/benchmarks/benchutils.py +57 -0
- commkit-1.0.0/benchmarks/conftest.py +102 -0
- commkit-1.0.0/benchmarks/workloads.py +87 -0
- commkit-1.0.0/commkit/__init__.py +74 -0
- commkit-1.0.0/commkit/_cuda/__init__.py +321 -0
- commkit-1.0.0/commkit/_cuda/compiler.py +88 -0
- commkit-1.0.0/commkit/_cuda/src/bps_min_d2.cu +104 -0
- commkit-1.0.0/commkit/_cuda/src/cs_block.cu +119 -0
- commkit-1.0.0/commkit/_cuda/src/selftest.cu +14 -0
- commkit-1.0.0/commkit/analysis/__init__.py +55 -0
- commkit-1.0.0/commkit/analysis/_common.py +236 -0
- commkit-1.0.0/commkit/analysis/allan.py +108 -0
- commkit-1.0.0/commkit/analysis/drift.py +213 -0
- commkit-1.0.0/commkit/analysis/interferometry.py +887 -0
- commkit-1.0.0/commkit/analysis/linewidth.py +480 -0
- commkit-1.0.0/commkit/analysis/trajectory.py +91 -0
- commkit-1.0.0/commkit/backend.py +507 -0
- commkit-1.0.0/commkit/coding/__init__.py +23 -0
- commkit-1.0.0/commkit/coding/base.py +17 -0
- commkit-1.0.0/commkit/coding/bch.py +6 -0
- commkit-1.0.0/commkit/coding/convolutional.py +7 -0
- commkit-1.0.0/commkit/coding/crc.py +7 -0
- commkit-1.0.0/commkit/coding/galois.py +8 -0
- commkit-1.0.0/commkit/coding/hamming.py +6 -0
- commkit-1.0.0/commkit/coding/interleaving.py +7 -0
- commkit-1.0.0/commkit/coding/ldpc.py +8 -0
- commkit-1.0.0/commkit/coding/polar.py +8 -0
- commkit-1.0.0/commkit/coding/ratematch.py +6 -0
- commkit-1.0.0/commkit/coding/reed_solomon.py +6 -0
- commkit-1.0.0/commkit/coding/turbo.py +8 -0
- commkit-1.0.0/commkit/core/__init__.py +32 -0
- commkit-1.0.0/commkit/core/frame.py +992 -0
- commkit-1.0.0/commkit/core/generation.py +581 -0
- commkit-1.0.0/commkit/core/signal.py +725 -0
- commkit-1.0.0/commkit/equalization/__init__.py +49 -0
- commkit-1.0.0/commkit/equalization/_block.py +1855 -0
- commkit-1.0.0/commkit/equalization/_common.py +606 -0
- commkit-1.0.0/commkit/equalization/_kernels_jax.py +1720 -0
- commkit-1.0.0/commkit/equalization/_kernels_numba.py +1704 -0
- commkit-1.0.0/commkit/equalization/blind.py +223 -0
- commkit-1.0.0/commkit/equalization/linear.py +365 -0
- commkit-1.0.0/commkit/equalization/polarization.py +790 -0
- commkit-1.0.0/commkit/equalization/result.py +191 -0
- commkit-1.0.0/commkit/equalization/sequential.py +2805 -0
- commkit-1.0.0/commkit/filtering.py +1120 -0
- commkit-1.0.0/commkit/frequency.py +1191 -0
- commkit-1.0.0/commkit/helpers.py +489 -0
- commkit-1.0.0/commkit/impairments/__init__.py +43 -0
- commkit-1.0.0/commkit/impairments/channel/__init__.py +20 -0
- commkit-1.0.0/commkit/impairments/channel/linear.py +310 -0
- commkit-1.0.0/commkit/impairments/channel/nonlinear.py +11 -0
- commkit-1.0.0/commkit/impairments/frontend.py +229 -0
- commkit-1.0.0/commkit/impairments/noise.py +105 -0
- commkit-1.0.0/commkit/impairments/source.py +219 -0
- commkit-1.0.0/commkit/io.py +308 -0
- commkit-1.0.0/commkit/logger.py +103 -0
- commkit-1.0.0/commkit/mapping/__init__.py +46 -0
- commkit-1.0.0/commkit/mapping/bits.py +240 -0
- commkit-1.0.0/commkit/mapping/constellation.py +153 -0
- commkit-1.0.0/commkit/mapping/gray.py +429 -0
- commkit-1.0.0/commkit/mapping/llr.py +253 -0
- commkit-1.0.0/commkit/mapping/shaping.py +218 -0
- commkit-1.0.0/commkit/metrics.py +949 -0
- commkit-1.0.0/commkit/multirate.py +476 -0
- commkit-1.0.0/commkit/plotting/__init__.py +78 -0
- commkit-1.0.0/commkit/plotting/analysis.py +627 -0
- commkit-1.0.0/commkit/plotting/constellation.py +483 -0
- commkit-1.0.0/commkit/plotting/equalizer.py +390 -0
- commkit-1.0.0/commkit/plotting/eye.py +388 -0
- commkit-1.0.0/commkit/plotting/spectral.py +575 -0
- commkit-1.0.0/commkit/plotting/sync.py +953 -0
- commkit-1.0.0/commkit/plotting/theme.py +203 -0
- commkit-1.0.0/commkit/plotting/waveform.py +200 -0
- commkit-1.0.0/commkit/py.typed +0 -0
- commkit-1.0.0/commkit/recovery/__init__.py +51 -0
- commkit-1.0.0/commkit/recovery/bps.py +337 -0
- commkit-1.0.0/commkit/recovery/corrections.py +751 -0
- commkit-1.0.0/commkit/recovery/pilots.py +803 -0
- commkit-1.0.0/commkit/recovery/pll.py +482 -0
- commkit-1.0.0/commkit/recovery/tikhonov.py +424 -0
- commkit-1.0.0/commkit/recovery/viterbi_viterbi.py +227 -0
- commkit-1.0.0/commkit/spectral.py +560 -0
- commkit-1.0.0/commkit/timing.py +841 -0
- commkit-1.0.0/examples/carrier_phase_analysis.py +378 -0
- commkit-1.0.0/examples/laser_linewidth_dsh.py +498 -0
- commkit-1.0.0/examples/laser_linewidth_homodyne_iq.py +358 -0
- commkit-1.0.0/examples/measurement_laser_linewidth_dsh.py +206 -0
- commkit-1.0.0/examples/measurement_laser_linewidth_homodyne_iq.py +212 -0
- commkit-1.0.0/pyproject.toml +133 -0
- commkit-1.0.0/tests/analysis/test_allan.py +25 -0
- commkit-1.0.0/tests/analysis/test_drift.py +44 -0
- commkit-1.0.0/tests/analysis/test_interferometry.py +258 -0
- commkit-1.0.0/tests/analysis/test_linewidth.py +135 -0
- commkit-1.0.0/tests/analysis/test_trajectory.py +50 -0
- commkit-1.0.0/tests/conftest.py +135 -0
- commkit-1.0.0/tests/core/test_frame.py +484 -0
- commkit-1.0.0/tests/core/test_psqam.py +314 -0
- commkit-1.0.0/tests/core/test_signal.py +904 -0
- commkit-1.0.0/tests/core/test_signal_mimo.py +197 -0
- commkit-1.0.0/tests/equalization/test_blind.py +202 -0
- commkit-1.0.0/tests/equalization/test_block.py +1032 -0
- commkit-1.0.0/tests/equalization/test_block_update.py +373 -0
- commkit-1.0.0/tests/equalization/test_bps_kernel.py +252 -0
- commkit-1.0.0/tests/equalization/test_cpr.py +961 -0
- commkit-1.0.0/tests/equalization/test_cs_kernel.py +173 -0
- commkit-1.0.0/tests/equalization/test_linear.py +260 -0
- commkit-1.0.0/tests/equalization/test_mimo.py +241 -0
- commkit-1.0.0/tests/equalization/test_polarization.py +300 -0
- commkit-1.0.0/tests/equalization/test_sequential.py +1166 -0
- commkit-1.0.0/tests/equalization/test_sequential_jax.py +556 -0
- commkit-1.0.0/tests/equalization/test_winit.py +407 -0
- commkit-1.0.0/tests/impairments/channel/test_channel_linear.py +265 -0
- commkit-1.0.0/tests/impairments/test_frontend.py +185 -0
- commkit-1.0.0/tests/impairments/test_noise.py +58 -0
- commkit-1.0.0/tests/impairments/test_source.py +146 -0
- commkit-1.0.0/tests/mapping/test_bits.py +159 -0
- commkit-1.0.0/tests/mapping/test_constellation.py +68 -0
- commkit-1.0.0/tests/mapping/test_gray.py +108 -0
- commkit-1.0.0/tests/mapping/test_llr.py +296 -0
- commkit-1.0.0/tests/recovery/test_bps.py +139 -0
- commkit-1.0.0/tests/recovery/test_corrections.py +352 -0
- commkit-1.0.0/tests/recovery/test_joint.py +94 -0
- commkit-1.0.0/tests/recovery/test_pilots.py +600 -0
- commkit-1.0.0/tests/recovery/test_pll.py +205 -0
- commkit-1.0.0/tests/recovery/test_tikhonov.py +276 -0
- commkit-1.0.0/tests/recovery/test_viterbi_viterbi.py +185 -0
- commkit-1.0.0/tests/test_backend.py +285 -0
- commkit-1.0.0/tests/test_cuda_infra.py +106 -0
- commkit-1.0.0/tests/test_filtering.py +429 -0
- commkit-1.0.0/tests/test_frequency.py +799 -0
- commkit-1.0.0/tests/test_helpers.py +225 -0
- commkit-1.0.0/tests/test_io.py +410 -0
- commkit-1.0.0/tests/test_logger.py +11 -0
- commkit-1.0.0/tests/test_metrics.py +572 -0
- commkit-1.0.0/tests/test_multirate.py +122 -0
- commkit-1.0.0/tests/test_plotting.py +898 -0
- commkit-1.0.0/tests/test_pulse_shaping.py +154 -0
- commkit-1.0.0/tests/test_spectral.py +345 -0
- commkit-1.0.0/tests/test_timing.py +985 -0
- 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/
|
commkit-1.0.0/.gitignore
ADDED
|
@@ -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
|
commkit-1.0.0/CLAUDE.md
ADDED
|
@@ -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.
|