|
| 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 |
0 commit comments