Coverage for src / thunderlab / fourier.py: 97%
32 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-03 13:48 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-03 13:48 +0000
1"""Fourier series
3Extract and normalize Fourier coefficients of a Fourier series of
4periodic functions, or synthesize periodic functions from Fourier
5coefficients.
7## Functions
9- `fourier_coeffs()`: extract Fourier coefficients from data.
10- `normalize_fourier_coeffs()`: set phase of first harmonics and offset to zero.
11- `fourier_synthesis()`: compute waveform from Fourier coefficients.
13"""
15import numpy as np
18def fourier_coeffs(data, ratetime, freq, n_harmonics):
19 """ Extract Fourier coefficients from data.
21 Decompose a periodic signal \\(x(t)\\) with known fundamental frequency
22 \\(f_1\\) into a Fourier series:
23 \\[ x(t) \\approx \\Re \\sum_{k=0}^n c_k e^{i 2 \\pi k f_1 t} \\]
24 with the Fourier coefficients
25 \\[ c_k = \\frac{2}{jT} \\int_{0}^{jT} x(t) e^{-i 2 \\pi k f_1 t} \\, dt \\]
26 integrated over integer multiples of the period \\(T=1/f_1\\).
28 Parameters
29 ----------
30 data: 1D array of float
31 Time series of data.
32 ratetime: float or 1-D array of float
33 Times corresponding to `data`.
34 If single float, then sampling rate of the data.
35 freq: float
36 Fundamental frequency of Fourier series.
37 n_harmonics: int
38 Number of harmonics inclusively the zeroth one.
39 That is, if `n_harmonics` is set to two,
40 then the zeroth harmonics (offset) and the fundamental
41 frequency (first harmonics) are returned.
43 Returns
44 -------
45 coeffs: 1D array of complex
46 For each harmonics the complex valued Fourier coefficient.
47 The first one is the offset and the second one is the coefficient
48 of the fundamental. If the number ofdata samples is less than
49 a single period, then a zero-sized array is returned.
50 """
51 if isinstance(ratetime, (list, tuple, np.ndarray)):
52 time = ratetime
53 else:
54 time = np.arange(len(data))/ratetime
55 # integrate over full periods:
56 n_periods = int(np.floor((time[-1] - time[0])*freq))
57 if n_periods < 1:
58 return np.zeros(0, dtype=complex)
59 n_max = np.argmax(time > time[0] + n_periods/freq)
60 data = data[:n_max]
61 time = time[:n_max]
62 # Fourier projections:
63 iomega = -2j*np.pi*freq*time
64 fac = 2/len(data) # = 2*deltat/T
65 coeffs = np.zeros(n_harmonics, dtype=complex)
66 for k in range(n_harmonics):
67 coeffs[k] = np.sum(data*np.exp(iomega*k))*fac
68 return coeffs
71def normalize_fourier_coeffs(coeffs):
72 """Set phase of first harmonics and offset to zero.
74 Parameters
75 ----------
76 coeffs: 1D array of complex
77 For each harmonic the complex valued Fourier coefficient
78 as, for example, returned by `fourier_coeffs()`.
79 The first one is the offset and the second one is the coefficient
80 of the fundamental.
82 Returns
83 -------
84 coeffs: 1D array of complex
85 The normalized Fourier coefficients.
86 """
87 phi1 = np.angle(coeffs[1])
88 for k in range(1, len(coeffs)):
89 coeffs[k] *= np.exp(-1j*k*phi1)
90 coeffs[0] = 0 + 0j
91 return coeffs
94def fourier_synthesis(freq, coeffs, ratetime, n=None):
95 """ Compute periodic waveform from Fourier coefficients.
97 Given the Fourier coefficients
98 \\[ c_k = \\frac{2}{jT} \\int_{0}^{jT} x(t) e^{-i 2 \\pi k f_1 t} \\, dt \\]
99 integrated over integer multiples of the period \\(T=1/f_1\\) of a signal
100 \\(x(t)\\) with fundamental frequency \\(f_1\\), compute
101 the Fourier series
102 \\[ x(t) \\approx \\Re \\sum_{k=0}^n c_k e^{i 2 \\pi k f_1 t} \\]
104 Parameters
105 ----------
106 freq: float
107 Fundamental frequency.
108 coeffs: 1D array of complex
109 For each harmonics the complex valued Fourier coefficient
110 as, for example, returned by `fourier_coeffs()`.
111 The first one is the offset.
112 ratetime: float or 1-D array of float
113 Time points for which the waveform is calculated.
114 If single float, then sampling rate of the computed waveform.
115 n: int
116 Number of samples if `ratetime` is float.
118 Returns
119 -------
120 wave: 1D array of float
121 Waveform computed from Fourier series with fundamental frequency
122 `freq` and Fourier coefficients `coeffs` for each harmonic.
123 The waveform is computed for a sampling rate `rate` and contains
124 `n` samples.
125 """
126 if isinstance(ratetime, (list, tuple, np.ndarray)):
127 time = ratetime
128 else:
129 time = np.arange(n)/ratetime
130 iomega = 2j*np.pi*freq*time
131 wave = np.zeros(len(time))
132 for k in range(len(coeffs)):
133 wave += np.real(coeffs[k]*np.exp(iomega*k))
134 return wave