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,203 @@
|
|
|
1
|
+
"""Shared plotting theme, layout, and formatting helpers."""
|
|
2
|
+
|
|
3
|
+
import matplotlib as mpl
|
|
4
|
+
import matplotlib.font_manager as fm
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from ..backend import to_device
|
|
9
|
+
from ..logger import logger
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def apply_default_theme() -> None:
|
|
13
|
+
"""
|
|
14
|
+
Applies the library's default visual theme to Matplotlib.
|
|
15
|
+
|
|
16
|
+
This theme configures publication-quality defaults, including:
|
|
17
|
+
- Roboto or standard sans-serif typography.
|
|
18
|
+
- Optimized figure dimensions and DPI.
|
|
19
|
+
- Consistent grid styling and axis formatting.
|
|
20
|
+
- Improved LaTeX math rendering.
|
|
21
|
+
|
|
22
|
+
Notes
|
|
23
|
+
-----
|
|
24
|
+
This function modifies `matplotlib.rcParams` globally. It is recommended
|
|
25
|
+
to call this at the start of a script or notebook for consistent styling.
|
|
26
|
+
"""
|
|
27
|
+
logger.debug("Applying default plotting theme.")
|
|
28
|
+
try:
|
|
29
|
+
font_prop = fm.FontProperties(family="Roboto", weight="regular")
|
|
30
|
+
fm.findfont(font_prop, fallback_to_default=False)
|
|
31
|
+
font_name = "Roboto"
|
|
32
|
+
except ValueError:
|
|
33
|
+
font_name = "sans"
|
|
34
|
+
logger.warning("Roboto font not found, falling back to default sans-serif.")
|
|
35
|
+
|
|
36
|
+
plt.style.use("dark_background")
|
|
37
|
+
|
|
38
|
+
mpl.rcParams.update(
|
|
39
|
+
{
|
|
40
|
+
"figure.figsize": (7, 4.9),
|
|
41
|
+
"font.family": font_name,
|
|
42
|
+
"font.size": 12,
|
|
43
|
+
"legend.fontsize": 10,
|
|
44
|
+
"lines.linewidth": 2,
|
|
45
|
+
"axes.linewidth": 1,
|
|
46
|
+
"axes.grid": True,
|
|
47
|
+
"grid.alpha": 0.4,
|
|
48
|
+
"axes.titleweight": "bold",
|
|
49
|
+
"figure.constrained_layout.use": True,
|
|
50
|
+
"savefig.dpi": 300,
|
|
51
|
+
"xtick.direction": "in",
|
|
52
|
+
"ytick.direction": "in",
|
|
53
|
+
"xtick.major.width": 1,
|
|
54
|
+
"ytick.major.width": 1,
|
|
55
|
+
"xtick.major.size": 4,
|
|
56
|
+
"ytick.major.size": 4,
|
|
57
|
+
"xtick.minor.width": 1,
|
|
58
|
+
"ytick.minor.width": 1,
|
|
59
|
+
"xtick.minor.size": 2,
|
|
60
|
+
"ytick.minor.size": 2,
|
|
61
|
+
"xtick.top": True,
|
|
62
|
+
"ytick.right": True,
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
plt.rcParams["mathtext.fontset"] = "custom"
|
|
67
|
+
plt.rcParams["mathtext.rm"] = font_name
|
|
68
|
+
plt.rcParams["mathtext.it"] = f"{font_name}:italic"
|
|
69
|
+
plt.rcParams["mathtext.bf"] = f"{font_name}:bold"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _create_subplot_grid(num_axes: int, max_cols: int = 2) -> tuple[int, int]:
|
|
73
|
+
"""
|
|
74
|
+
Computes a grid layout (rows, cols) for a given number of axes.
|
|
75
|
+
|
|
76
|
+
Limits the maximum number of columns to prevent excessively wide figures.
|
|
77
|
+
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
num_axes : int
|
|
81
|
+
Total number of subplots required.
|
|
82
|
+
max_cols : int, default 2
|
|
83
|
+
Maximum allowed number of columns.
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
nrows : int
|
|
88
|
+
Number of rows in the grid.
|
|
89
|
+
ncols : int
|
|
90
|
+
Number of columns in the grid.
|
|
91
|
+
"""
|
|
92
|
+
if num_axes <= max_cols:
|
|
93
|
+
return 1, num_axes
|
|
94
|
+
ncols = max_cols
|
|
95
|
+
nrows = (num_axes + ncols - 1) // ncols # Ceiling division
|
|
96
|
+
return nrows, ncols
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _grid_figsize(
|
|
100
|
+
nrows: int, ncols: int, panel: tuple[float, float] | None = None
|
|
101
|
+
) -> tuple[float, float]:
|
|
102
|
+
"""
|
|
103
|
+
Figsize for a subplot grid that preserves the default single-panel size.
|
|
104
|
+
|
|
105
|
+
Tiles ``panel`` (default: ``rcParams["figure.figsize"]``) across the
|
|
106
|
+
grid, so a single row keeps the default height, a single column keeps
|
|
107
|
+
the default width, and an NxN grid keeps the default size per panel.
|
|
108
|
+
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
nrows, ncols : int
|
|
112
|
+
Grid shape.
|
|
113
|
+
panel : (float, float), optional
|
|
114
|
+
Per-panel ``(width, height)`` in inches. Defaults to the theme's
|
|
115
|
+
``figure.figsize``.
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
(width, height) : tuple of float
|
|
120
|
+
"""
|
|
121
|
+
w, h = panel if panel is not None else mpl.rcParams["figure.figsize"]
|
|
122
|
+
return (w * ncols, h * nrows)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _square_figsize(n: int = 1) -> tuple[float, float]:
|
|
126
|
+
"""
|
|
127
|
+
Square panel size derived from the default figure height.
|
|
128
|
+
|
|
129
|
+
Used for constellation-style plots that require an equal aspect ratio,
|
|
130
|
+
where the default (non-square) ``figure.figsize`` doesn't apply directly.
|
|
131
|
+
Tiling ``n`` panels preserves the same per-panel size as ``_grid_figsize``.
|
|
132
|
+
"""
|
|
133
|
+
_, h = mpl.rcParams["figure.figsize"]
|
|
134
|
+
return (h * n, h * n)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _decimate_minmax(
|
|
138
|
+
x: np.ndarray, y: np.ndarray, max_points: int = 4000
|
|
139
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
140
|
+
"""Down-sample a line for fast plotting while preserving its envelope.
|
|
141
|
+
|
|
142
|
+
Buckets the data and keeps the per-bucket min **and** max (emitted in
|
|
143
|
+
x-order), so sharp features - spectral peaks, window edges, glitches -
|
|
144
|
+
survive, unlike plain striding. Matplotlib renders every vertex, so for
|
|
145
|
+
long oversampled records (N ≳ 10⁵) plotting the raw trace is the dominant
|
|
146
|
+
cost; reducing to a few thousand points is visually identical but orders of
|
|
147
|
+
magnitude faster.
|
|
148
|
+
|
|
149
|
+
Parameters
|
|
150
|
+
----------
|
|
151
|
+
x, y : np.ndarray
|
|
152
|
+
1-D arrays of equal length.
|
|
153
|
+
max_points : int, default 4000
|
|
154
|
+
Approximate cap on the number of plotted points. Returned length is
|
|
155
|
+
``≈ max_points`` (``2`` per bucket). Pass ``<= 0`` to disable.
|
|
156
|
+
|
|
157
|
+
Returns
|
|
158
|
+
-------
|
|
159
|
+
(x_dec, y_dec) : tuple of np.ndarray
|
|
160
|
+
Decimated arrays, or the inputs unchanged when already short enough.
|
|
161
|
+
"""
|
|
162
|
+
n = len(y)
|
|
163
|
+
if max_points <= 0 or n <= max_points:
|
|
164
|
+
return x, y
|
|
165
|
+
|
|
166
|
+
n_buckets = max(1, max_points // 2)
|
|
167
|
+
bucket = n // n_buckets
|
|
168
|
+
trimmed = n_buckets * bucket
|
|
169
|
+
yr = y[:trimmed].reshape(n_buckets, bucket)
|
|
170
|
+
xr = x[:trimmed].reshape(n_buckets, bucket)
|
|
171
|
+
|
|
172
|
+
rows = np.arange(n_buckets)
|
|
173
|
+
i_min = yr.argmin(axis=1)
|
|
174
|
+
i_max = yr.argmax(axis=1)
|
|
175
|
+
# Emit the two extrema per bucket in their original x-order so the line
|
|
176
|
+
# does not zig-zag backwards.
|
|
177
|
+
first_is_min = i_min <= i_max
|
|
178
|
+
x_out = np.empty(n_buckets * 2, dtype=x.dtype)
|
|
179
|
+
y_out = np.empty(n_buckets * 2, dtype=y.dtype)
|
|
180
|
+
x_out[0::2] = np.where(first_is_min, xr[rows, i_min], xr[rows, i_max])
|
|
181
|
+
y_out[0::2] = np.where(first_is_min, yr[rows, i_min], yr[rows, i_max])
|
|
182
|
+
x_out[1::2] = np.where(first_is_min, xr[rows, i_max], xr[rows, i_min])
|
|
183
|
+
y_out[1::2] = np.where(first_is_min, yr[rows, i_max], yr[rows, i_min])
|
|
184
|
+
|
|
185
|
+
if trimmed < n: # keep the tail the reshape dropped
|
|
186
|
+
x_out = np.concatenate([x_out, x[trimmed:]])
|
|
187
|
+
y_out = np.concatenate([y_out, y[trimmed:]])
|
|
188
|
+
return x_out, y_out
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _set_eng_formatter(ax, which: str, unit: str) -> None:
|
|
192
|
+
"""Apply an engineering (SI-prefix) tick formatter to an axis."""
|
|
193
|
+
fmt = mpl.ticker.EngFormatter(unit=unit, sep=" ")
|
|
194
|
+
if which in ("x", "both"):
|
|
195
|
+
ax.xaxis.set_major_formatter(fmt)
|
|
196
|
+
if which in ("y", "both"):
|
|
197
|
+
ax.yaxis.set_major_formatter(fmt)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _as_channels(arr) -> np.ndarray:
|
|
201
|
+
"""Bring to CPU and promote ``(N,)`` -> ``(1, N)``."""
|
|
202
|
+
arr = np.atleast_2d(np.asarray(to_device(arr, "cpu")))
|
|
203
|
+
return arr
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""Time-domain waveform plots."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from ..backend import dispatch, to_device
|
|
9
|
+
from ..core.signal import Signal
|
|
10
|
+
from ..logger import logger
|
|
11
|
+
from .theme import (
|
|
12
|
+
_create_subplot_grid,
|
|
13
|
+
_grid_figsize,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def plot_time_domain(
|
|
18
|
+
samples: Any,
|
|
19
|
+
sampling_rate: float = 1.0,
|
|
20
|
+
start_symbol: int = 0,
|
|
21
|
+
num_symbols: int | None = None,
|
|
22
|
+
sps: float | None = None,
|
|
23
|
+
ax: Any | None = None,
|
|
24
|
+
title: str | None = "Waveform",
|
|
25
|
+
show: bool = False,
|
|
26
|
+
**kwargs: Any,
|
|
27
|
+
) -> tuple[Any, Any] | None:
|
|
28
|
+
"""
|
|
29
|
+
Plots the time-domain representation of the signal.
|
|
30
|
+
|
|
31
|
+
For complex signals, both In-Phase (I) and Quadrature (Q) components
|
|
32
|
+
are plotted. Handles SI scaling (s, ms, us, etc.) for the time axis.
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
samples : array_like or Signal
|
|
37
|
+
Input signal samples. Shape: (..., N_samples).
|
|
38
|
+
sampling_rate : float, default 1.0
|
|
39
|
+
Sampling rate in Hz.
|
|
40
|
+
start_symbol : int, default 0
|
|
41
|
+
The starting symbol to plot.
|
|
42
|
+
num_symbols : int, optional
|
|
43
|
+
Limit plot to a specific number of symbol periods. Requires `sps`.
|
|
44
|
+
sps : float, optional
|
|
45
|
+
Samples per symbol (required if `num_symbols` is used).
|
|
46
|
+
ax : matplotlib.axes.Axes, optional
|
|
47
|
+
Existing axis to plot on.
|
|
48
|
+
title : str, optional
|
|
49
|
+
Plot title. Defaults to "Waveform".
|
|
50
|
+
show : bool, default False
|
|
51
|
+
If True, calls `plt.show()`.
|
|
52
|
+
**kwargs : Any
|
|
53
|
+
Additional keyword arguments passed to `ax.plot`.
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
fig : matplotlib.figure.Figure
|
|
58
|
+
The figure object.
|
|
59
|
+
ax : matplotlib.axes.Axes or ndarray
|
|
60
|
+
The axis or array of axes used for the plot.
|
|
61
|
+
"""
|
|
62
|
+
if isinstance(samples, Signal):
|
|
63
|
+
sig = samples
|
|
64
|
+
return plot_time_domain(
|
|
65
|
+
sig.samples,
|
|
66
|
+
sampling_rate=sig.sampling_rate,
|
|
67
|
+
start_symbol=start_symbol,
|
|
68
|
+
num_symbols=num_symbols,
|
|
69
|
+
sps=sig.sps,
|
|
70
|
+
ax=ax,
|
|
71
|
+
title=title,
|
|
72
|
+
show=show,
|
|
73
|
+
**kwargs,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
logger.debug("Generating time-domain plot.")
|
|
77
|
+
|
|
78
|
+
samples, xp, _ = dispatch(samples)
|
|
79
|
+
|
|
80
|
+
# Handle Multichannel
|
|
81
|
+
# Convention: (Channels, Time)
|
|
82
|
+
if samples.ndim > 1:
|
|
83
|
+
num_channels = samples.shape[0]
|
|
84
|
+
|
|
85
|
+
if ax is None:
|
|
86
|
+
nrows, ncols = _create_subplot_grid(num_channels)
|
|
87
|
+
fig, axes = plt.subplots(
|
|
88
|
+
nrows, ncols, figsize=_grid_figsize(nrows, ncols), squeeze=False
|
|
89
|
+
)
|
|
90
|
+
else:
|
|
91
|
+
if not isinstance(ax, (list, tuple, np.ndarray)):
|
|
92
|
+
logger.warning(
|
|
93
|
+
"Multiple channels detected but single axis provided. Overlaying plots."
|
|
94
|
+
)
|
|
95
|
+
axes = np.array([[ax] * num_channels])
|
|
96
|
+
fig = ax.figure
|
|
97
|
+
else:
|
|
98
|
+
axes = np.atleast_2d(ax)
|
|
99
|
+
fig = axes.flat[0].figure
|
|
100
|
+
|
|
101
|
+
for i in range(num_channels):
|
|
102
|
+
channel_samples = samples[i]
|
|
103
|
+
|
|
104
|
+
# Determine target axis using 2D indexing
|
|
105
|
+
row, col = divmod(i, axes.shape[1])
|
|
106
|
+
target_ax = axes[row, col] if row < axes.shape[0] else axes.flat[-1]
|
|
107
|
+
|
|
108
|
+
ch_title = f"{title} (Ch {i})" if title else f"Channel {i}"
|
|
109
|
+
|
|
110
|
+
plot_time_domain(
|
|
111
|
+
channel_samples,
|
|
112
|
+
sampling_rate=sampling_rate,
|
|
113
|
+
start_symbol=start_symbol,
|
|
114
|
+
num_symbols=num_symbols,
|
|
115
|
+
sps=sps,
|
|
116
|
+
ax=target_ax,
|
|
117
|
+
title=ch_title,
|
|
118
|
+
show=False,
|
|
119
|
+
**kwargs,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if show:
|
|
123
|
+
plt.show()
|
|
124
|
+
return None
|
|
125
|
+
return fig, axes
|
|
126
|
+
|
|
127
|
+
# --- 1D Logic ---
|
|
128
|
+
|
|
129
|
+
if ax is None:
|
|
130
|
+
fig, ax = plt.subplots()
|
|
131
|
+
else:
|
|
132
|
+
fig = ax.figure
|
|
133
|
+
|
|
134
|
+
samples = to_device(samples, "cpu")
|
|
135
|
+
|
|
136
|
+
start_idx = int(start_symbol * sps) if sps is not None else int(start_symbol)
|
|
137
|
+
|
|
138
|
+
if num_symbols is not None and sps is not None:
|
|
139
|
+
limit = start_idx + int(num_symbols * sps)
|
|
140
|
+
if limit > len(samples):
|
|
141
|
+
limit = len(samples)
|
|
142
|
+
logger.warning(
|
|
143
|
+
"Limit exceeds number of symbols. Plotting up to last symbol."
|
|
144
|
+
)
|
|
145
|
+
plot_samples = samples[start_idx:limit]
|
|
146
|
+
else:
|
|
147
|
+
plot_samples = samples[start_idx:]
|
|
148
|
+
|
|
149
|
+
time_axis = np.arange(len(plot_samples)) / sampling_rate
|
|
150
|
+
|
|
151
|
+
# Auto-scale time axis
|
|
152
|
+
max_time = time_axis[-1] if len(time_axis) > 0 else 0
|
|
153
|
+
if max_time < 1e-9:
|
|
154
|
+
scale_factor = 1e12
|
|
155
|
+
unit = "ps"
|
|
156
|
+
elif max_time < 1e-6:
|
|
157
|
+
scale_factor = 1e9
|
|
158
|
+
unit = "ns"
|
|
159
|
+
elif max_time < 1e-3:
|
|
160
|
+
scale_factor = 1e6
|
|
161
|
+
unit = "µs"
|
|
162
|
+
elif max_time < 1:
|
|
163
|
+
scale_factor = 1e3
|
|
164
|
+
unit = "ms"
|
|
165
|
+
else:
|
|
166
|
+
scale_factor = 1.0
|
|
167
|
+
unit = "s"
|
|
168
|
+
|
|
169
|
+
time_axis = time_axis * scale_factor
|
|
170
|
+
xlabel = f"Time [{unit}]"
|
|
171
|
+
|
|
172
|
+
if np.iscomplexobj(plot_samples):
|
|
173
|
+
ax.plot(
|
|
174
|
+
time_axis,
|
|
175
|
+
plot_samples.real,
|
|
176
|
+
label="I",
|
|
177
|
+
**kwargs,
|
|
178
|
+
)
|
|
179
|
+
ax.plot(
|
|
180
|
+
time_axis,
|
|
181
|
+
plot_samples.imag,
|
|
182
|
+
label="Q",
|
|
183
|
+
**kwargs,
|
|
184
|
+
)
|
|
185
|
+
ax.legend()
|
|
186
|
+
else:
|
|
187
|
+
ax.plot(
|
|
188
|
+
time_axis,
|
|
189
|
+
plot_samples,
|
|
190
|
+
**kwargs,
|
|
191
|
+
)
|
|
192
|
+
ax.set_xlabel(xlabel)
|
|
193
|
+
ax.set_ylabel("Amplitude")
|
|
194
|
+
if title is not None:
|
|
195
|
+
ax.set_title(title)
|
|
196
|
+
|
|
197
|
+
if show:
|
|
198
|
+
plt.show()
|
|
199
|
+
return None
|
|
200
|
+
return fig, ax
|
commkit/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Carrier phase recovery utilities.
|
|
3
|
+
|
|
4
|
+
This package provides routines for carrier phase recovery (CPR), including
|
|
5
|
+
streaming decision-directed PLL, block-based Viterbi-Viterbi, Blind Phase
|
|
6
|
+
Search, MAP Tikhonov-RTS, and pilot-aided methods, along with cycle-slip
|
|
7
|
+
correction and phase ambiguity resolution.
|
|
8
|
+
|
|
9
|
+
The public API is unchanged from when this was a single module:
|
|
10
|
+
``from commkit.recovery import recover_carrier_phase_bps, ...`` continues to
|
|
11
|
+
work.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
# Re-exported so ``patch("commkit.recovery.logger...")`` and similar
|
|
17
|
+
# attribute access on the package namespace keep working.
|
|
18
|
+
from ..logger import logger
|
|
19
|
+
from .bps import recover_carrier_phase_bps
|
|
20
|
+
from .corrections import (
|
|
21
|
+
correct_carrier_phase,
|
|
22
|
+
correct_cycle_slips,
|
|
23
|
+
correct_phase_rotation,
|
|
24
|
+
resolve_channel_permutation,
|
|
25
|
+
resolve_phase_ambiguity,
|
|
26
|
+
smooth_phase_wiener,
|
|
27
|
+
)
|
|
28
|
+
from .pilots import (
|
|
29
|
+
recover_carrier_phase_pilot_symbols,
|
|
30
|
+
recover_carrier_phase_pilot_tone,
|
|
31
|
+
recover_carrier_phase_pilot_tones,
|
|
32
|
+
)
|
|
33
|
+
from .pll import recover_carrier_phase_pll
|
|
34
|
+
from .tikhonov import recover_carrier_phase_tikhonov
|
|
35
|
+
from .viterbi_viterbi import recover_carrier_phase_viterbi_viterbi
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"correct_carrier_phase",
|
|
39
|
+
"correct_cycle_slips",
|
|
40
|
+
"correct_phase_rotation",
|
|
41
|
+
"recover_carrier_phase_bps",
|
|
42
|
+
"recover_carrier_phase_pilot_tone",
|
|
43
|
+
"recover_carrier_phase_pilot_tones",
|
|
44
|
+
"recover_carrier_phase_pilot_symbols",
|
|
45
|
+
"recover_carrier_phase_pll",
|
|
46
|
+
"recover_carrier_phase_tikhonov",
|
|
47
|
+
"recover_carrier_phase_viterbi_viterbi",
|
|
48
|
+
"resolve_channel_permutation",
|
|
49
|
+
"resolve_phase_ambiguity",
|
|
50
|
+
"smooth_phase_wiener",
|
|
51
|
+
]
|