Calculations/Data-Analyzer/+FitModels/DensityProfileBEC2DModel.m

816 lines
35 KiB
Matlab

classdef DensityProfileBEC2DModel < handle
%% DensityProfileBEC2DModel
% Author: Jianshun Gao
% Date: 2025-09-12
% Version: 1.0
%
% Description:
% This class provides methods to model, fit, and analyze 2D Bose-Einstein
% condensate (BEC) density profiles, including both condensed (Thomas-Fermi)
% and thermal (polylog) components.
%
% Properties:
% - params: Structure storing parameter values and bounds.
% - fitResult: MATLAB fit object after fitting.
% - gof: Goodness-of-fit structure from the fit.
%
% Methods:
% - cal_cond_frac(X, Y): Compute condensate fraction from fitted profile.
% - return_atom_number(X, Y): Compute total, BEC, and thermal atom numbers.
% - return_temperature(tof, omega): Estimate temperature from thermal cloud width.
%
% Static Methods:
% - ThomasFermi_2d: 2D Thomas-Fermi parabolic profile.
% - polylog: Series approximation of polylogarithm function.
% - polylog2_2d: 2D thermal cloud profile using polylog(2) function.
% - density_profile_BEC_2d: Full 2D density (BEC + thermal), optionally rotated.
% - density_1d: 1D slice of the density profile combining BEC and thermal parts.
%
% Usage:
% obj = DensityProfileBEC2DModel();
% atom_n = obj.return_atom_number(X, Y); % Atom numbers
% cond_frac = obj.cal_cond_frac(X, Y); % Condensate fraction
% T = obj.return_temperature(tof, omega); % Temperature
%
% Notes:
% - All static methods can be called independently without creating an object.
properties
% Conversion factors and default constants
fwhm_factor = 2*sqrt(2*log(2)); % FWHM factor for Gaussian
height_factor = 1/(2*pi); % Height normalization factor
prefix = ''; % Optional parameter prefix
tiny = 1e-15; % Small number to avoid division by zero
params; % Struct for parameter definitions
fitResult; % Result of 2D fit
cond_frac; % Condensate fraction
gof; % Goodness-of-fit structure
atom_n_conv = 144; % Conversion factor for atom number
pre_check = false; % Flag for pre-fit check
post_check = false; % Flag for post-fit check
is_debug = false; % Flag for debug plots
end
methods
function obj = DensityProfileBEC2DModel(prefix)
% Constructor
if nargin > 0
obj.prefix = prefix;
end
obj.set_paramhints_prefix();
end
function set_paramhints_prefix(obj)
% Initialize parameter hints and expressions
obj.params = struct();
% Minimum values
obj.params.([obj.prefix 'amp_bec']).min = 0;
obj.params.([obj.prefix 'amp_th']).min = 0;
obj.params.([obj.prefix 'x0_bec']).min = 0;
obj.params.([obj.prefix 'y0_bec']).min = 0;
obj.params.([obj.prefix 'x0_th']).min = 0;
obj.params.([obj.prefix 'y0_th']).min = 0;
obj.params.([obj.prefix 'sigmax_bec']).min = 0;
obj.params.([obj.prefix 'sigmay_bec']).min = 0;
obj.params.([obj.prefix 'sigma_th']).min = 0;
obj.params.([obj.prefix 'rot_angle']).min = -90;
obj.params.([obj.prefix 'rot_angle']).max = 90;
% Expressions for derived parameters
obj.params.([obj.prefix 'atom_number_bec']).expr = ...
[obj.prefix 'amp_bec / 5 * 2 * pi * ' obj.prefix 'sigmax_bec * ' obj.prefix 'sigmay_bec'];
obj.params.([obj.prefix 'atom_number_th']).expr = ...
[obj.prefix 'amp_th * 2 * pi * 1.20206 / 1.643 * ' obj.prefix 'sigma_th * ' obj.prefix 'sigma_th'];
obj.params.([obj.prefix 'condensate_fraction']).expr = ...
[obj.prefix 'atom_number_bec / (' obj.prefix 'atom_number_bec + ' obj.prefix 'atom_number_th)'];
end
function params = guess(obj, data, x, y, varargin)
% Estimate initial parameters for BEC + thermal cloud fit.
% Performs 1D slicing along the shorter axis of the image and initial amplitude guesses.
p = inputParser;
addParameter(p, 'negative', false);
addParameter(p, 'pureBECThreshold', 0.5);
addParameter(p, 'noBECThThreshold', 0.0);
addParameter(p, 'rot_angle', 0);
addParameter(p, 'vary_rot', false);
addParameter(p, 'is_debug', false);
addParameter(p, 'pre_check', false);
addParameter(p, 'post_check', false);
parse(p, varargin{:});
obj.is_debug = p.Results.is_debug;
obj.pre_check = p.Results.pre_check;
obj.post_check = p.Results.post_check;
x_width = length(unique(x));
y_width = length(unique(y));
x_1d = linspace(x(1), x(end), x_width);
y_1d = linspace(y(1), y(end), y_width);
data_2d = reshape(data, y_width, x_width)';
% Rotate image if needed
rot_angle = p.Results.rot_angle;
if rot_angle ~= 0
data_2d = imrotate(data_2d, rot_angle, 'bilinear', 'crop');
end
% Debug plot of input data
if obj.is_debug
figure;
imagesc(x_1d, y_1d, data_2d');
axis equal tight;
colorbar;
colormap('jet');
title('Input Data');
xlabel('x-axis');
ylabel('y-axis');
end
% Binarize and locate BEC
thresh = obj.calc_thresh(data_2d, 0.5);
center_pix = obj.calc_cen_pix(thresh);
center = obj.center_pix_conv(center_pix, x_1d, y_1d);
BEC_width_guess = obj.guess_BEC_width(thresh, center_pix);
if obj.is_debug
figure;
imagesc(x_1d, y_1d, thresh');
hold on;
plot(center(1), center(2), 'gx', 'MarkerSize', 25, 'LineWidth', 2);
axis equal tight;
colorbar;
colormap('jet');
title(sprintf('Binarized Image (BEC Width: x=%.0f, y=%.0f pixels)', BEC_width_guess(1), BEC_width_guess(2)));
xlabel('x-axis');
ylabel('y-axis');
end
% 1D slicing along the shorter axis
if BEC_width_guess(1) < BEC_width_guess(2)
if obj.is_debug
disp('x-axis is shorter, performing 1D fit along x-axis');
end
s_width_ind = 1;
x_fit = x_1d;
slice_range = round(center_pix(2) - BEC_width_guess(2)/2) : round(center_pix(2) + BEC_width_guess(2)/2);
X_guess = sum(data_2d(:, slice_range), 2) / length(slice_range);
else
if obj.is_debug
disp('y-axis is shorter, performing 1D fit along y-axis');
end
s_width_ind = 2;
x_fit = y_1d;
slice_range = round(center_pix(1) - BEC_width_guess(1)/2) : round(center_pix(1) + BEC_width_guess(1)/2);
X_guess = sum(data_2d(slice_range, :), 1) / length(slice_range);
end
max_val = max(X_guess);
% Construct initial parameter struct
params_1d = struct();
params_1d.x0_bec.value = center(s_width_ind);
params_1d.x0_bec.min = center(s_width_ind) - 10;
params_1d.x0_bec.max = center(s_width_ind) + 10;
params_1d.x0_th.value = center(s_width_ind);
params_1d.x0_th.min = center(s_width_ind) - 10;
params_1d.x0_th.max = center(s_width_ind) + 10;
params_1d.amp_bec.value = 0.5 * max_val;
params_1d.amp_bec.min = 0;
params_1d.amp_bec.max = 1.3 * max_val;
params_1d.amp_th.value = 0.5 * max_val;
params_1d.amp_th.min = 0;
params_1d.amp_th.max = 1.3 * max_val;
% params_1d.deltax.value = 3 * BEC_width_guess(s_width_ind);
% params_1d.deltax.min = 0;
% params_1d.deltax.max = max(x_width, y_width);
params_1d.sigma_bec.value = BEC_width_guess(s_width_ind) / 1.22;
params_1d.sigma_bec.min = 0;
params_1d.sigma_bec.max = 2 * BEC_width_guess(s_width_ind);
% params_1d.sigma_th.value = 3 * BEC_width_guess(1);
params_1d.sigma_th.value = 0.632 * params_1d.sigma_bec.value + 0.518 * 3 * BEC_width_guess(s_width_ind);
params_1d.sigma_th.min = 0;
params_1d.sigma_th.max = 3 * (0.632 * params_1d.sigma_bec.value + 0.518 * 3 * BEC_width_guess(s_width_ind));
% params_1d.sigma_th.expr = '0.632*sigma_bec + 0.518*deltax';
% Perform 1D bimodal fit
[fitResult_1d, gof_1d] = fit_1d_bimodal(x_fit, X_guess, params_1d);
% Extract fit coefficients
bval_1d = coeffvalues(fitResult_1d);
paramNames_1d = coeffnames(fitResult_1d);
for i = 1:length(paramNames_1d)
bval_1d_struct.(paramNames_1d{i}) = bval_1d(i);
end
if obj.is_debug
disp('Initial 1D fit parameters:');
disp(bval_1d);
figure;
plot(x_fit, X_guess, 'b-', 'LineWidth', 2);
hold on;
plot(x_fit, obj.density_1d(x_fit, bval_1d.x0_bec, bval_1d.x0_th, ...
bval_1d.amp_bec, bval_1d.amp_th, bval_1d.sigma_bec, bval_1d.sigma_th), 'r-', 'LineWidth', 2);
legend('Data', 'Fit');
if s_width_ind == 1
xlabel('x-axis'); title('1D Fit along x-axis');
else
xlabel('y-axis'); title('1D Fit along y-axis');
end
ylabel('Intensity');
end
% Scale amplitudes
blurred_data = imgaussfilt(data_2d, 1);
amp_conv_1d_2d = max(blurred_data(:)) / (bval_1d_struct.amp_bec + bval_1d_struct.amp_th);
max_val = max(data_2d(:));
% Create parameter struct
params = struct();
% Pre-check: decide if image is pure BEC or pure thermal cloud based on 1D fit result
if bval_1d_struct.amp_th / bval_1d_struct.amp_bec > 7 && obj.pre_check
if obj.is_debug
disp('Image seems to be pure thermal cloud (based on 1D fit amplitudes)');
end
params.([obj.prefix 'amp_bec']).value = 0;
params.([obj.prefix 'amp_bec']).vary = false;
params.([obj.prefix 'amp_th']).value = amp_conv_1d_2d * bval_1d_struct.amp_th;
params.([obj.prefix 'amp_th']).max = 1.3 * max_val;
params.([obj.prefix 'amp_th']).vary = true;
params.([obj.prefix 'x0_bec']).value = 1;
params.([obj.prefix 'x0_bec']).vary = false;
params.([obj.prefix 'y0_bec']).value = 1;
params.([obj.prefix 'y0_bec']).vary = false;
params.([obj.prefix 'x0_th']).value = center(1);
params.([obj.prefix 'x0_th']).min = center(1) - 10;
params.([obj.prefix 'x0_th']).max = center(1) + 10;
params.([obj.prefix 'x0_th']).vary = true;
params.([obj.prefix 'y0_th']).value = center(2);
params.([obj.prefix 'y0_th']).min = center(2) - 10;
params.([obj.prefix 'y0_th']).max = center(2) + 10;
params.([obj.prefix 'y0_th']).vary = true;
params.([obj.prefix 'sigmax_bec']).value = 1;
params.([obj.prefix 'sigmax_bec']).vary = false;
params.([obj.prefix 'sigmay_bec']).value = 1;
params.([obj.prefix 'sigmay_bec']).vary = false;
params.([obj.prefix 'sigma_th']).value = bval_1d_struct.sigma_th;
params.([obj.prefix 'sigma_th']).min = 0;
params.([obj.prefix 'sigma_th']).max = max(x_width, y_width);
params.([obj.prefix 'sigma_th']).vary = true;
elseif bval_1d_struct.amp_bec / bval_1d_struct.amp_th > 10 && obj.pre_check
if obj.is_debug
disp('Image seems to be pure BEC (based on 1D fit amplitudes)');
end
params.([obj.prefix 'amp_bec']).value = amp_conv_1d_2d * bval_1d_struct.amp_bec;
params.([obj.prefix 'amp_bec']).max = 1.3 * max_val;
params.([obj.prefix 'amp_bec']).vary = true;
params.([obj.prefix 'amp_th']).value = 0;
params.([obj.prefix 'amp_th']).vary = false;
params.([obj.prefix 'x0_bec']).value = center(1);
params.([obj.prefix 'x0_bec']).min = center(1) - 10;
params.([obj.prefix 'x0_bec']).max = center(1) + 10;
params.([obj.prefix 'x0_bec']).vary = true;
params.([obj.prefix 'y0_bec']).value = center(2);
params.([obj.prefix 'y0_bec']).min = center(2) - 10;
params.([obj.prefix 'y0_bec']).max = center(2) + 10;
params.([obj.prefix 'y0_bec']).vary = true;
params.([obj.prefix 'x0_th']).value = 1;
params.([obj.prefix 'x0_th']).vary = false;
params.([obj.prefix 'y0_th']).value = 1;
params.([obj.prefix 'y0_th']).vary = false;
params.([obj.prefix 'sigma_th']).value = 1;
params.([obj.prefix 'sigma_th']).vary = false;
if s_width_ind == 1
params.([obj.prefix 'sigmax_bec']).value = bval_1d_struct.sigma_bec;
params.([obj.prefix 'sigmax_bec']).max = 2 * BEC_width_guess(1);
params.([obj.prefix 'sigmax_bec']).vary = true;
params.([obj.prefix 'sigmay_bec']).value = BEC_width_guess(2) / 1.22;
params.([obj.prefix 'sigmay_bec']).max = 2 * BEC_width_guess(2);
params.([obj.prefix 'sigmay_bec']).vary = true;
else
params.([obj.prefix 'sigmax_bec']).value = BEC_width_guess(1) / 1.22;
params.([obj.prefix 'sigmax_bec']).max = 2 * BEC_width_guess(1);
params.([obj.prefix 'sigmax_bec']).vary = true;
params.([obj.prefix 'sigmay_bec']).value = bval_1d_struct.sigma_bec;
params.([obj.prefix 'sigmay_bec']).max = 2 * BEC_width_guess(2);
params.([obj.prefix 'sigmay_bec']).vary = true;
end
else
% Normal bimodal fit parameters
params.([obj.prefix 'amp_bec']).value = amp_conv_1d_2d * bval_1d_struct.amp_bec;
params.([obj.prefix 'amp_bec']).min = 0;
params.([obj.prefix 'amp_bec']).max = 1.3 * max_val;
params.([obj.prefix 'amp_bec']).vary = true;
params.([obj.prefix 'amp_th']).value = amp_conv_1d_2d * bval_1d_struct.amp_th;
params.([obj.prefix 'amp_th']).min = 0;
params.([obj.prefix 'amp_th']).max = 1.3 * max_val;
params.([obj.prefix 'amp_th']).vary = true;
params.([obj.prefix 'x0_bec']).value = center(1);
params.([obj.prefix 'x0_bec']).min = center(1) - 10;
params.([obj.prefix 'x0_bec']).max = center(1) + 10;
params.([obj.prefix 'x0_bec']).vary = true;
params.([obj.prefix 'y0_bec']).value = center(2);
params.([obj.prefix 'y0_bec']).min = center(2) - 10;
params.([obj.prefix 'y0_bec']).max = center(2) + 10;
params.([obj.prefix 'y0_bec']).vary = true;
params.([obj.prefix 'x0_th']).value = center(1);
params.([obj.prefix 'x0_th']).min = center(1) - 10;
params.([obj.prefix 'x0_th']).max = center(1) + 10;
params.([obj.prefix 'x0_th']).vary = true;
params.([obj.prefix 'y0_th']).value = center(2);
params.([obj.prefix 'y0_th']).min = center(2) - 10;
params.([obj.prefix 'y0_th']).max = center(2) + 10;
params.([obj.prefix 'y0_th']).vary = true;
params.([obj.prefix 'sigma_th']).value = bval_1d_struct.sigma_th;
params.([obj.prefix 'sigma_th']).min = 0;
params.([obj.prefix 'sigma_th']).max = max(x_width, y_width);
params.([obj.prefix 'sigma_th']).vary = true;
if s_width_ind == 1
params.([obj.prefix 'sigmax_bec']).value = bval_1d_struct.sigma_bec;
params.([obj.prefix 'sigmax_bec']).min = 0;
params.([obj.prefix 'sigmax_bec']).max = 2 * BEC_width_guess(1);
params.([obj.prefix 'sigmax_bec']).vary = true;
params.([obj.prefix 'sigmay_bec']).value = BEC_width_guess(2) / 1.22;
params.([obj.prefix 'sigmay_bec']).min = 0;
params.([obj.prefix 'sigmay_bec']).max = 2 * BEC_width_guess(2);
params.([obj.prefix 'sigmay_bec']).vary = true;
else
params.([obj.prefix 'sigmax_bec']).value = BEC_width_guess(1) / 1.22;
params.([obj.prefix 'sigmax_bec']).min = 0;
params.([obj.prefix 'sigmax_bec']).max = 2 * BEC_width_guess(1);
params.([obj.prefix 'sigmax_bec']).vary = true;
params.([obj.prefix 'sigmay_bec']).value = bval_1d_struct.sigma_bec;
params.([obj.prefix 'sigmay_bec']).min = 0;
params.([obj.prefix 'sigmay_bec']).max = 2 * BEC_width_guess(2);
params.([obj.prefix 'sigmay_bec']).vary = true;
end
end
params.([obj.prefix 'rot_angle']).value = rot_angle;
params.([obj.prefix 'rot_angle']).min = rot_angle - 30;
params.([obj.prefix 'rot_angle']).max = rot_angle + 30;
params.([obj.prefix 'rot_angle']).vary = p.Results.vary_rot;
if obj.is_debug
disp('Initial parameters:');
disp(params);
end
obj.params = params;
end
function [fitResult, gof] = fit(obj, data, x, y, varargin)
data = double(data);
% Perform fitting
if isempty(obj.params)
obj.guess(data, x, y, varargin{:});
end
% Prepare fitting data
[X, Y] = meshgrid(x, y);
xyData = [X(:), Y(:)];
zData = double(data(:));
% Create fit options
options = fitoptions('Method', 'NonlinearLeastSquares');
if obj.params.([obj.prefix 'rot_angle']).vary
% Define parameter order
paramOrder = {[obj.prefix 'amp_bec'], [obj.prefix 'amp_th'], ...
[obj.prefix 'x0_bec'], [obj.prefix 'y0_bec'], ...
[obj.prefix 'x0_th'], [obj.prefix 'y0_th'], ...
[obj.prefix 'sigmax_bec'], [obj.prefix 'sigmay_bec'], ...
[obj.prefix 'sigma_th'], [obj.prefix 'rot_angle']};
% Create StartPoint, Lower, and Upper vectors
startPoint = zeros(1, length(paramOrder));
lowerBounds = zeros(1, length(paramOrder));
upperBounds = zeros(1, length(paramOrder));
for i = 1:length(paramOrder)
paramName = paramOrder{i};
startPoint(i) = obj.params.(paramName).value;
lowerBounds(i) = obj.params.(paramName).min;
upperBounds(i) = obj.params.(paramName).max;
end
% Set fitting options
options.StartPoint = startPoint;
options.Lower = lowerBounds;
options.Upper = upperBounds;
% Define fit type
ft = fittype(@(amp_bec, amp_th, x0_bec, y0_bec, x0_th, y0_th, ...
sigmax_bec, sigmay_bec, sigma_th, rot_angle, x, y) ...
obj.density_profile_BEC_2d(x, y, amp_bec, amp_th, x0_bec, y0_bec, ...
x0_th, y0_th, sigmax_bec, sigmay_bec, sigma_th, rot_angle), ...
'independent', {'x', 'y'}, 'dependent', 'z');
else
% Define parameter order
paramOrder = {[obj.prefix 'amp_bec'], [obj.prefix 'amp_th'], ...
[obj.prefix 'x0_bec'], [obj.prefix 'y0_bec'], ...
[obj.prefix 'x0_th'], [obj.prefix 'y0_th'], ...
[obj.prefix 'sigmax_bec'], [obj.prefix 'sigmay_bec'], ...
[obj.prefix 'sigma_th']};
% Create StartPoint, Lower, and Upper vectors
startPoint = zeros(1, length(paramOrder));
lowerBounds = zeros(1, length(paramOrder));
upperBounds = zeros(1, length(paramOrder));
for i = 1:length(paramOrder)
paramName = paramOrder{i};
startPoint(i) = obj.params.(paramName).value;
lowerBounds(i) = obj.params.(paramName).min;
upperBounds(i) = obj.params.(paramName).max;
end
% Set fitting options
options.StartPoint = startPoint;
options.Lower = lowerBounds;
options.Upper = upperBounds;
% Define fit type
ft = fittype(@(amp_bec, amp_th, x0_bec, y0_bec, x0_th, y0_th, ...
sigmax_bec, sigmay_bec, sigma_th, x, y) ...
obj.density_profile_BEC_2d(x, y, amp_bec, amp_th, x0_bec, y0_bec, ...
x0_th, y0_th, sigmax_bec, sigmay_bec, sigma_th, 0), ...
'independent', {'x', 'y'}, 'dependent', 'z');
end
% Perform fitting
[obj.fitResult, obj.gof] = fit(xyData, zData, ft, options);
fitResult = obj.fitResult;
gof = obj.gof;
% Post-processing check
if obj.post_check
bval = coeffvalues(obj.fitResult);
paramNames = coeffnames(obj.fitResult);
% Extract parameter values
for i = 1:length(paramNames)
eval([paramNames{i} ' = bval(i);']);
end
% Calculate number of atoms around the BEC
tf_fit = obj.ThomasFermi_2d(xyData(:,1), xyData(:,2), x0_bec, y0_bec, amp_bec, sigmax_bec, sigmay_bec);
tf_fit_2 = obj.ThomasFermi_2d(xyData(:,1), xyData(:,2), x0_bec, y0_bec, amp_bec, 1.5*sigmax_bec, 1.5*sigmay_bec);
mask = tf_fit > 0;
mask_2 = tf_fit_2 > 0;
N_c = sum(zData(mask & ~mask_2));
N_a = obj.atom_n_conv * N_c;
% If too few atoms are found around the BEC, refit (BEC only)
if N_a < 6615
if obj.is_debug
disp('No thermal cloud detected, performing BEC-only fit');
end
% Update parameters
obj.params.([obj.prefix 'amp_th']).value = 0;
obj.params.([obj.prefix 'amp_th']).vary = false;
obj.params.([obj.prefix 'x0_th']).value = 1;
obj.params.([obj.prefix 'x0_th']).vary = false;
obj.params.([obj.prefix 'y0_th']).value = 1;
obj.params.([obj.prefix 'y0_th']).vary = false;
obj.params.([obj.prefix 'sigma_th']).value = 1;
obj.params.([obj.prefix 'sigma_th']).vary = false;
% Refit
[obj.fitResult, obj.gof] = fit(xyData, zData, ft, options);
fitResult = obj.fitResult;
gof = obj.gof;
end
end
% Calculate condensate fraction
obj.cond_frac = obj.cal_cond_frac(X, Y);
end
function thresh = calc_thresh(obj, data, thresh_val, sigma)
% Binarize image
if nargin < 3
thresh_val = 0.3;
end
if nargin < 4
sigma = 0.4;
end
blurred = imgaussfilt(data, sigma);
thresh = blurred < max(blurred(:)) * thresh_val;
thresh = double(~thresh); % Invert and convert to double precision
end
function center_pix = calc_cen_pix(obj, thresh)
% Calculate the center of the binarized image
[X, Y] = size(thresh);
thresh = thresh / sum(thresh(:));
% Edge distributions
dx = sum(thresh, 2);
dy = sum(thresh, 1);
% Expectation values
center_pix = [sum(dx .* (1:X)'), sum(dy .* (1:Y))];
end
function center = center_pix_conv(obj, center_pix, x, y)
% Convert pixel center to coordinate center
center = [x(round(center_pix(1))), y(round(center_pix(2)))];
end
function BEC_width_guess = guess_BEC_width(obj, thresh, center)
% Guess BEC width
[X, Y] = size(thresh);
BEC_width_guess = [sum(thresh(:, round(center(2)))), sum(thresh(round(center(1)), :))];
for i = 1:2
if BEC_width_guess(i) <= 0
BEC_width_guess(i) = 1;
end
end
end
function cond_frac = cal_cond_frac(obj, X, Y)
% Calculate condensate fraction
bval = coeffvalues(obj.fitResult);
paramNames = coeffnames(obj.fitResult);
% Extract parameter values
for i = 1:length(paramNames)
eval([paramNames{i} ' = bval(i);']);
end
if ~obj.params.([obj.prefix 'rot_angle']).vary
rot_angle = 0;
end
tf_fit = obj.ThomasFermi_2d(X, Y, x0_bec, y0_bec, amp_bec, sigmax_bec, sigmay_bec);
fit_total = obj.density_profile_BEC_2d(X, Y, amp_bec, amp_th, x0_bec, y0_bec, x0_th, y0_th, sigmax_bec, sigmay_bec, sigma_th, rot_angle);
N_bec = sum(tf_fit(:));
N_ges = sum(fit_total(:));
cond_frac = N_bec / N_ges;
end
function atom_n = return_atom_number(obj, X, Y, is_print)
% Calculate atom number
if nargin < 4
is_print = true;
end
bval = coeffvalues(obj.fitResult);
paramNames = coeffnames(obj.fitResult);
% Extract parameter values
for i = 1:length(paramNames)
eval([paramNames{i} ' = bval(i);']);
end
tf_fit = obj.ThomasFermi_2d(X, Y, x0_bec, y0_bec, amp_bec, sigmax_bec, sigmay_bec);
th_fit = obj.polylog2_2d(X, Y, x0_th, y0_th, amp_th, sigma_th, sigma_th);
N_bec = obj.atom_n_conv * sum(tf_fit(:));
N_th = obj.atom_n_conv * sum(th_fit(:));
N = N_bec + N_th;
frac = N_bec / N;
if is_print
fprintf('\nAtom numbers:\n');
fprintf(' N_bec: %.0f\n', N_bec);
fprintf(' N_th: %.0f\n', N_th);
fprintf(' N_total: %.0f\n', N);
fprintf(' Condensate fraction: %.2f %%\n', frac * 100);
end
atom_n = struct('N', N, 'N_bec', N_bec, 'N_th', N_th, 'cond_f', frac);
end
function T = return_temperature(obj, tof, omg, is_print, eff_pix)
% Calculate temperature
if nargin < 3
omg = [];
end
if nargin < 4
is_print = true;
end
if nargin < 5
eff_pix = 2.472e-6;
end
bval = coeffvalues(obj.fitResult);
paramNames = coeffnames(obj.fitResult);
% Extract parameter values
for i = 1:length(paramNames)
eval([paramNames{i} ' = bval(i);']);
end
R_th = sigma_th * eff_pix * sqrt(2);
% Physical constants
u = 1.66053906660e-27; % Atomic mass unit
k = 1.380649e-23; % Boltzmann constant
if isempty(omg)
T = R_th^2 * 164 * u / k / tof^2;
else
T = R_th^2 * 164 * u / k / (1/omg^2 + tof^2);
end
if is_print
fprintf('Temperature: %.2f nK\n', T * 1e9);
end
end
end
methods (Static)
function res = ThomasFermi_2d(x, y, centerx, centery, amplitude, sigmax, sigmay)
% Thomas-Fermi distribution function
% tiny = 1e-15;
res = (1 - ((x - centerx) / sigmax).^2 - ((y - centery) / sigmay).^2);
res(res < 0) = 0;
res = amplitude * res.^(3/2);
% res = amplitude * 5/(2*pi) / max(tiny, sigmax * sigmay) .* (res > 0) .* res;
end
function res = polylog(power, numerator)
% Polylogarithm function approximation
order = 20;
dataShape = size(numerator);
numerator = repmat(numerator(:), 1, order);
numerator = numerator .^ repmat(1:order, prod(dataShape), 1);
denominator = repmat((1:order), prod(dataShape), 1);
data = numerator ./ (denominator .^ power);
res = sum(data, 2);
res = reshape(res, dataShape);
end
function res = polylog2_2d(x, y, centerx, centery, amplitude, sigmax, sigmay)
% 2D polylogarithm function
% tiny = 1e-15;
arg = exp(-((x - centerx).^2 / (2 * sigmax^2)) - ((y - centery).^2 / (2 * sigmay^2)));
res = amplitude / (1.643) .* FitModels.DensityProfileBEC2DModel.polylog(2, arg);
end
function res = density_profile_BEC_2d(x, y, amp_bec, amp_th, x0_bec, y0_bec, x0_th, y0_th, sigmax_bec, sigmay_bec, sigma_th, rot_angle)
% BEC density profile function
if nargin < 12
rot_angle = 0;
end
% Rotate coordinates (if needed)
if rot_angle ~= 0
rot_angle_rad = -rot_angle * pi/180; % Negative sign means clockwise rotation
% Rotate coordinates
x_rot = x * cos(rot_angle_rad) + y * sin(rot_angle_rad);
y_rot = -x * sin(rot_angle_rad) + y * cos(rot_angle_rad);
% Rotate BEC center
x0_bec_rot = x0_bec * cos(rot_angle_rad) + y0_bec * sin(rot_angle_rad);
y0_bec_rot = -x0_bec * sin(rot_angle_rad) + y0_bec * cos(rot_angle_rad);
% Rotate thermal center
x0_th_rot = x0_th * cos(rot_angle_rad) + y0_th * sin(rot_angle_rad);
y0_th_rot = -x0_th * sin(rot_angle_rad) + y0_th * cos(rot_angle_rad);
x = x_rot;
y = y_rot;
x0_bec = x0_bec_rot;
y0_bec = y0_bec_rot;
x0_th = x0_th_rot;
y0_th = y0_th_rot;
end
% Calculate Thomas-Fermi part
TF_part = FitModels.DensityProfileBEC2DModel.ThomasFermi_2d(x, y, x0_bec, y0_bec, amp_bec, sigmax_bec, sigmay_bec);
% Calculate polylogarithm part
poly_part = FitModels.DensityProfileBEC2DModel.polylog2_2d(x, y, x0_th, y0_th, amp_th, sigma_th, sigma_th);
% Total sum
res = TF_part + poly_part;
end
function res = density_1d(x, x0_bec, x0_th, amp_bec, amp_th, sigma_bec, sigma_th)
% 1D density profile (Thomas-Fermi + thermal polylog)
thermal_part = amp_th / 1.643 * polylog_int(exp(-0.5 * (x - x0_th).^2 / sigma_th^2));
TF_part = amp_bec * (1 - ((x - x0_bec) / sigma_bec).^2);
TF_part(TF_part < 0) = 0;
TF_part = TF_part.^(3/2);
res = thermal_part + TF_part;
end
end
end
% Helper function: polylogarithm interpolation
function res = polylog_int(x)
% Create interpolation table (simplified version)
x_int = linspace(0, 1.00001, 1000);
poly_tab = zeros(size(x_int));
for i = 1:length(x_int)
poly_tab(i) = sum((x_int(i).^(1:20)) ./ (1:20).^2);
end
% Linear interpolation
res = interp1(x_int, poly_tab, x, 'linear', 'extrap');
end
function [fitResult, gof] = fit_1d_bimodal(x, y, initialParams)
% 1D bimodal fitting function
% Input:
% x - independent variable data
% y - dependent variable data
% initialParams - structure of initial parameters
% Output:
% fitResult - fitting result
% gof - goodness-of-fit statistics
% Define 1D bimodal fitting function
bimodal1d = @(amp_bec, amp_th, x0_bec, x0_th, sigma_bec, sigma_th, x) ...
FitModels.DensityProfileBEC2DModel.density_1d(x, x0_bec, x0_th, amp_bec, amp_th, sigma_bec, sigma_th);
paramNames = {'amp_bec', 'amp_th', 'x0_bec', 'x0_th', 'sigma_bec', 'sigma_th'};
% Create fit type
ft = fittype(bimodal1d, 'independent', 'x', 'dependent', 'y');
% Set fit options
options = fitoptions(ft);
% Set initial parameters and bounds
startPoint = zeros(1, length(paramNames));
lowerBounds = zeros(1, length(paramNames));
upperBounds = zeros(1, length(paramNames));
for i = 1:length(paramNames)
paramName = paramNames{i};
startPoint(i) = initialParams.(paramName).value;
lowerBounds(i) = initialParams.(paramName).min;
upperBounds(i) = initialParams.(paramName).max;
end
options.StartPoint = startPoint;
options.Lower = lowerBounds;
options.Upper = upperBounds;
% Perform fitting
[fitResult, gof] = fit(x(:), y(:), ft, options);
end