Calculations/Data-Analyzer/+Analyzer/fitSingleGaussianCurvesToRadialSpectralDistribution.m

141 lines
5.4 KiB
Matlab

function [fitResults, rawCurves] = fitSingleGaussianCurvesToRadialSpectralDistribution(S_k_all, k_rho_vals, varargin)
%% fitSingleGaussianCurvesToRadialSpectralDistribution
% Fits a single-Gaussian model to multiple radial spectral curves (S(k_rho)),
% truncating each curve to user-specified k_rho range.
%
% Inputs:
% S_k_all - 1xN cell array of radial spectral curves
% k_rho_vals - vector of corresponding k_rho values
%
% Optional name-value pairs:
% 'KRhoRange' - [k_min, k_max] (default: [min(k_rho_vals), max(k_rho_vals)])
% 'ResidualThreshold' - threshold for residual validation (default: 0.3)
%
% Outputs:
% fitResults - struct array with fields:
% .pFit, .kFit, .xFit, .yFit, .kFine, .isValid, .residual, .maxAbs, .R2, .fitMaxKRho
% rawCurves - struct array of truncated raw curves for plotting
% --- Parse optional inputs ---
p = inputParser;
addParameter(p, 'KRhoRange', [min(k_rho_vals), max(k_rho_vals)], @(x) isnumeric(x) && numel(x)==2);
addParameter(p, 'ResidualThreshold', 0.3, @(x) isnumeric(x) && isscalar(x));
parse(p, varargin{:});
opts = p.Results;
Ncurves = numel(S_k_all);
fitResults = struct('pFit',[],'kFit',[],'xFit',[],'yFit',[],...
'kFine',[],'isValid',[],'residual',[],'maxAbs',[],'R2',[],'fitMaxKRho',[]);
rawCurves = struct('x', cell(1, Ncurves), 'k', cell(1, Ncurves));
for k = 1:Ncurves
% --- Truncate curve to k_rho and amplitude ranges ---
xRaw = S_k_all{k};
% --- Normalize and smooth ---
xNorm = xRaw / max(xRaw);
xSmooth = smooth(xNorm, 5, 'moving');
mask = (k_rho_vals(:) >= opts.KRhoRange(1)) & (k_rho_vals(:) <= opts.KRhoRange(2));
x = xSmooth(mask);
kVals = k_rho_vals(mask);
rawCurves(k).x = x;
rawCurves(k).k = kVals;
if any(isnan(xRaw))
warning('Curve %d has NaNs, skipping.', k);
fitResults(k) = fillNaNStructRadial();
continue;
elseif numel(x) < 5
warning('Curve %d has too few points after truncation, skipping.', k);
fitResults(k) = fillNaNStructRadial();
continue;
elseif isempty(x)
warning('Curve %d is empty after truncation.', k);
fitResults(k) = fillZeroStructRadial(kVals, opts.KRhoRange(2));
continue;
end
try
%% Single Gaussian fit with offset: A*exp(-(k-mu)^2/(2*sigma^2)) + C
singleGauss = @(p,k) p(1) * exp(-0.5*((k - p(2))/max(p(3),1e-6)).^2) + p(4);
% Initial guess
[~, idxMax] = max(x);
A_guess = x(idxMax) - min(x); % amplitude above baseline
mu_guess = kVals(idxMax);
sigma_guess = 0.1 * (opts.KRhoRange(2)-opts.KRhoRange(1));
C_guess = min(x); % baseline offset
p0 = [A_guess, mu_guess, sigma_guess, C_guess];
lb = [0, opts.KRhoRange(1), 1e-6, -Inf];
ub = [2*max(x), opts.KRhoRange(2), opts.KRhoRange(2), Inf];
optsLSQ = optimoptions('lsqcurvefit','Display','off', ...
'MaxFunctionEvaluations',1e4,'MaxIterations',1e4);
pFit = lsqcurvefit(singleGauss, p0, kVals(:), x(:), lb, ub, optsLSQ);
% --- Post-fit diagnostics ---
r = x(:) - singleGauss(pFit, kVals(:));
residualRMS = sqrt(mean(r.^2));
maxAbsR = max(abs(r));
kFine = linspace(min(kVals), max(kVals), 500);
yFit = singleGauss(pFit, kFine);
fitMaxKRho = pFit(2);
SS_res = sum(r.^2);
SS_tot = sum((x(:) - mean(x(:))).^2);
R2 = 1 - SS_res/SS_tot;
% Store results
fitResults(k).pFit = pFit;
fitResults(k).kFit = kVals;
fitResults(k).xFit = x;
fitResults(k).kFine = kFine;
fitResults(k).yFit = yFit;
fitResults(k).isValid = residualRMS <= opts.ResidualThreshold;
fitResults(k).residual = residualRMS;
fitResults(k).maxAbs = maxAbsR;
fitResults(k).R2 = R2;
fitResults(k).fitMaxKRho = fitMaxKRho;
catch
warning('Curve %d fit failed completely.', k);
fitResults(k) = fillNaNStructRadial();
end
end
end
%% --- Helper: NaN struct for failed fits ---
function s = fillNaNStructRadial()
s = struct( ...
'pFit', nan(1,3), ...
'kFit', nan, ...
'xFit', nan, ...
'yFit', nan, ...
'kFine', nan, ...
'isValid', false, ...
'residual', nan, ...
'maxAbs', nan, ...
'R2', nan, ...
'fitMaxKRho', nan ...
);
end
%% --- Helper: Zero struct for empty curves ---
function s = fillZeroStructRadial(kVals, MaxKRho)
s = struct( ...
'pFit', zeros(1,3), ...
'kFit', kVals, ...
'xFit', zeros(size(kVals)), ...
'yFit', zeros(size(kVals)), ...
'kFine', zeros(1,500), ...
'isValid', false, ...
'residual', zeros(size(kVals)), ...
'maxAbs', zeros(size(kVals)), ...
'R2', zeros(size(kVals)), ...
'fitMaxKRho', MaxKRho ...
);
end