BergEC-jl/EC.jl

110 lines
4.2 KiB
Julia

using SparseArrays, LinearAlgebra, Arpack, Plots
include("helper.jl")
"EC model for a Hamiltonian family H(c) = H0 + c * H1"
mutable struct affine_EC
H0::SparseMatrixCSC{ComplexF64}
H1::SparseMatrixCSC{ComplexF64}
weights::Vector{ComplexF64}
trained::Bool
H0_EC
H1_EC
N_EC
training_E::Vector{ComplexF64}
exact_E::Vector{ComplexF64}
extrapolated_E::Vector{ComplexF64}
affine_EC(H0::SparseMatrixCSC{ComplexF64}, H1::SparseMatrixCSC{ComplexF64}, weights::Vector{ComplexF64}=ones(ComplexF64, size(H0, 1))) = new(H0, H1, weights, false, nothing, nothing, nothing, ComplexF64[], ComplexF64[], ComplexF64[])
end
"Train an EC model for a given range of c values.
If a list is provided for ref_eval, they are used as reference values for picking the closest eigenvalues at each sampling point.
If a single number is provided for ref_eval, it is used as a reference for the first point, and the previous eigenvalue is used as the reference for each successive point.
Set orthonormalize_threshold=0 to skip Gram-Schmidt orthonormalization and use GEVP. Otherwise this value is used as the threshold for dropping redundant vectors."
function train!(EC::affine_EC, c_vals; ref_eval=-10.0, orthonormalize_threshold=1e-5, CAEC=false, verbose=true, tol=1e-5)
training_vecs = Vector{ComplexF64}[]
for c in c_vals
verbose && println("Training for c = $c")
global current_E
if ref_eval isa Number
current_E = ref_eval
ref_eval = nothing
elseif !isnothing(ref_eval)
current_E = popfirst!(ref_eval)
end
H = EC.H0 + c .* EC.H1
evals, evecs = eigs(H, sigma=current_E, maxiter=5000, tol=tol, ritzvec=true, check=1)
current_E = nearest(evals, current_E)
push!(EC.training_E, current_E)
push!(training_vecs, evecs[:, nearestIndex(evals, current_E)])
end
CAEC && append!(training_vecs, conj.(training_vecs))
if orthonormalize_threshold 0
training_vecs = gram_schmidt!(training_vecs, EC.weights, orthonormalize_threshold; verbose=verbose)
end
EC_basis = hcat(training_vecs...)
weights_mat = spdiagm(EC.weights)
EC.H0_EC = transpose(EC_basis) * weights_mat * EC.H0 * EC_basis
EC.H1_EC = transpose(EC_basis) * weights_mat * EC.H1 * EC_basis
if orthonormalize_threshold == 0
EC.N_EC = transpose(EC_basis) * weights_mat * EC_basis
end
EC.trained = true
end
"Extrapolate using a trained EC model for a given range of c values
If a list is provided for ref_eval, they are used as reference values for picking the closest eigenvalues at each point.
If a single number is provided for ref_eval, it is used as a reference for the first point, and the previous eigenvalue is used as the reference for each successive point."
function extrapolate!(EC::affine_EC, c_vals; ref_eval=EC.training_E[end], verbose=true, tol=1e-5)
@assert EC.trained "EC model must be trained using train() before extrapolation"
for c in c_vals
verbose && println("Extact solution for c = $c")
global current_E
if ref_eval isa Number
current_E = ref_eval
ref_eval = nothing
elseif !isnothing(ref_eval)
current_E = popfirst!(ref_eval)
end
H = EC.H0 + c .* EC.H1
evals, _ = eigs(H, sigma=current_E, maxiter=5000, tol=tol, ritzvec=false, check=1)
current_E = nearest(evals, current_E)
push!(EC.exact_E, current_E)
verbose && println("Extrapolating for c = $c")
H_EC = EC.H0_EC + c .* EC.H1_EC
evals = isnothing(EC.N_EC) ? eigvals(H_EC) : eigvals(H_EC, EC.N_EC)
push!(EC.extrapolated_E, nearest(evals, current_E))
end
end
"Export EC data as CSV file"
exportCSV(EC::affine_EC, filename) = exportCSV(filename, (EC.training_E, EC.exact_E, EC.extrapolated_E), ("training", "exact", "extrapolated"))
"Plot EC data and optionally save figure to a file"
function plot(EC::affine_EC, save_fig_filename=nothing)
scatter(real.(EC.training_E), imag.(EC.training_E), label="training")
scatter!(real.(EC.exact_E), imag.(EC.exact_E), label="exact")
scatter!(real.(EC.extrapolated_E), imag.(EC.extrapolated_E), label="extrapolated")
isnothing(save_fig_filename) || savefig(save_fig_filename)
end