Skip to content

Frequency-dependent resolution on a 3-mass chain

This example demonstrates sidFreqBTFDR on a lightly-damped 3-mass SMD chain (Plant C) with three well-separated modes in the bottom decade of the spectrum. A short fixed BT window smears the modes, a long fixed window resolves them at the cost of high-frequency variance, and a per-frequency resolution vector combines the best of both.

See spec/EXAMPLES.md section 3.3 for the binding specification.

Source: matlab/examples/exampleFreqDepRes.m

%% exampleFreqDepRes - Frequency-dependent resolution on a 3-mass chain
%
% This example demonstrates sidFreqBTFDR on a lightly-damped 3-mass
% SMD chain (Plant C) with three well-separated modes in the bottom
% decade of the spectrum. A short fixed BT window smears the modes,
% a long fixed window resolves them at the cost of high-frequency
% variance, and a per-frequency resolution vector combines the best
% of both.
%
% See spec/EXAMPLES.md section 3.3 for the binding specification.

runner__nCompleted = 0;

%% Generate test data
% Plant C: m = [1 1 1], k = [300 200 100], c = [8 8 8]. Three modes at
% approximately 6.4, 15.1, 25.1 rad/s. Force at mass 1, measure x1.

rng(2);

m  = [1; 1; 1];
k  = [300; 200; 100];
c  = [8; 8; 8];
F  = [1; 0; 0];
Ts = 0.01;
N  = 6000;

[Ad, Bd] = util_msd(m, k, c, F, Ts);
C_out = [1 0 0 0 0 0];

u = randn(N, 1);
x = zeros(N + 1, 6);
for step = 1:N
    x(step + 1, :) = (Ad * x(step, :)' + Bd * u(step))';
end
y = x(2:end, 1) + 5e-4 * randn(N, 1);

runner__nCompleted = runner__nCompleted + 1;
fprintf('  Section %d completed: Generate test data.\n', runner__nCompleted);

%% Fixed-window sidFreqBT: the resolution-variance trade-off
% Short window (M = 15): smooth but smears the three modes.
% Long window (M = 80): captures the peaks but has high variance.

r_small = sidFreqBT(y, u, 'WindowSize', 15, 'SampleTime', Ts);
r_large = sidFreqBT(y, u, 'WindowSize', 80, 'SampleTime', Ts);

w = r_small.Frequency;
nf = length(w);
G_true = zeros(nf, 1);
I6 = eye(6);
for i = 1:nf
    G_true(i) = C_out * ((exp(1j * w(i)) * I6 - Ad) \ Bd);
end

figure;
semilogx(w, 20*log10(abs(r_small.Response)), 'b', 'DisplayName', 'BT M = 15');
hold on;
semilogx(w, 20*log10(abs(r_large.Response)), 'r', 'DisplayName', 'BT M = 80');
semilogx(w, 20*log10(abs(G_true)), 'k--', 'LineWidth', 1.5, ...
    'DisplayName', 'True');
hold off;
xlabel('Frequency (rad/sample)');
ylabel('Magnitude (dB)');
title('Fixed window: resolution vs variance trade-off');
legend('Location', 'southwest');
grid on;

runner__nCompleted = runner__nCompleted + 1;
fprintf('  Section %d completed: %s.\n', runner__nCompleted, ...
    'Fixed-window sidFreqBT: the resolution-variance trade-off');

%% Scalar resolution with sidFreqBTFDR
% Resolution R sets the window size as M = ceil(2*pi / R). Smaller R
% = finer resolution (larger window).

result_fdr = sidFreqBTFDR(y, u, 'Resolution', 0.2, 'SampleTime', Ts);

figure;
sidBodePlot(result_fdr);
title('sidFreqBTFDR with scalar resolution R = 0.2');

runner__nCompleted = runner__nCompleted + 1;
fprintf('  Section %d completed: Scalar resolution with sidFreqBTFDR.\n', ...
    runner__nCompleted);

%% Per-frequency resolution vector
% Use fine resolution (R small) at low frequencies where the modes are,
% coarse resolution (R large) at high frequencies where the response
% is smooth.

nf2 = length(result_fdr.Frequency);
R_vec = linspace(0.1, 1.5, nf2)';

result_vec = sidFreqBTFDR(y, u, 'Resolution', R_vec, 'SampleTime', Ts);

figure;
subplot(2, 1, 1);
plot(result_vec.Frequency, result_vec.WindowSize, 'b');
xlabel('Frequency (rad/sample)');
ylabel('Window size M');
title('Per-frequency window size');
grid on;

w2 = result_vec.Frequency;
G_true2 = zeros(length(w2), 1);
for i = 1:length(w2)
    G_true2(i) = C_out * ((exp(1j * w2(i)) * I6 - Ad) \ Bd);
end

subplot(2, 1, 2);
semilogx(w2, 20*log10(abs(result_vec.Response)), 'b', ...
    'DisplayName', 'BTFDR (variable R)');
hold on;
semilogx(w2, 20*log10(abs(G_true2)), 'k--', 'LineWidth', 1.5, ...
    'DisplayName', 'True');
hold off;
xlabel('Frequency (rad/sample)');
ylabel('Magnitude (dB)');
title('BTFDR with per-frequency resolution');
legend('Location', 'southwest');
grid on;

runner__nCompleted = runner__nCompleted + 1;
fprintf('  Section %d completed: Per-frequency resolution vector.\n', ...
    runner__nCompleted);

%% Compare BT vs BTFDR side by side
% BTFDR adapts: big window near the modes, small window at high
% frequencies where the response is featureless.

r_bt  = sidFreqBT(y, u, 'WindowSize', 30, 'SampleTime', Ts);
r_fdr = sidFreqBTFDR(y, u, 'Resolution', 0.3, 'SampleTime', Ts);

w3 = r_bt.Frequency;
G_true3 = zeros(length(w3), 1);
for i = 1:length(w3)
    G_true3(i) = C_out * ((exp(1j * w3(i)) * I6 - Ad) \ Bd);
end

figure;
semilogx(r_bt.Frequency, 20*log10(abs(r_bt.Response)), 'b', ...
    'DisplayName', 'BT (M = 30)');
hold on;
semilogx(r_fdr.Frequency, 20*log10(abs(r_fdr.Response)), 'r', ...
    'DisplayName', 'BTFDR (R = 0.3)');
semilogx(w3, 20*log10(abs(G_true3)), 'k--', 'LineWidth', 1.5, ...
    'DisplayName', 'True');
hold off;
xlabel('Frequency (rad/sample)');
ylabel('Magnitude (dB)');
title('BT: fixed vs frequency-dependent resolution');
legend('Location', 'southwest');
grid on;

runner__nCompleted = runner__nCompleted + 1;
fprintf('  Section %d completed: Compare BT vs BTFDR side by side.\n', ...
    runner__nCompleted);

fprintf('exampleFreqDepRes: %d/%d sections completed\n', ...
    runner__nCompleted, runner__nCompleted);