4. Frequency-Domain Correlation: Comparing Spectral Content
Why Frequency-Domain Correlation Matters
Time-domain CORA scores tell you how much two curves differ, but they rarely tell you where in the spectrum the disagreement lives. A CAE signal can match a test signal closely in overall amplitude yet diverge badly in a narrow band around a structural resonance — a discrepancy that a single time-domain proximity number will average away. Frequency-domain correlation exposes that structure. By transforming both signals with an FFT and comparing magnitude and phase as functions of frequency, you can localize poor agreement to specific bands and decide whether the root cause is a missing mode, a frequency shift, excessive damping, or a sensor/processing artifact.
This case builds a complete, reproducible frequency-domain comparison workflow: prepare matched CAE and test signals, compute single-sided amplitude spectra, derive magnitude and phase coherence metrics, and flag the bands where the two signals disagree. The example uses synthetic signals with a known ground truth so that every metric can be checked against an expected answer.
Inputs and Signal Setup
Both signals must share the same sampling rate and duration before any spectral comparison is meaningful — this is the precondition established during preprocessing. For this worked example, assume a sampling frequency fs = 2048 Hz and a record length of N = 4096 samples, giving a frequency resolution of df = fs / N = 0.5 Hz and a Nyquist limit of 1024 Hz. The test signal is modeled as a sum of three damped sinusoids plus broadband noise; the CAE signal reproduces two of them faithfully but shifts the third resonance upward by 6 Hz and under-predicts its amplitude by roughly 30%. That deliberate mismatch is what the metrics must detect.
import numpy as np
fs, N = 2048.0, 4096
t = np.arange(N) / fs
rng = np.random.default_rng(7)
# Ground-truth components: (frequency Hz, amplitude, damping ratio)
modes = [(120.0, 1.00, 0.02), (340.0, 0.60, 0.03), (610.0, 0.40, 0.015)]
def synth(modes, noise_std=0.0):
sig = np.zeros_like(t)
for f, a, z in modes:
sig += a * np.exp(-z * 2*np.pi*f * t) * np.sin(2*np.pi*f*t)
return sig + rng.normal(0.0, noise_std, t.size)
test = synth(modes, noise_std=0.02)
# CAE: mode 3 shifted +6 Hz and amplitude scaled to 0.70
cae_modes = [(120.0, 1.00, 0.02), (340.0, 0.60, 0.03), (616.0, 0.28, 0.015)]
cae = synth(cae_modes, noise_std=0.0)
The test signal carries measurement noise (σ = 0.02) while the CAE signal is noise-free, which mirrors the usual reality: the simulation is deterministic, the measurement is not. This asymmetry matters when interpreting coherence, because noise lowers coherence even where the underlying dynamics agree.
Computing Single-Sided Amplitude Spectra
Apply a Hann window before the FFT to suppress spectral leakage from the non-integer number of cycles in the record, then correct for the window's coherent gain so amplitudes remain physically meaningful. Only the positive-frequency half of the spectrum is retained, with interior bins doubled to preserve total energy.
def amplitude_spectrum(x, fs):
w = np.hanning(x.size)
cg = w.sum() / x.size # coherent gain of Hann window
X = np.fft.rfft(x * w)
amp = 2.0 * np.abs(X) / (x.size * cg)
amp[0] /= 2.0 # DC not doubled
if x.size % 2 == 0:
amp[-1] /= 2.0 # Nyquist not doubled
f = np.fft.rfftfreq(x.size, 1.0/fs)
return f, amp
f, A_test = amplitude_spectrum(test, fs)
_, A_cae = amplitude_spectrum(cae, fs)
With this normalization, a pure sinusoid of amplitude 1.0 at a bin center returns a peak near 1.0 in the spectrum. Verify this on a quick sanity signal before trusting the comparison — a common failure is forgetting the coherent-gain correction, which silently scales every amplitude by about 0.5 and makes all magnitude ratios wrong by a constant factor.
Magnitude and Phase Coherence Metrics
Two complementary metrics localize disagreement. The magnitude ratio in decibels, 20·log10(A_cae / A_test), shows where the CAE over- or under-predicts energy. The phase difference, angle(X_cae · conj(X_test)), reveals timing or frequency shifts: a resonance moved by Δf produces a phase error that swings through 180° across the peak. Where the test spectrum is dominated by noise, the phase becomes meaningless, so gate the phase metric by a magnitude threshold.
X_test = np.fft.rfft(test * np.hanning(N))
X_cae = np.fft.rfft(cae * np.hanning(N))
mag_ratio_db = 20*np.log10(np.maximum(A_cae, 1e-12) / np.maximum(A_test, 1e-12))
phase_diff = np.angle(X_cae * np.conj(X_test))
# Only trust phase where test energy is significant
floor = A_test.max() * 1e-2
valid = A_test > floor
# Flag bands with poor agreement
poor_mag = valid & (np.abs(mag_ratio_db) > 3.0) # >3 dB mismatch
poor_phase = valid & (np.abs(phase_diff) > np.deg2rad(30))
poor_band = poor_mag | poor_phase
print("Bands with poor magnitude agreement (Hz):",
f[poor_mag][:5], "...")
print("Bands with poor phase agreement (Hz):",
f[poor_phase][:5], "...")
For the synthetic case, expect the magnitude flag to light up around 610–616 Hz (the 30% amplitude deficit is about −3.1 dB, just over the threshold) and the phase flag to cluster across the shifted peak, where the 6 Hz offset rotates the phase well beyond 30°. The 120 Hz and 340 Hz modes should pass both tests, confirming the metrics isolate the injected fault rather than smearing it across the whole spectrum.
Reading the Correlation Map
The figure below summarizes how the two spectra relate across frequency. It plots the test and CAE amplitude envelopes, marks the three modal regions, and shades the band where the shifted third resonance causes both a magnitude deficit and a phase excursion. The key visual takeaway is that agreement is excellent below 500 Hz and collapses in a narrow window near 610 Hz — exactly the localization a time-domain score cannot provide.
Interpreting the Result and Acting on It
The correlation map points to a single actionable finding: the third mode is both shifted and under-predicted. A frequency shift of 6 Hz at 610 Hz is about 1%, which typically indicates a stiffness or mass discrepancy in the CAE model rather than a damping error — damping errors change peak width and height but leave the center frequency nearly fixed. The amplitude deficit of roughly 30% suggests the model is also missing participation from that mode, possibly because a component is modeled too stiffly or a connection is over-constrained.
Before adjusting the model, rule out the measurement side. Confirm that the test spectrum's peak is not an artifact of a resonance in the sensor mount or a filter roll-off near the band edge. A quick check: recompute the test spectrum with a different window (e.g., a flat-top window) and confirm the peak location is stable to within one bin. If it moves, the peak is a processing artifact, not a physical mode.
| Metric | Definition | Typical pass gate | What a failure implies |
|---|---|---|---|
| Magnitude ratio (dB) | 20·log10(A_cae / A_test) | |Δ| ≤ 3 dB | Amplitude scaling, missing/extra mode participation |
| Phase difference | angle(X_cae · conj(X_test)) | |Δφ| ≤ 30° | Frequency shift, time lag, damping mismatch |
| Coherence (γ²) | |S_xy|² / (S_xx · S_yy) | ≥ 0.8 in band | Noise dominance, nonlinearity, nonstationarity |
valid = A_test > floor.Verification Checklist
- Confirm
fsandNare identical for both signals; a mismatch invalidates every bin index. - Validate the amplitude normalization on a unit sinusoid before comparing real data.
- Check that flagged bands coincide with physical modes, not with window or filter artifacts.
- Re-run with a second window type; stable peak locations confirm physical content.
- Record the magnitude and phase gates used, so the correlation result is reproducible by another engineer.
Once the poor-agreement band is localized and confirmed physical, the next step is to trace it back to the model — but that diagnosis belongs to modal correlation, where mode shapes and frequencies are compared directly. Here the deliverable is the localization itself: a frequency-resolved map of where CAE and test diverge, with the metrics and thresholds that justify each flag.