Added functionality to load a initial wavefunction if necessary to bias the solver, modified the conjugate gradient descent algorithm.

This commit is contained in:
Karthik 2025-04-04 21:26:25 +02:00
parent e0991a9a29
commit 6566f53559
4 changed files with 153 additions and 158 deletions

View File

@ -20,7 +20,7 @@ OptionsStruct.UseApproximationForLHY = true;
OptionsStruct.IncludeDDICutOff = true;
OptionsStruct.CutoffType = 'Cylindrical';
OptionsStruct.SimulationMode = 'EnergyMinimization'; % 'ImaginaryTimeEvolution' | 'RealTimeEvolution' | 'EnergyMinimization'
OptionsStruct.GradientDescentMethod = 'HeavyBall'; % 'HeavyBall' | 'NonLinearCGD'
OptionsStruct.GradientDescentMethod = 'NonLinearCGD'; % 'HeavyBall' | 'NonLinearCGD'
OptionsStruct.MaxIterationsForGD = 2E5;
OptionsStruct.TimeStepSize = 1E-4; % in s
OptionsStruct.MinimumTimeStepSize = 2E-10; % in s
@ -50,35 +50,6 @@ sim.Potential = pot.trap(); % + pot.repulsive_chopstick(
Plotter.visualizeTrapPotential(sim.Potential,Params,Transf)
%% - Plot initial wavefunction
Plotter.visualizeWavefunction(psi,Params,Transf)
%% - Plot GS wavefunction
% SaveDirectory = './Results/Data_3D/CompleteLHY/AspectRatio2_8';
% SaveDirectory = './Results/Data_3D/CompleteLHY/AspectRatio3_7';
SaveDirectory = './Results/Data_3D/CompleteLHY/AspectRatio3_8';
JobNumber = 0;
Plotter.visualizeGSWavefunction(SaveDirectory, JobNumber)
%%
% SaveDirectory = './Results/Data_3D/CompleteLHY/BeyondSSD_SSD';
% SaveDirectory = './Results/Data_3D/CompleteLHY/BeyondSSD_Labyrinth';
SaveDirectory = './Results/Data_3D/CompleteLHY/BeyondSSD_Honeycomb';
JobNumber = 0;
Plotter.visualizeGSWavefunction(SaveDirectory, JobNumber)
%% - Plot GS wavefunction
SaveDirectory = './Results/Data_3D/ApproximateLHY/AspectRatio2_8';
% SaveDirectory = './Results/Data_3D/ApproximateLHY/AspectRatio3_7';
% SaveDirectory = './Results/Data_3D/ApproximateLHY/AspectRatio3_8';
% SaveDirectory = './Results/Data_3D/ApproximateLHY/AspectRatio3_9';
JobNumber = 3;
Plotter.visualizeGSWavefunction(SaveDirectory, JobNumber)
%%
% SaveDirectory = './Results/Data_3D/ApproximateLHY/BeyondSSD_SSD';
% SaveDirectory = './Results/Data_3D/ApproximateLHY/BeyondSSD_Labyrinth';
SaveDirectory = './Results/Data_3D/ApproximateLHY/BeyondSSD_Honeycomb';
JobNumber = 0;
Plotter.visualizeGSWavefunction(SaveDirectory, JobNumber)
%%
SaveDirectory = './Results/Data_3D/GradientDescent';
JobNumber = 0;
Plotter.visualizeGSWavefunction(SaveDirectory, JobNumber)
%%
% To reproduce results from the Blair Blakie paper:
@ -438,4 +409,40 @@ JobNumber = 2; % 82
% JobNumber = 3; % 83
Plotter.visualizeGSWavefunction2D(SaveDirectory, JobNumber)
[contrast, period_X, period_Y] = Scripts.analyzeGSWavefunction_in_plane_trap(SaveDirectory, JobNumber);
%% - Plot GS wavefunction
% SaveDirectory = './Results/Data_3D/CompleteLHY/AspectRatio2_8';
% SaveDirectory = './Results/Data_3D/CompleteLHY/AspectRatio3_7';
SaveDirectory = './Results/Data_3D/CompleteLHY/AspectRatio3_8';
JobNumber = 0;
Plotter.visualizeGSWavefunction(SaveDirectory, JobNumber)
%%
% SaveDirectory = './Results/Data_3D/CompleteLHY/BeyondSSD_SSD';
% SaveDirectory = './Results/Data_3D/CompleteLHY/BeyondSSD_Labyrinth';
SaveDirectory = './Results/Data_3D/CompleteLHY/BeyondSSD_Honeycomb';
JobNumber = 0;
Plotter.visualizeGSWavefunction(SaveDirectory, JobNumber)
%% - Plot GS wavefunction
SaveDirectory = './Results/Data_3D/ApproximateLHY/AspectRatio2_8';
% SaveDirectory = './Results/Data_3D/ApproximateLHY/AspectRatio3_7';
% SaveDirectory = './Results/Data_3D/ApproximateLHY/AspectRatio3_8';
% SaveDirectory = './Results/Data_3D/ApproximateLHY/AspectRatio3_9';
JobNumber = 3;
Plotter.visualizeGSWavefunction(SaveDirectory, JobNumber)
%%
% SaveDirectory = './Results/Data_3D/ApproximateLHY/BeyondSSD_SSD';
% SaveDirectory = './Results/Data_3D/ApproximateLHY/BeyondSSD_Labyrinth';
SaveDirectory = './Results/Data_3D/ApproximateLHY/BeyondSSD_Honeycomb';
JobNumber = 0;
Plotter.visualizeGSWavefunction(SaveDirectory, JobNumber)
%%
SaveDirectory = './Results/Data_3D/TiltedDipoles15';
JobNumber = 0;
Plotter.visualizeGSWavefunction(SaveDirectory, JobNumber)
%%
SaveDirectory = './Results/Data_3D/TiltedDipoles30';
JobNumber = 0;
Plotter.visualizeGSWavefunction(SaveDirectory, JobNumber)
%%
SaveDirectory = './Results/Data_3D/TiltedDipoles45';
JobNumber = 1;
Plotter.visualizeGSWavefunction(SaveDirectory, JobNumber)

View File

@ -1,18 +1,20 @@
%% Scaled parameters
theta = 0;
phi = 0;
ScalingFactor = (4/5)^2;
% - SSD: N = 1E5, as = 86ao
OptionsStruct = struct;
OptionsStruct.NumberOfAtoms = sqrt(ScalingFactor) * 5E5;
OptionsStruct.DipolarPolarAngle = deg2rad(0);
OptionsStruct.DipolarAzimuthAngle = 0;
OptionsStruct.ScatteringLength = 75;
OptionsStruct.NumberOfAtoms = 1E5;
OptionsStruct.DipolarPolarAngle = deg2rad(theta);
OptionsStruct.DipolarAzimuthAngle = deg2rad(phi);
OptionsStruct.ScatteringLength = 86;
AspectRatio = 2.8;
HorizontalTrapFrequency = 125/ScalingFactor;
VerticalTrapFrequency = AspectRatio * HorizontalTrapFrequency;
OptionsStruct.TrapFrequencies = [HorizontalTrapFrequency, HorizontalTrapFrequency, VerticalTrapFrequency];
% AspectRatio = 2.0;
% HorizontalTrapFrequency = 125;
% VerticalTrapFrequency = AspectRatio * HorizontalTrapFrequency;
% OptionsStruct.TrapFrequencies = [HorizontalTrapFrequency, HorizontalTrapFrequency, VerticalTrapFrequency];
OptionsStruct.TrapFrequencies = [150, 150, 300];
OptionsStruct.TrapPotentialType = 'Harmonic';
OptionsStruct.NumberOfGridPoints = [128, 128, 64];
@ -21,18 +23,18 @@ OptionsStruct.UseApproximationForLHY = true;
OptionsStruct.IncludeDDICutOff = true;
OptionsStruct.CutoffType = 'Cylindrical';
OptionsStruct.SimulationMode = 'ImaginaryTimeEvolution'; % 'ImaginaryTimeEvolution' | 'RealTimeEvolution'
OptionsStruct.TimeStepSize = 1E-4; % in s
OptionsStruct.MinimumTimeStepSize = 2E-6; % in s
OptionsStruct.TimeStepSize = 1E-3; % in s
OptionsStruct.MinimumTimeStepSize = 2E-6; % in s
OptionsStruct.TimeCutOff = 2E6; % in s
OptionsStruct.EnergyTolerance = 5E-10;
OptionsStruct.ResidualTolerance = 1E-08;
OptionsStruct.NoiseScaleFactor = 0.01;
OptionsStruct.PlotLive = false;
OptionsStruct.JobNumber = 2;
OptionsStruct.RunOnGPU = true;
OptionsStruct.PlotLive = true;
OptionsStruct.JobNumber = 0;
OptionsStruct.RunOnGPU = false;
OptionsStruct.SaveData = true;
OptionsStruct.SaveDirectory = sprintf('./Results/Data_3D/ApproximateLHY/AspectRatio%s', strrep(num2str(AspectRatio), '.', '_'));
OptionsStruct.SaveDirectory = './Results/Data_3D/TiltedDipoles0';
options = Helper.convertstruct2cell(OptionsStruct);
clear OptionsStruct
@ -43,46 +45,3 @@ sim.Potential = pot.trap();
%-% Run Simulation %-%
[Params, Transf, psi, V, VDk] = sim.run();
%{
%% - Gradient Descent Test
OptionsStruct = struct;
OptionsStruct.NumberOfAtoms = 8E4;
OptionsStruct.DipolarPolarAngle = deg2rad(0);
OptionsStruct.DipolarAzimuthAngle = 0;
OptionsStruct.ScatteringLength = 95;
OptionsStruct.TrapFrequencies = [30, 60, 90];
OptionsStruct.TrapPotentialType = 'Harmonic';
OptionsStruct.NumberOfGridPoints = [256, 128, 128];
OptionsStruct.Dimensions = [30, 20, 20];
OptionsStruct.UseApproximationForLHY = true;
OptionsStruct.IncludeDDICutOff = true;
OptionsStruct.CutoffType = 'Cylindrical';
OptionsStruct.SimulationMode = 'EnergyMinimization'; % 'ImaginaryTimeEvolution' | 'RealTimeEvolution' | 'EnergyMinimization'
OptionsStruct.MaxIterationsForGD = 2E4;
% OptionsStruct.TimeStepSize = 1E-4; % in s
% OptionsStruct.MinimumTimeStepSize = 2E-10; % in s
% OptionsStruct.TimeCutOff = 2E6; % in s
% OptionsStruct.EnergyTolerance = 5E-10;
% OptionsStruct.ResidualTolerance = 1E-08;
OptionsStruct.NoiseScaleFactor = 0.01;
OptionsStruct.PlotLive = true;
OptionsStruct.JobNumber = 0;
OptionsStruct.RunOnGPU = false;
OptionsStruct.SaveData = true;
OptionsStruct.SaveDirectory = './Results/Data_3D/GradientDescent';
options = Helper.convertstruct2cell(OptionsStruct);
clear OptionsStruct
sim = Simulator.DipolarGas(options{:});
pot = Simulator.Potentials(options{:});
sim.Potential = pot.trap(); % + pot.repulsive_chopstick();
%-% Run Simulation %-%
[Params, Transf, psi, V, VDk] = sim.run();
%}

View File

@ -41,8 +41,19 @@ function [psi,V,VDk] = initialize(this,Params,Transf,TransfRad)
end
% == Setting up the initial wavefunction == %
psi = this.setupWavefunction(Params,Transf);
WavefunctionFile = fullfile(this.SaveDirectory, 'psi_init.mat');
if isfile(WavefunctionFile)
loadedData = load(WavefunctionFile);
if isgpuarray(loadedData.psi)
psi = gather(loadedData.psi);
else
psi = loadedData.psi;
end
else
psi = this.setupWavefunction(Params,Transf);
end
if this.RunOnGPU
psi = gpuArray(psi);
end

View File

@ -12,6 +12,7 @@ function [psi] = runGradientDescent(this,psi,Params,Transf,VDk,V,Observ)
Observ.res = 1;
psi_old = psi; % Previous psi value (for heavy-ball method)
% Live Plotter
if this.PlotLive
Plotter.plotLive(psi,Params,Transf,Observ)
drawnow
@ -24,7 +25,6 @@ function [psi] = runGradientDescent(this,psi,Params,Transf,VDk,V,Observ)
J = compute_gradient(psi, Params, Transf, VDk, V);
% Calculate chemical potential and norm
% Can also be calculated as --> muchem = this.Calculator.calculateChemicalPotential(psi,Params,Transf,VDk,V);
muchem = sum(real(conj(psi(:)) .* J(:))) / sum(abs(psi(:)).^2);
% Calculate residual and check convergence
@ -83,12 +83,11 @@ function [psi] = runGradientDescent(this,psi,Params,Transf,VDk,V,Observ)
disp('Saving data...');
save(sprintf(strcat(this.SaveDirectory, '/Run_%03i/psi_gs.mat'),Params.njob),'psi','muchem','Observ','Transf','Params','VDk','V');
disp('Save complete!');
case 'NonLinearCGD'
% Define the function handle
f = @(X) this.Calculator.calculateTotalEnergy(X, Params, Transf, VDk, V)/Params.N;
case 'NonLinearCGD'
% Convergence Criteria:
Epsilon = 1E-5;
epsilon = 1E-13;
% Iteration Counter:
i = 1;
@ -98,65 +97,80 @@ function [psi] = runGradientDescent(this,psi,Params,Transf,VDk,V,Observ)
% Initialize the PrematureExitFlag to false
PrematureExitFlag = false;
% Live plotter
if this.PlotLive
Plotter.plotLive(psi,Params,Transf,Observ)
drawnow
end
% Minimization Loop
while true
% Compute gradient
J = compute_gradient(psi, Params, Transf, VDk, V);
% Check stopping criterion (Gradient norm)
if norm(J(:)) < Epsilon
disp('Tolerance reached: Gradient norm is below the specified epsilon.');
J = compute_gradient(psi, Params, Transf, VDk, V);
% Calculate chemical potential
muchem = real(conj(psi(:))' * J(:)) / norm(psi(:))^2;
% Calculate residual
residual = J - (muchem * psi);
% Compute energy difference between the last two saved energy values
if i == 1
energydifference = NaN;
elseif mod(i,100) == 0 && length(Observ.EVec) > 1
energydifference = abs(Observ.EVec(end) - Observ.EVec(end-1));
end
% Convergence check - if energy difference is below set tolerance, then exit
if energydifference <= epsilon
disp('Tolerance reached: Energy difference is below the specified epsilon.');
PrematureExitFlag = true; % Set flag to indicate premature exit
break;
elseif i >= this.MaxIterationsForCGD
elseif i >= this.MaxIterationsForGD % If set maximum number of iterations reached, then exit
disp('Maximum number of iterations for CGD reached.');
PrematureExitFlag = true; % Set flag to indicate premature exit
break;
end
% Initialize search direction if first iteration
if i == 1
S = -J;
else
% Update search direction
S = update_search_direction(S, J, J_old);
d = -residual;
else % Compute beta via Polak-Ribiere and create new direction
residual_new = residual;
beta = compute_beta(residual_new, residual_old);
d = -residual_new + beta * p_old;
end
% Step Size Optimization (Line Search)
alpha = optimize_step_size(f, psi, S, Params, Transf, VDk, V);
residual_old = residual;
p = d - (real(conj(d(:))' * psi(:)) .* psi);
p_old = p;
% Compute optimal theta to generate psi in the direction of minimum in the energy landscape
theta = compute_optimal_theta(p, muchem, psi, Params, Transf, VDk, V);
% Update solution
psi = psi + alpha * S;
psi = (cos(theta).*psi) + (sin(theta).*(p / norm(p(:))));
% Normalize psi
Norm = sum(abs(psi(:)).^2) * Transf.dx * Transf.dy * Transf.dz;
psi = sqrt(Params.N) * psi / sqrt(Norm);
% Store old gradient
J_old = J;
i = i + 1;
muchem = this.Calculator.calculateChemicalPotential(psi,Params,Transf,VDk,V);
if mod(i,500) == 0
Norm = sum(abs(psi(:)).^2) * Transf.dx * Transf.dy * Transf.dz;
psi = sqrt(Params.N) * psi / sqrt(Norm);
i = i + 1;
% Calculate chemical potential with new psi
muchem = real(conj(psi(:))' * J(:)) / norm(psi(:))^2;
if mod(i,100) == 0
% Change in Energy
% Collect Energy value
E = this.Calculator.calculateTotalEnergy(psi,Params,Transf,VDk,V);
E = E/Norm;
Observ.EVec = [Observ.EVec E];
% Chemical potential
% Collect Chemical potential value
Observ.mucVec = [Observ.mucVec muchem];
% Normalized residuals
% Collect Normalized residuals
res = this.Calculator.calculateNormalizedResiduals(psi,Params,Transf,VDk,V,muchem);
Observ.residual = [Observ.residual res];
Observ.res_idx = Observ.res_idx + 1;
% Live plotter
if this.PlotLive
Plotter.plotLive(psi,Params,Transf,Observ)
drawnow
@ -185,7 +199,7 @@ function [psi] = runGradientDescent(this,psi,Params,Transf,VDk,V,Observ)
end
%% Modules
% Numerical Gradient Calculation using the finite differences method
function J = compute_gradient(psi, Params, Transf, VDk, V)
% Operators
@ -210,44 +224,48 @@ function J = compute_gradient(psi, Params, Transf, VDk, V)
H = @(w) HKin(w) + HV(w) + Hint(w) + Hddi(w) + Hqf(w);
J = H(psi);
J = H(psi);
end
% Backtracking Line Search (Step Size Optimization)
function alpha = optimize_step_size(f, X, S, Params, Transf, VDk, V)
alpha = 1; % Initial step size
rho = 0.5; % Step size reduction factor
c = 1E-4; % Armijo condition constant
max_iter = 100; % Max iterations for backtracking
tol = 1E-4; % Tolerance for stopping
function g = compute_g(psi, p, Params, VDk)
rho = real(psi.*conj(p));
grad = compute_gradient(X, Params, Transf, VDk, V); % Compute gradient once
f_X = f(X); % Evaluate f(X) once
% Contact interactions
C = Params.gs*Params.N;
gint = @(w)(C.*rho).*w;
% DDIs
D = Params.gdd*Params.N;
rhotilde = fftn(rho);
Phi = real(ifftn(rhotilde.*VDk));
gaddi = @(w)(D.*Phi).*w;
for k = 1:max_iter
% Evaluate Armijo condition with precomputed f(X) and grad
if f(X + alpha * S) <= f_X + c * alpha * (S(:)' * grad(:))
break;
else
alpha = rho * alpha; % Reduce the step size
end
% Early stopping if step size becomes too small
if alpha < tol
break;
end
end
% Quantum fluctuations
eps_dd = Params.add/Params.as;
Q = (4/(3*pi^2)) * (C^(5/2)/Params.N) * (1 + ((3*eps_dd^2)/2));
gqf = @(w)(((3/2)*Q).*abs(psi).*rho).*w;
gop = @(w) gint(w) + gaddi(w) + gqf(w);
g = gop(psi);
end
% Update Search Direction
function S_new = update_search_direction(S, J_new, J_old)
% (Fletcher-Reeves method)
% beta = (norm(J_new(:))^2) / (norm(J_old(:))^2);
% S_new = -J_new + beta * S;
% Optimal direction via line search
function theta = compute_optimal_theta(p, muchem, psi, Params, Transf, VDk, V)
Hpsi = compute_gradient(psi, Params, Transf, VDk, V);
Hp = compute_gradient(p, Params, Transf, VDk, V);
g = compute_g(psi, p, Params, VDk);
numerator = real(conj(p(:))' * Hpsi(:))/norm(p(:));
denominator = muchem - ((conj(p(:))' * Hp(:)) + real(conj(g(:))' * p(:)))/norm(p(:))^2;
% (Polak-Ribiere method)
beta = max(0, (J_new(:)' * (J_new(:) - J_old(:))) / (norm(J_old(:))^2));
S_new = -J_new + beta * S;
theta = numerator / denominator;
end
% Optimal step size via Polak-Ribiere
function beta = compute_beta(residual_new, residual_old)
beta = max(0, (residual_new(:)' * (residual_new(:) - residual_old(:))) / (norm(residual_old(:))^2));
end