Skip to content

Commit 5509822

Browse files
committed
Refactored existing tests
1 parent b693827 commit 5509822

10 files changed

Lines changed: 452 additions & 387 deletions

.DS_Store

-6 KB
Binary file not shown.

__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Init file for tests."""

adc_eval/eval/spectrum.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77
def db_to_pow(value, places=3):
88
"""Convert dBW to W."""
99
if isinstance(value, np.ndarray):
10-
return 10 ** (0.1 * value)
10+
return round(10 ** (0.1 * value), places)
1111
return round(10 ** (0.1 * value), places)
1212

1313

1414
def dBW(value, places=1):
1515
"""Convert to dBW."""
1616
if isinstance(value, np.ndarray):
17-
return 10 * np.log10(value)
17+
return round(10 * np.log10(value), places)
1818
return round(10 * np.log10(value), places)
1919

2020

@@ -31,7 +31,7 @@ def sndr_sfdr(spectrum, freq, fs, nfft, leak, full_scale=0):
3131
spectrum[i] = 0
3232
bin_sig = np.argmax(spectrum)
3333
psig = sum(spectrum[i] for i in range(bin_sig - leak, bin_sig + leak + 1))
34-
spectrum_n = spectrum
34+
spectrum_n = spectrum.copy()
3535
spectrum_n[bin_sig] = 0
3636
fbin = fs / nfft
3737

@@ -172,10 +172,13 @@ def plot_spectrum(
172172
}
173173

174174
fscalar = {
175+
"uHz": 1e-6,
176+
"mHz": 1e-3,
175177
"Hz": 1,
176178
"kHz": 1e3,
177179
"MHz": 1e6,
178180
"GHz": 1e9,
181+
"THz": 1e12,
179182
}
180183

181184
if window not in windows:
@@ -206,7 +209,13 @@ def plot_spectrum(
206209
lut_key = yaxis.lower()
207210
scalar = yaxis_lut[lut_key][0]
208211
yunits = yaxis_lut[lut_key][1]
209-
xscale = fscalar[fscale]
212+
try:
213+
xscale = fscalar[fscale]
214+
except KeyError:
215+
print(
216+
f"WARNING: {fscale} not a valid option for fscale. Valid inputs are {fscalar.keys()}."
217+
)
218+
print(" Defaulting to Hz.")
210219

211220
psd_out = 10 * np.log10(pwr) - scalar
212221
if lut_key in ["magnitude"]:

tests/eval/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Init file for eval tests."""

tests/eval/test_calc_psd.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
"""Test the calc_psd method."""
2+
3+
import pytest
4+
import numpy as np
5+
from unittest import mock
6+
from adc_eval.eval import spectrum
7+
8+
9+
RTOL = 0.1
10+
NLEN = 2**18
11+
NFFT = 2**8
12+
DATA_SINE = [
13+
{
14+
"f1": np.random.randint(1, NFFT / 4 - 1),
15+
"f2": np.random.randint(NFFT / 4, NFFT / 2 - 1),
16+
"a1": np.random.uniform(low=0.5, high=0.8),
17+
"a2": np.random.uniform(low=0.1, high=0.4),
18+
}
19+
for _ in range(10)
20+
]
21+
22+
23+
@pytest.mark.parametrize("data", [np.random.randn(NLEN) for _ in range(10)])
24+
def test_calc_psd_randomized_dual(data):
25+
"""Test calc_psd with random data."""
26+
(_, _, freq, psd) = spectrum.calc_psd(data, 1, nfft=NFFT)
27+
mean_val = np.mean(psd)
28+
assert np.isclose(mean_val, 1, rtol=RTOL)
29+
30+
31+
@pytest.mark.parametrize("data", [np.random.randn(NLEN) for _ in range(10)])
32+
def test_calc_psd_randomized_single(data):
33+
"""Test calc_psd with random data and single-sided."""
34+
(freq, psd, _, _) = spectrum.calc_psd(data, 1, nfft=NFFT)
35+
mean_val = np.mean(psd)
36+
assert np.isclose(mean_val, 2, rtol=RTOL)
37+
38+
39+
def test_calc_psd_zeros_dual():
40+
"""Test calc_psd with zeros."""
41+
data = np.zeros(NLEN)
42+
(_, _, freq, psd) = spectrum.calc_psd(data, 1, nfft=NFFT)
43+
mean_val = np.mean(psd)
44+
assert np.isclose(mean_val, 0, rtol=RTOL)
45+
46+
47+
def test_calc_psd_zeros_single():
48+
"""Test calc_psd with zeros and single-sided.."""
49+
data = np.zeros(NLEN)
50+
(freq, psd, _, _) = spectrum.calc_psd(data, 1, nfft=NFFT)
51+
mean_val = np.mean(psd)
52+
assert np.isclose(mean_val, 0, rtol=RTOL)
53+
54+
55+
def test_calc_psd_ones_dual():
56+
"""Test calc_psd with ones."""
57+
data = np.ones(NLEN)
58+
(_, _, freq, psd) = spectrum.calc_psd(data, 1, nfft=NFFT)
59+
mean_val = np.mean(psd)
60+
assert np.isclose(mean_val, 1, rtol=RTOL)
61+
62+
63+
def test_calc_psd_ones_single():
64+
"""Test calc_psd with ones and single-sided."""
65+
data = np.ones(NLEN)
66+
(freq, psd, _, _) = spectrum.calc_psd(data, 1, nfft=NFFT)
67+
mean_val = np.mean(psd)
68+
assert np.isclose(mean_val, 2, rtol=RTOL)
69+
70+
71+
@pytest.mark.parametrize("data", DATA_SINE)
72+
def test_calc_psd_two_sine_dual(data):
73+
"""Test calc_psd with two sine waves."""
74+
fs = 1
75+
fbin = fs / NFFT
76+
f1 = data["f1"] * fbin
77+
f2 = data["f2"] * fbin
78+
a1 = data["a1"]
79+
a2 = data["a2"]
80+
81+
t = 1 / fs * np.linspace(0, NLEN - 1, NLEN)
82+
pin = a1 * np.sin(2 * np.pi * f1 * t) + a2 * np.sin(2 * np.pi * f2 * t)
83+
84+
(_, _, freq, psd) = spectrum.calc_psd(pin, fs, nfft=NFFT)
85+
86+
exp_peaks = [
87+
round(a1**2 / 4 * NFFT, 3),
88+
round(a2**2 / 4 * NFFT, 3),
89+
]
90+
91+
exp_f1 = [round(-f1, 2), round(f1, 2)]
92+
exp_f2 = [round(-f2, 2), round(f2, 2)]
93+
94+
peak1 = max(psd)
95+
ipeaks = np.where(psd >= peak1 * (1 - RTOL))[0]
96+
fpeaks = [round(freq[ipeaks[0]], 2), round(freq[ipeaks[1]], 2)]
97+
98+
assertmsg = f"f1={f1} | f2={f2} | a1={a1} | a2={a2}"
99+
100+
assert np.allclose(peak1, exp_peaks[0], rtol=RTOL), assertmsg
101+
assert np.allclose(fpeaks, exp_f1, rtol=RTOL), assertmsg
102+
103+
psd[ipeaks[0]] = 0
104+
psd[ipeaks[1]] = 0
105+
106+
peak2 = max(psd)
107+
ipeaks = np.where(psd >= peak2 * (1 - RTOL))[0]
108+
fpeaks = [round(freq[ipeaks[0]], 2), round(freq[ipeaks[1]], 2)]
109+
110+
assert np.allclose(peak2, exp_peaks[1], rtol=RTOL), assertmsg
111+
assert np.allclose(fpeaks, exp_f2), assertmsg
112+
113+
114+
@pytest.mark.parametrize("data", DATA_SINE)
115+
def test_calc_psd_two_sine_single(data):
116+
"""Test calc_psd with two sine waves, single-eided."""
117+
fs = 1
118+
fbin = fs / NFFT
119+
f1 = data["f1"] * fbin
120+
f2 = data["f2"] * fbin
121+
a1 = data["a1"]
122+
a2 = data["a2"]
123+
124+
t = 1 / fs * np.linspace(0, NLEN - 1, NLEN)
125+
pin = a1 * np.sin(2 * np.pi * f1 * t) + a2 * np.sin(2 * np.pi * f2 * t)
126+
127+
(freq, psd, _, _) = spectrum.calc_psd(pin, fs, nfft=NFFT)
128+
129+
exp_peaks = [
130+
round(a1**2 / 2 * NFFT, 3),
131+
round(a2**2 / 2 * NFFT, 3),
132+
]
133+
exp_f1 = round(f1, 2)
134+
exp_f2 = round(f2, 2)
135+
136+
peak1 = max(psd)
137+
ipeak = np.where(psd == peak1)[0][0]
138+
fpeak = round(freq[ipeak], 2)
139+
140+
assertmsg = f"f1={f1} | f2={f2} | a1={a1} | a2={a2}"
141+
142+
assert np.allclose(peak1, exp_peaks[0], rtol=RTOL), assertmsg
143+
assert np.allclose(fpeak, exp_f1), assertmsg
144+
145+
psd[ipeak] = 0
146+
147+
peak2 = max(psd)
148+
ipeak = np.where(psd == peak2)[0][0]
149+
fpeak = round(freq[ipeak], 2)
150+
151+
assert np.allclose(peak2, exp_peaks[1], rtol=RTOL), assertmsg
152+
assert np.allclose(fpeak, exp_f2), assertmsg

tests/eval/test_spectrum.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
"""Test the spectrum module."""
2+
3+
import pytest
4+
import numpy as np
5+
from unittest import mock
6+
from adc_eval.eval import spectrum
7+
8+
9+
RTOL = 0.05
10+
TEST_SNDR = [
11+
np.random.uniform(low=0.1, high=100, size=np.random.randint(4, 31))
12+
for _ in range(10)
13+
]
14+
TEST_VALS = [np.random.uniform(low=0.1, high=50) for _ in range(3)]
15+
PLACES = [i for i in range(6)]
16+
17+
18+
@pytest.mark.parametrize("data", TEST_VALS)
19+
@pytest.mark.parametrize("places", PLACES)
20+
def test_db_to_pow_places(data, places):
21+
"""Test the db_to_pow conversion with multiple places."""
22+
exp_val = round(10 ** (data / 10), places)
23+
assert exp_val == spectrum.db_to_pow(data, places=places)
24+
25+
26+
@pytest.mark.parametrize("data", TEST_VALS)
27+
@pytest.mark.parametrize("places", PLACES)
28+
def test_db_to_pow_ndarray(data, places):
29+
"""Test db_to_pow with ndarray input."""
30+
data = np.array(data)
31+
exp_val = np.array(round(10 ** (data / 10), places))
32+
assert exp_val == spectrum.db_to_pow(data, places=places)
33+
34+
35+
@pytest.mark.parametrize("data", TEST_VALS)
36+
@pytest.mark.parametrize("places", PLACES)
37+
def test_dbW(data, places):
38+
"""Test the dbW conversion with normal inputs."""
39+
exp_val = round(10 * np.log10(data), places)
40+
assert exp_val == spectrum.dBW(data, places=places)
41+
42+
43+
@pytest.mark.parametrize("data", TEST_VALS)
44+
@pytest.mark.parametrize("places", PLACES)
45+
def test_dbW_ndarray(data, places):
46+
"""Test dbW with ndarray input."""
47+
data = np.array(data)
48+
exp_val = np.array(round(10 * np.log10(data), places))
49+
assert exp_val == spectrum.dBW(data, places=places)
50+
51+
52+
@pytest.mark.parametrize("data", TEST_VALS)
53+
@pytest.mark.parametrize("places", PLACES)
54+
def test_enob(data, places):
55+
"""Test enob with muliple places."""
56+
exp_val = round(1 / 6.02 * (data - 1.76), places)
57+
assert exp_val == spectrum.enob(data, places=places)
58+
59+
60+
@mock.patch("adc_eval.eval.spectrum.calc_psd")
61+
def test_get_spectrum(mock_calc_psd):
62+
"""Test that the get_spectrum method returns power spectrum."""
63+
fs = 4
64+
nfft = 3
65+
data = np.array([1])
66+
exp_spectrum = np.array([fs / nfft])
67+
68+
mock_calc_psd.return_value = (None, data, None, data)
69+
70+
assert (None, exp_spectrum) == spectrum.get_spectrum(None, fs=fs, nfft=nfft)
71+
72+
73+
@pytest.mark.parametrize("data", TEST_SNDR)
74+
def test_sndr_sfdr_outputs(data):
75+
"""Test the sndr_sfdr method outputs."""
76+
freq = np.linspace(0, 1000, np.size(data))
77+
full_scale = -3
78+
nfft = 2**8
79+
fs = 1
80+
81+
psd_test = data.copy()
82+
psd_exp = data.copy()
83+
84+
result = spectrum.sndr_sfdr(psd_test, freq, fs, nfft, 0, full_scale=full_scale)
85+
86+
data[0] = 0
87+
psd_exp[0] = 0
88+
data_string = f"F = {freq}\nD = {data}"
89+
90+
indices = np.argsort(psd_exp)
91+
sbin = indices[-1]
92+
spurbin = indices[-2]
93+
sfreq = freq[sbin]
94+
spwr = psd_exp[sbin]
95+
96+
psd_exp[sbin] = 0
97+
spurfreq = freq[spurbin]
98+
spurpwr = psd_exp[spurbin]
99+
100+
noise_pwr = np.sum(psd_exp[1:])
101+
102+
exp_return = {
103+
"sig": {
104+
"freq": sfreq,
105+
"bin": sbin,
106+
"power": spwr,
107+
"dB": round(10 * np.log10(spwr), 1),
108+
"dBFS": round(10 * np.log10(spwr) - full_scale, 1),
109+
},
110+
"spur": {
111+
"freq": spurfreq,
112+
"bin": spurbin,
113+
"power": spurpwr,
114+
"dB": 10 * np.log10(spurpwr),
115+
"dBFS": round(10 * np.log10(spurpwr) - full_scale, 1),
116+
},
117+
"noise": {
118+
"floor": 2 * noise_pwr / nfft,
119+
"power": noise_pwr,
120+
"rms": np.sqrt(noise_pwr),
121+
"dBHz": round(10 * np.log10(2 * noise_pwr / nfft) - full_scale, 1),
122+
"NSD": round(
123+
10 * np.log10(2 * noise_pwr / nfft)
124+
- full_scale
125+
- 2 * 10 * np.log10(fs / nfft),
126+
1,
127+
),
128+
},
129+
"sndr": {
130+
"dBc": round(10 * np.log10(spwr / noise_pwr), 1),
131+
"dBFS": round(full_scale - 10 * np.log10(noise_pwr), 1),
132+
},
133+
"sfdr": {
134+
"dBc": round(10 * np.log10(spwr / spurpwr), 1),
135+
"dBFS": round(full_scale - 10 * np.log10(spurpwr), 1),
136+
},
137+
"enob": {
138+
"bits": round((full_scale - 10 * np.log10(noise_pwr) - 1.76) / 6.02, 1),
139+
},
140+
}
141+
142+
for key, val in exp_return.items():
143+
for measure, measure_val in val.items():
144+
msg = f"{data_string}\n{key} -> {measure} | Expected {measure_val} | Got {result[key][measure]}"
145+
assert np.allclose(measure_val, result[key][measure], rtol=RTOL), msg

0 commit comments

Comments
 (0)