using SparseArrays, LinearAlgebra, Arpack 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=nothing, orthonormalize_threshold=1e-5, CAEC=false, verbose=true, tol=1e-5) training_vecs = Vector{ComplexF64}[] current_E = -10.0 # randomly chosen reference value for c in c_vals verbose && println("Training for c = $c") if ref_eval isa Number current_E = ref_eval ref_eval = nothing elseif !isnothing(ref_eval) current_E = pop!(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 train 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=nothing, verbose=true, tol=1e-5) @assert EC.trained "EC model must be trained using train() before extrapolation" current_E = -10.0 # randomly chosen reference value for c in c_vals verbose && println("Extact solution for c = $c") if ref_eval isa Number current_E = ref_eval ref_eval = nothing elseif !isnothing(ref_eval) current_E = pop!(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