commkit 1.0.0__py3-none-any.whl
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/__init__.py +74 -0
- commkit/_cuda/__init__.py +321 -0
- commkit/_cuda/compiler.py +88 -0
- commkit/_cuda/src/bps_min_d2.cu +104 -0
- commkit/_cuda/src/cs_block.cu +119 -0
- commkit/_cuda/src/selftest.cu +14 -0
- commkit/analysis/__init__.py +55 -0
- commkit/analysis/_common.py +236 -0
- commkit/analysis/allan.py +108 -0
- commkit/analysis/drift.py +213 -0
- commkit/analysis/interferometry.py +887 -0
- commkit/analysis/linewidth.py +480 -0
- commkit/analysis/trajectory.py +91 -0
- commkit/backend.py +507 -0
- commkit/coding/__init__.py +23 -0
- commkit/coding/base.py +17 -0
- commkit/coding/bch.py +6 -0
- commkit/coding/convolutional.py +7 -0
- commkit/coding/crc.py +7 -0
- commkit/coding/galois.py +8 -0
- commkit/coding/hamming.py +6 -0
- commkit/coding/interleaving.py +7 -0
- commkit/coding/ldpc.py +8 -0
- commkit/coding/polar.py +8 -0
- commkit/coding/ratematch.py +6 -0
- commkit/coding/reed_solomon.py +6 -0
- commkit/coding/turbo.py +8 -0
- commkit/core/__init__.py +32 -0
- commkit/core/frame.py +992 -0
- commkit/core/generation.py +581 -0
- commkit/core/signal.py +725 -0
- commkit/equalization/__init__.py +49 -0
- commkit/equalization/_block.py +1855 -0
- commkit/equalization/_common.py +606 -0
- commkit/equalization/_kernels_jax.py +1720 -0
- commkit/equalization/_kernels_numba.py +1704 -0
- commkit/equalization/blind.py +223 -0
- commkit/equalization/linear.py +365 -0
- commkit/equalization/polarization.py +790 -0
- commkit/equalization/result.py +191 -0
- commkit/equalization/sequential.py +2805 -0
- commkit/filtering.py +1120 -0
- commkit/frequency.py +1191 -0
- commkit/helpers.py +489 -0
- commkit/impairments/__init__.py +43 -0
- commkit/impairments/channel/__init__.py +20 -0
- commkit/impairments/channel/linear.py +310 -0
- commkit/impairments/channel/nonlinear.py +11 -0
- commkit/impairments/frontend.py +229 -0
- commkit/impairments/noise.py +105 -0
- commkit/impairments/source.py +219 -0
- commkit/io.py +308 -0
- commkit/logger.py +103 -0
- commkit/mapping/__init__.py +46 -0
- commkit/mapping/bits.py +240 -0
- commkit/mapping/constellation.py +153 -0
- commkit/mapping/gray.py +429 -0
- commkit/mapping/llr.py +253 -0
- commkit/mapping/shaping.py +218 -0
- commkit/metrics.py +949 -0
- commkit/multirate.py +476 -0
- commkit/plotting/__init__.py +78 -0
- commkit/plotting/analysis.py +627 -0
- commkit/plotting/constellation.py +483 -0
- commkit/plotting/equalizer.py +390 -0
- commkit/plotting/eye.py +388 -0
- commkit/plotting/spectral.py +575 -0
- commkit/plotting/sync.py +953 -0
- commkit/plotting/theme.py +203 -0
- commkit/plotting/waveform.py +200 -0
- commkit/py.typed +0 -0
- commkit/recovery/__init__.py +51 -0
- commkit/recovery/bps.py +337 -0
- commkit/recovery/corrections.py +751 -0
- commkit/recovery/pilots.py +803 -0
- commkit/recovery/pll.py +482 -0
- commkit/recovery/tikhonov.py +424 -0
- commkit/recovery/viterbi_viterbi.py +227 -0
- commkit/spectral.py +560 -0
- commkit/timing.py +841 -0
- commkit-1.0.0.dist-info/METADATA +145 -0
- commkit-1.0.0.dist-info/RECORD +84 -0
- commkit-1.0.0.dist-info/WHEEL +4 -0
- commkit-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""Equalizer result containers and CPR state."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from ..backend import ArrayType, to_device
|
|
11
|
+
from ..logger import logger
|
|
12
|
+
|
|
13
|
+
# -----------------------------------------------------------------------------
|
|
14
|
+
# RESULT CONTAINER
|
|
15
|
+
# -----------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class CPRState:
|
|
20
|
+
"""Carrier-phase-recovery state for warm-starting across equalizer calls.
|
|
21
|
+
|
|
22
|
+
All arrays are CPU NumPy regardless of the equalizer backend - CPR state is
|
|
23
|
+
small and must survive device resets. Do not convert to CuPy.
|
|
24
|
+
|
|
25
|
+
Used/produced by ``lms()``, ``rls()``, and ``block_lms()`` when
|
|
26
|
+
``cpr_type`` is not None. Pass as ``cpr_state=result.cpr_state`` to the
|
|
27
|
+
next call to continue phase tracking without a re-lock transient.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
# PLL state (lms / rls with cpr_type='pll' or 'bps')
|
|
31
|
+
pll_phi: np.ndarray | None = None # (C,) float64
|
|
32
|
+
pll_freq: np.ndarray | None = None # (C,) float64
|
|
33
|
+
|
|
34
|
+
# BPS cross-block unwrap state (block_lms with cpr_type='bps')
|
|
35
|
+
bps_prev4: np.ndarray | None = None # (C,) float64
|
|
36
|
+
bps_offset4: np.ndarray | None = None # (C,) float64
|
|
37
|
+
bps_d2_hist: np.ndarray | None = None # (P, C, K-1) float32 - CPU copy
|
|
38
|
+
|
|
39
|
+
# Cycle-slip regression state (all CPR modes)
|
|
40
|
+
cs_buf_x: np.ndarray | None = None # (C, H) float64
|
|
41
|
+
cs_buf_y: np.ndarray | None = None # (C, H) float64
|
|
42
|
+
cs_buf_ptr: np.ndarray | None = None # (C,) int64
|
|
43
|
+
cs_buf_n: np.ndarray | None = None # (C,) int64
|
|
44
|
+
cs_stats: np.ndarray | None = None # (C, 4) float64
|
|
45
|
+
|
|
46
|
+
# JAX-specific BPS buffer state (JAX backend only; None for Numba)
|
|
47
|
+
jax_bps_buf: np.ndarray | None = None # (KB, C) complex64
|
|
48
|
+
jax_bps_buf_ptr: int | None = None # scalar int32
|
|
49
|
+
|
|
50
|
+
# Identity tags - used to validate shape compatibility on warm-start
|
|
51
|
+
cpr_type: str | None = None
|
|
52
|
+
num_ch: int = 0
|
|
53
|
+
symmetry: int = 4
|
|
54
|
+
bps_P: int = 0 # number of BPS test phases
|
|
55
|
+
bps_K: int = 0 # BPS block size
|
|
56
|
+
cs_H: int = 0 # cycle-slip history length
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class EqualizerResult:
|
|
61
|
+
"""Container for equalizer outputs.
|
|
62
|
+
|
|
63
|
+
Attributes
|
|
64
|
+
----------
|
|
65
|
+
y_hat : ArrayType
|
|
66
|
+
Equalized symbol sequence. Shape: ``(N_sym,)`` SISO or
|
|
67
|
+
``(C, N_sym)`` MIMO.
|
|
68
|
+
weights : ArrayType
|
|
69
|
+
Final tap weight vector. Shape: ``(num_taps,)`` for SISO
|
|
70
|
+
or ``(C, C, num_taps)`` for MIMO butterfly.
|
|
71
|
+
error : ArrayType
|
|
72
|
+
Error signal history. Shape: ``(N_sym,)`` or ``(C, N_sym)``.
|
|
73
|
+
weights_history : ArrayType or None
|
|
74
|
+
Tap weight evolution over time. Only populated when
|
|
75
|
+
``store_weights=True``.
|
|
76
|
+
num_train_symbols : int
|
|
77
|
+
Number of data-aided training symbols consumed (LMS/RLS). Used to
|
|
78
|
+
discard the DA-trained transient before computing steady-state metrics
|
|
79
|
+
like EVM, SNR, and BER.
|
|
80
|
+
input_norm_factor : float or np.ndarray
|
|
81
|
+
Normalization factor(s) ``rms(samples, axis=-1) * sqrt(sps)`` applied
|
|
82
|
+
per-channel by ``_normalize_inputs`` before equalization. For SISO
|
|
83
|
+
this is a plain ``float``; for MIMO it is a 1-D ``np.ndarray`` of
|
|
84
|
+
shape ``(C,)`` - one factor per input stream.
|
|
85
|
+
Stored so callers can apply the post-hoc power correction needed when
|
|
86
|
+
passing a different capture (e.g. vacuum noise) through the same
|
|
87
|
+
frozen taps without disturbing the per-channel ratio:
|
|
88
|
+
|
|
89
|
+
Example::
|
|
90
|
+
|
|
91
|
+
α = signal_result.input_norm_factor # float or (C,) array
|
|
92
|
+
β = noise_result.input_norm_factor
|
|
93
|
+
P_noise_corrected = (
|
|
94
|
+
np.mean(np.abs(noise_result.y_hat) ** 2, axis=-1) * (β / α) ** 2
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
``P_signal / P_noise_corrected`` is then the physically meaningful
|
|
98
|
+
per-channel signal-to-noise power ratio preserved through the DSP chain.
|
|
99
|
+
At ``sps=1`` this factor equals the plain per-channel RMS of the input symbols.
|
|
100
|
+
tail_trim : int
|
|
101
|
+
Number of symbols trimmed from the tail of ``y_hat`` to remove the
|
|
102
|
+
zero-padding contamination zone. Non-zero only for RLS (equals
|
|
103
|
+
``num_taps // 2``). If non-zero, trim reference arrays to match::
|
|
104
|
+
|
|
105
|
+
source_symbols = source_symbols[..., :-result.tail_trim]
|
|
106
|
+
source_bits = source_bits[..., :-result.tail_trim * bits_per_symbol]
|
|
107
|
+
phase_trajectory : np.ndarray or None
|
|
108
|
+
Per-symbol phase estimates produced by the inline CPR stage, in
|
|
109
|
+
radians. ``None`` when ``cpr_type=None``.
|
|
110
|
+
|
|
111
|
+
Shape: ``(N_sym,)`` for SISO, ``(C, N_sym)`` for MIMO butterfly.
|
|
112
|
+
|
|
113
|
+
The values are the instantaneous phase corrections *applied* to each
|
|
114
|
+
symbol before the hard decision and weight update, i.e.
|
|
115
|
+
``y_hat[n] = (W^H x[n]) · exp(-j · phase_trajectory[n])``.
|
|
116
|
+
Useful for post-hoc phase-noise analysis, cycle-slip diagnostics, and
|
|
117
|
+
as a warm-start phase estimate for a subsequent CPR stage.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
y_hat: ArrayType
|
|
121
|
+
weights: ArrayType
|
|
122
|
+
error: ArrayType
|
|
123
|
+
weights_history: ArrayType | None = None
|
|
124
|
+
num_train_symbols: int = 0
|
|
125
|
+
input_norm_factor: float | np.ndarray = 1.0
|
|
126
|
+
tail_trim: int = 0
|
|
127
|
+
phase_trajectory: ArrayType | None = None
|
|
128
|
+
cpr_state: CPRState | None = None
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _log_equalizer_exit(
|
|
132
|
+
result: EqualizerResult,
|
|
133
|
+
name: str,
|
|
134
|
+
debug_plot: bool = False,
|
|
135
|
+
check_convergence: bool = False,
|
|
136
|
+
plot_smoothing: int = 50,
|
|
137
|
+
) -> EqualizerResult:
|
|
138
|
+
"""Log exit MSE and optionally show a debug plot for an EqualizerResult."""
|
|
139
|
+
if result.error is not None:
|
|
140
|
+
n_sym = result.error.shape[-1] # time axis; (N_sym,) or (C, N_sym)
|
|
141
|
+
want_log = logger.isEnabledFor(logging.INFO)
|
|
142
|
+
# The convergence check emits a WARNING (normally always enabled), but
|
|
143
|
+
# only runs when explicitly requested and the signal is long enough.
|
|
144
|
+
want_conv = check_convergence and n_sym >= 20
|
|
145
|
+
|
|
146
|
+
# Skip the whole MSE computation - and its device->host transfer - when
|
|
147
|
+
# nothing will consume the result. When it is needed, transfer only the
|
|
148
|
+
# tail (and head, for convergence) windows rather than the full error
|
|
149
|
+
# array: ≤100 samples/channel instead of N.
|
|
150
|
+
if want_log or want_conv:
|
|
151
|
+
window = max(1, min(100, n_sym))
|
|
152
|
+
tail = to_device(result.error[..., -window:], "cpu")
|
|
153
|
+
|
|
154
|
+
if tail.ndim == 1: # SISO
|
|
155
|
+
mse_final = float(np.mean(np.abs(tail) ** 2))
|
|
156
|
+
mse_db = 10.0 * np.log10(mse_final + 1e-30)
|
|
157
|
+
if want_log:
|
|
158
|
+
logger.info(
|
|
159
|
+
"%s: exit MSE=%.1f dB (final %s symbols)", name, mse_db, window
|
|
160
|
+
)
|
|
161
|
+
else: # MIMO: per-channel MSE; keep the mean for the convergence check
|
|
162
|
+
per_ch_mse = np.mean(np.abs(tail) ** 2, axis=-1) # (C,)
|
|
163
|
+
if want_log:
|
|
164
|
+
parts = ", ".join(
|
|
165
|
+
f"ch{c}={10.0 * np.log10(m + 1e-30):.1f}"
|
|
166
|
+
for c, m in enumerate(per_ch_mse)
|
|
167
|
+
)
|
|
168
|
+
logger.info(
|
|
169
|
+
"%s: exit MSE (final %s symbols): %s dB", name, window, parts
|
|
170
|
+
)
|
|
171
|
+
mse_final = float(np.mean(per_ch_mse))
|
|
172
|
+
mse_db = 10.0 * np.log10(mse_final + 1e-30)
|
|
173
|
+
|
|
174
|
+
if want_conv:
|
|
175
|
+
init_window = max(1, min(100, n_sym // 10))
|
|
176
|
+
head = to_device(result.error[..., :init_window], "cpu")
|
|
177
|
+
mse_init = float(np.mean(np.abs(head) ** 2))
|
|
178
|
+
if mse_init > 0 and mse_final > mse_init * 0.9:
|
|
179
|
+
logger.warning(
|
|
180
|
+
"%s: convergence may be poor - final MSE (%.1f dB) not significantly below initial MSE (%.1f dB). Consider reducing step_size or increasing signal length.",
|
|
181
|
+
name,
|
|
182
|
+
mse_db,
|
|
183
|
+
10.0 * np.log10(mse_init + 1e-30),
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
if debug_plot:
|
|
187
|
+
from .. import plotting as _plotting # lazy import avoids circular dep
|
|
188
|
+
|
|
189
|
+
_plotting.plot_equalizer_result(result, smoothing=plot_smoothing)
|
|
190
|
+
|
|
191
|
+
return result
|