fittting data using nonlinear optimization fminsearch
    4 views (last 30 days)
  
       Show older comments
    
I have data as below that i want to fit to nonlinear equation. in theory it says that this data can be fit by taking square envelope and will obey equation 

my aim is to get optimum Qc based of the data. I wrote a code such below, but the result seems unreasonable (compare to other studies). 
clear all; clc; close all;
% Use current working directory
input_dir = pwd; % Directory containing ACF files
output_dir = pwd; % Directory to save outputs
% Get the list of ACF files
acf_files = dir(fullfile(input_dir, 'cBPCen1_s2D1D_ACF_HGSD_HHZ_.HHZ-.HHZ_2023-12_2023-12-01T00_00.txt'));
% Model function
freq=1;
model = @(Qc, tACF) (1 ./ tACF.^2) .*exp(-2 * pi * freq * tACF ./ Qc);
% Nonlinear optimization using fminsearch
objective_function = @(Qc, t, y_obs) sum((y_obs - model(Qc, t)).^2);
% Loop through all ACF files
for i = 1:length(acf_files)
    % Load ACF file
    acf_file = fullfile(input_dir, acf_files(i).name);
    data = load(acf_file);
    % ACF signal
    y_obs_ACF = data((length(data)/2):(end-2000))';
    t_ACF = 1:length(y_obs_ACF);
    % Compute the envelope
    envelope = abs(hilbert(y_obs_ACF)).^2; %square enveloped of ACF
    t_envel = t_ACF;
    y_obs_envel = envelope;
    % Initial guess for Qc
    Qc_initial = 10;
    % Fit ACF using fminsearch
    Qc_fitted_ACF = fminsearch(@(Qc) objective_function(Qc, t_ACF, y_obs_ACF), Qc_initial);
    % Calculate RMS error for ACF
    rms_error_ACF = sqrt(mean((y_obs_ACF - model(Qc_fitted_ACF, t_ACF)).^2));
    % Fit envelope using fminsearch
    Qc_fitted_envel = fminsearch(@(Qc) objective_function(Qc, t_envel, y_obs_envel), Qc_initial);
    % Calculate RMS error for envelope
    rms_error_envel = sqrt(mean((y_obs_envel - model(Qc_fitted_envel, t_envel)).^2));
    % Save the envelope to a text file
    envelope_file = fullfile(output_dir, ['envel_', acf_files(i).name]);
    save(envelope_file, 'envelope', '-ascii');
    % Plot both the ACF and the envelope fits
    figure;
    subplot(2, 1, 1);
    plot(t_ACF, y_obs_ACF, 'bo', 'DisplayName', 'ACF Data');
    hold on;
    plot(t_ACF, model(Qc_fitted_ACF, t_ACF), 'r-', 'LineWidth', 1.5, 'DisplayName', 'Fitted ACF');
    xlabel('Time (t)');
    ylabel('Amplitude');
    legend('Location', 'northeast');
    title(sprintf('ACF Fit - Qc = %.4f, RMS Error = %.4f', Qc_fitted_ACF, rms_error_ACF));
    grid on;
    subplot(2, 1, 2);
    plot(t_envel, y_obs_envel, 'bo', 'DisplayName', 'Envelope Data');
    hold on;
    plot(t_envel, model(Qc_fitted_envel, t_envel), 'r-', 'LineWidth', 1.5, 'DisplayName', 'Fitted Envelope');
    xlabel('Time (t)');
    ylabel('Amplitude');
    legend('Location', 'northeast');
    title(sprintf('Envelope Fit - Qc = %.4f, RMS Error = %.4f', Qc_fitted_envel, rms_error_envel));
    grid on;
end
this is my result. I don't know weather my i made mistake in my code or not. 

I wonder why i get unreasonable Q is, and that i try to plot how the exuation should look like if i vary the Qc up to reasonable value (again based on reserach paper it has range between 100 to 200 for freq =1, and it looks like below. As I increase my Qc, it does even slightly approach my envelope. 

so my question is
- does it because the eq can not really fit my data that make i get big value for Qc or do I make mistake in my code?
- what other suggestion that help to get optimum Qc based on my data is welcome.
1 Comment
  Sam Chak
      
      
 on 20 Dec 2024
				Hi @nirwana
Was your MATLAB code generated by a chatbot? If not, please share the link so we can look into the problem.
By the way, do you want to continue with this thread or the newly posted one?
Answers (2)
  Matt J
      
      
 on 12 Dec 2024
        I see two things that might be a problem. First, your model equation might be reasonable for the envelope of the ACF curves you are showing, but you are giving the entirety of the ACF data to the fitting routine, rather than just the data for its envelope. Second, you are choosing seemingly some random number Qc=10 for the initial guess. The more methodical way to develop an initial guess is to solve log(E(t))=.... for Qc.
  Mathieu NOE
      
 on 12 Dec 2024
        hello 
seems to me the data looks like a traditionnal exp decaying pulse  - I don't undertsand how you can have the 1/t²  amplitude term in the equation (and it would go to infinity at t = 0 ?? .)
A simple code without any  optimisation can give you already some ggod fit (could be further refined if needed with your prefered optimizer) 
NB : we don't know the sampling rate so time increment is 1 by default

y = readmatrix('cBPCen1_s2D1D_ACF_HGSD_HHZ_.HHZ-.HHZ_2023-12_2023-12-01T00_00.txt');
%% LINEAR DAMPING identification
[val,ind] = max(y); 
y = y(ind:ind+800); % second half of signal after the peak
t = 1:numel(y);
[Yp,Xp,Wp,Pp] = findpeaks(y,'NPeaks',6,'MinPeakHeight',val/50,'MinPeakDistance',10); % all positive and negative peaks
Xpp = t(Xp);
Ypp = y(Xp);
n_peaks=numel(Xpp);  
%---Calculate Logarithmic Decrement,  undamped natural frequency, damped natural frequency, damping ratio
Log_Dec = zeros(length(n_peaks));  
for nn = 1:n_peaks-1 %
    Log_Dec(nn)= log(Ypp(nn)/Ypp(nn+1));  % computed with n = 1 periods apart
end  
Mean_dec = mean(Log_Dec); %Calculate Average Logarithmic Decrement  
wn = 2*pi/mean(diff(Xpp)); %Calculate Average Pulsation
%Damping  
dzeta = 1/sqrt(1+((2*pi/(Mean_dec))^2)); % normalized damping coefficient
Q = 1/(dzeta);
yf = val*exp(-wn*t/Q); % enveloppe
yfs = yf.*cos(wn*t) ; % enveloppe * sinusoid
Rsquared = my_Rsquared_coeff(y(:),yfs(:));
figure(1),plot(t,y,t,yf,t,yfs,Xpp,Ypp,'dr')
title(['Data fit , R² = ' num2str(Rsquared)]);
xlabel('time [s]'); ylabel('amplitude')
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function Rsquared = my_Rsquared_coeff(data,data_fit)
    % R2 correlation coefficient computation
    % The total sum of squares
    sum_of_squares = sum((data-mean(data)).^2);
    % The sum of squares of residuals, also called the residual sum of squares:
    sum_of_squares_of_residuals = sum((data-data_fit).^2);
    % definition of the coefficient of correlation is
    Rsquared = 1 - sum_of_squares_of_residuals/sum_of_squares;
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1 Comment
  Mathieu NOE
      
 on 12 Dec 2024
				Added a second layer with fminsearch , but the IC obtained with my first code was already giving  good estimates 
Q (ic) =     8.4160
Q (final) =     7.7196
here  fminsearch tends to  improve the fit on the large amplitudes (first 3 periods)  , but the error is getting bigger afterwards

y = readmatrix('cBPCen1_s2D1D_ACF_HGSD_HHZ_.HHZ-.HHZ_2023-12_2023-12-01T00_00.txt');
%% LINEAR DAMPING identification
[val,ind] = max(y); 
y = y(ind:ind+800); % second half of signal after the peak
t = (1:numel(y))';
[Yp,Xp,Wp,Pp] = findpeaks(y,'NPeaks',6,'MinPeakHeight',val/50,'MinPeakDistance',10); % all positive and negative peaks
Xpp = t(Xp);
Ypp = y(Xp);
n_peaks=numel(Xpp);  
%%---Calculate Logarithmic Decrement,  undamped natural frequency, damped natural frequency, damping ratio
Log_Dec = zeros(length(n_peaks));  
for nn = 1:n_peaks-1 %
    Log_Dec(nn)= log(Ypp(nn)/Ypp(nn+1));  % computed with n = 1 periods apart
end  
Mean_dec = mean(Log_Dec); %Calculate Average Logarithmic Decrement  
wn = 2*pi/mean(diff(Xpp)); %Calculate Average Pulsation
%Damping  
dzeta = 1/sqrt(1+((2*pi/(Mean_dec))^2)); % normalized damping coefficient
Q = 1/(dzeta);
yf = val*exp(-wn*t/Q); % enveloppe
yfs = yf.*cos(wn*t) ; % enveloppe * sinusoid
Rsquared = my_Rsquared_coeff(y(:),yfs(:));
figure(1),plot(t,y,t,yf,t,yfs,Xpp,Ypp,'dr')
title(['Data fit , R² = ' num2str(Rsquared)]);
xlabel('time [s]'); ylabel('amplitude')
legend('data','enveloppe fit','signal fit','peaks');
%% step 2 : refine with fminsearch 
f = @(a,b,c,x) a.*exp(-b*x/c).*cos(b*x);
obj_fun = @(params) norm(f(params(1), params(2), params(3),t)-y);
IC = [val,wn,Q];
sol = fminsearch(obj_fun, IC);
val = sol(1);
wn = sol(2);
Q = sol(3);
yfit = f(val,wn,Q,t);
Rsquared = my_Rsquared_coeff(y(:),yfit(:));
figure(2),plot(t,y,t,yfit)
title(['Data fit , R² = ' num2str(Rsquared)]);
xlabel('time [s]'); ylabel('amplitude')
legend('data','signal fit');
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function Rsquared = my_Rsquared_coeff(data,data_fit)
    % R2 correlation coefficient computation
    % The total sum of squares
    sum_of_squares = sum((data-mean(data)).^2);
    % The sum of squares of residuals, also called the residual sum of squares:
    sum_of_squares_of_residuals = sum((data-data_fit).^2);
    % definition of the coefficient of correlation is
    Rsquared = 1 - sum_of_squares_of_residuals/sum_of_squares;
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
See Also
Categories
				Find more on Financial Toolbox in Help Center and File Exchange
			
	Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!


