Skip to content

Commit 647d8d8

Browse files
committed
Fix IndexError in THD() for low/fractional fundamental frequencies
THD() computes the harmonic count from the continuous fundamental (num_harmonics = int((fs/2)/frequency)) but indexes each harmonic with the rounded fundamental bin i = int(round(frequency*N/fs)). When the fractional bin rounds up, i * num_harmonics overshoots the rfft length len(f) = N//2+1, so the last harmonic's bin lands past Nyquist and f[i*h] is out of bounds. For a low fundamental (e.g. THD(sig, 12000, freq=5.994): i=6, num_harmonics=1001, i*1001=6006 > 6001) this raises IndexError. Guard the index and break the loop once i*h reaches len(f). A harmonic at bin >= len(f) is above the Nyquist frequency and cannot exist in the sampled spectrum, so it must be excluded; break (not continue) is correct because h increases monotonically. Harmonics in range are summed exactly as before, so results for normal signals are unchanged. Fixes #38.
1 parent 6bfa59b commit 647d8d8

2 files changed

Lines changed: 19 additions & 0 deletions

File tree

tests/test_thd.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,18 @@ def test_freq_parameter(self):
149149
explicit_thd = THD(signal, fs, freq=f)
150150
assert explicit_thd == pytest.approx(auto_thd)
151151

152+
def test_low_fundamental_no_indexerror(self):
153+
# Regression test for issue #38. num_harmonics is derived from the
154+
# continuous fundamental frequency, but harmonics are indexed by the
155+
# rounded fundamental bin i. For a low/fractional fundamental that
156+
# rounds up, i * num_harmonics overshot the rfft length and raised
157+
# IndexError instead of returning a THD value.
158+
fs = 12000 # Hz
159+
signal = sine_wave(6, fs) # 12000 samples, fundamental ~6 Hz
160+
result = THD(signal, fs, freq=5.994) # rounds to bin 6
161+
assert np.isfinite(result)
162+
assert result >= 0
163+
152164

153165
if __name__ == '__main__':
154166
pytest.main([__file__, "--capture=sys"])

waveform_analysis/thd.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,13 @@ def THD(signal, fs, *, freq=None, ref='f', verbose=False):
232232
num_harmonics = int((fs/2)/frequency)
233233
harmonic_amplitudes = []
234234
for h in range(2, num_harmonics + 1):
235+
# num_harmonics is derived from the continuous fundamental frequency,
236+
# but harmonics are indexed by the rounded fundamental bin i. When i
237+
# rounds up, i * h can overshoot the rfft length, landing above the
238+
# Nyquist frequency where no component can exist. h increases
239+
# monotonically, so break once we run past the spectrum (issue #38).
240+
if i * h >= len(f):
241+
break
235242
freq = frequency * h
236243
ampl = abs(f[i * h])
237244
harmonic_amplitudes.append(ampl)

0 commit comments

Comments
 (0)