FR_choice = 1; ABKG_choice = 1; % Get the full spectrum [B, a_s] = getFullFeschbachSpectrum(FR_choice, ABKG_choice); % Define the plotting range x_limits = [1.15, 2.7]; y_limits = [0, 150]; % Find indices within our x-range mask = (B >= x_limits(1)) & (B <= x_limits(2)); B_plot = B(mask); a_s_plot = a_s(mask); % Identify resonances to mask (looking at the spectrum, we'll mask around 2.174G and 2.336G) resonances_to_mask = [2.174, 2.336]; mask_width = 0.1; % Width to mask around each resonance (in G) % Create a mask for the regions to keep (1 = keep, 0 = mask) keep_mask = true(size(B_plot)); for res = resonances_to_mask keep_mask = keep_mask & (B_plot < (res - mask_width) | B_plot > (res + mask_width)); end % Create masked version B_masked = B_plot(keep_mask); a_s_masked = a_s_plot(keep_mask); % Interpolate over the masked regions a_s_interp = interp1(B_masked, a_s_masked, B_plot, 'pchip'); % --- Find the remaining resonances (peaks) in the interpolated data --- % Use findpeaks to detect peaks (adjust MinPeakProminence as needed) [peaks, locs] = findpeaks(abs(a_s_interp), 'MinPeakProminence', 20); % Tune this! remaining_resonances = B_plot(locs); % Positions of remaining resonances % --- Select the two resonances of interest (1.3G and 2.59G) --- % If automatic detection fails, manually define them: target_resonances = [1.3, 2.59]; % Manually specified (adjust as needed) % OR use the closest detected resonances: % target_resonances = remaining_resonances(abs(remaining_resonances - 1.3) < 0.2 & abs(remaining_resonances - 2.59) < 0.2); % --- Extract the curve BETWEEN these two resonances --- res1 = min(target_resonances); % Lower resonance (1.3G) res2 = max(target_resonances); % Upper resonance (2.59G) between_mask = (B_plot > res1) & (B_plot < res2); % No masking here, since resonances are already removed B_between = B_plot(between_mask); a_s_between = a_s_interp(between_mask); % --- Plotting --- figure(1); set(gcf,'Position',[100 100 950 750]) set(gca,'FontSize',16,'Box','On','Linewidth',2); hold on; % Plot the interpolated spectrum (without 2.174G and 2.336G) plot(B_plot, a_s_interp, 'k-', 'LineWidth', 1, 'DisplayName', 'Interpolated spectrum'); % Highlight the region between 1.3G and 2.59G plot(B_between, a_s_between, 'm-', 'LineWidth', 2, 'DisplayName', 'Between 1.3G and 2.59G'); % Formatting xlim(x_limits); ylim([0, 150]); xlabel('Magnetic Field (G)'); ylabel('Scattering Length (a_0)'); title('Interpolated Spectrum: Region Between 1.3G and 2.59G'); legend('Location', 'southeast'); grid on; hold off; %% % Load data FSData = load('20260605_ALSData.mat'); x = FSData.data(:,1); y = FSData.data(:,2) * 166 * 1E-5; y_err = FSData.data(:,3) * 166 * 1E-5; % --- Plotting --- figure(2); clf; set(gcf,'Position',[100 100 950 750]) set(gca,'FontSize',16,'Box','On','Linewidth',2); hold on; % Plot data with error bars, no connecting lines (only points) errorbar(x, y, y_err, 'ko', ... 'MarkerFaceColor', 'r', ... % Filled black circles 'MarkerSize', 6, ... % Size of data points 'LineStyle', 'none', ... % No connecting line 'LineWidth', 1.5, ... % Width of error bars 'DisplayName', 'As measured with ALS on a cold thermal cloud'); % Formatting xlabel('Magnetic Field (G)'); ylabel('Atom Number'); legend('Location', 'southeast'); grid on; box on; hold off; %% Combined plot figure(3); clf; set(gcf, 'Position', [100, 100, 950, 750]); set(gca, 'FontSize', 16, 'Box', 'On', 'LineWidth', 2); hold on; % ========== Define Shaded Regions ========== % Format: [x_start, x_end, label] regions = [ 2.45, 2.52, "BEC"; 2.35, 2.45, "SSD/SS"; 2.1, 2.35, "ID/IS"; 1.4, 2.1, "TBD" ]; % Colors for each region (semi-transparent) region_colors = [ 0.85, 0.9, 1.0; % light blue 0.9, 1.0, 0.85; % light green 1.0, 0.9, 0.85; % light red 0.95, 0.95, 0.95 % light gray ]; ylims = [0, 150]; % Adjust as needed for i = 1:size(regions,1) x1 = str2double(regions(i,1)); x2 = str2double(regions(i,2)); label = string(regions(i,3)); fill([x1 x2 x2 x1], [ylims(1) ylims(1) ylims(2) ylims(2)], ... region_colors(i,:), ... 'FaceAlpha', 0.9, 'EdgeColor', 'none', 'HandleVisibility', 'off'); text((x1 + x2)/2, mean(ylims), label, ... 'HorizontalAlignment', 'center', ... 'VerticalAlignment', 'middle', ... 'FontSize', 14, 'FontWeight', 'bold', ... 'Rotation', 90); % Vertical orientation end % ========== LEFT Y-AXIS ========== yyaxis left plot(B_plot, a_s_interp, 'k-', 'LineWidth', 1.5, 'DisplayName', 'Interpolated Dy-164 Feshbach spectrum'); plot(B_between, a_s_between, 'm-', 'LineWidth', 2.5, 'DisplayName', 'Region between 1.3G and 2.59G'); ylabel('Scattering Length (a_0)'); ylim(ylims); % ========== RIGHT Y-AXIS ========== yyaxis right ax = gca; ax.YColor = [0.75 0 0]; % Make right y-axis red to match data points FSData = load('20260605_ALSData.mat'); x = FSData.data(:,1); y = FSData.data(:,2) * 166 * 1E-5; y_err = FSData.data(:,3) * 166 * 1E-5; errorbar(x, y, y_err, 'ko', ... 'MarkerFaceColor', 'r', ... 'MarkerSize', 6, ... 'LineStyle', 'none', ... 'LineWidth', 1.5, ... 'DisplayName', 'As measured with ALS on a cold thermal cloud'); ylabel('Atom Number'); % ========== Shared Formatting ========== xlabel('Magnetic Field (G)'); xlim([1.15, 2.7]); grid on; legend('Location', 'southeast'); title('BEC-SSD/SS-ID/IS Regimes'); hold off; %% Helper functions function [t, B_ramp, a_check] = generateSmoothBRamp(FR_choice, ABKG_choice, a_start, a_end, selectedResRange, T, Nt, opts) % Time array t = linspace(0, T, Nt); if strcmp(opts.rampShape, 'linear') % Directly call helper for linear LUT ramp generation [t, B_ramp, a_check] = generateLinearBRampUsingLUT(FR_choice, ABKG_choice, a_start, a_end, selectedResRange, T, Nt); return end % Get target a_s(t) based on rampShape choice a_target = getTargetScatteringLength(t, T, a_start, a_end, opts.rampShape); % --- a(B) interpolation --- [B_between, a_between] = extractBetweenResonances(FR_choice, ABKG_choice, selectedResRange); valid_idx = a_between > 0 & a_between < 150; [a_sorted, sort_idx] = sort(a_between(valid_idx)); B_sorted = B_between(valid_idx); B_sorted = B_sorted(sort_idx); B_of_a = @(a) interp1(a_sorted, B_sorted, a, 'linear', 'extrap'); B_raw = B_of_a(a_target); % --- Smoothing --- switch opts.smoothingMethod case 'sgolay' B_smooth = sgolayfilt(B_raw, opts.sgolayOrder, opts.sgolayFrameLength); case 'lowpass' dt = T / (Nt - 1); Fs = 1 / dt; B_smooth = lowpass(B_raw, Fs / 20, Fs); case 'none' B_smooth = B_raw; otherwise error('Unknown smoothing method'); end % --- Bound the ramp --- B_smooth = min(max(B_smooth, opts.Bmin), opts.Bmax); % --- Enforce max dB/dt --- dt = T / (Nt - 1); for i = 2:Nt delta = B_smooth(i) - B_smooth(i-1); if abs(delta/dt) > opts.maxRampRate delta = sign(delta) * opts.maxRampRate * dt; B_smooth(i) = B_smooth(i-1) + delta; end end B_ramp = B_smooth; % --- Verify a_s(t) from B_ramp --- [a_bkg, resonanceB, resonancewB] = getResonanceParams(FR_choice, ABKG_choice, selectedResRange); a_of_B = @(B) arrayfun(@(b) ... a_bkg * prod(1 - resonancewB ./ (b - resonanceB)), B); a_check = a_of_B(B_ramp); end function a_target = getTargetScatteringLength(t, T, a_start, a_end, rampShape) % If rampShape is a function handle, use it directly (for flexibility) if isa(rampShape, 'function_handle') a_target = rampShape(t); return end switch lower(rampShape) case 'exponential' tau = T / 3; base = (1 - exp(-t / tau)) / (1 - exp(-T / tau)); a_target = a_start + (a_end - a_start) * base; case 'sigmoid' s = 10 / T; center = T / 2; sigmoid = @(x) 1 ./ (1 + exp(-s * (x - center))); base = (sigmoid(t) - sigmoid(t(1))) / (sigmoid(t(end)) - sigmoid(t(1))); a_target = a_start + (a_end - a_start) * base; otherwise error('Unknown ramp shape: %s', rampShape); end end function [B_between, a_between] = extractBetweenResonances(FR_choice, ABKG_choice, selectedRange) [a_bkg, resonanceB, resonancewB] = getResonanceParams(FR_choice, ABKG_choice, selectedRange); [~, idx] = sort(resonancewB, 'descend'); B1 = resonanceB(idx(1)); B2 = resonanceB(idx(2)); w1 = resonancewB(idx(1)); w2 = resonancewB(idx(2)); Bvis = linspace(min(B1, B2) - 20*min(w1,w2), max(B1, B2) + 20*min(w1,w2), 2000); a_of_B = @(B) arrayfun(@(b) ... a_bkg * prod(1 - resonancewB ./ (b - resonanceB)), B); avis = a_of_B(Bvis); between_idx = Bvis >= min(B1,B2) & Bvis <= max(B1,B2); B_between = Bvis(between_idx); a_between = avis(between_idx); end function [B_range, a_values] = fullResonanceCurve(FR_choice, ABKG_choice, selectedRange) [a_bkg, resonanceB, resonancewB] = getResonanceParams(FR_choice, ABKG_choice, selectedRange); B_range = linspace(min(resonanceB)-0.2, max(resonanceB)+0.2, 3000); a_of_B = @(B) arrayfun(@(b) ... a_bkg * prod(1 - resonancewB ./ (b - resonanceB)), B); a_values = a_of_B(B_range); end function [a_bkg, resonanceB, resonancewB] = getResonanceParams(FR_choice, ABKG_choice, selectedRange) if FR_choice == 1 a_bkg_list = [85.5, 93.5, 77.5]; resonanceB = [1.295, 1.306, 2.174, 2.336, 2.591, 2.740, 2.803, ... 2.780, 3.357, 4.949, 5.083, 7.172, 7.204, 7.134, 76.9]; resonancewB = [0.009, 0.010, 0.0005, 0.0005, 0.001, 0.0005, 0.021, ... 0.015, 0.043, 0.0005, 0.130, 0.024, 0.0005, 0.036, 3.1]; else a_bkg_list = [87.2, 95.2, 79.2]; resonanceB = [1.298, 2.802, 3.370, 5.092, 7.154, 2.592, 2.338, 2.177]; resonancewB = [0.018, 0.047, 0.048, 0.145, 0.020, 0.008, 0.001, 0.001]; end a_bkg = a_bkg_list(ABKG_choice); % --- Filter resonanceB and resonancewB if selectedRange is provided --- if nargin >= 3 && ~isempty(selectedRange) minB = min(selectedRange); maxB = max(selectedRange); keep_idx = (resonanceB >= minB) & (resonanceB <= maxB); % Keep only the lowest and highest resonance in the selected range if sum(keep_idx) >= 2 B_sub = resonanceB(keep_idx); w_sub = resonancewB(keep_idx); [~, idx_lo] = min(B_sub); [~, idx_hi] = max(B_sub); resonanceB = [B_sub(idx_lo), B_sub(idx_hi)]; resonancewB = [w_sub(idx_lo), w_sub(idx_hi)]; else error('Selected resonance range does not include at least two resonances.'); end end end function [t, B_ramp, a_check] = generateLinearBRampUsingLUT(FR_choice, ABKG_choice, a_start, a_end, selectedResRange, T, Nt) % Time vector t = linspace(0, T, Nt); % 1) Generate LUT of B and a_s(B) [B_between, a_between] = extractBetweenResonances(FR_choice, ABKG_choice, selectedResRange); % Restrict to physically meaningful range (optional) valid_idx = a_between > 0 & a_between < 150; B_lut = B_between(valid_idx); a_lut = a_between(valid_idx); % 2) Generate linear a_s ramp in time a_target = linspace(a_start, a_end, Nt); % 3) Interpolate B(t) from LUT a_s -> B % Make sure a_lut is sorted ascending [a_lut_sorted, idx_sort] = sort(a_lut); B_lut_sorted = B_lut(idx_sort); B_ramp = interp1(a_lut_sorted, B_lut_sorted, a_target, 'linear', 'extrap'); % 4) Compute resulting a_s(t) for verification [a_bkg, resonanceB, resonancewB] = getResonanceParams(FR_choice, ABKG_choice, selectedResRange); a_of_B = @(B) arrayfun(@(b) ... a_bkg * prod(1 - resonancewB ./ (b - resonanceB)), B); a_check = a_of_B(B_ramp); end function [B, a_s] = getFullFeschbachSpectrum(FR_choice, ABKG_choice) % plotScatteringLength(FR_choice, ABKG_choice) % Plots the Dy-164 scattering length vs B field based on PhysRevX.9.021012 Suppl. Fig. S5 % % Inputs: % FR_choice: 1 = new resonance parameters, 2 = old % ABKG_choice: 1 = lower a_bkg, 2 = mid, 3 = upper % % Example: % plotScatteringLength(1, 1); if nargin < 1, FR_choice = 1; end if nargin < 2, ABKG_choice = 1; end % Choose background scattering length if FR_choice == 1 % New values switch ABKG_choice case 1, a_bkg = 85.5; case 2, a_bkg = 93.5; case 3, a_bkg = 77.5; end % Resonance positions (G) and widths (G) resonanceB = [1.295, 1.306, 2.174, 2.336, 2.591, 2.740, 2.803, 2.780, ... 3.357, 4.949, 5.083, 7.172, 7.204, 7.134, 76.9]; resonancewB = [0.009, 0.010, 0.0005, 0.0005, 0.0010, 0.0005, 0.021, ... 0.015, 0.043, 0.0005, 0.130, 0.024, 0.0005, 0.036, 3.1]; else % Old values switch ABKG_choice case 1, a_bkg = 87.2; case 2, a_bkg = 95.2; case 3, a_bkg = 79.2; end % Resonance positions (G) and widths (G) resonanceB = [1.298, 2.802, 3.370, 5.092, 7.154, 2.592, 2.338, 2.177]; resonancewB = [0.018, 0.047, 0.048, 0.145, 0.020, 0.008, 0.001, 0.001]; end % Magnetic field range for plotting (G) B = linspace(0.5, 8, 2000); % Compute scattering length using product formula a_s = a_bkg * ones(size(B)); for j = 1:length(resonanceB) a_s = a_s .* (1 - resonancewB(j) ./ (B - resonanceB(j))); end end function [t, B_ramp, a_check] = generateLinearBRamp(B_between, a_s_between, a_start, a_end, T, Nt) % Generates a B-field ramp (B_ramp) to produce a linear a_s ramp from a_start to a_end. % Uses precomputed LUT: B_between (magnetic field) and a_s_between (scattering length). % % Inputs: % B_between - Magnetic field values (G) between resonances [vector] % a_s_between - Corresponding scattering lengths (a_0) [vector] % a_start, a_end - Target scattering length range (a_0) % T - Total ramp time (s) % Nt - Number of time steps % % Outputs: % t - Time vector (s) % B_ramp - Generated B-field ramp (G) % a_check - Verified a_s(t) using interpolation (a_0) % --- 1. Time vector --- t = linspace(0, T, Nt); % --- 2. Ensure LUT is sorted and unique --- [a_s_sorted, sort_idx] = unique(a_s_between); % Remove duplicates and sort B_sorted = B_between(sort_idx); % --- 3. Generate target linear a_s ramp --- a_target = linspace(a_start, a_end, Nt); % --- 4. Interpolate B(t) from a_s -> B --- B_ramp = interp1(a_s_sorted, B_sorted, a_target, 'pchip', 'extrap'); % --- 5. Verify a_s(t) by re-interpolating --- a_check = interp1(B_sorted, a_s_sorted, B_ramp, 'pchip'); end