Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 0.0.6 #102

Merged
merged 43 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
01c9f96
Set default `sample_rate` to `None`
mhostetter Aug 15, 2023
a2b0392
Add `sdr.plot.raster()`
mhostetter Aug 15, 2023
46ff0f5
Add `sdr.plot.eye()`
mhostetter Aug 15, 2023
b47b2b4
Update feature list
mhostetter Aug 15, 2023
411f002
Fix typo
mhostetter Aug 15, 2023
1c2a25f
Fix raster y limits
mhostetter Aug 15, 2023
94d9d72
Fix line widths on `raster()` plot
mhostetter Aug 15, 2023
2a1e1dc
Support raster of complex data
mhostetter Aug 15, 2023
38e24f6
Support eye diagrams of complex signals
mhostetter Aug 15, 2023
cda5009
Expand dictionary
mhostetter Aug 15, 2023
6c5b303
Fix linter errors
mhostetter Aug 15, 2023
d312829
Tighten `raster()` y-limits
mhostetter Aug 15, 2023
ffa8190
Make `eye()` y-axis symmetric
mhostetter Aug 15, 2023
7ba3881
Support 2D input to `raster()`
mhostetter Aug 15, 2023
50c2319
Change default colormap for `raster()`
mhostetter Aug 15, 2023
aa897c8
Only add colorbar with index-based coloring
mhostetter Aug 15, 2023
090f61a
Enable colorbar by default
mhostetter Aug 15, 2023
9fee01e
Use system default float precision
mhostetter Aug 16, 2023
81a45a0
Use system default complex float precision
mhostetter Aug 16, 2023
272624c
Fix bug in scaling PSDs
mhostetter Aug 16, 2023
556ee87
Fix bug in dB to linear conversion
mhostetter Aug 16, 2023
0027aa4
Fix bug in FSPL in the near-field region
mhostetter Aug 16, 2023
abeda57
Add an example plot to `fspl()`
mhostetter Aug 16, 2023
d377120
Test FSPL in near field
mhostetter Aug 16, 2023
c91f5ef
Allow for returning power measurements in dB
mhostetter Aug 16, 2023
e096808
Allow for returning voltage measurements in dB
mhostetter Aug 16, 2023
a514d9a
Support real mixing
mhostetter Aug 18, 2023
e5cff03
Add `sdr.upsample()`
mhostetter Aug 18, 2023
956c572
Add unit tests for `sdr.upsample()`
mhostetter Aug 18, 2023
1173ed2
Improve warning statement
mhostetter Aug 18, 2023
db66961
Add `sdr.downsample()`
mhostetter Aug 19, 2023
808e2af
Add unit tests for `sdr.downsample()`
mhostetter Aug 19, 2023
565cb1f
Improve example plots
mhostetter Aug 19, 2023
2a6c6df
Remove unnecessary conversion to scalar
mhostetter Aug 19, 2023
f418e50
Suppress `log(0)` warning in entropy function
mhostetter Aug 19, 2023
e435fba
Format `.toml` file
mhostetter Aug 19, 2023
18f2e16
Exclude plot functions from coverage
mhostetter Aug 19, 2023
ab2f229
Expand dictionary
mhostetter Aug 19, 2023
8ab16cf
Fix `.toml` syntax
mhostetter Aug 19, 2023
574d350
Fix codecov path
mhostetter Aug 19, 2023
0a7db7d
Use `linear()` inside library
mhostetter Aug 19, 2023
c3b39c3
Add `sdr.wavelength()`
mhostetter Aug 19, 2023
3a794e1
Add release notes for v0.0.6
mhostetter Aug 21, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,36 @@
],
"cSpell.words": [
"awgn",
"Baseband",
"baseband",
"downsample",
"downsampled",
"downsamples",
"downsampling",
"exponentials",
"Feedforward",
"feedforward",
"figsize",
"hexdump",
"intersymbol",
"lowpass",
"matplotlib",
"multirate",
"Nyquist",
"overdamped",
"passband",
"periodogram",
"periodograms",
"polyphase",
"pyplot",
"QPSK",
"resampler",
"resamplers",
"savefig",
"scipy",
"sinc",
"stopband",
"underdamped",
"upsampled",
"upsamples",
"upsampling"
]
}
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ View all available classes and functions in the [API Reference](https://mhostett
additive white Gaussian noise (AWGN), frequency offset, sample rate offset, IQ imbalance.
- **Link budgets**: Channel capacities, free-space path loss, antenna gains.
- **Data manipulation**: Packing and unpacking binary data, hexdumping binary data.
- **Plotting**: Time-domain, periodogram, spectrogram, BER, SER, constellation, symbol map, impulse response,
step response, magnitude response, phase response, phase delay, group delay, and zeros/poles.
- **Plotting**: Time-domain, raster, periodogram, spectrogram, constellation, symbol map, eye diagram,
bit error rate (BER), symbol error rate (SER), impulse response, step response, magnitude response, phase response,
phase delay, group delay, and zeros/poles.

## Examples

Expand Down
2 changes: 1 addition & 1 deletion docs/examples/phase-locked-loop.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@
"N = 75 # samples\n",
"theta_0 = 2 * np.pi / 10 # radians/sample\n",
"x = np.exp(1j * (theta_0 * np.arange(N) + np.pi)) # Input signal\n",
"y = np.ones(x.size + 1, dtype=np.complex64) # Output signal\n",
"y = np.ones(x.size + 1, dtype=complex) # Output signal\n",
"phase_error = np.zeros(x.size)\n",
"freq_estimate = np.zeros(x.size)\n",
"\n",
Expand Down
16 changes: 8 additions & 8 deletions docs/examples/pulse-shapes.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,10 @@
"w, H_rc_0p9 = scipy.signal.freqz(rc_0p9, 1, worN=1024, whole=False, fs=sps)\n",
"\n",
"# Compute the relative power in the main lobe of the pulses\n",
"P_rect = 10 * np.log10(np.cumsum(np.abs(H_rect) ** 2) / np.sum(np.abs(H_rect) ** 2))\n",
"P_rc_0p1 = 10 * np.log10(np.cumsum(np.abs(H_rc_0p1) ** 2) / np.sum(np.abs(H_rc_0p1) ** 2))\n",
"P_rc_0p5 = 10 * np.log10(np.cumsum(np.abs(H_rc_0p5) ** 2) / np.sum(np.abs(H_rc_0p5) ** 2))\n",
"P_rc_0p9 = 10 * np.log10(np.cumsum(np.abs(H_rc_0p9) ** 2) / np.sum(np.abs(H_rc_0p9) ** 2))\n",
"P_rect = sdr.db(np.cumsum(np.abs(H_rect) ** 2) / np.sum(np.abs(H_rect) ** 2))\n",
"P_rc_0p1 = sdr.db(np.cumsum(np.abs(H_rc_0p1) ** 2) / np.sum(np.abs(H_rc_0p1) ** 2))\n",
"P_rc_0p5 = sdr.db(np.cumsum(np.abs(H_rc_0p5) ** 2) / np.sum(np.abs(H_rc_0p5) ** 2))\n",
"P_rc_0p9 = sdr.db(np.cumsum(np.abs(H_rc_0p9) ** 2) / np.sum(np.abs(H_rc_0p9) ** 2))\n",
"\n",
"plt.figure(figsize=(10, 5))\n",
"plt.plot(w, P_rect, color=\"k\", linestyle=\":\", label=\"Rectangular\")\n",
Expand Down Expand Up @@ -406,10 +406,10 @@
"w, H_srrc_0p9 = scipy.signal.freqz(srrc_0p9, 1, worN=1024, whole=False, fs=sps)\n",
"\n",
"# Compute the relative power in the main lobe of the pulses\n",
"P_rect = 10 * np.log10(np.cumsum(np.abs(H_rect) ** 2) / np.sum(np.abs(H_rect) ** 2))\n",
"P_srrc_0p1 = 10 * np.log10(np.cumsum(np.abs(H_srrc_0p1) ** 2) / np.sum(np.abs(H_srrc_0p1) ** 2))\n",
"P_srrc_0p5 = 10 * np.log10(np.cumsum(np.abs(H_srrc_0p5) ** 2) / np.sum(np.abs(H_srrc_0p5) ** 2))\n",
"P_srrc_0p9 = 10 * np.log10(np.cumsum(np.abs(H_srrc_0p9) ** 2) / np.sum(np.abs(H_srrc_0p9) ** 2))\n",
"P_rect = sdr.db(np.cumsum(np.abs(H_rect) ** 2) / np.sum(np.abs(H_rect) ** 2))\n",
"P_srrc_0p1 = sdr.db(np.cumsum(np.abs(H_srrc_0p1) ** 2) / np.sum(np.abs(H_srrc_0p1) ** 2))\n",
"P_srrc_0p5 = sdr.db(np.cumsum(np.abs(H_srrc_0p5) ** 2) / np.sum(np.abs(H_srrc_0p5) ** 2))\n",
"P_srrc_0p9 = sdr.db(np.cumsum(np.abs(H_srrc_0p9) ** 2) / np.sum(np.abs(H_srrc_0p9) ** 2))\n",
"\n",
"plt.figure(figsize=(10, 5))\n",
"plt.plot(w, P_rect, color=\"k\", linestyle=\":\", label=\"Rectangular\")\n",
Expand Down
5 changes: 3 additions & 2 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ View all available classes and functions in the `API Reference <https://mhostett
additive white Gaussian noise (AWGN), frequency offset, sample rate offset, IQ imbalance.
- **Link budgets**: Channel capacities, free-space path loss, antenna gains.
- **Data manipulation**: Packing and unpacking binary data, hexdumping binary data.
- **Plotting**: Time-domain, periodogram, spectrogram, BER, SER, constellation, symbol map, impulse response,
step response, magnitude response, phase response, phase delay, group delay, and zeros/poles.
- **Plotting**: Time-domain, raster, periodogram, spectrogram, constellation, symbol map, eye diagram,
bit error rate (BER), symbol error rate (SER), impulse response, step response, magnitude response, phase response,
phase delay, group delay, and zeros/poles.

.. toctree::
:caption: Examples
Expand Down
18 changes: 18 additions & 0 deletions docs/release-notes/v0.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@ tocdepth: 2

# v0.0

## v0.0.6

*Released August 20, 2023*

### Changes

- Added raster plots in `sdr.plot.raster()`.
- Added eye diagrams in `sdr.plot.eye()`.
- Added upsampling (without anti-aliasing filtering) in `sdr.upsample()`.
- Added downsampling (without anti-aliasing filtering) in `sdr.downsample()`.
- Added wavelength calculation in `sdr.wavelength()`.
- Supported real sinusoid mixing.
- Supported returning measurements in dB.

### Contributors

- Matt Hostetter ([@mhostetter](https://github.com/mhostetter))

## v0.0.5

*Released August 13, 2023*
Expand Down
25 changes: 8 additions & 17 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
[build-system]
requires = [
"setuptools >= 62",
"wheel",
"setuptools_scm[toml] >= 6.2"
]
requires = ["setuptools >= 62", "wheel", "setuptools_scm[toml] >= 6.2"]

[project]
name = "sdr"
authors = [
{name = "Matt Hostetter", email = "[email protected]"},
]
authors = [{ name = "Matt Hostetter", email = "[email protected]" }]
description = "A Python package for software-defined radio"
readme = "README.md"
license = {text = "MIT"}
license = { text = "MIT" }
keywords = [
"software-defined radio",
"sdr",
Expand Down Expand Up @@ -54,7 +48,7 @@ dependencies = [
"scipy",
"matplotlib",
# "numba >= 0.55, < 0.58", # v0.55 is needed for support of NumPy 1.21
"typing_extensions >= 4.0.0", # v4.0.0 is needed for use of Self (Python 3.11+) and Literal (Python 3.8+)
"typing_extensions >= 4.0.0", # v4.0.0 is needed for use of Self (Python 3.11+) and Literal (Python 3.8+)
]
dynamic = ["version"]

Expand Down Expand Up @@ -93,11 +87,9 @@ where = ["src"]
universal = false

[tool.pylint]
ignore-paths = [
"src/sdr/_version.py",
]
ignore-paths = ["src/sdr/_version.py"]
disable = [
"comparison-with-callable", # pylint doesn't understand metaclass properties
"comparison-with-callable", # pylint doesn't understand metaclass properties
"fixme",
"global-statement",
"invalid-name",
Expand Down Expand Up @@ -132,9 +124,7 @@ profile = "black"
[tool.pytest.ini_options]
minversion = "6.2"
addopts = "-s --showlocals"
testpaths = [
"tests"
]
testpaths = ["tests"]

[tool.coverage.report]
exclude_lines = [
Expand All @@ -145,3 +135,4 @@ exclude_lines = [
"raise NotImplementedError",
"raise RuntimeError",
]
omit = ["*/plot/*"]
16 changes: 7 additions & 9 deletions src/sdr/_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,6 @@ def linear(
conversions-decibels
"""
x = np.asarray(x)
if not np.all(x >= 0):
raise ValueError("Argument 'x' must be non-negative.")

if type in ["value", "power"]:
return 10 ** (x / 10)
Expand Down Expand Up @@ -178,8 +176,8 @@ def ebn0_to_esn0(ebn0: npt.ArrayLike, bps: int, rate: int = 1) -> np.ndarray:
conversions-from-ebn0
"""
ebn0 = np.asarray(ebn0) # Energy per information bit
ecn0 = ebn0 + 10 * np.log10(rate) # Energy per coded bit
esn0 = ecn0 + 10 * np.log10(bps) # Energy per symbol
ecn0 = ebn0 + db(rate) # Energy per coded bit
esn0 = ecn0 + db(bps) # Energy per symbol
return esn0


Expand Down Expand Up @@ -219,7 +217,7 @@ def ebn0_to_snr(ebn0: npt.ArrayLike, bps: int, rate: int = 1, sps: int = 1) -> n
conversions-from-ebn0
"""
esn0 = ebn0_to_esn0(ebn0, bps, rate=rate) # SNR per symbol
snr = esn0 - 10 * np.log10(sps) # SNR per sample
snr = esn0 - db(sps) # SNR per sample
return snr


Expand Down Expand Up @@ -263,8 +261,8 @@ def esn0_to_ebn0(esn0: npt.ArrayLike, bps: int, rate: int = 1) -> np.ndarray:
conversions-from-esn0
"""
esn0 = np.asarray(esn0)
ecn0 = esn0 - 10 * np.log10(bps) # Energy per coded bit
ebn0 = ecn0 - 10 * np.log10(rate) # Energy per information bit
ecn0 = esn0 - db(bps) # Energy per coded bit
ebn0 = ecn0 - db(rate) # Energy per information bit
return ebn0


Expand Down Expand Up @@ -302,7 +300,7 @@ def esn0_to_snr(esn0: npt.ArrayLike, sps: int = 1) -> np.ndarray:
conversions-from-esn0
"""
esn0 = np.asarray(esn0) # SNR per symbol
snr = esn0 - 10 * np.log10(sps) # SNR per sample
snr = esn0 - db(sps) # SNR per sample
return snr


Expand Down Expand Up @@ -386,5 +384,5 @@ def snr_to_esn0(snr: npt.ArrayLike, sps: int = 1) -> np.ndarray:
conversions-from-snr
"""
snr = np.asarray(snr)
esn0 = snr + 10 * np.log10(sps)
esn0 = snr + db(sps)
return esn0
2 changes: 1 addition & 1 deletion src/sdr/_farrow.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def __init__(self, streaming: bool = False):
[1 / 6, -1, 1 / 2, 1 / 3],
[0, 0, 1, 0],
],
dtype=np.float32,
dtype=float,
)

self.reset()
Expand Down
4 changes: 2 additions & 2 deletions src/sdr/_filter/_fir.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ def impulse_response(self, N: int | None = None) -> np.ndarray:
raise ValueError("Argument 'N' must be greater than or equal to the filter length.")

# Delta impulse function
d = np.zeros(N - self.taps.size + 1, dtype=np.float32)
d = np.zeros(N - self.taps.size + 1, dtype=float)
d[0] = 1

h = scipy.signal.convolve(d, self.taps, mode="full")
Expand Down Expand Up @@ -255,7 +255,7 @@ def step_response(self, N: int | None = None) -> np.ndarray:
raise ValueError("Argument 'N' must be greater than or equal to the filter length.")

# Unit step function
u = np.ones(N - self.taps.size + 1, dtype=np.float32)
u = np.ones(N - self.taps.size + 1, dtype=float)

s = scipy.signal.convolve(u, self.taps, mode="full")

Expand Down
4 changes: 2 additions & 2 deletions src/sdr/_filter/_iir.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ def impulse_response(self, N: int = 100) -> np.ndarray:
See the :ref:`iir-filters` example.
"""
# Delta impulse function
d = np.zeros(N, dtype=np.float32)
d = np.zeros(N, dtype=float)
d[0] = 1

zi = self._state
Expand All @@ -242,7 +242,7 @@ def step_response(self, N: int = 100) -> np.ndarray:
See the :ref:`iir-filters` example.
"""
# Unit step function
u = np.ones(N, dtype=np.float32)
u = np.ones(N, dtype=float)

state = self._state
s = self(u)
Expand Down
4 changes: 2 additions & 2 deletions src/sdr/_filter/_polyphase.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,12 +327,12 @@ def __init__(
taps = multirate_taps(rate, 1)
elif taps == "linear":
self._method = "linear"
taps = np.zeros(2 * rate, dtype=np.float32)
taps = np.zeros(2 * rate, dtype=float)
taps[:rate] = np.arange(0, rate) / rate
taps[rate:] = np.arange(rate, 0, -1) / rate
elif taps == "zoh":
self._method = "zoh"
taps = np.ones(rate, dtype=np.float32)
taps = np.ones(rate, dtype=float)
else:
raise ValueError(f"Argument 'taps' must be 'kaiser', 'linear', 'zoh', or an array-like, not {taps}.")

Expand Down
32 changes: 30 additions & 2 deletions src/sdr/_link_budget/_antenna.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,37 @@
import numpy.typing as npt
import scipy.constants

from .._conversion import db
from .._helper import export


@export
def wavelength(freq: float) -> float:
r"""
Calculates the wavelength $\lambda$ of a electromagnetic wave with frequency $f$.

$$\lambda = \frac{c}{f}$$

Arguments:
freq: The frequency $f$ in Hz of the signal.

Returns:
The wavelength $\lambda$ in meters.

Examples:
The wavelength of a 1 GHz signal is 0.3 meters.

.. ipython:: python

sdr.wavelength(1e9)

Group:
link-budget-antennas
"""
freq = np.asarray(freq)
return scipy.constants.speed_of_light / freq


@export
def parabolic_antenna(
freq: float,
Expand Down Expand Up @@ -61,9 +89,9 @@ def parabolic_antenna(
if not np.all((0 <= efficiency) & (efficiency <= 1)):
raise ValueError("Argument 'efficiency' must be between 0 and 1.")

lambda_ = scipy.constants.speed_of_light / freq # Wavelength in meters
lambda_ = wavelength(freq) # Wavelength in meters
G = (np.pi * diameter / lambda_) ** 2 * efficiency # Gain in linear units
G = 10 * np.log10(G) # Gain in dBi
G = db(G) # Gain in dBi

theta = np.arcsin(3.83 * lambda_ / (np.pi * diameter)) # Beamwidth in radians
theta = np.rad2deg(theta) # Beamwidth in degrees
Expand Down
Loading