def test_fftcoef_detrend(): for n in (50, 51): t = np.arange(n) / n x = np.random.randn(n) xd = signal.detrend(x) mag, phase, frq = dsp.fftcoef(x, 1 / t[1], dodetrend=True) A, B, frq = dsp.fftcoef(x, 1 / t[1], coef="ab", window=np.ones(x.shape), dodetrend=True) w = 2 * np.pi * frq[1] # reconstruct with magnitude and phase: x2 = 0.0 for k, (m, p, f) in enumerate(zip(mag, phase, frq)): x2 = x2 + m * np.sin(k * w * t - p) assert np.allclose(xd, x2) # reconstruct with A and B: x3 = 0.0 for k, (a, b, f) in enumerate(zip(A, B, frq)): x3 = x3 + a * np.cos(k * w * t) + b * np.sin(k * w * t) assert np.allclose(xd, x3) assert_raises(ValueError, dsp.fftcoef, x, 1 / t[1], window=np.ones(x.size - 1))
def test_fftcoef_maxdf(): n = 16 t = np.arange(n) / n x = np.random.randn(n) mag, phase, frq = dsp.fftcoef(x, 1 / t[1]) mag1, phase1, frq1 = dsp.fftcoef(x, 1 / t[1], maxdf=0.6) assert frq1[1] <= 0.6 assert np.allclose(mag1[::2], mag)
def test_fftcoef_fold(): for n in (10, 11): t = np.arange(n) / n x = np.random.randn(n) mag, phase, frq = dsp.fftcoef(x, 1 / t[1], fold=True) mag1, phase1, frq = dsp.fftcoef(x, 1 / t[1], fold=False) if not (n & 1): mag1[1:-1] *= 2.0 else: mag1[1:] *= 2.0 assert np.allclose(mag, mag1) assert np.allclose(phase, phase1)
def test_fftcoef_recon(): for n in (50, 51): t = np.arange(n) / n x = np.random.randn(n) mag, phase, frq = dsp.fftcoef(x, 1 / t[1]) A, B, frq = dsp.fftcoef(x, 1 / t[1], coef="ab", window=np.ones(x.shape)) w = 2 * np.pi * frq[1] # reconstruct with magnitude and phase: x2 = 0.0 for k, (m, p, f) in enumerate(zip(mag, phase, frq)): x2 = x2 + m * np.sin(k * w * t - p) assert np.allclose(x, x2) # reconstruct with A and B: x3 = 0.0 for k, (a, b, f) in enumerate(zip(A, B, frq)): x3 = x3 + a * np.cos(k * w * t) + b * np.sin(k * w * t) assert np.allclose(x, x3)
def test_fftcoef(): for n in (50, 51): t = np.arange(n) / n x = 4.0 * np.sin(2 * np.pi * 4 * t) mag, phase, frq = dsp.fftcoef(x, 1 / t[1]) # lowest frequency = 1/T = 1.0 # highest frequency = sr/2 = 500.0 nfrq = (n // 2) + 1 assert np.allclose(frq, 0.0 + np.arange(nfrq)) x2 = np.zeros(len(frq)) x2[4] = 4.0 assert np.allclose(mag, x2) assert np.allclose(phase[4], 0.0) x = 4.0 * np.cos(2 * np.pi * 4 * t) mag, phase, frq = dsp.fftcoef(x, 1 / t[1]) # lowest frequency = 1/T = 1.0 # highest frequency = sr/2 = 500.0 nfrq = (n // 2) + 1 assert np.allclose(frq, 0.0 + np.arange(nfrq)) x2 = np.zeros(len(frq)) x2[4] = 4.0 assert np.allclose(mag, x2) assert np.allclose(phase[4], -np.pi / 2) sig = np.column_stack((x, x, x)) mag1, phase1, frq1 = dsp.fftcoef(sig, 1 / t[1], axis=0) assert np.allclose(frq, frq1) for i in range(3): assert np.allclose(mag1[:, i], mag) assert np.allclose(phase1[:, i], phase) sig = sig.reshape(1, 1, -1, 3, 1) mag2, phase2, frq2 = dsp.fftcoef(sig, 1 / t[1], axis=2) assert np.allclose(frq, frq2) assert np.allclose(mag1, mag2.reshape(-1, 3)) assert np.allclose(phase1, phase2.reshape(-1, 3))
def _plot_era(self): """ Plots input data against reduced model. Notes ----- If the parameter `input_labels` is left empty, the approximate fit plot will not have a corresponding legend. Otherwise, the data and its approximations will appear with a corresponding legend. If the number of input labels provided does not match the number of input signals, this will return an error. If the parameter `FFT` is set to True, this function will generate an FFT plot of the input data in the corresponding color coding of the approximate fit plot. This will appear below the approximate fit plot and serves as a helpful comparison of detected modal data. If the scaling of the FFT plot is suboptimal, it can be adjusted by changing the values input to `FFT_range`, which will serve as a maximum cutoff or minimum/maximum cutoff pair, depending on whether one or two values are given. """ if not self.show_plot: return y = self.resp_era if not hasattr(self, "time"): self.time = np.arange(0, self.n_tsteps) / self.sr + self.t0 # plot each input in its own window for j in range(self.n_inputs): fig = plt.figure(f"ERA Fit, input {j}", clear=True) if self.FFT: ax1 = fig.add_subplot(211) ax2 = fig.add_subplot(212) else: ax1 = fig.add_subplot(111) # Will execute if the user has specified signal labels if self.input_labels: # Plot original data for i in range(self.resp.shape[0]): ax1.plot( self.time, self.resp[i, :, j], label=f"{self.input_labels[i]} (Data)", ) # reset color cycle so that the colors line up ax1.set_prop_cycle(None) # Plot ERA fit to data for i in range(self.resp.shape[0]): ax1.plot( self.time, y[i, :, j], "--", label=f"{self.input_labels[i]} (ERA Fit)", ) # Legend will appear next to plot ax1.legend(bbox_to_anchor=(1.04, 1), loc="upper left") else: # Plot original data ax1.plot(self.time, self.resp[:, :, j].T, label="Data") # reset color cycle so that the colors line up ax1.set_prop_cycle(None) # Plot ERA fit to data ax1.plot(self.time, y[:, :, j].T, "--", label="ERA fit") # Labeling plot ax1.set_xlabel("Time (s)") ax1.set_ylabel("Response") ax1.set_title("Data (solid) vs. ERA Reduced Model (dashed)") # Will execute if the user requests an FFT if self.FFT: # Recovering magnitude, phase, and frequency FFT data mag, phase, frq = dsp.fftcoef(self.resp[:, :, j].T, self.sr, axis=0, maxdf=0.2) # No defined limits will plot entire FFT if not self.FFT_range: ax2.plot(frq, mag) # In the case of defined limits, they will be processed else: # Converts integer/float input to a list for indexing purposes if isinstance(self.FFT_range, numbers.Real): self.FFT_range = [self.FFT_range] # If only one limit is provided, it is taken as a maximum limit if len(self.FFT_range) == 1: maxlim = max(np.where(frq <= self.FFT_range[0])[0]) ax2.plot(frq[:maxlim], mag[:maxlim]) # If a pair of limits is provided, it will be used # as minimum/maximum limits elif len(self.FFT_range) == 2: minlim = max(np.where(frq <= self.FFT_range[0])[0]) maxlim = max(np.where(frq <= self.FFT_range[1])[0]) ax2.plot(frq[minlim:maxlim], mag[minlim:maxlim]) # Labeling FFT plot ax2.set_xlabel("Frequency (Hz)") ax2.set_ylabel("Magnitude") ax2.set_title("Magnitude of Frequency Responses of Data") fig.tight_layout() fig.canvas.draw() plt.show()