From c734c45fa20fd6c6e7c1488eb2d3d9c1defe6a42 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Mon, 31 Mar 2025 03:07:29 +0800 Subject: [PATCH 01/82] I have remove all tab --- Project.toml | 2 + docs/src/man/Block_Lanczos.md | 43 +++++++ src/algorithms.jl | 4 +- src/apply.jl | 2 +- src/eigsolve/lanczos.jl | 103 +++++++++++++++ src/factorizations/lanczos.jl | 234 ++++++++++++++++++++++++++++++++++ test/eigsolve.jl | 83 ++++++++++++ test/runtests.jl | 2 +- 8 files changed, 470 insertions(+), 3 deletions(-) create mode 100644 docs/src/man/Block_Lanczos.md diff --git a/Project.toml b/Project.toml index cb0ba1d9..e241720a 100644 --- a/Project.toml +++ b/Project.toml @@ -8,6 +8,7 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" PackageExtensionCompat = "65ce6f38-6b18-4e1d-a461-8949797d7930" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" [weakdeps] @@ -26,6 +27,7 @@ Logging = "1" PackageExtensionCompat = "1" Printf = "1" Random = "1" +SparseArrays = "1.11.0" Test = "1" TestExtras = "0.2,0.3" VectorInterface = "0.5" diff --git a/docs/src/man/Block_Lanczos.md b/docs/src/man/Block_Lanczos.md new file mode 100644 index 00000000..7a083bef --- /dev/null +++ b/docs/src/man/Block_Lanczos.md @@ -0,0 +1,43 @@ +# General Block Lanczos +$A$ is a Hermitian matrix, +$$A = Q T Q'$$ + +$T$ is a tridiagonal matrix, +$$T = \begin{pmatrix} + \alpha_1 & \beta_1 \\ + \beta_1 & \alpha_2 & \beta_2 \\ + & \beta_2 & \ddots & \ddots \\ + && \ddots & \alpha_{n-1} & \beta_{n-1} \\ + &&& \beta_{n-1} & \alpha_n +\end{pmatrix}$$ + +But $\beta \neq 0$ and thus eigenvalues of $T$ are different from each other. + +To get multiple eigenvalues, we can use block Lanczos. + +$$A = Q T Q', \quad Q = [X_1|..|X_n],\quad \text{size}(X_i) = (p,p)$$ + +$$T = \begin{pmatrix} + M_1 & B_1'\\ + B_1 & M_2 & B_2'\\ + & B_2 & \ddots & \ddots \\ + && \ddots & M_{n-1} & B_{n-1}'\\ + &&& B_{n-1} & M_n +\end{pmatrix}$$ + +It's advantage is to get multiple eigenvalues. But it's disadvantage is that it can cause ghost eigenvalues because of the loss of orthogonality of the Lanczos vectors. + +So my solution is to make sure each $X_i$ we get is orthogonal to $X_{i_1},..,X_1$: + +$$X_i = X_i - Q_{i-1}*(Q_{i-1}'X_i)\\ +Q_{i-1} = [X_1|..|X_{i-1}] +$$ + +I use Modified Schidi's method to force the orthogonality of the basis And use some skills to improve the method and speed up. + +# Difference between Block Lanczos and Lanczos in code + +1. I don't use inner because it maps a couple of matrix to a scalar. +2. I have add test to make sure Block Lanczos can work for map input +3. I add SaprseArray to do test for sparse matrix input + diff --git a/src/algorithms.jl b/src/algorithms.jl index 91f6f214..c678879e 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -108,6 +108,7 @@ Use `Arnoldi` for non-symmetric or non-Hermitian linear operators. See also: `factorize`, `eigsolve`, `exponentiate`, `Arnoldi`, `Orthogonalizer` """ struct Lanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm + block_size::Int orth::O krylovdim::Int maxiter::Int @@ -116,13 +117,14 @@ struct Lanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm verbosity::Int end function Lanczos(; + block_size::Int=1, krylovdim::Int=KrylovDefaults.krylovdim[], maxiter::Int=KrylovDefaults.maxiter[], tol::Real=KrylovDefaults.tol[], orth::Orthogonalizer=KrylovDefaults.orth, eager::Bool=false, verbosity::Int=KrylovDefaults.verbosity[]) - return Lanczos(orth, krylovdim, maxiter, tol, eager, verbosity) + return Lanczos(block_size, orth, krylovdim, maxiter, tol, eager, verbosity) end """ diff --git a/src/apply.jl b/src/apply.jl index 2a7f9160..deb64228 100644 --- a/src/apply.jl +++ b/src/apply.jl @@ -1,4 +1,4 @@ -apply(A::AbstractMatrix, x::AbstractVector) = A * x +apply(A::AbstractMatrix, x::AbstractVecOrMat) = A * x apply(f, x) = f(x) function apply(operator, x, α₀, α₁) diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index b23f98fd..1b1f041b 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -4,6 +4,9 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::Lanczos; maxiter=alg.maxiter, eager=alg.eager, orth=alg.orth)) + if alg.block_size > 1 + return block_lanczos_reortho(A, x₀, howmany, which, alg) + end krylovdim = alg.krylovdim maxiter = alg.maxiter if howmany > krylovdim @@ -150,3 +153,103 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::Lanczos; vectors, ConvergenceInfo(converged, residuals, normresiduals, numiter, numops) end + + +function block_lanczos_reortho(A, x₀::AbstractMatrix{S}, howmany::Int, which::Selector, alg::Lanczos) where S + block_size = alg.block_size + maxiter = alg.maxiter + tol = alg.tol + verbosity = alg.verbosity + n = size(x₀, 1) + max_blocks = n ÷ block_size + + iter = BlockLanczosIterator(A, x₀, block_size, maxiter, alg.orth) + fact = initialize(iter; verbosity = verbosity) + numops = 2 + + converge_check = max(1, 100 ÷ block_size) + + local values, vectors, residuals, normresiduals, num_converged + converged = false + + function _res(fact, A, howmany, tol, block_size) + T = triblockdiag(fact) + + D, U = eigen(Hermitian((T+T')/2)) + + by, rev = eigsort(which) + p = sortperm(D; by = by, rev = rev) + D = D[p] + U = U[:, p] + + howmany_actual = min(howmany, length(D)) + values = D[1:howmany_actual] + + basis_so_far = view(fact.V, :, 1:fact.k*block_size) + vectors = Vector{typeof(fact.V[:, 1])}(undef, howmany_actual) + + for i in 1:howmany_actual + vectors[i] = similar(basis_so_far, size(basis_so_far, 1)) + mul!(vectors[i], basis_so_far, view(U, :, i)) + end + + residuals = Vector{typeof(vectors[1])}(undef, howmany_actual) + normresiduals = Vector{Float64}(undef, howmany_actual) + + for i in 1:howmany_actual + residuals[i] = apply(A, vectors[i]) + axpy!(-values[i], vectors[i], residuals[i]) # residuals[i] -= values[i] * vectors[i] + normresiduals[i] = norm(residuals[i]) + end + + num_converged = count(nr -> nr <= tol, normresiduals) + return values, vectors, residuals, normresiduals, num_converged + end + + for numiter in 2:min(maxiter, max_blocks - 2) + expand!(iter, fact; verbosity = verbosity) + numops += 1 + + if (numiter % converge_check == 0) || (fact.normR < tol) + values, vectors, residuals, normresiduals, num_converged = + _res(fact, A, howmany, tol, block_size) + + if verbosity >= EACHITERATION_LEVEL + @info "Block Lanczos eigsolve in iteration $numiter: $num_converged values converged, normres = $(normres2string(normresiduals[1:min(howmany, length(normresiduals))]))" + end + + # This convergence condition refers to https://www.netlib.org/utk/people/JackDongarra/etemplates/node251.html + if num_converged >= howmany || fact.normR < tol + converged = true + break + end + end + end + + if !converged + values, vectors, residuals, normresiduals, num_converged = + _res(fact, A, howmany, tol, block_size) + end + + if (fact.k * block_size > alg.krylovdim) + @warn "The real Krylov dimension is $(fact.k * block_size), which is larger than the maximum allowed dimension $(alg.krylovdim)." + # In this version we don't shrink the factorization because it might cause issues, different from the ordinary Lanczos. + # Why it happens remains to be investigated. + end + + if (num_converged < howmany) && verbosity >= WARN_LEVEL + @warn """Block Lanczos eigsolve stopped without full convergence after $(fact.k) iterations: + * $num_converged eigenvalues converged + * norm of residuals = $(normres2string(normresiduals)) + * number of operations = $numops""" + elseif verbosity >= STARTSTOP_LEVEL + @info """Block Lanczos eigsolve finished after $(fact.k) iterations: + * $num_converged eigenvalues converged + * norm of residuals = $(normres2string(normresiduals)) + * number of operations = $numops""" + end + + return values, + vectors, + ConvergenceInfo(num_converged, residuals, normresiduals, fact.k, numops) +end \ No newline at end of file diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index 31d9375b..62ba35cc 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -365,3 +365,237 @@ function lanczosrecurrence(operator, V::OrthonormalBasis, β, orth::ModifiedGram end return w, α, β end + + +# block_lanczos.jl + +mutable struct BlockLanczosFactorization{T,S <: Real} <: KrylovFactorization{T,S} + k::Int + block_size::Int + V::Matrix{T} # Lanczos basis matrix + M::Matrix{T} # diagonal block matrices (projections) + B::Matrix{T} # connection matrices between blocks + R::Matrix{T} # residual block + normR::S # norm of residual + + tmp::Matrix{T} +end + +Base.length(F::BlockLanczosFactorization) = F.k +Base.eltype(F::BlockLanczosFactorization) = eltype(typeof(F)) +Base.eltype(::Type{<:BlockLanczosFactorization{T,<:Any}}) where {T} = T + + +function basis(F::BlockLanczosFactorization) + return F.V +end + +residual(F::BlockLanczosFactorization) = F.R +normres(F::BlockLanczosFactorization) = F.normR + +struct BlockLanczosIterator{F,T,O<:Orthogonalizer} <: KrylovIterator{F,T} + operator::F + x₀::Matrix{T} + block_size::Int + maxiter::Int + orth::O + function BlockLanczosIterator{F,T,O}(operator::F, + x₀::Matrix{T}, + block_size::Int, + maxiter::Int, + orth::O) where {F,T,O<:Orthogonalizer} + if block_size < 1 + error("block size must be at least 1") + end + return new{F,T,O}(operator, x₀, block_size, maxiter, orth) + end +end + +function BlockLanczosIterator(operator::F, + x₀::Matrix{T}, + block_size::Int, + maxiter::Int, + orth::O=KrylovDefaults.orth) where {F,T,O<:Orthogonalizer} + return BlockLanczosIterator{F,T,O}(operator, x₀, block_size, maxiter, orth) +end + +function Base.iterate(iter::BlockLanczosIterator) + state = initialize(iter) + return state, state +end + +function Base.iterate(iter::BlockLanczosIterator, state::BlockLanczosFactorization) + nr = normres(state) + if nr < eps(typeof(nr)) + return nothing + else + state = expand!(iter, deepcopy(state)) + return state, state + end +end + +function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.verbosity[]) + maxiter = iter.maxiter + x₀ = iter.x₀ + iszero(x₀) && throw(ArgumentError("initial vector should not have norm zero")) + block_size = iter.block_size + A = iter.operator + T = eltype(x₀) + n = size(x₀, 1) + + V_mat = Matrix{T}(undef, n, block_size * (maxiter + 1)) + M_mat = Matrix{T}(undef, block_size, block_size * (maxiter + 1)) + B_mat = Matrix{T}(undef, block_size, block_size * maxiter) + R_mat = Matrix{T}(undef, n, block_size) + tmp_mat = Matrix{T}(undef, n, block_size) + + x₀_view = view(V_mat, :, 1:block_size) + copyto!(x₀_view, x₀) + if norm(x₀_view'*x₀_view - I) > 1e-12 + x₀_q, _ = qr(x₀_view) + copyto!(x₀_view, Matrix(x₀_q)) + end + + A_x₀ = copy!(tmp_mat, apply(A, x₀_view)) + M₁_view = view(M_mat, :, 1:block_size) + mul!(M₁_view, x₀_view', A_x₀) + M₁_view .= (M₁_view .+ M₁_view') ./ 2 + + residual = mul!(A_x₀, x₀_view, M₁_view, -1.0, 1.0) + + next_basis_view = view(V_mat, :, block_size+1:2*block_size) + next_basis_q, B₁ = qr(residual) + copyto!(next_basis_view, Matrix(next_basis_q)) + + mul!(tmp_mat, x₀_view, x₀_view' * next_basis_view) + next_basis_view .-= tmp_mat + + for j in 1:block_size + col_view = view(next_basis_view, :, j) + col_view ./= sqrt(sum(abs2, col_view)) + end + + # This orthogonalization method refers to "ABLE: AN ADAPTIVE BLOCK LANCZOS METHOD FOR NON-HERMITIAN EIGENVALUE PROBLEMS" + # But it ignores the orthogonality in one block and I add it here. This check is necessary. + if norm(next_basis_view'*next_basis_view - I) > 1e-12 + next_basis_q = qr(next_basis_view).Q + copyto!(next_basis_view, Matrix(next_basis_q)) + end + + A_x₁ = apply(A, next_basis_view) + M₂_view = view(M_mat, :, block_size+1:2*block_size) + mul!(M₂_view, next_basis_view', A_x₁) + M₂_view .= (M₂_view .+ M₂_view') ./ 2 + + B₁_view = view(B_mat, :, 1:block_size) + copyto!(B₁_view, B₁) + + # residual_next = A_x₁ - next_basis_view * M₂_view - x₀_view * B₁_view' + copy!(R_mat, A_x₁) + mul!(R_mat, next_basis_view, M₂_view, -1.0, 1.0) + mul!(R_mat, x₀_view, B₁_view', -1.0, 1.0) + + normR = norm(R_mat) + + if verbosity > EACHITERATION_LEVEL + @info "Block Lanczos initiation at dimension 2: subspace normres = $(normres2string(normR))" + end + + return BlockLanczosFactorization( + 2, + block_size, + V_mat, + M_mat, + B_mat, + R_mat, + normR, + tmp_mat + ) +end + +function expand!(iter::BlockLanczosIterator, state::BlockLanczosFactorization; verbosity::Int=KrylovDefaults.verbosity[]) + k = state.k + p = state.block_size + + current_basis = view(state.V, :, (k-1)*p+1:k*p) + residual = state.R + + next_basis_q = qr(residual).Q + next_basis_view = view(state.V, :, k*p+1:(k+1)*p) + copyto!(next_basis_view, Matrix(next_basis_q)) + + basis_so_far = view(state.V, :, 1:k*p) + + mul!(state.tmp, basis_so_far, basis_so_far' * next_basis_view) + next_basis_view .-= state.tmp + + for j in 1:p + col_view = view(next_basis_view, :, j) + col_view ./= sqrt(sum(abs2, col_view)) + end + + if norm(next_basis_view'*next_basis_view - I) > 1e-12 + next_basis_q = qr(next_basis_view).Q + copyto!(next_basis_view, Matrix(next_basis_q)) + end + + connection_view = view(state.B, :, (k-1)*p+1:k*p) + mul!(connection_view, next_basis_view', residual) + + A_next = apply(iter.operator, next_basis_view) + next_projection_view = view(state.M, :, k*p+1:(k+1)*p) + mul!(next_projection_view, next_basis_view', A_next) + next_projection_view .= (next_projection_view .+ next_projection_view') ./ 2 + + # residual_next = A_next - next_basis_view * next_projection_view - current_basis * connection_view' + copy!(state.R, A_next) + mul!(state.R, next_basis_view, next_projection_view, -1.0, 1.0) + mul!(state.R, current_basis, connection_view', -1.0, 1.0) + + state.normR = norm(state.R) + state.k += 1 + + if verbosity > EACHITERATION_LEVEL + orthogonality_error = 0.0 + for i in 1:state.k*p + for j in i:state.k*p + v_i = view(state.V, :, i) + v_j = view(state.V, :, j) + expected = i == j ? 1.0 : 0.0 + orthogonality_error = max(orthogonality_error, abs(dot(v_i, v_j) - expected)) + end + end + + @info "Block Lanczos expansion to dimension $(state.k): orthogonality error = $orthogonality_error, normres = $(normres2string(state.normR))" + end +end + +# build the block tridiagonal matrix from the BlockLanczosFactorization +function triblockdiag(F::BlockLanczosFactorization) + k = F.k + p = F.block_size + n = k * p + + T = similar(F.M, n, n) + fill!(T, zero(eltype(T))) + + for i in 1:k + idx_i = (i-1)*p+1:i*p + T_block = view(T, idx_i, idx_i) + M_block = view(F.M, :, (i-1)*p+1:i*p) + copyto!(T_block, M_block) + + if i < k + idx_ip1 = i*p+1:(i+1)*p + B_block = view(F.B, :, (i-1)*p+1:i*p) + + T_block_up = view(T, idx_i, idx_ip1) + copyto!(T_block_up, B_block') + + T_block_down = view(T, idx_ip1, idx_i) + copyto!(T_block_down, B_block) + end + end + + return T +end \ No newline at end of file diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 7b442076..f01e4ad0 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -359,3 +359,86 @@ end @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=1)) @test_logs (:info,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=2)) end + +@testset "Block Lanczos - eigsolve for large sparse matrix and map input" begin + + function toric_code_strings(m::Int, n::Int) + li = LinearIndices((m, n)) + bottom(i, j) = li[mod1(i, m), mod1(j, n)] + m * n + right(i, j) = li[mod1(i, m), mod1(j, n)] + xstrings = Vector{Int}[] + zstrings = Vector{Int}[] + for i ∈ 1:m, j ∈ 1:n + # face center + push!(xstrings, [bottom(i, j - 1), right(i, j), bottom(i, j), right(i - 1, j)]) + # cross + push!(zstrings, [right(i, j), bottom(i, j), right(i, j + 1), bottom(i + 1, j)]) + end + return xstrings, zstrings + end + + function pauli_kron(n::Int, ops::Pair{Int, Char}...) + mat = sparse(1.0I, 2^n, 2^n) + for (pos, op) in ops + if op == 'X' + σ = sparse([0 1; 1 0]) + elseif op == 'Y' + σ = sparse([0 -im; im 0]) + elseif op == 'Z' + σ = sparse([1 0; 0 -1]) + elseif op == 'I' + σ = sparse(1.0I, 2, 2) + else + error("Unknown Pauli operator $op") + end + + left = sparse(1.0I, 2^(pos - 1), 2^(pos - 1)) + right = sparse(1.0I, 2^(n - pos), 2^(n - pos)) + mat = kron(left, kron(σ, right)) * mat + end + return mat + end + + # define the function to construct the Hamiltonian matrix + function toric_code_hamiltonian_matrix(m::Int, n::Int) + xstrings, zstrings = toric_code_strings(m, n) + N = 2 * m * n # total number of qubits + + # initialize the Hamiltonian matrix as a zero matrix + H = spzeros(2^N, 2^N) + + # add the X-type operator terms + for xs in xstrings[1:end-1] + ops = [i => 'X' for i in xs] + H += pauli_kron(N, ops...) + end + + for zs in zstrings[1:end-1] + ops = [i => 'Z' for i in zs] + H += pauli_kron(N, ops...) + end + + return H + end + + h_mat = toric_code_hamiltonian_matrix(3, 3) + + Random.seed!(4) + p = 8 # block size + X1 = Matrix(qr(rand(2^18, p)).Q) + get_value_num = 10 + tol = 1e-8 + + # matrix input + D, U, info = eigsolve(-h_mat, X1, get_value_num, :SR, Lanczos(block_size = p, maxiter = 20, tol = tol)) + @show D[1:get_value_num] + @test count(x -> abs(x + 16.0) < 2.0 - tol, D[1:get_value_num]) == 4 + @test count(x -> abs(x + 16.0) < tol, D[1:get_value_num]) == 4 + + # map input + D, U, info = eigsolve(x -> -h_mat * x, X1, get_value_num, :SR, Lanczos(block_size = p, maxiter = 20, tol = tol)) + @show D[1:get_value_num] + @test count(x -> abs(x + 16.0) < 1.9, D[1:get_value_num]) == 4 + @test count(x -> abs(x + 16.0) < 1e-8, D[1:get_value_num]) == 4 + +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 3958786c..6e69f418 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,7 +2,7 @@ using Random Random.seed!(76543210) using Test, TestExtras, Logging -using LinearAlgebra +using LinearAlgebra, SparseArrays using KrylovKit using VectorInterface From adbaf1aa0485463a2fad876fc0a76f0a41a10e50 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Mon, 31 Mar 2025 14:23:48 +0800 Subject: [PATCH 02/82] remove blocklanczos.md, all tabs and polish codes to be more easy to read --- Project.toml | 5 +- docs/src/man/Block_Lanczos.md | 43 --------- src/eigsolve/lanczos.jl | 93 +++++++++---------- src/factorizations/lanczos.jl | 167 +++++++++++++++++----------------- test/eigsolve.jl | 162 ++++++++++++++++----------------- 5 files changed, 212 insertions(+), 258 deletions(-) delete mode 100644 docs/src/man/Block_Lanczos.md diff --git a/Project.toml b/Project.toml index e241720a..c17275ab 100644 --- a/Project.toml +++ b/Project.toml @@ -8,7 +8,6 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" PackageExtensionCompat = "65ce6f38-6b18-4e1d-a461-8949797d7930" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" [weakdeps] @@ -27,7 +26,6 @@ Logging = "1" PackageExtensionCompat = "1" Printf = "1" Random = "1" -SparseArrays = "1.11.0" Test = "1" TestExtras = "0.2,0.3" VectorInterface = "0.5" @@ -43,6 +41,7 @@ Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" +SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" [targets] -test = ["Test", "Aqua", "Logging", "TestExtras", "ChainRulesTestUtils", "ChainRulesCore", "FiniteDifferences", "Zygote"] +test = ["Test", "Aqua", "Logging", "TestExtras", "ChainRulesTestUtils", "ChainRulesCore", "FiniteDifferences", "Zygote", "SparseArrays"] diff --git a/docs/src/man/Block_Lanczos.md b/docs/src/man/Block_Lanczos.md deleted file mode 100644 index 7a083bef..00000000 --- a/docs/src/man/Block_Lanczos.md +++ /dev/null @@ -1,43 +0,0 @@ -# General Block Lanczos -$A$ is a Hermitian matrix, -$$A = Q T Q'$$ - -$T$ is a tridiagonal matrix, -$$T = \begin{pmatrix} - \alpha_1 & \beta_1 \\ - \beta_1 & \alpha_2 & \beta_2 \\ - & \beta_2 & \ddots & \ddots \\ - && \ddots & \alpha_{n-1} & \beta_{n-1} \\ - &&& \beta_{n-1} & \alpha_n -\end{pmatrix}$$ - -But $\beta \neq 0$ and thus eigenvalues of $T$ are different from each other. - -To get multiple eigenvalues, we can use block Lanczos. - -$$A = Q T Q', \quad Q = [X_1|..|X_n],\quad \text{size}(X_i) = (p,p)$$ - -$$T = \begin{pmatrix} - M_1 & B_1'\\ - B_1 & M_2 & B_2'\\ - & B_2 & \ddots & \ddots \\ - && \ddots & M_{n-1} & B_{n-1}'\\ - &&& B_{n-1} & M_n -\end{pmatrix}$$ - -It's advantage is to get multiple eigenvalues. But it's disadvantage is that it can cause ghost eigenvalues because of the loss of orthogonality of the Lanczos vectors. - -So my solution is to make sure each $X_i$ we get is orthogonal to $X_{i_1},..,X_1$: - -$$X_i = X_i - Q_{i-1}*(Q_{i-1}'X_i)\\ -Q_{i-1} = [X_1|..|X_{i-1}] -$$ - -I use Modified Schidi's method to force the orthogonality of the basis And use some skills to improve the method and speed up. - -# Difference between Block Lanczos and Lanczos in code - -1. I don't use inner because it maps a couple of matrix to a scalar. -2. I have add test to make sure Block Lanczos can work for map input -3. I add SaprseArray to do test for sparse matrix input - diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index 1b1f041b..1d84e695 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -154,8 +154,8 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::Lanczos; ConvergenceInfo(converged, residuals, normresiduals, numiter, numops) end - -function block_lanczos_reortho(A, x₀::AbstractMatrix{S}, howmany::Int, which::Selector, alg::Lanczos) where S +function block_lanczos_reortho(A, x₀::AbstractMatrix{S}, howmany::Int, which::Selector, + alg::Lanczos) where {S} block_size = alg.block_size maxiter = alg.maxiter tol = alg.tol @@ -164,61 +164,62 @@ function block_lanczos_reortho(A, x₀::AbstractMatrix{S}, howmany::Int, which:: max_blocks = n ÷ block_size iter = BlockLanczosIterator(A, x₀, block_size, maxiter, alg.orth) - fact = initialize(iter; verbosity = verbosity) - numops = 2 + fact = initialize(iter; verbosity=verbosity) + numops = 2 # how many times we apply A - converge_check = max(1, 100 ÷ block_size) + converge_check = max(1, 100 ÷ block_size) # Periodic check for convergence local values, vectors, residuals, normresiduals, num_converged converged = false - function _res(fact, A, howmany, tol, block_size) - T = triblockdiag(fact) - - D, U = eigen(Hermitian((T+T')/2)) - - by, rev = eigsort(which) - p = sortperm(D; by = by, rev = rev) - D = D[p] - U = U[:, p] - - howmany_actual = min(howmany, length(D)) - values = D[1:howmany_actual] - - basis_so_far = view(fact.V, :, 1:fact.k*block_size) - vectors = Vector{typeof(fact.V[:, 1])}(undef, howmany_actual) - - for i in 1:howmany_actual - vectors[i] = similar(basis_so_far, size(basis_so_far, 1)) - mul!(vectors[i], basis_so_far, view(U, :, i)) - end - - residuals = Vector{typeof(vectors[1])}(undef, howmany_actual) - normresiduals = Vector{Float64}(undef, howmany_actual) - - for i in 1:howmany_actual - residuals[i] = apply(A, vectors[i]) - axpy!(-values[i], vectors[i], residuals[i]) # residuals[i] -= values[i] * vectors[i] - normresiduals[i] = norm(residuals[i]) - end - - num_converged = count(nr -> nr <= tol, normresiduals) - return values, vectors, residuals, normresiduals, num_converged - end + function _res(fact, A, howmany, tol, block_size) + T = triblockdiag(fact) + D, U = eigen(Hermitian((T + T') / 2)) + + by, rev = eigsort(which) + p = sortperm(D; by=by, rev=rev) + D = D[p] + U = U[:, p] + + howmany_actual = min(howmany, length(D)) + values = D[1:howmany_actual] + + basis_so_far = view(fact.V, :, 1:(fact.k * block_size)) + vectors = Vector{typeof(fact.V[:, 1])}(undef, howmany_actual) + + for i in 1:howmany_actual + vectors[i] = similar(basis_so_far, size(basis_so_far, 1)) + mul!(vectors[i], basis_so_far, view(U, :, i)) + end + + residuals = Vector{typeof(vectors[1])}(undef, howmany_actual) + normresiduals = Vector{Float64}(undef, howmany_actual) + + for i in 1:howmany_actual + residuals[i] = apply(A, vectors[i]) + axpy!(-values[i], vectors[i], residuals[i]) # residuals[i] -= values[i] * vectors[i] + normresiduals[i] = norm(residuals[i]) + end + + num_converged = count(nr -> nr <= tol, normresiduals) + return values, vectors, residuals, normresiduals, num_converged + end for numiter in 2:min(maxiter, max_blocks - 2) - expand!(iter, fact; verbosity = verbosity) + expand!(iter, fact; verbosity=verbosity) numops += 1 + # Although norm(Rk) is not our convergence condition, when norm(Rk) is to small, we may lose too much precision and orthogonalization. if (numiter % converge_check == 0) || (fact.normR < tol) - values, vectors, residuals, normresiduals, num_converged = - _res(fact, A, howmany, tol, block_size) - + values, vectors, residuals, normresiduals, num_converged = _res(fact, A, + howmany, tol, + block_size) + if verbosity >= EACHITERATION_LEVEL @info "Block Lanczos eigsolve in iteration $numiter: $num_converged values converged, normres = $(normres2string(normresiduals[1:min(howmany, length(normresiduals))]))" end - # This convergence condition refers to https://www.netlib.org/utk/people/JackDongarra/etemplates/node251.html + # This convergence condition refers to https://www.netlib.org/utk/people/JackDongarra/etemplates/node251.html if num_converged >= howmany || fact.normR < tol converged = true break @@ -227,8 +228,8 @@ function block_lanczos_reortho(A, x₀::AbstractMatrix{S}, howmany::Int, which:: end if !converged - values, vectors, residuals, normresiduals, num_converged = - _res(fact, A, howmany, tol, block_size) + values, vectors, residuals, normresiduals, num_converged = _res(fact, A, howmany, + tol, block_size) end if (fact.k * block_size > alg.krylovdim) @@ -252,4 +253,4 @@ function block_lanczos_reortho(A, x₀::AbstractMatrix{S}, howmany::Int, which:: return values, vectors, ConvergenceInfo(num_converged, residuals, normresiduals, fact.k, numops) -end \ No newline at end of file +end diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index 62ba35cc..e47d5ea6 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -366,26 +366,24 @@ function lanczosrecurrence(operator, V::OrthonormalBasis, β, orth::ModifiedGram return w, α, β end - # block_lanczos.jl -mutable struct BlockLanczosFactorization{T,S <: Real} <: KrylovFactorization{T,S} - k::Int - block_size::Int +mutable struct BlockLanczosFactorization{T,S<:Real} <: KrylovFactorization{T,S} + k::Int + block_size::Int V::Matrix{T} # Lanczos basis matrix M::Matrix{T} # diagonal block matrices (projections) B::Matrix{T} # connection matrices between blocks R::Matrix{T} # residual block normR::S # norm of residual - - tmp::Matrix{T} + + tmp::Matrix{T} # temporary matrix. Used to decrease allocations. end Base.length(F::BlockLanczosFactorization) = F.k Base.eltype(F::BlockLanczosFactorization) = eltype(typeof(F)) Base.eltype(::Type{<:BlockLanczosFactorization{T,<:Any}}) where {T} = T - function basis(F::BlockLanczosFactorization) return F.V end @@ -400,10 +398,10 @@ struct BlockLanczosIterator{F,T,O<:Orthogonalizer} <: KrylovIterator{F,T} maxiter::Int orth::O function BlockLanczosIterator{F,T,O}(operator::F, - x₀::Matrix{T}, - block_size::Int, - maxiter::Int, - orth::O) where {F,T,O<:Orthogonalizer} + x₀::Matrix{T}, + block_size::Int, + maxiter::Int, + orth::O) where {F,T,O<:Orthogonalizer} if block_size < 1 error("block size must be at least 1") end @@ -412,10 +410,10 @@ struct BlockLanczosIterator{F,T,O<:Orthogonalizer} <: KrylovIterator{F,T} end function BlockLanczosIterator(operator::F, - x₀::Matrix{T}, - block_size::Int, - maxiter::Int, - orth::O=KrylovDefaults.orth) where {F,T,O<:Orthogonalizer} + x₀::Matrix{T}, + block_size::Int, + maxiter::Int, + orth::O=KrylovDefaults.orth) where {F,T,O<:Orthogonalizer} return BlockLanczosIterator{F,T,O}(operator, x₀, block_size, maxiter, orth) end @@ -442,34 +440,33 @@ function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.ve A = iter.operator T = eltype(x₀) n = size(x₀, 1) - + V_mat = Matrix{T}(undef, n, block_size * (maxiter + 1)) M_mat = Matrix{T}(undef, block_size, block_size * (maxiter + 1)) B_mat = Matrix{T}(undef, block_size, block_size * maxiter) R_mat = Matrix{T}(undef, n, block_size) tmp_mat = Matrix{T}(undef, n, block_size) - + x₀_view = view(V_mat, :, 1:block_size) copyto!(x₀_view, x₀) - if norm(x₀_view'*x₀_view - I) > 1e-12 - x₀_q, _ = qr(x₀_view) - copyto!(x₀_view, Matrix(x₀_q)) - end - + + # The the key step different from current papers. We have to detect orthogonality in one block. + norm(x₀_view' * x₀_view - I) > 1e-12 ? copyto!(x₀_view, Matrix(qr(x₀_view).Q)) : () + A_x₀ = copy!(tmp_mat, apply(A, x₀_view)) M₁_view = view(M_mat, :, 1:block_size) mul!(M₁_view, x₀_view', A_x₀) M₁_view .= (M₁_view .+ M₁_view') ./ 2 - + residual = mul!(A_x₀, x₀_view, M₁_view, -1.0, 1.0) - - next_basis_view = view(V_mat, :, block_size+1:2*block_size) + + next_basis_view = view(V_mat, :, (block_size + 1):(2 * block_size)) next_basis_q, B₁ = qr(residual) copyto!(next_basis_view, Matrix(next_basis_q)) - + mul!(tmp_mat, x₀_view, x₀_view' * next_basis_view) next_basis_view .-= tmp_mat - + for j in 1:block_size col_view = view(next_basis_view, :, j) col_view ./= sqrt(sum(abs2, col_view)) @@ -477,95 +474,95 @@ function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.ve # This orthogonalization method refers to "ABLE: AN ADAPTIVE BLOCK LANCZOS METHOD FOR NON-HERMITIAN EIGENVALUE PROBLEMS" # But it ignores the orthogonality in one block and I add it here. This check is necessary. - if norm(next_basis_view'*next_basis_view - I) > 1e-12 + if norm(next_basis_view' * next_basis_view - I) > 1e-12 next_basis_q = qr(next_basis_view).Q copyto!(next_basis_view, Matrix(next_basis_q)) end - + A_x₁ = apply(A, next_basis_view) - M₂_view = view(M_mat, :, block_size+1:2*block_size) + M₂_view = view(M_mat, :, (block_size + 1):(2 * block_size)) mul!(M₂_view, next_basis_view', A_x₁) M₂_view .= (M₂_view .+ M₂_view') ./ 2 - + B₁_view = view(B_mat, :, 1:block_size) copyto!(B₁_view, B₁) - + # residual_next = A_x₁ - next_basis_view * M₂_view - x₀_view * B₁_view' copy!(R_mat, A_x₁) mul!(R_mat, next_basis_view, M₂_view, -1.0, 1.0) mul!(R_mat, x₀_view, B₁_view', -1.0, 1.0) - + normR = norm(R_mat) - + if verbosity > EACHITERATION_LEVEL @info "Block Lanczos initiation at dimension 2: subspace normres = $(normres2string(normR))" end - return BlockLanczosFactorization( - 2, - block_size, - V_mat, - M_mat, - B_mat, - R_mat, - normR, - tmp_mat - ) + return BlockLanczosFactorization(2, + block_size, + V_mat, + M_mat, + B_mat, + R_mat, + normR, + tmp_mat) end -function expand!(iter::BlockLanczosIterator, state::BlockLanczosFactorization; verbosity::Int=KrylovDefaults.verbosity[]) +function expand!(iter::BlockLanczosIterator, state::BlockLanczosFactorization; + verbosity::Int=KrylovDefaults.verbosity[]) k = state.k p = state.block_size - - current_basis = view(state.V, :, (k-1)*p+1:k*p) + residual = state.R - - next_basis_q = qr(residual).Q - next_basis_view = view(state.V, :, k*p+1:(k+1)*p) - copyto!(next_basis_view, Matrix(next_basis_q)) - - basis_so_far = view(state.V, :, 1:k*p) - + + next_basis_view = view(state.V, :, (k * p + 1):((k + 1) * p)) + copyto!(next_basis_view, Matrix(qr(residual).Q)) + + basis_so_far = view(state.V, :, 1:(k * p)) + current_basis = view(state.V, :, ((k - 1) * p + 1):(k * p)) + + # Modify Schmidt orthogonalization to blocks. mul!(state.tmp, basis_so_far, basis_so_far' * next_basis_view) next_basis_view .-= state.tmp - + for j in 1:p col_view = view(next_basis_view, :, j) col_view ./= sqrt(sum(abs2, col_view)) end - - if norm(next_basis_view'*next_basis_view - I) > 1e-12 - next_basis_q = qr(next_basis_view).Q - copyto!(next_basis_view, Matrix(next_basis_q)) - end - - connection_view = view(state.B, :, (k-1)*p+1:k*p) + + norm(next_basis_view' * next_basis_view - I) > 1e-12 ? + copyto!(next_basis_view, Matrix(qr(next_basis_view).Q)) : () + + connection_view = view(state.B, :, ((k - 1) * p + 1):(k * p)) mul!(connection_view, next_basis_view', residual) - - A_next = apply(iter.operator, next_basis_view) - next_projection_view = view(state.M, :, k*p+1:(k+1)*p) - mul!(next_projection_view, next_basis_view', A_next) + + A_xₖ = apply(iter.operator, next_basis_view) + next_projection_view = view(state.M, :, (k * p + 1):((k + 1) * p)) + mul!(next_projection_view, next_basis_view', A_xₖ) + + #to make sure the matrix is hermitian, I don't use axpby! next_projection_view .= (next_projection_view .+ next_projection_view') ./ 2 - + # residual_next = A_next - next_basis_view * next_projection_view - current_basis * connection_view' - copy!(state.R, A_next) + copy!(state.R, A_xₖ) mul!(state.R, next_basis_view, next_projection_view, -1.0, 1.0) mul!(state.R, current_basis, connection_view', -1.0, 1.0) - + state.normR = norm(state.R) state.k += 1 - + if verbosity > EACHITERATION_LEVEL orthogonality_error = 0.0 - for i in 1:state.k*p - for j in i:state.k*p + for i in 1:(state.k * p) + for j in i:(state.k * p) v_i = view(state.V, :, i) v_j = view(state.V, :, j) expected = i == j ? 1.0 : 0.0 - orthogonality_error = max(orthogonality_error, abs(dot(v_i, v_j) - expected)) + orthogonality_error = max(orthogonality_error, + abs(dot(v_i, v_j) - expected)) end end - + @info "Block Lanczos expansion to dimension $(state.k): orthogonality error = $orthogonality_error, normres = $(normres2string(state.normR))" end end @@ -575,27 +572,27 @@ function triblockdiag(F::BlockLanczosFactorization) k = F.k p = F.block_size n = k * p - + T = similar(F.M, n, n) fill!(T, zero(eltype(T))) - + for i in 1:k - idx_i = (i-1)*p+1:i*p + idx_i = ((i - 1) * p + 1):(i * p) T_block = view(T, idx_i, idx_i) - M_block = view(F.M, :, (i-1)*p+1:i*p) + M_block = view(F.M, :, ((i - 1) * p + 1):(i * p)) copyto!(T_block, M_block) - + if i < k - idx_ip1 = i*p+1:(i+1)*p - B_block = view(F.B, :, (i-1)*p+1:i*p) - + idx_ip1 = (i * p + 1):((i + 1) * p) + B_block = view(F.B, :, ((i - 1) * p + 1):(i * p)) + T_block_up = view(T, idx_i, idx_ip1) copyto!(T_block_up, B_block') - + T_block_down = view(T, idx_ip1, idx_i) copyto!(T_block_down, B_block) end end - + return T -end \ No newline at end of file +end diff --git a/test/eigsolve.jl b/test/eigsolve.jl index f01e4ad0..49cdeb6a 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -359,86 +359,86 @@ end @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=1)) @test_logs (:info,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=2)) end - +using Test @testset "Block Lanczos - eigsolve for large sparse matrix and map input" begin + function toric_code_strings(m::Int, n::Int) + li = LinearIndices((m, n)) + bottom(i, j) = li[mod1(i, m), mod1(j, n)] + m * n + right(i, j) = li[mod1(i, m), mod1(j, n)] + xstrings = Vector{Int}[] + zstrings = Vector{Int}[] + for i in 1:m, j in 1:n + # face center + push!(xstrings, [bottom(i, j - 1), right(i, j), bottom(i, j), right(i - 1, j)]) + # cross + push!(zstrings, [right(i, j), bottom(i, j), right(i, j + 1), bottom(i + 1, j)]) + end + return xstrings, zstrings + end + + function pauli_kron(n::Int, ops::Pair{Int,Char}...) + mat = sparse(1.0I, 2^n, 2^n) + for (pos, op) in ops + if op == 'X' + σ = sparse([0 1; 1 0]) + elseif op == 'Y' + σ = sparse([0 -im; im 0]) + elseif op == 'Z' + σ = sparse([1 0; 0 -1]) + elseif op == 'I' + σ = sparse(1.0I, 2, 2) + else + error("Unknown Pauli operator $op") + end + + left = sparse(1.0I, 2^(pos - 1), 2^(pos - 1)) + right = sparse(1.0I, 2^(n - pos), 2^(n - pos)) + mat = kron(left, kron(σ, right)) * mat + end + return mat + end + + # define the function to construct the Hamiltonian matrix + function toric_code_hamiltonian_matrix(m::Int, n::Int) + xstrings, zstrings = toric_code_strings(m, n) + N = 2 * m * n # total number of qubits - function toric_code_strings(m::Int, n::Int) - li = LinearIndices((m, n)) - bottom(i, j) = li[mod1(i, m), mod1(j, n)] + m * n - right(i, j) = li[mod1(i, m), mod1(j, n)] - xstrings = Vector{Int}[] - zstrings = Vector{Int}[] - for i ∈ 1:m, j ∈ 1:n - # face center - push!(xstrings, [bottom(i, j - 1), right(i, j), bottom(i, j), right(i - 1, j)]) - # cross - push!(zstrings, [right(i, j), bottom(i, j), right(i, j + 1), bottom(i + 1, j)]) - end - return xstrings, zstrings - end - - function pauli_kron(n::Int, ops::Pair{Int, Char}...) - mat = sparse(1.0I, 2^n, 2^n) - for (pos, op) in ops - if op == 'X' - σ = sparse([0 1; 1 0]) - elseif op == 'Y' - σ = sparse([0 -im; im 0]) - elseif op == 'Z' - σ = sparse([1 0; 0 -1]) - elseif op == 'I' - σ = sparse(1.0I, 2, 2) - else - error("Unknown Pauli operator $op") - end - - left = sparse(1.0I, 2^(pos - 1), 2^(pos - 1)) - right = sparse(1.0I, 2^(n - pos), 2^(n - pos)) - mat = kron(left, kron(σ, right)) * mat - end - return mat - end - - # define the function to construct the Hamiltonian matrix - function toric_code_hamiltonian_matrix(m::Int, n::Int) - xstrings, zstrings = toric_code_strings(m, n) - N = 2 * m * n # total number of qubits - - # initialize the Hamiltonian matrix as a zero matrix - H = spzeros(2^N, 2^N) - - # add the X-type operator terms - for xs in xstrings[1:end-1] - ops = [i => 'X' for i in xs] - H += pauli_kron(N, ops...) - end - - for zs in zstrings[1:end-1] - ops = [i => 'Z' for i in zs] - H += pauli_kron(N, ops...) - end - - return H - end - - h_mat = toric_code_hamiltonian_matrix(3, 3) - - Random.seed!(4) - p = 8 # block size - X1 = Matrix(qr(rand(2^18, p)).Q) - get_value_num = 10 - tol = 1e-8 - - # matrix input - D, U, info = eigsolve(-h_mat, X1, get_value_num, :SR, Lanczos(block_size = p, maxiter = 20, tol = tol)) - @show D[1:get_value_num] - @test count(x -> abs(x + 16.0) < 2.0 - tol, D[1:get_value_num]) == 4 - @test count(x -> abs(x + 16.0) < tol, D[1:get_value_num]) == 4 - - # map input - D, U, info = eigsolve(x -> -h_mat * x, X1, get_value_num, :SR, Lanczos(block_size = p, maxiter = 20, tol = tol)) - @show D[1:get_value_num] - @test count(x -> abs(x + 16.0) < 1.9, D[1:get_value_num]) == 4 - @test count(x -> abs(x + 16.0) < 1e-8, D[1:get_value_num]) == 4 - -end \ No newline at end of file + # initialize the Hamiltonian matrix as a zero matrix + H = spzeros(2^N, 2^N) + + # add the X-type operator terms + for xs in xstrings[1:(end - 1)] + ops = [i => 'X' for i in xs] + H += pauli_kron(N, ops...) + end + + for zs in zstrings[1:(end - 1)] + ops = [i => 'Z' for i in zs] + H += pauli_kron(N, ops...) + end + + return H + end + + h_mat = toric_code_hamiltonian_matrix(3, 3) + + Random.seed!(4) + p = 8 # block size + X1 = Matrix(qr(rand(2^18, p)).Q) + get_value_num = 10 + tol = 1e-8 + + # matrix input + D, U, info = eigsolve(-h_mat, X1, get_value_num, :SR, + Lanczos(; block_size=p, maxiter=20, tol=tol)) + @show D[1:get_value_num] + @test count(x -> abs(x + 16.0) < 2.0 - tol, D[1:get_value_num]) == 4 + @test count(x -> abs(x + 16.0) < tol, D[1:get_value_num]) == 4 + + # map input + D, U, info = eigsolve(x -> -h_mat * x, X1, get_value_num, :SR, + Lanczos(; block_size=p, maxiter=20, tol=tol)) + @show D[1:get_value_num] + @test count(x -> abs(x + 16.0) < 1.9, D[1:get_value_num]) == 4 + @test count(x -> abs(x + 16.0) < 1e-8, D[1:get_value_num]) == 4 +end From 7ad95837f9f4dd3455b4631c2d1f76580dcde6f4 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Mon, 31 Mar 2025 16:47:59 +0800 Subject: [PATCH 03/82] review --- src/eigsolve/lanczos.jl | 77 ++++++++++++++++++----------------- src/factorizations/lanczos.jl | 42 ++++++++++--------- test/eigsolve.jl | 4 +- test/factorize.jl | 2 + 4 files changed, 67 insertions(+), 58 deletions(-) diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index 1d84e695..8067e7b6 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -154,8 +154,9 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::Lanczos; ConvergenceInfo(converged, residuals, normresiduals, numiter, numops) end -function block_lanczos_reortho(A, x₀::AbstractMatrix{S}, howmany::Int, which::Selector, +function block_lanczos_reortho(A, x₀, howmany::Int, which::Selector, alg::Lanczos) where {S} + @assert alg.block_size == blocksize(x₀) block_size = alg.block_size maxiter = alg.maxiter tol = alg.tol @@ -172,48 +173,15 @@ function block_lanczos_reortho(A, x₀::AbstractMatrix{S}, howmany::Int, which:: local values, vectors, residuals, normresiduals, num_converged converged = false - function _res(fact, A, howmany, tol, block_size) - T = triblockdiag(fact) - D, U = eigen(Hermitian((T + T') / 2)) - - by, rev = eigsort(which) - p = sortperm(D; by=by, rev=rev) - D = D[p] - U = U[:, p] - - howmany_actual = min(howmany, length(D)) - values = D[1:howmany_actual] - - basis_so_far = view(fact.V, :, 1:(fact.k * block_size)) - vectors = Vector{typeof(fact.V[:, 1])}(undef, howmany_actual) - - for i in 1:howmany_actual - vectors[i] = similar(basis_so_far, size(basis_so_far, 1)) - mul!(vectors[i], basis_so_far, view(U, :, i)) - end - - residuals = Vector{typeof(vectors[1])}(undef, howmany_actual) - normresiduals = Vector{Float64}(undef, howmany_actual) - - for i in 1:howmany_actual - residuals[i] = apply(A, vectors[i]) - axpy!(-values[i], vectors[i], residuals[i]) # residuals[i] -= values[i] * vectors[i] - normresiduals[i] = norm(residuals[i]) - end - - num_converged = count(nr -> nr <= tol, normresiduals) - return values, vectors, residuals, normresiduals, num_converged - end - for numiter in 2:min(maxiter, max_blocks - 2) expand!(iter, fact; verbosity=verbosity) numops += 1 # Although norm(Rk) is not our convergence condition, when norm(Rk) is to small, we may lose too much precision and orthogonalization. if (numiter % converge_check == 0) || (fact.normR < tol) - values, vectors, residuals, normresiduals, num_converged = _res(fact, A, + values, vectors, residuals, normresiduals, num_converged = _residual(fact, A, howmany, tol, - block_size) + block_size, which) if verbosity >= EACHITERATION_LEVEL @info "Block Lanczos eigsolve in iteration $numiter: $num_converged values converged, normres = $(normres2string(normresiduals[1:min(howmany, length(normresiduals))]))" @@ -228,8 +196,8 @@ function block_lanczos_reortho(A, x₀::AbstractMatrix{S}, howmany::Int, which:: end if !converged - values, vectors, residuals, normresiduals, num_converged = _res(fact, A, howmany, - tol, block_size) + values, vectors, residuals, normresiduals, num_converged = _residual(fact, A, howmany, + tol, block_size, which) end if (fact.k * block_size > alg.krylovdim) @@ -254,3 +222,36 @@ function block_lanczos_reortho(A, x₀::AbstractMatrix{S}, howmany::Int, which:: vectors, ConvergenceInfo(num_converged, residuals, normresiduals, fact.k, numops) end + +function _residual(fact, A, howmany, tol, block_size, which) + T = triblockdiag(fact) + D, U = eigen(Hermitian((T + T') / 2)) # TODO: use keyword sortby + + by, rev = eigsort(which) + p = sortperm(D; by=by, rev=rev) + D = D[p] + U = U[:, p] + + howmany_actual = min(howmany, length(D)) + values = D[1:howmany_actual] + + basis_so_far = view(fact.V, :, 1:(fact.k * block_size)) + vectors = Vector{typeof(fact.V[:, 1])}(undef, howmany_actual) + + for i in 1:howmany_actual + vectors[i] = similar(basis_so_far, size(basis_so_far, 1)) + mul!(vectors[i], basis_so_far, view(U, :, i)) + end + + residuals = Vector{typeof(vectors[1])}(undef, howmany_actual) + normresiduals = Vector{Float64}(undef, howmany_actual) + + for i in 1:howmany_actual + residuals[i] = apply(A, vectors[i]) + axpy!(-values[i], vectors[i], residuals[i]) # residuals[i] -= values[i] * vectors[i] + normresiduals[i] = norm(residuals[i]) + end + + num_converged = count(nr -> nr <= tol, normresiduals) + return values, vectors, residuals, normresiduals, num_converged +end \ No newline at end of file diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index e47d5ea6..6d4ef170 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -366,18 +366,16 @@ function lanczosrecurrence(operator, V::OrthonormalBasis, β, orth::ModifiedGram return w, α, β end -# block_lanczos.jl - mutable struct BlockLanczosFactorization{T,S<:Real} <: KrylovFactorization{T,S} k::Int - block_size::Int - V::Matrix{T} # Lanczos basis matrix - M::Matrix{T} # diagonal block matrices (projections) - B::Matrix{T} # connection matrices between blocks - R::Matrix{T} # residual block + const block_size::Int + const V::Matrix{T} # Lanczos basis matrix + const M::Matrix{T} # diagonal block matrices (projections) + const B::Matrix{T} # connection matrices between blocks + const R::Matrix{T} # residual block normR::S # norm of residual - tmp::Matrix{T} # temporary matrix. Used to decrease allocations. + const tmp::Matrix{T} # temporary matrix. Used to decrease allocations. end Base.length(F::BlockLanczosFactorization) = F.k @@ -417,6 +415,7 @@ function BlockLanczosIterator(operator::F, return BlockLanczosIterator{F,T,O}(operator, x₀, block_size, maxiter, orth) end +# TODO: delete function Base.iterate(iter::BlockLanczosIterator) state = initialize(iter) return state, state @@ -432,6 +431,8 @@ function Base.iterate(iter::BlockLanczosIterator, state::BlockLanczosFactorizati end end +is_orthonormal(x) = isapprox(x' * x, I, atol=1e-12) + function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.verbosity[]) maxiter = iter.maxiter x₀ = iter.x₀ @@ -441,17 +442,17 @@ function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.ve T = eltype(x₀) n = size(x₀, 1) - V_mat = Matrix{T}(undef, n, block_size * (maxiter + 1)) - M_mat = Matrix{T}(undef, block_size, block_size * (maxiter + 1)) - B_mat = Matrix{T}(undef, block_size, block_size * maxiter) - R_mat = Matrix{T}(undef, n, block_size) - tmp_mat = Matrix{T}(undef, n, block_size) + V_mat = similar(iter.x₀, n, block_size * (maxiter + 1)) + M_mat = similar(iter.x₀, block_size, block_size * (maxiter + 1)) + B_mat = similar(iter.x₀, block_size, block_size * maxiter) + R_mat = similar(iter.x₀, n, block_size) + tmp_mat = similar(iter.x₀, n, block_size) x₀_view = view(V_mat, :, 1:block_size) copyto!(x₀_view, x₀) # The the key step different from current papers. We have to detect orthogonality in one block. - norm(x₀_view' * x₀_view - I) > 1e-12 ? copyto!(x₀_view, Matrix(qr(x₀_view).Q)) : () + is_orthonormal(x₀_view) && copyto!(x₀_view, Matrix(qr(x₀_view).Q)) A_x₀ = copy!(tmp_mat, apply(A, x₀_view)) M₁_view = view(M_mat, :, 1:block_size) @@ -460,6 +461,7 @@ function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.ve residual = mul!(A_x₀, x₀_view, M₁_view, -1.0, 1.0) + # get the next basis vector next_basis_view = view(V_mat, :, (block_size + 1):(2 * block_size)) next_basis_q, B₁ = qr(residual) copyto!(next_basis_view, Matrix(next_basis_q)) @@ -467,18 +469,19 @@ function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.ve mul!(tmp_mat, x₀_view, x₀_view' * next_basis_view) next_basis_view .-= tmp_mat + # normalize the columns of next_basis_view for j in 1:block_size - col_view = view(next_basis_view, :, j) - col_view ./= sqrt(sum(abs2, col_view)) + normalize!(view(next_basis_view, :, j)) end # This orthogonalization method refers to "ABLE: AN ADAPTIVE BLOCK LANCZOS METHOD FOR NON-HERMITIAN EIGENVALUE PROBLEMS" # But it ignores the orthogonality in one block and I add it here. This check is necessary. - if norm(next_basis_view' * next_basis_view - I) > 1e-12 + if !is_orthonormal(next_basis_view) next_basis_q = qr(next_basis_view).Q copyto!(next_basis_view, Matrix(next_basis_q)) end + # DRY principle!! A_x₁ = apply(A, next_basis_view) M₂_view = view(M_mat, :, (block_size + 1):(2 * block_size)) mul!(M₂_view, next_basis_view', A_x₁) @@ -530,8 +533,9 @@ function expand!(iter::BlockLanczosIterator, state::BlockLanczosFactorization; col_view ./= sqrt(sum(abs2, col_view)) end - norm(next_basis_view' * next_basis_view - I) > 1e-12 ? - copyto!(next_basis_view, Matrix(qr(next_basis_view).Q)) : () + if !is_orthonormal(next_basis_view) + copyto!(next_basis_view, Matrix(qr(next_basis_view).Q)) + end connection_view = view(state.B, :, ((k - 1) * p + 1):(k * p)) mul!(connection_view, next_basis_view', residual) diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 49cdeb6a..b8ab60bb 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -359,7 +359,7 @@ end @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=1)) @test_logs (:info,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=2)) end -using Test + @testset "Block Lanczos - eigsolve for large sparse matrix and map input" begin function toric_code_strings(m::Int, n::Int) li = LinearIndices((m, n)) @@ -442,3 +442,5 @@ using Test @test count(x -> abs(x + 16.0) < 1.9, D[1:get_value_num]) == 4 @test count(x -> abs(x + 16.0) < 1e-8, D[1:get_value_num]) == 4 end + +# TODO: test complex numbers \ No newline at end of file diff --git a/test/factorize.jl b/test/factorize.jl index 078857b3..49103399 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -296,3 +296,5 @@ end end end end + +# TODO: add more tests for block lanczos \ No newline at end of file From 055c1548077701caaf7c35131cf97f8b8e6b2e22 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Fri, 4 Apr 2025 00:04:52 +0800 Subject: [PATCH 04/82] inner product version --- func_list.jl | 7 + src/eigsolve/lanczos.jl | 29 ++-- src/factorizations/krylov.jl | 4 + src/factorizations/lanczos.jl | 261 ++++++++++++++++++---------------- src/innerproductvec.jl | 139 ++++++++++++------ src/orthonormal.jl | 54 +++++++ test/eigsolve.jl | 111 ++++++++------- test/orthonormal.jl | 147 +++++++++++++++++++ test/runtests.jl | 3 + 9 files changed, 518 insertions(+), 237 deletions(-) create mode 100644 func_list.jl create mode 100644 test/orthonormal.jl diff --git a/func_list.jl b/func_list.jl new file mode 100644 index 00000000..f970c07d --- /dev/null +++ b/func_list.jl @@ -0,0 +1,7 @@ +# mul!(A,B,M,a,b) = A .= (B .* M) * a .+ b * A + +# axpy!(a,x,y) = y .= a .* x .+ y + +# axpby!(a,x,b,y) = y .= a .* x .+ b .* y + + diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index 8067e7b6..eed29585 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -155,8 +155,8 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::Lanczos; end function block_lanczos_reortho(A, x₀, howmany::Int, which::Selector, - alg::Lanczos) where {S} - @assert alg.block_size == blocksize(x₀) + alg::Lanczos) + @assert alg.block_size == size(x₀, 2) block_size = alg.block_size maxiter = alg.maxiter tol = alg.tol @@ -206,6 +206,9 @@ function block_lanczos_reortho(A, x₀, howmany::Int, which::Selector, # Why it happens remains to be investigated. end + basis_view = fact.V.basis[1:fact.k*block_size] + @show norm(blockinner(basis_view, basis_view)-I) + if (num_converged < howmany) && verbosity >= WARN_LEVEL @warn """Block Lanczos eigsolve stopped without full convergence after $(fact.k) iterations: * $num_converged eigenvalues converged @@ -223,28 +226,24 @@ function block_lanczos_reortho(A, x₀, howmany::Int, which::Selector, ConvergenceInfo(num_converged, residuals, normresiduals, fact.k, numops) end -function _residual(fact, A, howmany, tol, block_size, which) - T = triblockdiag(fact) - D, U = eigen(Hermitian((T + T') / 2)) # TODO: use keyword sortby - +function _residual(fact::BlockLanczosFactorization, A, howmany::Int, tol::Real, block_size::Int, which::Selector) + TDB = triblockdiag(fact) + D, U = eigen(Hermitian((TDB + TDB') / 2)) # TODO: use keyword sortby by, rev = eigsort(which) p = sortperm(D; by=by, rev=rev) D = D[p] U = U[:, p] + V = fact.V.basis + T = eltype(V) howmany_actual = min(howmany, length(D)) values = D[1:howmany_actual] - basis_so_far = view(fact.V, :, 1:(fact.k * block_size)) - vectors = Vector{typeof(fact.V[:, 1])}(undef, howmany_actual) - - for i in 1:howmany_actual - vectors[i] = similar(basis_so_far, size(basis_so_far, 1)) - mul!(vectors[i], basis_so_far, view(U, :, i)) - end + basis_sofar_view = view(V, 1:(fact.k * block_size)) + vectors = mul_vm(basis_sofar_view, U) - residuals = Vector{typeof(vectors[1])}(undef, howmany_actual) - normresiduals = Vector{Float64}(undef, howmany_actual) + residuals = Vector{T}(undef, howmany_actual) + normresiduals = Vector{InnerNumType(T)}(undef, howmany_actual) for i in 1:howmany_actual residuals[i] = apply(A, vectors[i]) diff --git a/src/factorizations/krylov.jl b/src/factorizations/krylov.jl index 18be6885..c110c076 100644 --- a/src/factorizations/krylov.jl +++ b/src/factorizations/krylov.jl @@ -29,6 +29,10 @@ factorizations of a given linear map and a starting vector. """ abstract type KrylovFactorization{T,S} end +# T is the type of the elements in the inner produnct space. +# S is the type of number field of the space. and SR is the real part of S. +abstract type BlockKrylovFactorization{T,S,SR} end + """ abstract type KrylovIterator{F,T} diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index 6d4ef170..d83807ff 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -366,37 +366,45 @@ function lanczosrecurrence(operator, V::OrthonormalBasis, β, orth::ModifiedGram return w, α, β end -mutable struct BlockLanczosFactorization{T,S<:Real} <: KrylovFactorization{T,S} + +# block lanczos +# TODO: do I need block_size field? +mutable struct BlockLanczosFactorization{T,S,SR<:Real} <: BlockKrylovFactorization{T,S,SR} k::Int const block_size::Int - const V::Matrix{T} # Lanczos basis matrix - const M::Matrix{T} # diagonal block matrices (projections) - const B::Matrix{T} # connection matrices between blocks - const R::Matrix{T} # residual block - normR::S # norm of residual - - const tmp::Matrix{T} # temporary matrix. Used to decrease allocations. + const V::OrthonormalBasis{T} # Block Lanczos Basis + const M::AbstractMatrix{S} # diagonal block matrices + const B::AbstractMatrix{S} # connection matrices between blocks + const R::OrthonormalBasis{T} # residual block + normR::SR end Base.length(F::BlockLanczosFactorization) = F.k +#= where use eltype? Base.eltype(F::BlockLanczosFactorization) = eltype(typeof(F)) Base.eltype(::Type{<:BlockLanczosFactorization{T,<:Any}}) where {T} = T +=# -function basis(F::BlockLanczosFactorization) - return F.V -end - +basis(F::BlockLanczosFactorization) = F.V +# blocksize(F::BlockLanczosFactorization) = F.block_size residual(F::BlockLanczosFactorization) = F.R normres(F::BlockLanczosFactorization) = F.normR + +# Now our orthogonalizer is only ModifiedGramSchmidt2. +# Dimension of Krylov subspace in BlockLanczosIterator is usually much bigger than lanczos. +# So ClassicalGramSchmidt and ModifiedGramSchmidt1 is numerically unstable. +# I don't add IR orthogonalizer because I find it sometimes unstable and I am studying it. +# Householder reorthogonalization is theoretically stable and saves memory, but the algorithm I implemented is not stable. +# In the future, I will add IR and Householder orthogonalizer. struct BlockLanczosIterator{F,T,O<:Orthogonalizer} <: KrylovIterator{F,T} operator::F - x₀::Matrix{T} + x₀::Vector{T} block_size::Int maxiter::Int orth::O function BlockLanczosIterator{F,T,O}(operator::F, - x₀::Matrix{T}, + x₀::Vector{T}, block_size::Int, maxiter::Int, orth::O) where {F,T,O<:Orthogonalizer} @@ -407,15 +415,31 @@ struct BlockLanczosIterator{F,T,O<:Orthogonalizer} <: KrylovIterator{F,T} end end +# Is there a better way to get the type of the output of inner product? What about global variable? +InnerNumType(::Type{T}) where T = eltype(T) +InnerNumType(v::T) where T = InnerNumType(T) function BlockLanczosIterator(operator::F, - x₀::Matrix{T}, + x₀::AbstractVector{T}, block_size::Int, maxiter::Int, - orth::O=KrylovDefaults.orth) where {F,T,O<:Orthogonalizer} + orth::O=ModifiedGramSchmidt2()) where {F,T,O<:Orthogonalizer} + if orth != ModifiedGramSchmidt2() + @error "BlockLanczosIterator only supports ModifiedGramSchmidt2 orthogonalizer" + end + T == InnerProductVec && (InnerNumType(::Type{T}) = typeof(inner(x₀[1], x₀[1]))) return BlockLanczosIterator{F,T,O}(operator, x₀, block_size, maxiter, orth) end +function BlockLanczosIterator(operator::F, + x₀::AbstractMatrix, + block_size::Int, + maxiter::Int, + orth::O=ModifiedGramSchmidt2()) where {F,O<:Orthogonalizer} + return BlockLanczosIterator{F,typeof(x₀[:,1]),O}(operator, [x₀[:,i] for i in 1:size(x₀,2)], block_size, maxiter, orth) +end + -# TODO: delete +#= +I save these 2 functions for future use. But I have not tested them. function Base.iterate(iter::BlockLanczosIterator) state = initialize(iter) return state, state @@ -430,143 +454,132 @@ function Base.iterate(iter::BlockLanczosIterator, state::BlockLanczosFactorizati return state, state end end +=# -is_orthonormal(x) = isapprox(x' * x, I, atol=1e-12) +# It's used to make use of the orthogonality in one block. +is_block_orthonormal(x::AbstractVector{T}) where T = isapprox(blockinner(x,x), I, atol=1e4*eps(InnerNumType(T))) + +function compute_residual!(R::AbstractVector{T}, A_X::AbstractVector{T}, X::AbstractVector{T}, M::AbstractMatrix, X_prev::AbstractVector, B_prev::AbstractMatrix) where T + @inbounds for j in 1:length(R) + copyto!(R[j], A_X[j]) + for i in 1:length(X) + axpy!(-M[i,j], X[i], R[j]) + end + for i in 1:length(X_prev) + axpy!(-B_prev[i,j], X_prev[i], R[j]) + end + end + return R +end + +function ortho_basis!(basis_new::AbstractVector{T}, basis_sofar::AbstractVector{T}) where T + mul!(basis_new, basis_sofar, - blockinner(basis_sofar, basis_new)) + return basis_new +end function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.verbosity[]) + x₀_vec = iter.x₀ + iszero(norm(x₀_vec)) && throw(ArgumentError("initial vector should not have norm zero")) + maxiter = iter.maxiter - x₀ = iter.x₀ - iszero(x₀) && throw(ArgumentError("initial vector should not have norm zero")) block_size = iter.block_size A = iter.operator - T = eltype(x₀) - n = size(x₀, 1) - - V_mat = similar(iter.x₀, n, block_size * (maxiter + 1)) - M_mat = similar(iter.x₀, block_size, block_size * (maxiter + 1)) - B_mat = similar(iter.x₀, block_size, block_size * maxiter) - R_mat = similar(iter.x₀, n, block_size) - tmp_mat = similar(iter.x₀, n, block_size) - - x₀_view = view(V_mat, :, 1:block_size) - copyto!(x₀_view, x₀) - - # The the key step different from current papers. We have to detect orthogonality in one block. - is_orthonormal(x₀_view) && copyto!(x₀_view, Matrix(qr(x₀_view).Q)) - - A_x₀ = copy!(tmp_mat, apply(A, x₀_view)) - M₁_view = view(M_mat, :, 1:block_size) - mul!(M₁_view, x₀_view', A_x₀) + T = eltype(x₀_vec) + S = InnerNumType(T) + + V_basis = similar(x₀_vec, block_size * (maxiter + 1)) + M = zeros(S, block_size, block_size * (maxiter + 1)) + B = zeros(S, block_size, block_size * maxiter) + R = [similar(x₀_vec[i]) for i in 1:block_size] + + x₀_basis_view = view(V_basis, 1:block_size) + copyto!(x₀_basis_view, x₀_vec) + + !is_block_orthonormal(x₀_basis_view) && abstract_qr!(x₀_basis_view) + + Ax₀ = [apply(A, x) for x in x₀_basis_view] + M₁_view = view(M, :, 1:block_size) + inner!(M₁_view, x₀_basis_view, Ax₀) + M₁_view .= (M₁_view .+ M₁_view') ./ 2 - residual = mul!(A_x₀, x₀_view, M₁_view, -1.0, 1.0) - - # get the next basis vector - next_basis_view = view(V_mat, :, (block_size + 1):(2 * block_size)) - next_basis_q, B₁ = qr(residual) - copyto!(next_basis_view, Matrix(next_basis_q)) - - mul!(tmp_mat, x₀_view, x₀_view' * next_basis_view) - next_basis_view .-= tmp_mat + # We have to write it as a form of matrix multiplication. Get R1 + residual = mul!(Ax₀, x₀_basis_view, - M₁_view) - # normalize the columns of next_basis_view - for j in 1:block_size - normalize!(view(next_basis_view, :, j)) - end + # QR decomposition of residual to get the next basis. Get X2 and B1 + next_basis_view = view(V_basis, block_size+1:2*block_size) + copyto!(next_basis_view, residual) + abstract_qr!(next_basis_view) - # This orthogonalization method refers to "ABLE: AN ADAPTIVE BLOCK LANCZOS METHOD FOR NON-HERMITIAN EIGENVALUE PROBLEMS" - # But it ignores the orthogonality in one block and I add it here. This check is necessary. - if !is_orthonormal(next_basis_view) - next_basis_q = qr(next_basis_view).Q - copyto!(next_basis_view, Matrix(next_basis_q)) + # Orthogonalization processing + mul!(next_basis_view, x₀_basis_view, - blockinner(x₀_basis_view, next_basis_view)) + @inbounds @simd for i in 1:length(next_basis_view) + normalize!(next_basis_view[i]) end - - # DRY principle!! - A_x₁ = apply(A, next_basis_view) - M₂_view = view(M_mat, :, (block_size + 1):(2 * block_size)) - mul!(M₂_view, next_basis_view', A_x₁) + !is_block_orthonormal(next_basis_view) && abstract_qr!(next_basis_view) + B₁_view = view(B, :, 1:block_size) + inner!(B₁_view, next_basis_view, residual) + + # Calculate the next block + Ax₁ = [apply(A, x) for x in next_basis_view] + M₂_view = view(M, :, block_size+1:2*block_size) + inner!(M₂_view, next_basis_view, Ax₁) + M₂_view .= (M₂_view .+ M₂_view') ./ 2 - B₁_view = view(B_mat, :, 1:block_size) - copyto!(B₁_view, B₁) - - # residual_next = A_x₁ - next_basis_view * M₂_view - x₀_view * B₁_view' - copy!(R_mat, A_x₁) - mul!(R_mat, next_basis_view, M₂_view, -1.0, 1.0) - mul!(R_mat, x₀_view, B₁_view', -1.0, 1.0) + # Calculate the new residual. Get R2 + compute_residual!(R, Ax₁, next_basis_view, M₂_view, x₀_basis_view, B₁_view) + ortho_basis!(R, view(V_basis, 1:2*block_size)) - normR = norm(R_mat) + normR = norm(R) if verbosity > EACHITERATION_LEVEL @info "Block Lanczos initiation at dimension 2: subspace normres = $(normres2string(normR))" end return BlockLanczosFactorization(2, - block_size, - V_mat, - M_mat, - B_mat, - R_mat, - normR, - tmp_mat) + block_size, + OrthonormalBasis(V_basis), + M, + B, + OrthonormalBasis(R), + normR) end function expand!(iter::BlockLanczosIterator, state::BlockLanczosFactorization; verbosity::Int=KrylovDefaults.verbosity[]) k = state.k + println("iteration=$k") p = state.block_size - - residual = state.R - - next_basis_view = view(state.V, :, (k * p + 1):((k + 1) * p)) - copyto!(next_basis_view, Matrix(qr(residual).Q)) - - basis_so_far = view(state.V, :, 1:(k * p)) - current_basis = view(state.V, :, ((k - 1) * p + 1):(k * p)) - - # Modify Schmidt orthogonalization to blocks. - mul!(state.tmp, basis_so_far, basis_so_far' * next_basis_view) - next_basis_view .-= state.tmp - - for j in 1:p - col_view = view(next_basis_view, :, j) - col_view ./= sqrt(sum(abs2, col_view)) - end - - if !is_orthonormal(next_basis_view) - copyto!(next_basis_view, Matrix(qr(next_basis_view).Q)) - end - - connection_view = view(state.B, :, ((k - 1) * p + 1):(k * p)) - mul!(connection_view, next_basis_view', residual) - - A_xₖ = apply(iter.operator, next_basis_view) - next_projection_view = view(state.M, :, (k * p + 1):((k + 1) * p)) - mul!(next_projection_view, next_basis_view', A_xₖ) - - #to make sure the matrix is hermitian, I don't use axpby! - next_projection_view .= (next_projection_view .+ next_projection_view') ./ 2 - - # residual_next = A_next - next_basis_view * next_projection_view - current_basis * connection_view' - copy!(state.R, A_xₖ) - mul!(state.R, next_basis_view, next_projection_view, -1.0, 1.0) - mul!(state.R, current_basis, connection_view', -1.0, 1.0) - + Rₖ = state.R.basis + + # Get the current residual as the initial value of the new basis. Get Xnext + Xnext_view = view(state.V.basis, k*p+1:(k+1)*p) + copyto!(Xnext_view, Rₖ) + _, Bₖ = abstract_qr!(Xnext_view) + + # Calculate the connection matrix + Bₖ_view = view(state.B, :, (k-1)*p+1:k*p) + copyto!(Bₖ_view, Bₖ) + # Apply the operator and calculate the M. Get Mnext + Axₖnext = [apply(iter.operator, x) for x in Xnext_view] + Mnext_view = view(state.M, :, k*p+1:(k+1)*p) + inner!(Mnext_view, Xnext_view, Axₖnext) + + # Make sure Mnext is hermitian + Mnext_view .= (Mnext_view .+ Mnext_view') ./ 2 + + # Calculate the new residual. Get Rnext + compute_residual!(Rₖ, Axₖnext, Xnext_view, Mnext_view, state.V.basis[((k-1)*p+1):k*p], Bₖ_view) + ortho_basis!(Rₖ, view(state.V.basis, 1:(k+1)*p)) state.normR = norm(state.R) state.k += 1 if verbosity > EACHITERATION_LEVEL - orthogonality_error = 0.0 - for i in 1:(state.k * p) - for j in i:(state.k * p) - v_i = view(state.V, :, i) - v_j = view(state.V, :, j) - expected = i == j ? 1.0 : 0.0 - orthogonality_error = max(orthogonality_error, - abs(dot(v_i, v_j) - expected)) - end - end - + orthogonality_error = maximum(abs(inner(u,v)-(i==j)) + for (i,u) in enumerate(state.V.basis[1:state.k*p]), + (j,v) in enumerate(state.V.basis[1:state.k*p])) + @info "Block Lanczos expansion to dimension $(state.k): orthogonality error = $orthogonality_error, normres = $(normres2string(state.normR))" end end diff --git a/src/innerproductvec.jl b/src/innerproductvec.jl index f4f6951f..94a1de76 100644 --- a/src/innerproductvec.jl +++ b/src/innerproductvec.jl @@ -1,5 +1,5 @@ """ - v = InnerProductVec(vec, dotf) + v = InnerProductVec(vec, dotf) Create a new vector `v` from an existing vector `dotf` with a modified inner product given by `inner`. The vector `vec`, which can be any type (not necessarily `Vector`) that supports @@ -16,112 +16,161 @@ In a (linear) map applied to `v`, the original vector can be obtained as `v.vec` as `v[]`. """ struct InnerProductVec{F,T} - vec::T - dotf::F + vec::T + dotf::F end Base.:-(v::InnerProductVec) = InnerProductVec(-v.vec, v.dotf) function Base.:+(v::InnerProductVec{F}, w::InnerProductVec{F}) where {F} - return InnerProductVec(v.vec + w.vec, v.dotf) + return InnerProductVec(v.vec + w.vec, v.dotf) end +function Base.sum(v::AbstractVector{InnerProductVec}) + @assert length(v) > 0 + res = copy(v[1]) + @inbounds for i in 2:length(v) + res += v[i] + end + return res +end + function Base.:-(v::InnerProductVec{F}, w::InnerProductVec{F}) where {F} - return InnerProductVec(v.vec - w.vec, v.dotf) + return InnerProductVec(v.vec - w.vec, v.dotf) end + Base.:*(v::InnerProductVec, a::Number) = InnerProductVec(v.vec * a, v.dotf) Base.:*(a::Number, v::InnerProductVec) = InnerProductVec(a * v.vec, v.dotf) +Base.:*(v::AbstractVector{InnerProductVec}, a::Number) = [v[i] * a for i in 1:length(v)] +Base.:*(a::Number, v::AbstractVector{InnerProductVec}) = [a * v[i] for i in 1:length(v)] +function Base.:*(v::AbstractVector, V::AbstractVector) + @assert length(v) == length(V) + return sum(v .* V) +end +# It's in fact a kind of Linear map +mul_vm(v::AbstractVector, A::AbstractMatrix) = [v * A[:, i] for i in 1:size(A, 2)] + Base.:/(v::InnerProductVec, a::Number) = InnerProductVec(v.vec / a, v.dotf) +Base.:/(v::AbstractVector{InnerProductVec}, a::Number) = [v[i] / a for i in 1:length(v)] Base.:\(a::Number, v::InnerProductVec) = InnerProductVec(a \ v.vec, v.dotf) +# I can't understand well why the last function exists so I don't implement it's block version. function Base.similar(v::InnerProductVec, ::Type{T}=scalartype(v)) where {T} - return InnerProductVec(similar(v.vec), v.dotf) + return InnerProductVec(similar(v.vec), v.dotf) end Base.getindex(v::InnerProductVec) = v.vec function Base.copy!(w::InnerProductVec{F}, v::InnerProductVec{F}) where {F} - copy!(w.vec, v.vec) - return w + copy!(w.vec, v.vec) + return w end function LinearAlgebra.mul!(w::InnerProductVec{F}, - a::Number, - v::InnerProductVec{F}) where {F} - mul!(w.vec, a, v.vec) - return w + a::Number, + v::InnerProductVec{F}) where {F} + mul!(w.vec, a, v.vec) + return w end function LinearAlgebra.mul!(w::InnerProductVec{F}, - v::InnerProductVec{F}, - a::Number) where {F} - mul!(w.vec, v.vec, a) - return w + v::InnerProductVec{F}, + a::Number) where {F} + mul!(w.vec, v.vec, a) + return w end function LinearAlgebra.rmul!(v::InnerProductVec, a::Number) rmul!(v.vec, a) return v end +function LinearAlgebra.mul!(A::AbstractVector{T},B::AbstractVector{T},M::AbstractMatrix) where T + @inbounds for i in eachindex(A) + @simd for j in eachindex(B) + A[i] += B[j] * M[j,i] + end + end + return A +end function LinearAlgebra.axpy!(a::Number, - v::InnerProductVec{F}, - w::InnerProductVec{F}) where {F} - axpy!(a, v.vec, w.vec) - return w + v::InnerProductVec{F}, + w::InnerProductVec{F}) where {F} + axpy!(a, v.vec, w.vec) + return w end function LinearAlgebra.axpby!(a::Number, - v::InnerProductVec{F}, - b, - w::InnerProductVec{F}) where {F} - axpby!(a, v.vec, b, w.vec) - return w + v::InnerProductVec{F}, + b, + w::InnerProductVec{F}) where {F} + axpby!(a, v.vec, b, w.vec) + return w end function LinearAlgebra.dot(v::InnerProductVec{F}, w::InnerProductVec{F}) where {F} - return v.dotf(v.vec, w.vec) + return v.dotf(v.vec, w.vec) end VectorInterface.scalartype(::Type{<:InnerProductVec{F,T}}) where {F,T} = scalartype(T) function VectorInterface.zerovector(v::InnerProductVec, T::Type{<:Number}) - return InnerProductVec(zerovector(v.vec, T), v.dotf) + return InnerProductVec(zerovector(v.vec, T), v.dotf) end function VectorInterface.scale(v::InnerProductVec, a::Number) - return InnerProductVec(scale(v.vec, a), v.dotf) + return InnerProductVec(scale(v.vec, a), v.dotf) end function VectorInterface.scale!!(v::InnerProductVec, a::Number) - return InnerProductVec(scale!!(v.vec, a), v.dotf) + return InnerProductVec(scale!!(v.vec, a), v.dotf) end function VectorInterface.scale!(v::InnerProductVec, a::Number) - scale!(v.vec, a) - return v + scale!(v.vec, a) + return v end function VectorInterface.scale!!(w::InnerProductVec{F}, v::InnerProductVec{F}, - a::Number) where {F} - return InnerProductVec(scale!!(w.vec, v.vec, a), w.dotf) + a::Number) where {F} + return InnerProductVec(scale!!(w.vec, v.vec, a), w.dotf) end function VectorInterface.scale!(w::InnerProductVec{F}, v::InnerProductVec{F}, - a::Number) where {F} - scale!(w.vec, v.vec, a) - return w + a::Number) where {F} + scale!(w.vec, v.vec, a) + return w end function VectorInterface.add(v::InnerProductVec{F}, w::InnerProductVec{F}, a::Number, - b::Number) where {F} - return InnerProductVec(add(v.vec, w.vec, a, b), v.dotf) + b::Number) where {F} + return InnerProductVec(add(v.vec, w.vec, a, b), v.dotf) end function VectorInterface.add!!(v::InnerProductVec{F}, w::InnerProductVec{F}, a::Number, - b::Number) where {F} - return InnerProductVec(add!!(v.vec, w.vec, a, b), v.dotf) + b::Number) where {F} + return InnerProductVec(add!!(v.vec, w.vec, a, b), v.dotf) end function VectorInterface.add!(v::InnerProductVec{F}, w::InnerProductVec{F}, a::Number, - b::Number) where {F} - add!(v.vec, w.vec, a, b) - return v + b::Number) where {F} + add!(v.vec, w.vec, a, b) + return v end function VectorInterface.inner(v::InnerProductVec{F}, w::InnerProductVec{F}) where {F} - return v.dotf(v.vec, w.vec) + return v.dotf(v.vec, w.vec) +end + +# TODO: Add tests +function inner!(M::AbstractMatrix, + v::AbstractVector, + w::AbstractVector) + @assert size(M) == (length(v), length(w)) "Matrix dimensions must match" + @inbounds for j in eachindex(w), i in eachindex(v) + M[i, j] = inner(v[i], w[j]) + end + return M +end +function blockinner(x::AbstractVector{T}, y::AbstractVector{T}) where T + m, n = length(x), length(y) + res = Matrix{InnerNumType(T)}(undef, m, n) + @inbounds for i in 1:m + @simd for j in 1:n + res[i, j] = inner(x[i], y[j]) + end + end + return res end - VectorInterface.norm(v::InnerProductVec) = sqrt(real(inner(v, v))) diff --git a/src/orthonormal.jl b/src/orthonormal.jl index a0970f60..0d81fd28 100644 --- a/src/orthonormal.jl +++ b/src/orthonormal.jl @@ -33,6 +33,8 @@ Base.IteratorSize(::Type{<:OrthonormalBasis}) = Base.HasLength() Base.IteratorEltype(::Type{<:OrthonormalBasis}) = Base.HasEltype() Base.length(b::OrthonormalBasis) = length(b.basis) +# blocksize(b::OrthonormalBasis) does have the same logic with Base.length(b::OrthonormalBasis), but I make sense for block lanczos. +blocksize(b::OrthonormalBasis) = length(b.basis) Base.eltype(b::OrthonormalBasis{T}) where {T} = T Base.iterate(b::OrthonormalBasis) = Base.iterate(b.basis) @@ -574,3 +576,55 @@ and its concrete subtypes [`ClassicalGramSchmidt`](@ref), [`ModifiedGramSchmidt` [`ClassicalGramSchmidtIR`](@ref) and [`ModifiedGramSchmidtIR`](@ref). """ orthonormalize, orthonormalize!! + +# TODO : Test + +abstract_qr!(A::OrthonormalBasis{T};arg...) where T = abstract_qr!(A.basis;arg...) +function abstract_qr!(A::AbstractVector{T};arg...) where T + if T<:InnerProductVec + return _abstract_qr!(A;arg...) + else + Aq,B = qr(hcat(A...)) + Am = Matrix(Aq) + @inbounds @simd for i in eachindex(A) + A[i] = view(Am,:,i) + end + return A,B + end +end + +function _abstract_qr!(block::OrthonormalBasis{T}; + alg::Orthogonalizer=ModifiedGramSchmidt(), + tol::Real=1e4*eps(InnerNumType(T))) where {T} + n = length(block) + R = zeros(InnerNumType(T), n, n) + + coeffs = Vector{InnerNumType(T)}(undef, n) + + @inbounds for j in 1:n + _, β, coeffs = orthonormalize!!(block[j], OrthonormalBasis(block.basis[1:j-1]), + coeffs, alg) + if β ≤ tol * norm(block[j]) + error("Column $j is linearly dependent (β = $β)") + end + + R[1:j-1, j] = coeffs[1:j-1] + R[j, j] = β + end + + return block, R +end + +function abstract_qr(block::OrthonormalBasis{T}; + alg::Orthogonalizer=ModifiedGramSchmidt(), + tol::Real=1e4*eps(InnerNumType(T))) where {T} + block, R = abstract_qr!(copy(block); alg, tol) + return R +end + + + + + + + diff --git a/test/eigsolve.jl b/test/eigsolve.jl index b8ab60bb..f7a92940 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -360,73 +360,77 @@ end @test_logs (:info,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=2)) end -@testset "Block Lanczos - eigsolve for large sparse matrix and map input" begin - function toric_code_strings(m::Int, n::Int) - li = LinearIndices((m, n)) - bottom(i, j) = li[mod1(i, m), mod1(j, n)] + m * n - right(i, j) = li[mod1(i, m), mod1(j, n)] - xstrings = Vector{Int}[] - zstrings = Vector{Int}[] - for i in 1:m, j in 1:n - # face center - push!(xstrings, [bottom(i, j - 1), right(i, j), bottom(i, j), right(i - 1, j)]) - # cross - push!(zstrings, [right(i, j), bottom(i, j), right(i, j + 1), bottom(i + 1, j)]) - end - return xstrings, zstrings +using Test, SparseArrays, Random, LinearAlgebra + +function toric_code_strings(m::Int, n::Int) + li = LinearIndices((m, n)) + bottom(i, j) = li[mod1(i, m), mod1(j, n)] + m * n + right(i, j) = li[mod1(i, m), mod1(j, n)] + xstrings = Vector{Int}[] + zstrings = Vector{Int}[] + for i in 1:m, j in 1:n + # face center + push!(xstrings, [bottom(i, j - 1), right(i, j), bottom(i, j), right(i - 1, j)]) + # cross + push!(zstrings, [right(i, j), bottom(i, j), right(i, j + 1), bottom(i + 1, j)]) end + return xstrings, zstrings +end - function pauli_kron(n::Int, ops::Pair{Int,Char}...) - mat = sparse(1.0I, 2^n, 2^n) - for (pos, op) in ops - if op == 'X' - σ = sparse([0 1; 1 0]) - elseif op == 'Y' - σ = sparse([0 -im; im 0]) - elseif op == 'Z' - σ = sparse([1 0; 0 -1]) - elseif op == 'I' - σ = sparse(1.0I, 2, 2) - else - error("Unknown Pauli operator $op") - end - - left = sparse(1.0I, 2^(pos - 1), 2^(pos - 1)) - right = sparse(1.0I, 2^(n - pos), 2^(n - pos)) - mat = kron(left, kron(σ, right)) * mat +function pauli_kron(n::Int, ops::Pair{Int,Char}...) + mat = sparse(1.0I, 2^n, 2^n) + for (pos, op) in ops + if op == 'X' + σ = sparse([0 1; 1 0]) + elseif op == 'Y' + σ = sparse([0 -im; im 0]) + elseif op == 'Z' + σ = sparse([1 0; 0 -1]) + elseif op == 'I' + σ = sparse(1.0I, 2, 2) + else + error("Unknown Pauli operator $op") end - return mat - end - # define the function to construct the Hamiltonian matrix - function toric_code_hamiltonian_matrix(m::Int, n::Int) - xstrings, zstrings = toric_code_strings(m, n) - N = 2 * m * n # total number of qubits + left = sparse(1.0I, 2^(pos - 1), 2^(pos - 1)) + right = sparse(1.0I, 2^(n - pos), 2^(n - pos)) + mat = kron(left, kron(σ, right)) * mat + end + return mat +end - # initialize the Hamiltonian matrix as a zero matrix - H = spzeros(2^N, 2^N) +# define the function to construct the Hamiltonian matrix +function toric_code_hamiltonian_matrix(m::Int, n::Int) + xstrings, zstrings = toric_code_strings(m, n) + N = 2 * m * n # total number of qubits - # add the X-type operator terms - for xs in xstrings[1:(end - 1)] - ops = [i => 'X' for i in xs] - H += pauli_kron(N, ops...) - end + # initialize the Hamiltonian matrix as a zero matrix + H = spzeros(2^N, 2^N) - for zs in zstrings[1:(end - 1)] - ops = [i => 'Z' for i in zs] - H += pauli_kron(N, ops...) - end + # add the X-type operator terms + for xs in xstrings[1:(end - 1)] + ops = [i => 'X' for i in xs] + H += pauli_kron(N, ops...) + end - return H + for zs in zstrings[1:(end - 1)] + ops = [i => 'Z' for i in zs] + H += pauli_kron(N, ops...) end - h_mat = toric_code_hamiltonian_matrix(3, 3) + return H +end + +using KrylovKit +@testset "Block Lanczos - eigsolve for large sparse matrix and map input" begin Random.seed!(4) p = 8 # block size - X1 = Matrix(qr(rand(2^18, p)).Q) + sites_num = 3 + X1 = Matrix(qr(rand(2^(2*sites_num^2), p)).Q) get_value_num = 10 tol = 1e-8 + h_mat = toric_code_hamiltonian_matrix(sites_num, sites_num) # matrix input D, U, info = eigsolve(-h_mat, X1, get_value_num, :SR, @@ -435,12 +439,13 @@ end @test count(x -> abs(x + 16.0) < 2.0 - tol, D[1:get_value_num]) == 4 @test count(x -> abs(x + 16.0) < tol, D[1:get_value_num]) == 4 - # map input + #= map input D, U, info = eigsolve(x -> -h_mat * x, X1, get_value_num, :SR, Lanczos(; block_size=p, maxiter=20, tol=tol)) @show D[1:get_value_num] @test count(x -> abs(x + 16.0) < 1.9, D[1:get_value_num]) == 4 @test count(x -> abs(x + 16.0) < 1e-8, D[1:get_value_num]) == 4 + =# end # TODO: test complex numbers \ No newline at end of file diff --git a/test/orthonormal.jl b/test/orthonormal.jl new file mode 100644 index 00000000..f0be5874 --- /dev/null +++ b/test/orthonormal.jl @@ -0,0 +1,147 @@ +@testset "abstract_qr" begin + @testset "Dense matrix tests" begin + n = 10 + k = 5 + vecs = [randn(n) for _ in 1:k] + A = hcat(vecs...) + B = OrthonormalBasis{Vector{Float64}}(vecs) + + for orth in [ClassicalGramSchmidt(), ModifiedGramSchmidt(), + ClassicalGramSchmidt2(), ModifiedGramSchmidt2(), + ClassicalGramSchmidtIR(0.7), ModifiedGramSchmidtIR(0.7)] + Q, R = abstract_qr(B, alg=orth) + + for i in 1:length(Q), j in 1:length(Q) + expected = i == j ? 1.0 : 0.0 + @test abs(inner(Q[i], Q[j]) - expected) < 1e-10 + end + + for i in 2:size(R, 1), j in 1:i-1 + @test abs(R[i, j]) < 1e-10 + end + + reconstructed = zeros(n, k) + for j in 1:k + for i in 1:length(Q) + reconstructed[:, j] .+= Q[i] .* R[i, j] + end + end + @test norm(reconstructed - A) / norm(A) < 1e-10 + end + end + + @testset "Linearly dependent vectors" begin + n = 10 + vecs = [randn(n) for _ in 1:4] + push!(vecs, vecs[1] + vecs[2]) + B = OrthonormalBasis{Vector{Float64}}(vecs) + + Q, R = abstract_qr(B) + + singular_values = svdvals(R) + rank_R = count(s -> s > 1e-10, singular_values) + @test rank_R == 4 + + for i in 1:length(Q), j in 1:length(Q) + expected = i == j ? 1.0 : 0.0 + @test abs(inner(Q[i], Q[j]) - expected) < 1e-10 + end + end + + @testset "Complex vectors" begin + n = 8 + k = 4 + vecs = [randn(ComplexF64, n) for _ in 1:k] + A = hcat(vecs...) + B = OrthonormalBasis{Vector{ComplexF64}}(vecs) + + Q, R = abstract_qr(B) + + for i in 1:length(Q), j in 1:length(Q) + expected = i == j ? 1.0 : 0.0 + @test abs(inner(Q[i], Q[j]) - expected) < 1e-10 + end + + reconstructed = zeros(ComplexF64, n, k) + for j in 1:k + for i in 1:length(Q) + reconstructed[:, j] .+= Q[i] .* R[i, j] + end + end + @test norm(reconstructed - A) / norm(A) < 1e-10 + end + + @testset "Custom vector types" begin + struct MyVector{T} + data::Vector{T} + end + + Base.similar(v::MyVector) = MyVector(similar(v.data)) + Base.copyto!(dest::MyVector, src::MyVector) = (copyto!(dest.data, src.data); dest) + KrylovKit.inner(x::MyVector, y::MyVector) = dot(x.data, y.data) + Base.length(v::MyVector) = length(v.data) + Base.:*(v::MyVector, α::Number) = MyVector(v.data * α) + Base.getindex(v::MyVector, i) = v.data[i] + Base.setindex!(v::MyVector, val, i) = (v.data[i] = val; v) + LinearAlgebra.norm(v::MyVector) = norm(v.data) + + n = 5 + k = 3 + vecs = [MyVector(randn(n)) for _ in 1:k] + B = OrthonormalBasis{MyVector{Float64}}(vecs) + + try + Q, R = abstract_qr(B) + @test length(Q) == k + @test size(R) == (k, k) + catch e + @test false + end + end +end + + +using KrylovKit,LinearAlgebra +n = 10 +vecs = [randn(n) for _ in 1:4] +B = KrylovKit.OrthonormalBasis{Vector{Float64}}(vecs) + +_,R = KrylovKit.abstract_qr!(B; tol=1e-10) +R +C = B.basis +C' +D = zeros(length(C),length(C)) +for i in 1:length(C) + for j in 1:length(C) + D[i,j] = C[i]'*C[j] + end +end +D + +using LinearAlgebra +function my_f!(A) + Aq,B = qr(A) + A .= Matrix(Aq) +end +A = [1.0 2 ;3 4] +my_f!(A) +A + + + +using LinearAlgebra +function LinearAlgebra.mul!(A::AbstractVector{T},B::AbstractVector{T},M::AbstractMatrix) where T + @inbounds for i in eachindex(A) + @simd for j in eachindex(B) + A[i] += M[j,i] * B[j] + end + end + return A +end +A = [rand(4) for _ in 1:2] +Ac = copy(A) +B = [rand(4) for _ in 1:2] +M = rand(2,2) +mul!(A,B,M) +hcat(Ac...) + hcat(B...) * M +A \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 6e69f418..be3a60a1 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -62,6 +62,9 @@ end @testset "Svdsolve differentiation rules" verbose = true begin include("ad/svdsolve.jl") end +@testset "Orthonormal" verbose = true begin + include("orthonormal.jl") +end t = time() - t # Issues From 29447c7c0ec8e985674f1920aacab0c451123719 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Sat, 5 Apr 2025 05:21:23 +0800 Subject: [PATCH 05/82] mutable block lanczos and tests to be added --- src/algorithms.jl | 4 +- src/eigsolve/lanczos.jl | 62 ++-- src/factorizations/lanczos.jl | 237 +++++++------ src/innerproductvec.jl | 145 ++++---- src/orthonormal.jl | 188 +++++------ test/eigsolve.jl | 15 +- test/factorize.jl | 3 +- test/innerproductvec.jl | 0 test/orthonormal.jl | 147 --------- test/tmp.jl | 41 +++ tmp_src.jl | 604 ++++++++++++++++++++++++++++++++++ 11 files changed, 966 insertions(+), 480 deletions(-) create mode 100644 test/innerproductvec.jl create mode 100644 test/tmp.jl create mode 100644 tmp_src.jl diff --git a/src/algorithms.jl b/src/algorithms.jl index c678879e..91f6f214 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -108,7 +108,6 @@ Use `Arnoldi` for non-symmetric or non-Hermitian linear operators. See also: `factorize`, `eigsolve`, `exponentiate`, `Arnoldi`, `Orthogonalizer` """ struct Lanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm - block_size::Int orth::O krylovdim::Int maxiter::Int @@ -117,14 +116,13 @@ struct Lanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm verbosity::Int end function Lanczos(; - block_size::Int=1, krylovdim::Int=KrylovDefaults.krylovdim[], maxiter::Int=KrylovDefaults.maxiter[], tol::Real=KrylovDefaults.tol[], orth::Orthogonalizer=KrylovDefaults.orth, eager::Bool=false, verbosity::Int=KrylovDefaults.verbosity[]) - return Lanczos(block_size, orth, krylovdim, maxiter, tol, eager, verbosity) + return Lanczos(orth, krylovdim, maxiter, tol, eager, verbosity) end """ diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index eed29585..5217a619 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -4,7 +4,7 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::Lanczos; maxiter=alg.maxiter, eager=alg.eager, orth=alg.orth)) - if alg.block_size > 1 + if (typeof(x₀) <: AbstractMatrix && size(x₀,2)>1)||eltype(x₀) <: Union{InnerProductVec,AbstractVector} return block_lanczos_reortho(A, x₀, howmany, which, alg) end krylovdim = alg.krylovdim @@ -156,32 +156,36 @@ end function block_lanczos_reortho(A, x₀, howmany::Int, which::Selector, alg::Lanczos) - @assert alg.block_size == size(x₀, 2) - block_size = alg.block_size + @assert (typeof(x₀) <: AbstractMatrix && size(x₀,2)>1)||eltype(x₀) <: Union{InnerProductVec,AbstractVector} maxiter = alg.maxiter tol = alg.tol verbosity = alg.verbosity - n = size(x₀, 1) - max_blocks = n ÷ block_size + if typeof(x₀) <: AbstractMatrix + x₀_vec = [x₀[:,i] for i in 1:size(x₀,2)] + else + x₀_vec = x₀ + end + bs_now = length(x₀_vec) - iter = BlockLanczosIterator(A, x₀, block_size, maxiter, alg.orth) + iter = BlockLanczosIterator(A, x₀_vec, maxiter, alg.orth) fact = initialize(iter; verbosity=verbosity) numops = 2 # how many times we apply A - converge_check = max(1, 100 ÷ block_size) # Periodic check for convergence + converge_check = max(1, 100 ÷ bs_now) # Periodic check for convergence - local values, vectors, residuals, normresiduals, num_converged + local values, residuals, normresiduals, num_converged + vectors = [similar(x₀_vec[1]) for _ in 1:howmany] converged = false - for numiter in 2:min(maxiter, max_blocks - 2) + for numiter in 2:maxiter expand!(iter, fact; verbosity=verbosity) numops += 1 # Although norm(Rk) is not our convergence condition, when norm(Rk) is to small, we may lose too much precision and orthogonalization. - if (numiter % converge_check == 0) || (fact.normR < tol) - values, vectors, residuals, normresiduals, num_converged = _residual(fact, A, + if (numiter % converge_check == 0) || (fact.normR < tol) || (fact.R_size < 2) + values, vectors, residuals, normresiduals, num_converged = _residual!(fact, A, howmany, tol, - block_size, which) + which,vectors) if verbosity >= EACHITERATION_LEVEL @info "Block Lanczos eigsolve in iteration $numiter: $num_converged values converged, normres = $(normres2string(normresiduals[1:min(howmany, length(normresiduals))]))" @@ -197,25 +201,22 @@ function block_lanczos_reortho(A, x₀, howmany::Int, which::Selector, if !converged values, vectors, residuals, normresiduals, num_converged = _residual(fact, A, howmany, - tol, block_size, which) + tol, which) end - if (fact.k * block_size > alg.krylovdim) - @warn "The real Krylov dimension is $(fact.k * block_size), which is larger than the maximum allowed dimension $(alg.krylovdim)." + if (fact.all_size > alg.krylovdim) + @warn "The real Krylov dimension is $(fact.all_size), which is larger than the maximum allowed dimension $(alg.krylovdim)." # In this version we don't shrink the factorization because it might cause issues, different from the ordinary Lanczos. # Why it happens remains to be investigated. end - basis_view = fact.V.basis[1:fact.k*block_size] - @show norm(blockinner(basis_view, basis_view)-I) - if (num_converged < howmany) && verbosity >= WARN_LEVEL - @warn """Block Lanczos eigsolve stopped without full convergence after $(fact.k) iterations: + @warn """Block Lanczos eigsolve stopped without full convergence after $(fact.all_size) iterations: * $num_converged eigenvalues converged * norm of residuals = $(normres2string(normresiduals)) * number of operations = $numops""" elseif verbosity >= STARTSTOP_LEVEL - @info """Block Lanczos eigsolve finished after $(fact.k) iterations: + @info """Block Lanczos eigsolve finished after $(fact.all_size) iterations: * $num_converged eigenvalues converged * norm of residuals = $(normres2string(normresiduals)) * number of operations = $numops""" @@ -223,11 +224,12 @@ function block_lanczos_reortho(A, x₀, howmany::Int, which::Selector, return values, vectors, - ConvergenceInfo(num_converged, residuals, normresiduals, fact.k, numops) + ConvergenceInfo(num_converged, residuals, normresiduals, fact.all_size, numops) end -function _residual(fact::BlockLanczosFactorization, A, howmany::Int, tol::Real, block_size::Int, which::Selector) - TDB = triblockdiag(fact) +function _residual!(fact::BlockLanczosFactorization, A, howmany::Int, tol::Real, which::Selector, vectors) + all_size = fact.all_size + TDB = view(fact.TDB, 1:all_size, 1:all_size) D, U = eigen(Hermitian((TDB + TDB') / 2)) # TODO: use keyword sortby by, rev = eigsort(which) p = sortperm(D; by=by, rev=rev) @@ -235,15 +237,23 @@ function _residual(fact::BlockLanczosFactorization, A, howmany::Int, tol::Real, U = U[:, p] V = fact.V.basis T = eltype(V) + S = eltype(TDB) howmany_actual = min(howmany, length(D)) values = D[1:howmany_actual] - basis_sofar_view = view(V, 1:(fact.k * block_size)) - vectors = mul_vm(basis_sofar_view, U) + basis_sofar_view = view(V, 1:all_size) + + # TODO: the slowest part + @time @inbounds for i in 1:howmany_actual + copyto!(vectors[i], basis_sofar_view[1]) + for j in 2:all_size + axpy!(U[j,i], basis_sofar_view[j], vectors[i]) + end + end residuals = Vector{T}(undef, howmany_actual) - normresiduals = Vector{InnerNumType(T)}(undef, howmany_actual) + normresiduals = Vector{S}(undef, howmany_actual) for i in 1:howmany_actual residuals[i] = apply(A, vectors[i]) diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index d83807ff..484d84d2 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -370,23 +370,23 @@ end # block lanczos # TODO: do I need block_size field? mutable struct BlockLanczosFactorization{T,S,SR<:Real} <: BlockKrylovFactorization{T,S,SR} - k::Int - const block_size::Int + all_size::Int const V::OrthonormalBasis{T} # Block Lanczos Basis - const M::AbstractMatrix{S} # diagonal block matrices - const B::AbstractMatrix{S} # connection matrices between blocks - const R::OrthonormalBasis{T} # residual block + const TDB::AbstractMatrix{S} # TDB matrix, S is the matrix type + const R::OrthonormalBasis{T} # residual block + R_size::Int normR::SR + + const tmp:: AbstractMatrix{S} # temporary matrix for ortho_basis! end -Base.length(F::BlockLanczosFactorization) = F.k +Base.length(F::BlockLanczosFactorization) = F.all_size #= where use eltype? Base.eltype(F::BlockLanczosFactorization) = eltype(typeof(F)) Base.eltype(::Type{<:BlockLanczosFactorization{T,<:Any}}) where {T} = T =# basis(F::BlockLanczosFactorization) = F.V -# blocksize(F::BlockLanczosFactorization) = F.block_size residual(F::BlockLanczosFactorization) = F.R normres(F::BlockLanczosFactorization) = F.normR @@ -400,41 +400,38 @@ normres(F::BlockLanczosFactorization) = F.normR struct BlockLanczosIterator{F,T,O<:Orthogonalizer} <: KrylovIterator{F,T} operator::F x₀::Vector{T} - block_size::Int maxiter::Int + num_field::Type orth::O function BlockLanczosIterator{F,T,O}(operator::F, x₀::Vector{T}, - block_size::Int, maxiter::Int, + num_field::Type, orth::O) where {F,T,O<:Orthogonalizer} - if block_size < 1 - error("block size must be at least 1") + if length(x₀) < 2 || norm(x₀) < 1e4 * eps(num_field) + error("initial vector should not have norm zero") end - return new{F,T,O}(operator, x₀, block_size, maxiter, orth) + return new{F,T,O}(operator, x₀, maxiter, num_field, orth) end end # Is there a better way to get the type of the output of inner product? What about global variable? -InnerNumType(::Type{T}) where T = eltype(T) -InnerNumType(v::T) where T = InnerNumType(T) function BlockLanczosIterator(operator::F, x₀::AbstractVector{T}, - block_size::Int, maxiter::Int, + num_field::Type, orth::O=ModifiedGramSchmidt2()) where {F,T,O<:Orthogonalizer} if orth != ModifiedGramSchmidt2() @error "BlockLanczosIterator only supports ModifiedGramSchmidt2 orthogonalizer" end - T == InnerProductVec && (InnerNumType(::Type{T}) = typeof(inner(x₀[1], x₀[1]))) - return BlockLanczosIterator{F,T,O}(operator, x₀, block_size, maxiter, orth) + return BlockLanczosIterator{F,T,O}(operator, x₀, maxiter, num_field, orth) end function BlockLanczosIterator(operator::F, - x₀::AbstractMatrix, - block_size::Int, - maxiter::Int, - orth::O=ModifiedGramSchmidt2()) where {F,O<:Orthogonalizer} - return BlockLanczosIterator{F,typeof(x₀[:,1]),O}(operator, [x₀[:,i] for i in 1:size(x₀,2)], block_size, maxiter, orth) + x₀::AbstractVector{T}, + maxiter::Int, + orth::O=ModifiedGramSchmidt2()) where {F,T,O<:Orthogonalizer} + S = typeof(inner(x₀[1], x₀[1])) + return BlockLanczosIterator{F,T,O}(operator, x₀, maxiter, S, orth) end @@ -454,82 +451,59 @@ function Base.iterate(iter::BlockLanczosIterator, state::BlockLanczosFactorizati return state, state end end -=# - -# It's used to make use of the orthogonality in one block. -is_block_orthonormal(x::AbstractVector{T}) where T = isapprox(blockinner(x,x), I, atol=1e4*eps(InnerNumType(T))) - -function compute_residual!(R::AbstractVector{T}, A_X::AbstractVector{T}, X::AbstractVector{T}, M::AbstractMatrix, X_prev::AbstractVector, B_prev::AbstractMatrix) where T - @inbounds for j in 1:length(R) - copyto!(R[j], A_X[j]) - for i in 1:length(X) - axpy!(-M[i,j], X[i], R[j]) - end - for i in 1:length(X_prev) - axpy!(-B_prev[i,j], X_prev[i], R[j]) - end - end - return R -end - -function ortho_basis!(basis_new::AbstractVector{T}, basis_sofar::AbstractVector{T}) where T - mul!(basis_new, basis_sofar, - blockinner(basis_sofar, basis_new)) - return basis_new -end +=# function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.verbosity[]) x₀_vec = iter.x₀ iszero(norm(x₀_vec)) && throw(ArgumentError("initial vector should not have norm zero")) maxiter = iter.maxiter - block_size = iter.block_size + bs_now = length(x₀_vec) # block size now A = iter.operator - T = eltype(x₀_vec) - S = InnerNumType(T) + S = iter.num_field + + V_basis = similar(x₀_vec, bs_now * (maxiter + 1)) + for i in 1:length(V_basis) + V_basis[i] = similar(x₀_vec[1]) + end + R = [similar(x₀_vec[i]) for i in 1:bs_now] + TDB = zeros(S, bs_now * (maxiter + 1), bs_now * (maxiter + 1)) - V_basis = similar(x₀_vec, block_size * (maxiter + 1)) - M = zeros(S, block_size, block_size * (maxiter + 1)) - B = zeros(S, block_size, block_size * maxiter) - R = [similar(x₀_vec[i]) for i in 1:block_size] + X₁_view = view(V_basis, 1:bs_now) + copyto!.(X₁_view, x₀_vec) - x₀_basis_view = view(V_basis, 1:block_size) - copyto!(x₀_basis_view, x₀_vec) + abstract_qr!(X₁_view,S) - !is_block_orthonormal(x₀_basis_view) && abstract_qr!(x₀_basis_view) - - Ax₀ = [apply(A, x) for x in x₀_basis_view] - M₁_view = view(M, :, 1:block_size) - inner!(M₁_view, x₀_basis_view, Ax₀) + Ax₀ = [apply(A, x) for x in X₁_view] + M₁_view = view(TDB, 1:bs_now, 1:bs_now) + inner!(M₁_view, X₁_view, Ax₀) - M₁_view .= (M₁_view .+ M₁_view') ./ 2 + symmetrize!(M₁_view) # We have to write it as a form of matrix multiplication. Get R1 - residual = mul!(Ax₀, x₀_basis_view, - M₁_view) + residual = mul!(Ax₀, X₁_view, - M₁_view) # QR decomposition of residual to get the next basis. Get X2 and B1 - next_basis_view = view(V_basis, block_size+1:2*block_size) - copyto!(next_basis_view, residual) - abstract_qr!(next_basis_view) - - # Orthogonalization processing - mul!(next_basis_view, x₀_basis_view, - blockinner(x₀_basis_view, next_basis_view)) - @inbounds @simd for i in 1:length(next_basis_view) - normalize!(next_basis_view[i]) - end - !is_block_orthonormal(next_basis_view) && abstract_qr!(next_basis_view) - B₁_view = view(B, :, 1:block_size) - inner!(B₁_view, next_basis_view, residual) + B₁, good_idx = abstract_qr!(residual,S) + bs_next = length(good_idx) + X₂_view = view(V_basis, bs_now+1:bs_now+bs_next) + copyto!.(X₂_view, residual[good_idx]) + B₁_view = view(TDB, bs_now+1:bs_now+bs_next, 1:bs_now) + copyto!(B₁_view, B₁) + copyto!(view(TDB, 1:bs_now, bs_now+1:bs_now+bs_next), B₁_view') # Calculate the next block - Ax₁ = [apply(A, x) for x in next_basis_view] - M₂_view = view(M, :, block_size+1:2*block_size) - inner!(M₂_view, next_basis_view, Ax₁) + Ax₂ = [apply(A, x) for x in X₂_view] + M₂_view = view(TDB, bs_now+1:bs_now+bs_next, bs_now+1:bs_now+bs_next) + inner!(M₂_view, X₂_view, Ax₂) - M₂_view .= (M₂_view .+ M₂_view') ./ 2 + symmetrize!(M₂_view) # Calculate the new residual. Get R2 - compute_residual!(R, Ax₁, next_basis_view, M₂_view, x₀_basis_view, B₁_view) - ortho_basis!(R, view(V_basis, 1:2*block_size)) + compute_residual!(R, Ax₂, X₂_view, M₂_view, X₁_view, B₁_view) + tmp = Matrix{S}(undef, (maxiter+1)*bs_now, bs_next) + tmp_view = view(tmp, 1:bs_now+bs_next, 1:bs_next) + ortho_basis!(R, view(V_basis, 1:bs_now+bs_next), tmp_view) normR = norm(R) @@ -537,79 +511,88 @@ function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.ve @info "Block Lanczos initiation at dimension 2: subspace normres = $(normres2string(normR))" end - return BlockLanczosFactorization(2, - block_size, + + return BlockLanczosFactorization(bs_now+bs_next, OrthonormalBasis(V_basis), - M, - B, + TDB, OrthonormalBasis(R), - normR) + bs_next, + normR, + tmp) end function expand!(iter::BlockLanczosIterator, state::BlockLanczosFactorization; verbosity::Int=KrylovDefaults.verbosity[]) - k = state.k - println("iteration=$k") - p = state.block_size - Rₖ = state.R.basis + all_size = state.all_size + @show all_size + Rₖ = view(state.R.basis, 1:state.R_size) + S = iter.num_field + bs_now = length(Rₖ) # Get the current residual as the initial value of the new basis. Get Xnext - Xnext_view = view(state.V.basis, k*p+1:(k+1)*p) - copyto!(Xnext_view, Rₖ) - _, Bₖ = abstract_qr!(Xnext_view) - + Bₖ, good_idx = abstract_qr!(Rₖ,S) + bs_next = length(good_idx) + Xnext_view = view(state.V.basis, all_size+1:all_size+bs_next) + copyto!.(Xnext_view, Rₖ[good_idx]) # Calculate the connection matrix - Bₖ_view = view(state.B, :, (k-1)*p+1:k*p) + Bₖ_view = view(state.TDB, all_size+1:all_size+bs_next, all_size-bs_now+1:all_size) copyto!(Bₖ_view, Bₖ) + copyto!(view(state.TDB, all_size-bs_now+1:all_size, all_size+1:all_size+bs_next), Bₖ_view') + # Apply the operator and calculate the M. Get Mnext Axₖnext = [apply(iter.operator, x) for x in Xnext_view] - Mnext_view = view(state.M, :, k*p+1:(k+1)*p) + Mnext_view = view(state.TDB, all_size+1:all_size+bs_next, all_size+1:all_size+bs_next) inner!(Mnext_view, Xnext_view, Axₖnext) - - # Make sure Mnext is hermitian - Mnext_view .= (Mnext_view .+ Mnext_view') ./ 2 + symmetrize!(Mnext_view) # Calculate the new residual. Get Rnext - compute_residual!(Rₖ, Axₖnext, Xnext_view, Mnext_view, state.V.basis[((k-1)*p+1):k*p], Bₖ_view) - ortho_basis!(Rₖ, view(state.V.basis, 1:(k+1)*p)) - state.normR = norm(state.R) - state.k += 1 + Xnow_view = view(state.V.basis, all_size-bs_now+1:all_size) + Rₖ[1:bs_next] = Rₖ[good_idx] + Rₖnext_view = view(state.R.basis, 1:bs_next) + compute_residual!(Rₖnext_view, Axₖnext, Xnext_view, Mnext_view, Xnow_view, Bₖ_view) + tmp_view = view(state.tmp, 1:(all_size+bs_next), 1:bs_next) + ortho_basis!(Rₖnext_view, view(state.V.basis, 1:all_size+bs_next), tmp_view) + state.normR = norm(Rₖnext_view) + state.all_size += bs_next + state.R_size = bs_next if verbosity > EACHITERATION_LEVEL orthogonality_error = maximum(abs(inner(u,v)-(i==j)) - for (i,u) in enumerate(state.V.basis[1:state.k*p]), - (j,v) in enumerate(state.V.basis[1:state.k*p])) + for (i,u) in enumerate(state.V.basis[1:(all_size+bs_next)]), + (j,v) in enumerate(state.V.basis[1:(all_size+bs_next)])) - @info "Block Lanczos expansion to dimension $(state.k): orthogonality error = $orthogonality_error, normres = $(normres2string(state.normR))" + @info "Block Lanczos expansion to dimension $(state.all_size): orthogonality error = $orthogonality_error, normres = $(normres2string(state.normR))" end end -# build the block tridiagonal matrix from the BlockLanczosFactorization -function triblockdiag(F::BlockLanczosFactorization) - k = F.k - p = F.block_size - n = k * p - - T = similar(F.M, n, n) - fill!(T, zero(eltype(T))) - - for i in 1:k - idx_i = ((i - 1) * p + 1):(i * p) - T_block = view(T, idx_i, idx_i) - M_block = view(F.M, :, ((i - 1) * p + 1):(i * p)) - copyto!(T_block, M_block) - - if i < k - idx_ip1 = (i * p + 1):((i + 1) * p) - B_block = view(F.B, :, ((i - 1) * p + 1):(i * p)) +function compute_residual!(R::AbstractVector{T}, A_X::AbstractVector{T}, X::AbstractVector{T}, M::AbstractMatrix, X_prev::AbstractVector{T}, B_prev::AbstractMatrix) where T + @inbounds for j in 1:length(X) + r_j = R[j] + copyto!(r_j, A_X[j]) + @simd for i in 1:length(X) + axpy!(- M[i,j], X[i], r_j) + end + @simd for i in 1:length(X_prev) + axpy!(- B_prev[i,j], X_prev[i], r_j) + end + end + return R +end - T_block_up = view(T, idx_i, idx_ip1) - copyto!(T_block_up, B_block') +function ortho_basis!(basis_new::AbstractVector{T}, basis_sofar::AbstractVector{T}, tmp::AbstractMatrix) where T + inner!(tmp, basis_sofar, basis_new) + mul!(basis_new, basis_sofar, - tmp) + return basis_new +end - T_block_down = view(T, idx_ip1, idx_i) - copyto!(T_block_down, B_block) +function symmetrize!(A::AbstractMatrix) + n = size(A, 1) + @inbounds for j in 1:n + A[j,j] = real(A[j,j]) + @simd for i in j+1:n + avg = (A[i,j] + A[j,i]) / 2 + A[i,j] = A[j,i] = avg end end - - return T + return A end diff --git a/src/innerproductvec.jl b/src/innerproductvec.jl index 94a1de76..54522d91 100644 --- a/src/innerproductvec.jl +++ b/src/innerproductvec.jl @@ -1,5 +1,5 @@ """ - v = InnerProductVec(vec, dotf) + v = InnerProductVec(vec, dotf) Create a new vector `v` from an existing vector `dotf` with a modified inner product given by `inner`. The vector `vec`, which can be any type (not necessarily `Vector`) that supports @@ -16,25 +16,25 @@ In a (linear) map applied to `v`, the original vector can be obtained as `v.vec` as `v[]`. """ struct InnerProductVec{F,T} - vec::T - dotf::F + vec::T + dotf::F end Base.:-(v::InnerProductVec) = InnerProductVec(-v.vec, v.dotf) function Base.:+(v::InnerProductVec{F}, w::InnerProductVec{F}) where {F} - return InnerProductVec(v.vec + w.vec, v.dotf) + return InnerProductVec(v.vec + w.vec, v.dotf) end function Base.sum(v::AbstractVector{InnerProductVec}) - @assert length(v) > 0 - res = copy(v[1]) - @inbounds for i in 2:length(v) - res += v[i] - end - return res + @assert length(v) > 0 + res = copy(v[1]) + @inbounds for i in 2:length(v) + res += v[i] + end + return res end function Base.:-(v::InnerProductVec{F}, w::InnerProductVec{F}) where {F} - return InnerProductVec(v.vec - w.vec, v.dotf) + return InnerProductVec(v.vec - w.vec, v.dotf) end Base.:*(v::InnerProductVec, a::Number) = InnerProductVec(v.vec * a, v.dotf) @@ -42,11 +42,9 @@ Base.:*(a::Number, v::InnerProductVec) = InnerProductVec(a * v.vec, v.dotf) Base.:*(v::AbstractVector{InnerProductVec}, a::Number) = [v[i] * a for i in 1:length(v)] Base.:*(a::Number, v::AbstractVector{InnerProductVec}) = [a * v[i] for i in 1:length(v)] function Base.:*(v::AbstractVector, V::AbstractVector) - @assert length(v) == length(V) - return sum(v .* V) + return sum(v .* V) end # It's in fact a kind of Linear map -mul_vm(v::AbstractVector, A::AbstractMatrix) = [v * A[:, i] for i in 1:size(A, 2)] Base.:/(v::InnerProductVec, a::Number) = InnerProductVec(v.vec / a, v.dotf) Base.:/(v::AbstractVector{InnerProductVec}, a::Number) = [v[i] / a for i in 1:length(v)] @@ -54,28 +52,28 @@ Base.:\(a::Number, v::InnerProductVec) = InnerProductVec(a \ v.vec, v.dotf) # I can't understand well why the last function exists so I don't implement it's block version. function Base.similar(v::InnerProductVec, ::Type{T}=scalartype(v)) where {T} - return InnerProductVec(similar(v.vec), v.dotf) + return InnerProductVec(similar(v.vec), v.dotf) end Base.getindex(v::InnerProductVec) = v.vec function Base.copy!(w::InnerProductVec{F}, v::InnerProductVec{F}) where {F} - copy!(w.vec, v.vec) - return w + copy!(w.vec, v.vec) + return w end function LinearAlgebra.mul!(w::InnerProductVec{F}, - a::Number, - v::InnerProductVec{F}) where {F} - mul!(w.vec, a, v.vec) - return w + a::Number, + v::InnerProductVec{F}) where {F} + mul!(w.vec, a, v.vec) + return w end function LinearAlgebra.mul!(w::InnerProductVec{F}, - v::InnerProductVec{F}, - a::Number) where {F} - mul!(w.vec, v.vec, a) - return w + v::InnerProductVec{F}, + a::Number) where {F} + mul!(w.vec, v.vec, a) + return w end function LinearAlgebra.rmul!(v::InnerProductVec, a::Number) @@ -92,85 +90,96 @@ function LinearAlgebra.mul!(A::AbstractVector{T},B::AbstractVector{T},M::Abstrac end function LinearAlgebra.axpy!(a::Number, - v::InnerProductVec{F}, - w::InnerProductVec{F}) where {F} - axpy!(a, v.vec, w.vec) - return w + v::InnerProductVec{F}, + w::InnerProductVec{F}) where {F} + axpy!(a, v.vec, w.vec) + return w end function LinearAlgebra.axpby!(a::Number, - v::InnerProductVec{F}, - b, - w::InnerProductVec{F}) where {F} - axpby!(a, v.vec, b, w.vec) - return w + v::InnerProductVec{F}, + b, + w::InnerProductVec{F}) where {F} + axpby!(a, v.vec, b, w.vec) + return w end function LinearAlgebra.dot(v::InnerProductVec{F}, w::InnerProductVec{F}) where {F} - return v.dotf(v.vec, w.vec) + return v.dotf(v.vec, w.vec) end VectorInterface.scalartype(::Type{<:InnerProductVec{F,T}}) where {F,T} = scalartype(T) function VectorInterface.zerovector(v::InnerProductVec, T::Type{<:Number}) - return InnerProductVec(zerovector(v.vec, T), v.dotf) + return InnerProductVec(zerovector(v.vec, T), v.dotf) end function VectorInterface.scale(v::InnerProductVec, a::Number) - return InnerProductVec(scale(v.vec, a), v.dotf) + return InnerProductVec(scale(v.vec, a), v.dotf) end function VectorInterface.scale!!(v::InnerProductVec, a::Number) - return InnerProductVec(scale!!(v.vec, a), v.dotf) + return InnerProductVec(scale!!(v.vec, a), v.dotf) end function VectorInterface.scale!(v::InnerProductVec, a::Number) - scale!(v.vec, a) - return v + scale!(v.vec, a) + return v end function VectorInterface.scale!!(w::InnerProductVec{F}, v::InnerProductVec{F}, - a::Number) where {F} - return InnerProductVec(scale!!(w.vec, v.vec, a), w.dotf) + a::Number) where {F} + return InnerProductVec(scale!!(w.vec, v.vec, a), w.dotf) end function VectorInterface.scale!(w::InnerProductVec{F}, v::InnerProductVec{F}, - a::Number) where {F} - scale!(w.vec, v.vec, a) - return w + a::Number) where {F} + scale!(w.vec, v.vec, a) + return w end function VectorInterface.add(v::InnerProductVec{F}, w::InnerProductVec{F}, a::Number, - b::Number) where {F} - return InnerProductVec(add(v.vec, w.vec, a, b), v.dotf) + b::Number) where {F} + return InnerProductVec(add(v.vec, w.vec, a, b), v.dotf) end function VectorInterface.add!!(v::InnerProductVec{F}, w::InnerProductVec{F}, a::Number, - b::Number) where {F} - return InnerProductVec(add!!(v.vec, w.vec, a, b), v.dotf) + b::Number) where {F} + return InnerProductVec(add!!(v.vec, w.vec, a, b), v.dotf) end function VectorInterface.add!(v::InnerProductVec{F}, w::InnerProductVec{F}, a::Number, - b::Number) where {F} - add!(v.vec, w.vec, a, b) - return v + b::Number) where {F} + add!(v.vec, w.vec, a, b) + return v end function VectorInterface.inner(v::InnerProductVec{F}, w::InnerProductVec{F}) where {F} - return v.dotf(v.vec, w.vec) + return v.dotf(v.vec, w.vec) end # TODO: Add tests function inner!(M::AbstractMatrix, - v::AbstractVector, - w::AbstractVector) - @assert size(M) == (length(v), length(w)) "Matrix dimensions must match" - @inbounds for j in eachindex(w), i in eachindex(v) - M[i, j] = inner(v[i], w[j]) - end - return M -end -function blockinner(x::AbstractVector{T}, y::AbstractVector{T}) where T - m, n = length(x), length(y) - res = Matrix{InnerNumType(T)}(undef, m, n) - @inbounds for i in 1:m - @simd for j in 1:n - res[i, j] = inner(x[i], y[j]) + x::AbstractVector, + y::AbstractVector) + @assert size(M) == (length(x), length(y)) "Matrix dimensions must match" + @inbounds for j in eachindex(y) + yj = y[j] + for i in eachindex(x) + M[i, j] = inner(x[i], yj) end end - return res + return M end VectorInterface.norm(v::InnerProductVec) = sqrt(real(inner(v, v))) + +function blockinner(v::AbstractVector, w::AbstractVector) + M = Matrix{Float64}(undef, length(v), length(w)) + inner!(M, v, w) + return M +end +#= +n=10000000; +m=10; +x = [rand(n) for i in 1:m]; +y = [rand(n) for i in 1:m]; + +M = blockinner(x,y); +Mh = hcat(x...)' * hcat(y...); + +norm(M - Mh) + +=# \ No newline at end of file diff --git a/src/orthonormal.jl b/src/orthonormal.jl index 0d81fd28..d441a082 100644 --- a/src/orthonormal.jl +++ b/src/orthonormal.jl @@ -67,7 +67,7 @@ const BLOCKSIZE = 4096 # this uses functionality beyond VectorInterface, but can be faster _use_multithreaded_array_kernel(y) = _use_multithreaded_array_kernel(typeof(y)) _use_multithreaded_array_kernel(::Type) = false -function _use_multithreaded_array_kernel(::Type{<:Array{T}}) where {T<:Number} +function _use_multithreaded_array_kernel(::Type{<:Array{T}}) where {T <: Number} return isbitstype(T) && get_num_threads() > 1 end function _use_multithreaded_array_kernel(::Type{<:OrthonormalBasis{T}}) where {T} @@ -88,11 +88,11 @@ projecting the vector `x` onto the subspace spanned by `b`; more specifically th for all ``j ∈ r``. """ function project!!(y::AbstractVector, - b::OrthonormalBasis, - x, - α::Number=true, - β::Number=false, - r=Base.OneTo(length(b))) + b::OrthonormalBasis, + x, + α::Number = true, + β::Number = false, + r = Base.OneTo(length(b))) # no specialized routine for IndexLinear x because reduction dimension is large dimension length(y) == length(r) || throw(DimensionMismatch()) if get_num_threads() > 1 @@ -134,11 +134,11 @@ this computes ``` """ function unproject!!(y, - b::OrthonormalBasis, - x::AbstractVector, - α::Number=true, - β::Number=false, - r=Base.OneTo(length(b))) + b::OrthonormalBasis, + x::AbstractVector, + α::Number = true, + β::Number = false, + r = Base.OneTo(length(b))) if _use_multithreaded_array_kernel(y) return unproject_linear_multithreaded!(y, b, x, α, β, r) end @@ -155,11 +155,11 @@ function unproject!!(y, return y end function unproject_linear_multithreaded!(y::AbstractArray, - b::OrthonormalBasis{<:AbstractArray}, - x::AbstractVector, - α::Number=true, - β::Number=false, - r=Base.OneTo(length(b))) + b::OrthonormalBasis{<:AbstractArray}, + x::AbstractVector, + α::Number = true, + β::Number = false, + r = Base.OneTo(length(b))) # multi-threaded implementation, similar to BLAS level 2 matrix vector multiplication m = length(y) n = length(r) @@ -180,12 +180,12 @@ function unproject_linear_multithreaded!(y::AbstractArray, return y end function unproject_linear_kernel!(y::AbstractArray, - b::OrthonormalBasis{<:AbstractArray}, - x::AbstractVector, - I, - α::Number, - β::Number, - r) + b::OrthonormalBasis{<:AbstractArray}, + x::AbstractVector, + I, + α::Number, + β::Number, + r) @inbounds begin if β == 0 @simd for i in I @@ -219,11 +219,11 @@ Perform a rank 1 update of a basis `b`, i.e. update the basis vectors as It is the user's responsibility to make sure that the result is still an orthonormal basis. """ @fastmath function rank1update!(b::OrthonormalBasis, - y, - x::AbstractVector, - α::Number=true, - β::Number=true, - r=Base.OneTo(length(b))) + y, + x::AbstractVector, + α::Number = true, + β::Number = true, + r = Base.OneTo(length(b))) if _use_multithreaded_array_kernel(y) return rank1update_linear_multithreaded!(b, y, x, α, β, r) end @@ -241,11 +241,11 @@ It is the user's responsibility to make sure that the result is still an orthono return b end @fastmath function rank1update_linear_multithreaded!(b::OrthonormalBasis{<:AbstractArray}, - y::AbstractArray, - x::AbstractVector, - α::Number, - β::Number, - r) + y::AbstractArray, + x::AbstractVector, + α::Number, + β::Number, + r) # multi-threaded implementation, similar to BLAS level 2 matrix vector multiplication m = length(y) n = length(r) @@ -274,7 +274,7 @@ end end if I + blocksize - 1 <= m @simd for i in Base.OneTo(blocksize) - Vj[I - 1 + i] += y[I - 1 + i] * xj + Vj[I-1+i] += y[I-1+i] * xj end else @simd for i in I:m @@ -336,7 +336,7 @@ function basistransform!(b::OrthonormalBasis{T}, U::AbstractMatrix) where {T} # end function basistransform_linear_multithreaded!(b::OrthonormalBasis{<:AbstractArray}, - U::AbstractMatrix) # U should be unitary or isometric + U::AbstractMatrix) # U should be unitary or isometric m, n = size(U) m == length(b) || throw(DimensionMismatch()) K = length(b[1]) @@ -390,17 +390,17 @@ function orthogonalize!!(v::T, b::OrthonormalBasis{T}, alg::Orthogonalizer) wher end function orthogonalize!!(v::T, - b::OrthonormalBasis{T}, - x::AbstractVector, - ::ClassicalGramSchmidt) where {T} + b::OrthonormalBasis{T}, + x::AbstractVector, + ::ClassicalGramSchmidt) where {T} x = project!!(x, b, v) v = unproject!!(v, b, x, -1, 1) return (v, x) end function reorthogonalize!!(v::T, - b::OrthonormalBasis{T}, - x::AbstractVector, - ::ClassicalGramSchmidt) where {T} + b::OrthonormalBasis{T}, + x::AbstractVector, + ::ClassicalGramSchmidt) where {T} s = similar(x) ## EXTRA ALLOCATION s = project!!(s, b, v) v = unproject!!(v, b, s, -1, 1) @@ -408,16 +408,16 @@ function reorthogonalize!!(v::T, return (v, x) end function orthogonalize!!(v::T, - b::OrthonormalBasis{T}, - x::AbstractVector, - ::ClassicalGramSchmidt2) where {T} + b::OrthonormalBasis{T}, + x::AbstractVector, + ::ClassicalGramSchmidt2) where {T} (v, x) = orthogonalize!!(v, b, x, ClassicalGramSchmidt()) return reorthogonalize!!(v, b, x, ClassicalGramSchmidt()) end function orthogonalize!!(v::T, - b::OrthonormalBasis{T}, - x::AbstractVector, - alg::ClassicalGramSchmidtIR) where {T} + b::OrthonormalBasis{T}, + x::AbstractVector, + alg::ClassicalGramSchmidtIR) where {T} nold = norm(v) (v, x) = orthogonalize!!(v, b, x, ClassicalGramSchmidt()) nnew = norm(v) @@ -430,9 +430,9 @@ function orthogonalize!!(v::T, end function orthogonalize!!(v::T, - b::OrthonormalBasis{T}, - x::AbstractVector, - ::ModifiedGramSchmidt) where {T} + b::OrthonormalBasis{T}, + x::AbstractVector, + ::ModifiedGramSchmidt) where {T} for (i, q) in enumerate(b) s = inner(q, v) v = add!!(v, q, -s) @@ -441,9 +441,9 @@ function orthogonalize!!(v::T, return (v, x) end function reorthogonalize!!(v::T, - b::OrthonormalBasis{T}, - x::AbstractVector, - ::ModifiedGramSchmidt) where {T} + b::OrthonormalBasis{T}, + x::AbstractVector, + ::ModifiedGramSchmidt) where {T} for (i, q) in enumerate(b) s = inner(q, v) v = add!!(v, q, -s) @@ -452,16 +452,16 @@ function reorthogonalize!!(v::T, return (v, x) end function orthogonalize!!(v::T, - b::OrthonormalBasis{T}, - x::AbstractVector, - ::ModifiedGramSchmidt2) where {T} + b::OrthonormalBasis{T}, + x::AbstractVector, + ::ModifiedGramSchmidt2) where {T} (v, x) = orthogonalize!!(v, b, x, ModifiedGramSchmidt()) return reorthogonalize!!(v, b, x, ModifiedGramSchmidt()) end function orthogonalize!!(v::T, - b::OrthonormalBasis{T}, - x::AbstractVector, - alg::ModifiedGramSchmidtIR) where {T} + b::OrthonormalBasis{T}, + x::AbstractVector, + alg::ModifiedGramSchmidtIR) where {T} nold = norm(v) (v, x) = orthogonalize!!(v, b, x, ModifiedGramSchmidt()) nnew = norm(v) @@ -478,15 +478,15 @@ orthogonalize!!(v::T, q::T, alg::Orthogonalizer) where {T} = _orthogonalize!!(v, # avoid method ambiguity on Julia 1.0 according to Aqua.jl function _orthogonalize!!(v::T, - q::T, - alg::Union{ClassicalGramSchmidt,ModifiedGramSchmidt}) where {T} + q::T, + alg::Union{ClassicalGramSchmidt, ModifiedGramSchmidt}) where {T} s = inner(q, v) v = add!!(v, q, -s) return (v, s) end function _orthogonalize!!(v::T, - q::T, - alg::Union{ClassicalGramSchmidt2,ModifiedGramSchmidt2}) where {T} + q::T, + alg::Union{ClassicalGramSchmidt2, ModifiedGramSchmidt2}) where {T} s = inner(q, v) v = add!!(v, q, -s) ds = inner(q, v) @@ -494,8 +494,8 @@ function _orthogonalize!!(v::T, return (v, s + ds) end function _orthogonalize!!(v::T, - q::T, - alg::Union{ClassicalGramSchmidtIR,ModifiedGramSchmidtIR}) where {T} + q::T, + alg::Union{ClassicalGramSchmidtIR, ModifiedGramSchmidtIR}) where {T} nold = norm(v) s = inner(q, v) v = add!!(v, q, -s) @@ -578,53 +578,33 @@ and its concrete subtypes [`ClassicalGramSchmidt`](@ref), [`ModifiedGramSchmidt` orthonormalize, orthonormalize!! # TODO : Test - -abstract_qr!(A::OrthonormalBasis{T};arg...) where T = abstract_qr!(A.basis;arg...) -function abstract_qr!(A::AbstractVector{T};arg...) where T - if T<:InnerProductVec - return _abstract_qr!(A;arg...) - else - Aq,B = qr(hcat(A...)) - Am = Matrix(Aq) - @inbounds @simd for i in eachindex(A) - A[i] = view(Am,:,i) - end - return A,B - end -end - -function _abstract_qr!(block::OrthonormalBasis{T}; - alg::Orthogonalizer=ModifiedGramSchmidt(), - tol::Real=1e4*eps(InnerNumType(T))) where {T} +function abstract_qr!(block::AbstractVector{T}, S::Type; + tol::Real = 1e4 * eps(S)) where {T} n = length(block) - R = zeros(InnerNumType(T), n, n) - - coeffs = Vector{InnerNumType(T)}(undef, n) - + rank_shrink = false + idx = ones(Int64,n) + R = zeros(S, n, n) @inbounds for j in 1:n - _, β, coeffs = orthonormalize!!(block[j], OrthonormalBasis(block.basis[1:j-1]), - coeffs, alg) - if β ≤ tol * norm(block[j]) - error("Column $j is linearly dependent (β = $β)") + αⱼ = block[j] + for i in 1:j-1 + R[i, j] = inner(block[i], αⱼ) + αⱼ -= R[i, j] * block[i] + end + β = norm(αⱼ) + if !(β ≤ tol) + R[j, j] = β + block[j] = αⱼ / β + else + block[j] *= S(0) + rank_shrink = true + idx[j] = 0 end - - R[1:j-1, j] = coeffs[1:j-1] - R[j, j] = β end - - return block, R -end - -function abstract_qr(block::OrthonormalBasis{T}; - alg::Orthogonalizer=ModifiedGramSchmidt(), - tol::Real=1e4*eps(InnerNumType(T))) where {T} - block, R = abstract_qr!(copy(block); alg, tol) - return R + good_idx = findall(idx .> 0) + return R[good_idx,:], good_idx end - - diff --git a/test/eigsolve.jl b/test/eigsolve.jl index f7a92940..9e3bbf7b 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -360,7 +360,7 @@ end @test_logs (:info,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=2)) end -using Test, SparseArrays, Random, LinearAlgebra +using Test, SparseArrays, Random, LinearAlgebra, Profile function toric_code_strings(m::Int, n::Int) li = LinearIndices((m, n)) @@ -425,16 +425,23 @@ end using KrylovKit @testset "Block Lanczos - eigsolve for large sparse matrix and map input" begin Random.seed!(4) - p = 8 # block size sites_num = 3 + p = 5 # block size X1 = Matrix(qr(rand(2^(2*sites_num^2), p)).Q) get_value_num = 10 - tol = 1e-8 + tol = 1e-6 h_mat = toric_code_hamiltonian_matrix(sites_num, sites_num) # matrix input - D, U, info = eigsolve(-h_mat, X1, get_value_num, :SR, + #= + Profile.clear() + local D, U, info + @profile D, U, info = eigsolve(-h_mat, X1, get_value_num, :SR, Lanczos(; block_size=p, maxiter=20, tol=tol)) + Profile.print(format=:flat, sortedby=:count) + =# + D, U, info = eigsolve(-h_mat, X1, get_value_num, :SR, + Lanczos(; maxiter=20, tol=tol)) @show D[1:get_value_num] @test count(x -> abs(x + 16.0) < 2.0 - tol, D[1:get_value_num]) == 4 @test count(x -> abs(x + 16.0) < tol, D[1:get_value_num]) == 4 diff --git a/test/factorize.jl b/test/factorize.jl index 49103399..8419a520 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -297,4 +297,5 @@ end end end -# TODO: add more tests for block lanczos \ No newline at end of file +# TODO: add more tests for block lanczos + diff --git a/test/innerproductvec.jl b/test/innerproductvec.jl new file mode 100644 index 00000000..e69de29b diff --git a/test/orthonormal.jl b/test/orthonormal.jl index f0be5874..e69de29b 100644 --- a/test/orthonormal.jl +++ b/test/orthonormal.jl @@ -1,147 +0,0 @@ -@testset "abstract_qr" begin - @testset "Dense matrix tests" begin - n = 10 - k = 5 - vecs = [randn(n) for _ in 1:k] - A = hcat(vecs...) - B = OrthonormalBasis{Vector{Float64}}(vecs) - - for orth in [ClassicalGramSchmidt(), ModifiedGramSchmidt(), - ClassicalGramSchmidt2(), ModifiedGramSchmidt2(), - ClassicalGramSchmidtIR(0.7), ModifiedGramSchmidtIR(0.7)] - Q, R = abstract_qr(B, alg=orth) - - for i in 1:length(Q), j in 1:length(Q) - expected = i == j ? 1.0 : 0.0 - @test abs(inner(Q[i], Q[j]) - expected) < 1e-10 - end - - for i in 2:size(R, 1), j in 1:i-1 - @test abs(R[i, j]) < 1e-10 - end - - reconstructed = zeros(n, k) - for j in 1:k - for i in 1:length(Q) - reconstructed[:, j] .+= Q[i] .* R[i, j] - end - end - @test norm(reconstructed - A) / norm(A) < 1e-10 - end - end - - @testset "Linearly dependent vectors" begin - n = 10 - vecs = [randn(n) for _ in 1:4] - push!(vecs, vecs[1] + vecs[2]) - B = OrthonormalBasis{Vector{Float64}}(vecs) - - Q, R = abstract_qr(B) - - singular_values = svdvals(R) - rank_R = count(s -> s > 1e-10, singular_values) - @test rank_R == 4 - - for i in 1:length(Q), j in 1:length(Q) - expected = i == j ? 1.0 : 0.0 - @test abs(inner(Q[i], Q[j]) - expected) < 1e-10 - end - end - - @testset "Complex vectors" begin - n = 8 - k = 4 - vecs = [randn(ComplexF64, n) for _ in 1:k] - A = hcat(vecs...) - B = OrthonormalBasis{Vector{ComplexF64}}(vecs) - - Q, R = abstract_qr(B) - - for i in 1:length(Q), j in 1:length(Q) - expected = i == j ? 1.0 : 0.0 - @test abs(inner(Q[i], Q[j]) - expected) < 1e-10 - end - - reconstructed = zeros(ComplexF64, n, k) - for j in 1:k - for i in 1:length(Q) - reconstructed[:, j] .+= Q[i] .* R[i, j] - end - end - @test norm(reconstructed - A) / norm(A) < 1e-10 - end - - @testset "Custom vector types" begin - struct MyVector{T} - data::Vector{T} - end - - Base.similar(v::MyVector) = MyVector(similar(v.data)) - Base.copyto!(dest::MyVector, src::MyVector) = (copyto!(dest.data, src.data); dest) - KrylovKit.inner(x::MyVector, y::MyVector) = dot(x.data, y.data) - Base.length(v::MyVector) = length(v.data) - Base.:*(v::MyVector, α::Number) = MyVector(v.data * α) - Base.getindex(v::MyVector, i) = v.data[i] - Base.setindex!(v::MyVector, val, i) = (v.data[i] = val; v) - LinearAlgebra.norm(v::MyVector) = norm(v.data) - - n = 5 - k = 3 - vecs = [MyVector(randn(n)) for _ in 1:k] - B = OrthonormalBasis{MyVector{Float64}}(vecs) - - try - Q, R = abstract_qr(B) - @test length(Q) == k - @test size(R) == (k, k) - catch e - @test false - end - end -end - - -using KrylovKit,LinearAlgebra -n = 10 -vecs = [randn(n) for _ in 1:4] -B = KrylovKit.OrthonormalBasis{Vector{Float64}}(vecs) - -_,R = KrylovKit.abstract_qr!(B; tol=1e-10) -R -C = B.basis -C' -D = zeros(length(C),length(C)) -for i in 1:length(C) - for j in 1:length(C) - D[i,j] = C[i]'*C[j] - end -end -D - -using LinearAlgebra -function my_f!(A) - Aq,B = qr(A) - A .= Matrix(Aq) -end -A = [1.0 2 ;3 4] -my_f!(A) -A - - - -using LinearAlgebra -function LinearAlgebra.mul!(A::AbstractVector{T},B::AbstractVector{T},M::AbstractMatrix) where T - @inbounds for i in eachindex(A) - @simd for j in eachindex(B) - A[i] += M[j,i] * B[j] - end - end - return A -end -A = [rand(4) for _ in 1:2] -Ac = copy(A) -B = [rand(4) for _ in 1:2] -M = rand(2,2) -mul!(A,B,M) -hcat(Ac...) + hcat(B...) * M -A \ No newline at end of file diff --git a/test/tmp.jl b/test/tmp.jl new file mode 100644 index 00000000..7d5ec822 --- /dev/null +++ b/test/tmp.jl @@ -0,0 +1,41 @@ +using LinearAlgebra +function compute_residual!(R::AbstractVector{T}, A_X::AbstractVector{T}, X::AbstractVector{T}, M::AbstractMatrix, X_prev::AbstractVector{T}, B_prev::AbstractMatrix) where T + @inbounds for j in 1:length(X) + r_j = R[j] + copyto!(r_j, A_X[j]) + @simd for i in 1:length(X) + axpy!(- M[i,j], X[i], r_j) + end + @simd for i in 1:length(X_prev) + axpy!(- B_prev[i,j], X_prev[i], r_j) + end + end + return R +end +n=1000000 +m=10 +AX = [rand(n) for i in 1:m]; +X = [rand(n) for i in 1:m]; +M = rand(m,m); +X_prev = [rand(n) for i in 1:m]; +B_prev = rand(m,m); +R = [rand(n) for i in 1:m]; +compute_residual!(R, AX, X, M, X_prev, B_prev); + +AX_h = hcat(AX...); +X_h = hcat(X...); +X_prev_h = hcat(X_prev...); +R_h = AX_h - X_h * M - X_prev_h * B_prev; + +norm(R_h - hcat(R...)) + + +using LinearAlgebra +x = [rand(5) for _ in 1:3]; +y = [rand(5) for _ in 1:3]; +x +y +y = deepcopy(x) +y[1][1] =1.0 +y +x \ No newline at end of file diff --git a/tmp_src.jl b/tmp_src.jl new file mode 100644 index 00000000..a7c1ba90 --- /dev/null +++ b/tmp_src.jl @@ -0,0 +1,604 @@ +# lanczos.jl +""" + mutable struct LanczosFactorization{T,S<:Real} <: KrylovFactorization{T,S} + +Structure to store a Lanczos factorization of a real symmetric or complex hermitian linear +map `A` of the form + +```julia +A * V = V * B + r * b' +``` + +For a given Lanczos factorization `fact` of length `k = length(fact)`, the basis `V` is +obtained via [`basis(fact)`](@ref basis) and is an instance of [`OrthonormalBasis{T}`](@ref +Basis), with also `length(V) == k` and where `T` denotes the type of vector like objects +used in the problem. The Rayleigh quotient `B` is obtained as +[`rayleighquotient(fact)`](@ref) and is of type `SymTridiagonal{S<:Real}` with `size(B) == +(k,k)`. The residual `r` is obtained as [`residual(fact)`](@ref) and is of type `T`. One can +also query [`normres(fact)`](@ref) to obtain `norm(r)`, the norm of the residual. The vector +`b` has no dedicated name but can be obtained via [`rayleighextension(fact)`](@ref). It +takes the default value ``e_k``, i.e. the unit vector of all zeros and a one in the last +entry, which is represented using [`SimpleBasisVector`](@ref). + +A Lanczos factorization `fact` can be destructured as `V, B, r, nr, b = fact` with +`nr = norm(r)`. + +`LanczosFactorization` is mutable because it can [`expand!`](@ref) or [`shrink!`](@ref). +See also [`LanczosIterator`](@ref) for an iterator that constructs a progressively expanding +Lanczos factorizations of a given linear map and a starting vector. See +[`ArnoldiFactorization`](@ref) and [`ArnoldiIterator`](@ref) for a Krylov factorization that +works for general (non-symmetric) linear maps. +""" +mutable struct LanczosFactorization{T,S<:Real} <: KrylovFactorization{T,S} + k::Int # current Krylov dimension + V::OrthonormalBasis{T} # basis of length k + αs::Vector{S} + βs::Vector{S} + r::T +end + +Base.length(F::LanczosFactorization) = F.k +Base.sizehint!(F::LanczosFactorization, n) = begin + sizehint!(F.V, n) + sizehint!(F.αs, n) + sizehint!(F.βs, n) + return F +end +Base.eltype(F::LanczosFactorization) = eltype(typeof(F)) +Base.eltype(::Type{<:LanczosFactorization{<:Any,S}}) where {S} = S + +function basis(F::LanczosFactorization) + return length(F.V) == F.k ? F.V : + error("Not keeping vectors during Lanczos factorization") +end +rayleighquotient(F::LanczosFactorization) = SymTridiagonal(F.αs, F.βs) +residual(F::LanczosFactorization) = F.r +@inbounds normres(F::LanczosFactorization) = F.βs[F.k] +rayleighextension(F::LanczosFactorization) = SimpleBasisVector(F.k, F.k) + +# Lanczos iteration for constructing the orthonormal basis of a Krylov subspace. +""" + struct LanczosIterator{F,T,O<:Orthogonalizer} <: KrylovIterator{F,T} + LanczosIterator(f, v₀, [orth::Orthogonalizer = KrylovDefaults.orth, keepvecs::Bool = true]) + +Iterator that takes a linear map `f::F` (supposed to be real symmetric or complex hermitian) +and an initial vector `v₀::T` and generates an expanding `LanczosFactorization` thereof. In +particular, `LanczosIterator` uses the +[Lanczos iteration](https://en.wikipedia.org/wiki/Lanczos_algorithm) scheme to build a +successively expanding Lanczos factorization. While `f` cannot be tested to be symmetric or +hermitian directly when the linear map is encoded as a general callable object or function, +it is tested whether the imaginary part of `inner(v, f(v))` is sufficiently small to be +neglected. + +The argument `f` can be a matrix, or a function accepting a single argument `v`, so that +`f(v)` implements the action of the linear map on the vector `v`. + +The optional argument `orth` specifies which [`Orthogonalizer`](@ref) to be used. The +default value in [`KrylovDefaults`](@ref) is to use [`ModifiedGramSchmidtIR`](@ref), which +possibly uses reorthogonalization steps. One can use to discard the old vectors that span +the Krylov subspace by setting the final argument `keepvecs` to `false`. This, however, is +only possible if an `orth` algorithm is used that does not rely on reorthogonalization, such +as `ClassicalGramSchmidt()` or `ModifiedGramSchmidt()`. In that case, the iterator strictly +uses the Lanczos three-term recurrence relation. + +When iterating over an instance of `LanczosIterator`, the values being generated are +instances of [`LanczosFactorization`](@ref), which can be immediately destructured into a +[`basis`](@ref), [`rayleighquotient`](@ref), [`residual`](@ref), [`normres`](@ref) and +[`rayleighextension`](@ref), for example as + +```julia +for (V, B, r, nr, b) in LanczosIterator(f, v₀) + # do something + nr < tol && break # a typical stopping criterion +end +``` + +Note, however, that if `keepvecs=false` in `LanczosIterator`, the basis `V` cannot be +extracted. + +Since the iterator does not know the dimension of the underlying vector space of +objects of type `T`, it keeps expanding the Krylov subspace until the residual norm `nr` +falls below machine precision `eps(typeof(nr))`. + +The internal state of `LanczosIterator` is the same as the return value, i.e. the +corresponding `LanczosFactorization`. However, as Julia's Base iteration interface (using +`Base.iterate`) requires that the state is not mutated, a `deepcopy` is produced upon every +next iteration step. + +Instead, you can also mutate the `KrylovFactorization` in place, using the following +interface, e.g. for the same example above + +```julia +iterator = LanczosIterator(f, v₀) +factorization = initialize(iterator) +while normres(factorization) > tol + expand!(iterator, factorization) + V, B, r, nr, b = factorization + # do something +end +``` + +Here, [`initialize(::KrylovIterator)`](@ref) produces the first Krylov factorization of +length 1, and `expand!(::KrylovIterator, ::KrylovFactorization)`(@ref) expands the +factorization in place. See also [`initialize!(::KrylovIterator, +::KrylovFactorization)`](@ref) to initialize in an already existing factorization (most +information will be discarded) and [`shrink!(::KrylovFactorization, k)`](@ref) to shrink an +existing factorization down to length `k`. +""" +struct LanczosIterator{F,T,O<:Orthogonalizer} <: KrylovIterator{F,T} + operator::F + x₀::T + orth::O + keepvecs::Bool + function LanczosIterator{F,T,O}(operator::F, + x₀::T, + orth::O, + keepvecs::Bool) where {F,T,O<:Orthogonalizer} + if !keepvecs && isa(orth, Reorthogonalizer) + error("Cannot use reorthogonalization without keeping all Krylov vectors") + end + return new{F,T,O}(operator, x₀, orth, keepvecs) + end +end +function LanczosIterator(operator::F, + x₀::T, + orth::O=KrylovDefaults.orth, + keepvecs::Bool=true) where {F,T,O<:Orthogonalizer} + return LanczosIterator{F,T,O}(operator, x₀, orth, keepvecs) +end + +Base.IteratorSize(::Type{<:LanczosIterator}) = Base.SizeUnknown() +Base.IteratorEltype(::Type{<:LanczosIterator}) = Base.EltypeUnknown() + +function Base.iterate(iter::LanczosIterator) + state = initialize(iter) + return state, state +end +function Base.iterate(iter::LanczosIterator, state::LanczosFactorization) + nr = normres(state) + if nr < eps(typeof(nr)) + return nothing + else + state = expand!(iter, deepcopy(state)) + return state, state + end +end + +function warn_nonhermitian(α, β₁, β₂) + n = hypot(α, β₁, β₂) + if abs(imag(α)) / n > eps(one(n))^(2 / 5) + @warn "ignoring imaginary component $(imag(α)) from total weight $n: operator might not be hermitian?" α β₁ β₂ + end + return nothing +end + +function initialize(iter::LanczosIterator; verbosity::Int=KrylovDefaults.verbosity[]) + # initialize without using eltype + x₀ = iter.x₀ + β₀ = norm(x₀) + iszero(β₀) && throw(ArgumentError("initial vector should not have norm zero")) + Ax₀ = apply(iter.operator, x₀) + α = inner(x₀, Ax₀) / (β₀ * β₀) + T = typeof(α) # scalar type of the Rayleigh quotient + # this line determines the vector type that we will henceforth use + # vector scalar type can be different from `T`, e.g. for real inner products + v = add!!(scale(Ax₀, zero(α)), x₀, 1 / β₀) + if typeof(Ax₀) != typeof(v) + r = add!!(zerovector(v), Ax₀, 1 / β₀) + else + r = scale!!(Ax₀, 1 / β₀) + end + βold = norm(r) + r = add!!(r, v, -α) # should we use real(α) here? + β = norm(r) + # possibly reorthogonalize + if iter.orth isa Union{ClassicalGramSchmidt2,ModifiedGramSchmidt2} + dα = inner(v, r) + α += dα + r = add!!(r, v, -dα) # should we use real(dα) here? + β = norm(r) + elseif iter.orth isa Union{ClassicalGramSchmidtIR,ModifiedGramSchmidtIR} + while eps(one(β)) < β < iter.orth.η * βold + βold = β + dα = inner(v, r) + α += dα + r = add!!(r, v, -dα) # should we use real(dα) here? + β = norm(r) + end + end + verbosity >= WARN_LEVEL && warn_nonhermitian(α, zero(β), β) + V = OrthonormalBasis([v]) + αs = [real(α)] + βs = [β] + if verbosity > EACHITERATION_LEVEL + @info "Lanczos initiation at dimension 1: subspace normres = $(normres2string(β))" + end + return LanczosFactorization(1, V, αs, βs, r) +end +function initialize!(iter::LanczosIterator, state::LanczosFactorization; + verbosity::Int=KrylovDefaults.verbosity[]) + x₀ = iter.x₀ + V = state.V + while length(V) > 1 + pop!(V) + end + αs = empty!(state.αs) + βs = empty!(state.βs) + + V[1] = scale!!(V[1], x₀, 1 / norm(x₀)) + w = apply(iter.operator, V[1]) + r, α = orthogonalize!!(w, V[1], iter.orth) + β = norm(r) + verbosity >= WARN_LEVEL && warn_nonhermitian(α, zero(β), β) + + state.k = 1 + push!(αs, real(α)) + push!(βs, β) + state.r = r + if verbosity > EACHITERATION_LEVEL + @info "Lanczos initiation at dimension 1: subspace normres = $(normres2string(β))" + end + return state +end +function expand!(iter::LanczosIterator, state::LanczosFactorization; + verbosity::Int=KrylovDefaults.verbosity[]) + βold = normres(state) + V = state.V + r = state.r + V = push!(V, scale!!(r, 1 / βold)) + r, α, β = lanczosrecurrence(iter.operator, V, βold, iter.orth) + verbosity >= WARN_LEVEL && warn_nonhermitian(α, βold, β) + + αs = push!(state.αs, real(α)) + βs = push!(state.βs, β) + + !iter.keepvecs && popfirst!(state.V) # remove oldest V if not keepvecs + + state.k += 1 + state.r = r + if verbosity > EACHITERATION_LEVEL + @info "Lanczos expansion to dimension $(state.k): subspace normres = $(normres2string(β))" + end + return state +end +function shrink!(state::LanczosFactorization, k; verbosity::Int=KrylovDefaults.verbosity[]) + length(state) == length(state.V) || + error("we cannot shrink LanczosFactorization without keeping Lanczos vectors") + length(state) <= k && return state + V = state.V + while length(V) > k + 1 + pop!(V) + end + r = pop!(V) + resize!(state.αs, k) + resize!(state.βs, k) + state.k = k + β = normres(state) + if verbosity > EACHITERATION_LEVEL + @info "Lanczos reduction to dimension $k: subspace normres = $(normres2string(β))" + end + state.r = scale!!(r, β) + return state +end + +# Exploit hermiticity to "simplify" orthonormalization process: +# Lanczos three-term recurrence relation +function lanczosrecurrence(operator, V::OrthonormalBasis, β, orth::ClassicalGramSchmidt) + v = V[end] + w = apply(operator, v) + α = inner(v, w) + w = add!!(w, V[end - 1], -β) + w = add!!(w, v, -α) + β = norm(w) + return w, α, β +end +function lanczosrecurrence(operator, V::OrthonormalBasis, β, orth::ModifiedGramSchmidt) + v = V[end] + w = apply(operator, v) + w = add!!(w, V[end - 1], -β) + α = inner(v, w) + w = add!!(w, v, -α) + β = norm(w) + return w, α, β +end +function lanczosrecurrence(operator, V::OrthonormalBasis, β, orth::ClassicalGramSchmidt2) + v = V[end] + w = apply(operator, v) + α = inner(v, w) + w = add!!(w, V[end - 1], -β) + w = add!!(w, v, -α) + + w, s = orthogonalize!!(w, V, ClassicalGramSchmidt()) + α += s[end] + β = norm(w) + return w, α, β +end +function lanczosrecurrence(operator, V::OrthonormalBasis, β, orth::ModifiedGramSchmidt2) + v = V[end] + w = apply(operator, v) + w = add!!(w, V[end - 1], -β) + w, α = orthogonalize!!(w, v, ModifiedGramSchmidt()) + + s = α + for q in V + w, s = orthogonalize!!(w, q, ModifiedGramSchmidt()) + end + α += s + β = norm(w) + return w, α, β +end +function lanczosrecurrence(operator, V::OrthonormalBasis, β, orth::ClassicalGramSchmidtIR) + v = V[end] + w = apply(operator, v) + α = inner(v, w) + w = add!!(w, V[end - 1], -β) + w = add!!(w, v, -α) + + ab2 = abs2(α) + abs2(β) + β = norm(w) + nold = sqrt(abs2(β) + ab2) + while eps(one(β)) < β < orth.η * nold + nold = β + w, s = orthogonalize!!(w, V, ClassicalGramSchmidt()) + α += s[end] + β = norm(w) + end + return w, α, β +end +function lanczosrecurrence(operator, V::OrthonormalBasis, β, orth::ModifiedGramSchmidtIR) + v = V[end] + w = apply(operator, v) + w = add!!(w, V[end - 1], -β) + + w, α = orthogonalize!!(w, v, ModifiedGramSchmidt()) + ab2 = abs2(α) + abs2(β) + β = norm(w) + nold = sqrt(abs2(β) + ab2) + while eps(one(β)) < β < orth.η * nold + nold = β + s = zero(α) + for q in V + w, s = orthogonalize!!(w, q, ModifiedGramSchmidt()) + end + α += s + β = norm(w) + end + return w, α, β +end + + +# block lanczos +# TODO: do I need block_size field? +mutable struct BlockLanczosFactorization{T,S,SR<:Real} <: BlockKrylovFactorization{T,S,SR} + all_size::Int + const V::OrthonormalBasis{T} # Block Lanczos Basis + const TDB::AbstractMatrix{S} # TDB matrix, S is the matrix type + const R::OrthonormalBasis{T} # residual block + R_size::Int + normR::SR + + const tmp:: AbstractMatrix{S} # temporary matrix for ortho_basis! +end + +Base.length(F::BlockLanczosFactorization) = F.all_size +#= where use eltype? +Base.eltype(F::BlockLanczosFactorization) = eltype(typeof(F)) +Base.eltype(::Type{<:BlockLanczosFactorization{T,<:Any}}) where {T} = T +=# + +basis(F::BlockLanczosFactorization) = F.V +residual(F::BlockLanczosFactorization) = F.R +normres(F::BlockLanczosFactorization) = F.normR + + +# Now our orthogonalizer is only ModifiedGramSchmidt2. +# Dimension of Krylov subspace in BlockLanczosIterator is usually much bigger than lanczos. +# So ClassicalGramSchmidt and ModifiedGramSchmidt1 is numerically unstable. +# I don't add IR orthogonalizer because I find it sometimes unstable and I am studying it. +# Householder reorthogonalization is theoretically stable and saves memory, but the algorithm I implemented is not stable. +# In the future, I will add IR and Householder orthogonalizer. +struct BlockLanczosIterator{F,T,O<:Orthogonalizer} <: KrylovIterator{F,T} + operator::F + x₀::Vector{T} + maxiter::Int + num_field::Type + orth::O + function BlockLanczosIterator{F,T,O}(operator::F, + x₀::Vector{T}, + maxiter::Int, + num_field::Type, + orth::O) where {F,T,O<:Orthogonalizer} + if length(x₀) < 1 || norm(x₀) < 1e4 * eps(num_field) + error("initial vector should not have norm zero") + end + return new{F,T,O}(operator, x₀, maxiter, num_field, orth) + end +end + +# Is there a better way to get the type of the output of inner product? What about global variable? +function BlockLanczosIterator(operator::F, + x₀::AbstractVector{T}, + maxiter::Int, + num_field::Type, + orth::O=ModifiedGramSchmidt2()) where {F,T,O<:Orthogonalizer} + if orth != ModifiedGramSchmidt2() + @error "BlockLanczosIterator only supports ModifiedGramSchmidt2 orthogonalizer" + end + return BlockLanczosIterator{F,T,O}(operator, x₀, maxiter, num_field, orth) +end +function BlockLanczosIterator(operator::F, + x₀::AbstractVector{T}, + maxiter::Int, + orth::O=ModifiedGramSchmidt2()) where {F,T,O<:Orthogonalizer} + S = typeof(inner(x₀[1], x₀[1])) + return BlockLanczosIterator{F,T,O}(operator, x₀, maxiter, S, orth) +end + + +#= +I save these 2 functions for future use. But I have not tested them. +function Base.iterate(iter::BlockLanczosIterator) + state = initialize(iter) + return state, state +end + +function Base.iterate(iter::BlockLanczosIterator, state::BlockLanczosFactorization) + nr = normres(state) + if nr < eps(typeof(nr)) + return nothing + else + state = expand!(iter, deepcopy(state)) + return state, state + end +end +=# + +function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.verbosity[]) + x₀_vec = iter.x₀ + iszero(norm(x₀_vec)) && throw(ArgumentError("initial vector should not have norm zero")) + + maxiter = iter.maxiter + bs_now = length(x₀_vec) # block size now + A = iter.operator + S = iter.num_field + + V_basis = similar(x₀_vec, bs_now * (maxiter + 1)) + R = [similar(x₀_vec[i]) for i in 1:bs_now] + TDB = zeros(S, bs_now * (maxiter + 1), bs_now * (maxiter + 1)) + + X₁_view = view(V_basis, 1:bs_now) + copyto!(X₁_view, x₀_vec) + + abstract_qr!(X₁_view,S) + + Ax₀ = [apply(A, x) for x in X₁_view] + M₁_view = view(TDB, 1:bs_now, 1:bs_now) + inner!(M₁_view, X₁_view, Ax₀) + + symmetrize!(M₁_view) + + # We have to write it as a form of matrix multiplication. Get R1 + residual = mul!(Ax₀, X₁_view, - M₁_view) + + # QR decomposition of residual to get the next basis. Get X2 and B1 + B₁, good_idx = abstract_qr!(residual,S) + bs_next = length(good_idx) + X₂_view = view(V_basis, bs_now+1:bs_now+bs_next) + copyto!(X₂_view, residual[good_idx]) + B₁_view = view(TDB, bs_now+1:bs_now+bs_next, 1:bs_now) + copyto!(B₁_view, B₁) + copyto!(view(TDB, 1:bs_now, bs_now+1:bs_now+bs_next), B₁_view') + + # Calculate the next block + Ax₂ = [apply(A, x) for x in X₂_view] + M₂_view = view(TDB, bs_now+1:bs_now+bs_next, bs_now+1:bs_now+bs_next) + inner!(M₂_view, X₂_view, Ax₂) + + symmetrize!(M₂_view) + + # Calculate the new residual. Get R2 + compute_residual!(R, Ax₂, X₂_view, M₂_view, X₁_view, B₁_view) + tmp = Matrix{S}(undef, (maxiter+1)*bs_now, bs_next) + tmp_view = view(tmp, 1:bs_now+bs_next, 1:bs_next) + ortho_basis!(R, view(V_basis, 1:bs_now+bs_next), tmp_view) + + normR = norm(R) + + if verbosity > EACHITERATION_LEVEL + @info "Block Lanczos initiation at dimension 2: subspace normres = $(normres2string(normR))" + end + + + return BlockLanczosFactorization(bs_now+bs_next, + OrthonormalBasis(V_basis), + TDB, + OrthonormalBasis(R), + bs_next, + normR, + tmp) +end + +function expand!(iter::BlockLanczosIterator, state::BlockLanczosFactorization; + verbosity::Int=KrylovDefaults.verbosity[]) + all_size = state.all_size + @show all_size + Rₖ = state.R.basis[1:state.R_size] + S = iter.num_field + bs_now = length(Rₖ) + + # Get the current residual as the initial value of the new basis. Get Xnext + Bₖ, good_idx = abstract_qr!(Rₖ,S) + bs_next = length(good_idx) + Xnext_view = view(state.V.basis, all_size+1:all_size+bs_next) + copyto!(Xnext_view, Rₖ[good_idx]) + + # Calculate the connection matrix + Bₖ_view = view(state.TDB, all_size+1:all_size+bs_next, all_size-bs_now+1:all_size) + copyto!(Bₖ_view, Bₖ) + copyto!(view(state.TDB, all_size-bs_now+1:all_size, all_size+1:all_size+bs_next), Bₖ_view') + + # Apply the operator and calculate the M. Get Mnext + Axₖnext = [apply(iter.operator, x) for x in Xnext_view] + Mnext_view = view(state.TDB, all_size+1:all_size+bs_next, all_size+1:all_size+bs_next) + inner!(Mnext_view, Xnext_view, Axₖnext) + symmetrize!(Mnext_view) + + # Calculate the new residual. Get Rnext + + Axnext_h = hcat(Axₖnext...) + Xnext_h = hcat(Xnext_view...) + Xk_h = hcat(state.V.basis[all_size-bs_now+1:all_size]...) + V_h = hcat(state.V.basis[1:all_size+bs_next]...) + Rnext_h = Axnext_h - Xnext_h * Mnext_view - Xk_h * Bₖ_view + @show norm(Rnext_h' * Xnext_h) + + compute_residual!(Rₖ, Axₖnext, Xnext_view, Mnext_view, state.V.basis[all_size-bs_now+1:all_size], Bₖ_view) + @show norm(blockinner(Xnext_view, Rₖ)) + tmp_view = view(state.tmp, 1:(all_size+bs_next), 1:bs_next) + ortho_basis!(Rₖ, view(state.V.basis, 1:all_size+bs_next), tmp_view) + state.normR = norm(state.R) + @show state.normR + @show norm(blockinner(state.V.basis[1:(all_size+bs_next)], Rₖ)) + state.all_size += bs_next + state.R_size = bs_next + + if verbosity > EACHITERATION_LEVEL + orthogonality_error = maximum(abs(inner(u,v)-(i==j)) + for (i,u) in enumerate(state.V.basis[1:(all_size+bs_next)]), + (j,v) in enumerate(state.V.basis[1:(all_size+bs_next)])) + + @info "Block Lanczos expansion to dimension $(state.all_size): orthogonality error = $orthogonality_error, normres = $(normres2string(state.normR))" + end +end + +function compute_residual!(R::AbstractVector{T}, A_X::AbstractVector{T}, X::AbstractVector{T}, M::AbstractMatrix, X_prev::AbstractVector{T}, B_prev::AbstractMatrix) where T + @inbounds for j in 1:length(X) + r_j = R[j] + copyto!(r_j, A_X[j]) + @simd for i in 1:length(X) + axpy!(- M[i,j], X[i], r_j) + end + @simd for i in 1:length(X_prev) + axpy!(- B_prev[j,i]', X_prev[i], r_j) + end + end + return R +end + +function ortho_basis!(basis_new::AbstractVector{T}, basis_sofar::AbstractVector{T}, tmp::AbstractMatrix) where T + inner!(tmp, basis_sofar, basis_new) + mul!(basis_new, basis_sofar, - tmp) + return basis_new +end + +function symmetrize!(A::AbstractMatrix) + n = size(A, 1) + @inbounds for j in 1:n + A[j,j] = real(A[j,j]) + @simd for i in j+1:n + avg = (A[i,j] + A[j,i]) / 2 + A[i,j] = A[j,i] = avg + end + end + return A +end From bf0b9fa37a21df08c2cf1739939d8854cbab4622 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Sat, 5 Apr 2025 05:22:41 +0800 Subject: [PATCH 06/82] save --- test/tmp.jl | 41 ---- tmp_src.jl | 604 ---------------------------------------------------- 2 files changed, 645 deletions(-) delete mode 100644 test/tmp.jl delete mode 100644 tmp_src.jl diff --git a/test/tmp.jl b/test/tmp.jl deleted file mode 100644 index 7d5ec822..00000000 --- a/test/tmp.jl +++ /dev/null @@ -1,41 +0,0 @@ -using LinearAlgebra -function compute_residual!(R::AbstractVector{T}, A_X::AbstractVector{T}, X::AbstractVector{T}, M::AbstractMatrix, X_prev::AbstractVector{T}, B_prev::AbstractMatrix) where T - @inbounds for j in 1:length(X) - r_j = R[j] - copyto!(r_j, A_X[j]) - @simd for i in 1:length(X) - axpy!(- M[i,j], X[i], r_j) - end - @simd for i in 1:length(X_prev) - axpy!(- B_prev[i,j], X_prev[i], r_j) - end - end - return R -end -n=1000000 -m=10 -AX = [rand(n) for i in 1:m]; -X = [rand(n) for i in 1:m]; -M = rand(m,m); -X_prev = [rand(n) for i in 1:m]; -B_prev = rand(m,m); -R = [rand(n) for i in 1:m]; -compute_residual!(R, AX, X, M, X_prev, B_prev); - -AX_h = hcat(AX...); -X_h = hcat(X...); -X_prev_h = hcat(X_prev...); -R_h = AX_h - X_h * M - X_prev_h * B_prev; - -norm(R_h - hcat(R...)) - - -using LinearAlgebra -x = [rand(5) for _ in 1:3]; -y = [rand(5) for _ in 1:3]; -x -y -y = deepcopy(x) -y[1][1] =1.0 -y -x \ No newline at end of file diff --git a/tmp_src.jl b/tmp_src.jl deleted file mode 100644 index a7c1ba90..00000000 --- a/tmp_src.jl +++ /dev/null @@ -1,604 +0,0 @@ -# lanczos.jl -""" - mutable struct LanczosFactorization{T,S<:Real} <: KrylovFactorization{T,S} - -Structure to store a Lanczos factorization of a real symmetric or complex hermitian linear -map `A` of the form - -```julia -A * V = V * B + r * b' -``` - -For a given Lanczos factorization `fact` of length `k = length(fact)`, the basis `V` is -obtained via [`basis(fact)`](@ref basis) and is an instance of [`OrthonormalBasis{T}`](@ref -Basis), with also `length(V) == k` and where `T` denotes the type of vector like objects -used in the problem. The Rayleigh quotient `B` is obtained as -[`rayleighquotient(fact)`](@ref) and is of type `SymTridiagonal{S<:Real}` with `size(B) == -(k,k)`. The residual `r` is obtained as [`residual(fact)`](@ref) and is of type `T`. One can -also query [`normres(fact)`](@ref) to obtain `norm(r)`, the norm of the residual. The vector -`b` has no dedicated name but can be obtained via [`rayleighextension(fact)`](@ref). It -takes the default value ``e_k``, i.e. the unit vector of all zeros and a one in the last -entry, which is represented using [`SimpleBasisVector`](@ref). - -A Lanczos factorization `fact` can be destructured as `V, B, r, nr, b = fact` with -`nr = norm(r)`. - -`LanczosFactorization` is mutable because it can [`expand!`](@ref) or [`shrink!`](@ref). -See also [`LanczosIterator`](@ref) for an iterator that constructs a progressively expanding -Lanczos factorizations of a given linear map and a starting vector. See -[`ArnoldiFactorization`](@ref) and [`ArnoldiIterator`](@ref) for a Krylov factorization that -works for general (non-symmetric) linear maps. -""" -mutable struct LanczosFactorization{T,S<:Real} <: KrylovFactorization{T,S} - k::Int # current Krylov dimension - V::OrthonormalBasis{T} # basis of length k - αs::Vector{S} - βs::Vector{S} - r::T -end - -Base.length(F::LanczosFactorization) = F.k -Base.sizehint!(F::LanczosFactorization, n) = begin - sizehint!(F.V, n) - sizehint!(F.αs, n) - sizehint!(F.βs, n) - return F -end -Base.eltype(F::LanczosFactorization) = eltype(typeof(F)) -Base.eltype(::Type{<:LanczosFactorization{<:Any,S}}) where {S} = S - -function basis(F::LanczosFactorization) - return length(F.V) == F.k ? F.V : - error("Not keeping vectors during Lanczos factorization") -end -rayleighquotient(F::LanczosFactorization) = SymTridiagonal(F.αs, F.βs) -residual(F::LanczosFactorization) = F.r -@inbounds normres(F::LanczosFactorization) = F.βs[F.k] -rayleighextension(F::LanczosFactorization) = SimpleBasisVector(F.k, F.k) - -# Lanczos iteration for constructing the orthonormal basis of a Krylov subspace. -""" - struct LanczosIterator{F,T,O<:Orthogonalizer} <: KrylovIterator{F,T} - LanczosIterator(f, v₀, [orth::Orthogonalizer = KrylovDefaults.orth, keepvecs::Bool = true]) - -Iterator that takes a linear map `f::F` (supposed to be real symmetric or complex hermitian) -and an initial vector `v₀::T` and generates an expanding `LanczosFactorization` thereof. In -particular, `LanczosIterator` uses the -[Lanczos iteration](https://en.wikipedia.org/wiki/Lanczos_algorithm) scheme to build a -successively expanding Lanczos factorization. While `f` cannot be tested to be symmetric or -hermitian directly when the linear map is encoded as a general callable object or function, -it is tested whether the imaginary part of `inner(v, f(v))` is sufficiently small to be -neglected. - -The argument `f` can be a matrix, or a function accepting a single argument `v`, so that -`f(v)` implements the action of the linear map on the vector `v`. - -The optional argument `orth` specifies which [`Orthogonalizer`](@ref) to be used. The -default value in [`KrylovDefaults`](@ref) is to use [`ModifiedGramSchmidtIR`](@ref), which -possibly uses reorthogonalization steps. One can use to discard the old vectors that span -the Krylov subspace by setting the final argument `keepvecs` to `false`. This, however, is -only possible if an `orth` algorithm is used that does not rely on reorthogonalization, such -as `ClassicalGramSchmidt()` or `ModifiedGramSchmidt()`. In that case, the iterator strictly -uses the Lanczos three-term recurrence relation. - -When iterating over an instance of `LanczosIterator`, the values being generated are -instances of [`LanczosFactorization`](@ref), which can be immediately destructured into a -[`basis`](@ref), [`rayleighquotient`](@ref), [`residual`](@ref), [`normres`](@ref) and -[`rayleighextension`](@ref), for example as - -```julia -for (V, B, r, nr, b) in LanczosIterator(f, v₀) - # do something - nr < tol && break # a typical stopping criterion -end -``` - -Note, however, that if `keepvecs=false` in `LanczosIterator`, the basis `V` cannot be -extracted. - -Since the iterator does not know the dimension of the underlying vector space of -objects of type `T`, it keeps expanding the Krylov subspace until the residual norm `nr` -falls below machine precision `eps(typeof(nr))`. - -The internal state of `LanczosIterator` is the same as the return value, i.e. the -corresponding `LanczosFactorization`. However, as Julia's Base iteration interface (using -`Base.iterate`) requires that the state is not mutated, a `deepcopy` is produced upon every -next iteration step. - -Instead, you can also mutate the `KrylovFactorization` in place, using the following -interface, e.g. for the same example above - -```julia -iterator = LanczosIterator(f, v₀) -factorization = initialize(iterator) -while normres(factorization) > tol - expand!(iterator, factorization) - V, B, r, nr, b = factorization - # do something -end -``` - -Here, [`initialize(::KrylovIterator)`](@ref) produces the first Krylov factorization of -length 1, and `expand!(::KrylovIterator, ::KrylovFactorization)`(@ref) expands the -factorization in place. See also [`initialize!(::KrylovIterator, -::KrylovFactorization)`](@ref) to initialize in an already existing factorization (most -information will be discarded) and [`shrink!(::KrylovFactorization, k)`](@ref) to shrink an -existing factorization down to length `k`. -""" -struct LanczosIterator{F,T,O<:Orthogonalizer} <: KrylovIterator{F,T} - operator::F - x₀::T - orth::O - keepvecs::Bool - function LanczosIterator{F,T,O}(operator::F, - x₀::T, - orth::O, - keepvecs::Bool) where {F,T,O<:Orthogonalizer} - if !keepvecs && isa(orth, Reorthogonalizer) - error("Cannot use reorthogonalization without keeping all Krylov vectors") - end - return new{F,T,O}(operator, x₀, orth, keepvecs) - end -end -function LanczosIterator(operator::F, - x₀::T, - orth::O=KrylovDefaults.orth, - keepvecs::Bool=true) where {F,T,O<:Orthogonalizer} - return LanczosIterator{F,T,O}(operator, x₀, orth, keepvecs) -end - -Base.IteratorSize(::Type{<:LanczosIterator}) = Base.SizeUnknown() -Base.IteratorEltype(::Type{<:LanczosIterator}) = Base.EltypeUnknown() - -function Base.iterate(iter::LanczosIterator) - state = initialize(iter) - return state, state -end -function Base.iterate(iter::LanczosIterator, state::LanczosFactorization) - nr = normres(state) - if nr < eps(typeof(nr)) - return nothing - else - state = expand!(iter, deepcopy(state)) - return state, state - end -end - -function warn_nonhermitian(α, β₁, β₂) - n = hypot(α, β₁, β₂) - if abs(imag(α)) / n > eps(one(n))^(2 / 5) - @warn "ignoring imaginary component $(imag(α)) from total weight $n: operator might not be hermitian?" α β₁ β₂ - end - return nothing -end - -function initialize(iter::LanczosIterator; verbosity::Int=KrylovDefaults.verbosity[]) - # initialize without using eltype - x₀ = iter.x₀ - β₀ = norm(x₀) - iszero(β₀) && throw(ArgumentError("initial vector should not have norm zero")) - Ax₀ = apply(iter.operator, x₀) - α = inner(x₀, Ax₀) / (β₀ * β₀) - T = typeof(α) # scalar type of the Rayleigh quotient - # this line determines the vector type that we will henceforth use - # vector scalar type can be different from `T`, e.g. for real inner products - v = add!!(scale(Ax₀, zero(α)), x₀, 1 / β₀) - if typeof(Ax₀) != typeof(v) - r = add!!(zerovector(v), Ax₀, 1 / β₀) - else - r = scale!!(Ax₀, 1 / β₀) - end - βold = norm(r) - r = add!!(r, v, -α) # should we use real(α) here? - β = norm(r) - # possibly reorthogonalize - if iter.orth isa Union{ClassicalGramSchmidt2,ModifiedGramSchmidt2} - dα = inner(v, r) - α += dα - r = add!!(r, v, -dα) # should we use real(dα) here? - β = norm(r) - elseif iter.orth isa Union{ClassicalGramSchmidtIR,ModifiedGramSchmidtIR} - while eps(one(β)) < β < iter.orth.η * βold - βold = β - dα = inner(v, r) - α += dα - r = add!!(r, v, -dα) # should we use real(dα) here? - β = norm(r) - end - end - verbosity >= WARN_LEVEL && warn_nonhermitian(α, zero(β), β) - V = OrthonormalBasis([v]) - αs = [real(α)] - βs = [β] - if verbosity > EACHITERATION_LEVEL - @info "Lanczos initiation at dimension 1: subspace normres = $(normres2string(β))" - end - return LanczosFactorization(1, V, αs, βs, r) -end -function initialize!(iter::LanczosIterator, state::LanczosFactorization; - verbosity::Int=KrylovDefaults.verbosity[]) - x₀ = iter.x₀ - V = state.V - while length(V) > 1 - pop!(V) - end - αs = empty!(state.αs) - βs = empty!(state.βs) - - V[1] = scale!!(V[1], x₀, 1 / norm(x₀)) - w = apply(iter.operator, V[1]) - r, α = orthogonalize!!(w, V[1], iter.orth) - β = norm(r) - verbosity >= WARN_LEVEL && warn_nonhermitian(α, zero(β), β) - - state.k = 1 - push!(αs, real(α)) - push!(βs, β) - state.r = r - if verbosity > EACHITERATION_LEVEL - @info "Lanczos initiation at dimension 1: subspace normres = $(normres2string(β))" - end - return state -end -function expand!(iter::LanczosIterator, state::LanczosFactorization; - verbosity::Int=KrylovDefaults.verbosity[]) - βold = normres(state) - V = state.V - r = state.r - V = push!(V, scale!!(r, 1 / βold)) - r, α, β = lanczosrecurrence(iter.operator, V, βold, iter.orth) - verbosity >= WARN_LEVEL && warn_nonhermitian(α, βold, β) - - αs = push!(state.αs, real(α)) - βs = push!(state.βs, β) - - !iter.keepvecs && popfirst!(state.V) # remove oldest V if not keepvecs - - state.k += 1 - state.r = r - if verbosity > EACHITERATION_LEVEL - @info "Lanczos expansion to dimension $(state.k): subspace normres = $(normres2string(β))" - end - return state -end -function shrink!(state::LanczosFactorization, k; verbosity::Int=KrylovDefaults.verbosity[]) - length(state) == length(state.V) || - error("we cannot shrink LanczosFactorization without keeping Lanczos vectors") - length(state) <= k && return state - V = state.V - while length(V) > k + 1 - pop!(V) - end - r = pop!(V) - resize!(state.αs, k) - resize!(state.βs, k) - state.k = k - β = normres(state) - if verbosity > EACHITERATION_LEVEL - @info "Lanczos reduction to dimension $k: subspace normres = $(normres2string(β))" - end - state.r = scale!!(r, β) - return state -end - -# Exploit hermiticity to "simplify" orthonormalization process: -# Lanczos three-term recurrence relation -function lanczosrecurrence(operator, V::OrthonormalBasis, β, orth::ClassicalGramSchmidt) - v = V[end] - w = apply(operator, v) - α = inner(v, w) - w = add!!(w, V[end - 1], -β) - w = add!!(w, v, -α) - β = norm(w) - return w, α, β -end -function lanczosrecurrence(operator, V::OrthonormalBasis, β, orth::ModifiedGramSchmidt) - v = V[end] - w = apply(operator, v) - w = add!!(w, V[end - 1], -β) - α = inner(v, w) - w = add!!(w, v, -α) - β = norm(w) - return w, α, β -end -function lanczosrecurrence(operator, V::OrthonormalBasis, β, orth::ClassicalGramSchmidt2) - v = V[end] - w = apply(operator, v) - α = inner(v, w) - w = add!!(w, V[end - 1], -β) - w = add!!(w, v, -α) - - w, s = orthogonalize!!(w, V, ClassicalGramSchmidt()) - α += s[end] - β = norm(w) - return w, α, β -end -function lanczosrecurrence(operator, V::OrthonormalBasis, β, orth::ModifiedGramSchmidt2) - v = V[end] - w = apply(operator, v) - w = add!!(w, V[end - 1], -β) - w, α = orthogonalize!!(w, v, ModifiedGramSchmidt()) - - s = α - for q in V - w, s = orthogonalize!!(w, q, ModifiedGramSchmidt()) - end - α += s - β = norm(w) - return w, α, β -end -function lanczosrecurrence(operator, V::OrthonormalBasis, β, orth::ClassicalGramSchmidtIR) - v = V[end] - w = apply(operator, v) - α = inner(v, w) - w = add!!(w, V[end - 1], -β) - w = add!!(w, v, -α) - - ab2 = abs2(α) + abs2(β) - β = norm(w) - nold = sqrt(abs2(β) + ab2) - while eps(one(β)) < β < orth.η * nold - nold = β - w, s = orthogonalize!!(w, V, ClassicalGramSchmidt()) - α += s[end] - β = norm(w) - end - return w, α, β -end -function lanczosrecurrence(operator, V::OrthonormalBasis, β, orth::ModifiedGramSchmidtIR) - v = V[end] - w = apply(operator, v) - w = add!!(w, V[end - 1], -β) - - w, α = orthogonalize!!(w, v, ModifiedGramSchmidt()) - ab2 = abs2(α) + abs2(β) - β = norm(w) - nold = sqrt(abs2(β) + ab2) - while eps(one(β)) < β < orth.η * nold - nold = β - s = zero(α) - for q in V - w, s = orthogonalize!!(w, q, ModifiedGramSchmidt()) - end - α += s - β = norm(w) - end - return w, α, β -end - - -# block lanczos -# TODO: do I need block_size field? -mutable struct BlockLanczosFactorization{T,S,SR<:Real} <: BlockKrylovFactorization{T,S,SR} - all_size::Int - const V::OrthonormalBasis{T} # Block Lanczos Basis - const TDB::AbstractMatrix{S} # TDB matrix, S is the matrix type - const R::OrthonormalBasis{T} # residual block - R_size::Int - normR::SR - - const tmp:: AbstractMatrix{S} # temporary matrix for ortho_basis! -end - -Base.length(F::BlockLanczosFactorization) = F.all_size -#= where use eltype? -Base.eltype(F::BlockLanczosFactorization) = eltype(typeof(F)) -Base.eltype(::Type{<:BlockLanczosFactorization{T,<:Any}}) where {T} = T -=# - -basis(F::BlockLanczosFactorization) = F.V -residual(F::BlockLanczosFactorization) = F.R -normres(F::BlockLanczosFactorization) = F.normR - - -# Now our orthogonalizer is only ModifiedGramSchmidt2. -# Dimension of Krylov subspace in BlockLanczosIterator is usually much bigger than lanczos. -# So ClassicalGramSchmidt and ModifiedGramSchmidt1 is numerically unstable. -# I don't add IR orthogonalizer because I find it sometimes unstable and I am studying it. -# Householder reorthogonalization is theoretically stable and saves memory, but the algorithm I implemented is not stable. -# In the future, I will add IR and Householder orthogonalizer. -struct BlockLanczosIterator{F,T,O<:Orthogonalizer} <: KrylovIterator{F,T} - operator::F - x₀::Vector{T} - maxiter::Int - num_field::Type - orth::O - function BlockLanczosIterator{F,T,O}(operator::F, - x₀::Vector{T}, - maxiter::Int, - num_field::Type, - orth::O) where {F,T,O<:Orthogonalizer} - if length(x₀) < 1 || norm(x₀) < 1e4 * eps(num_field) - error("initial vector should not have norm zero") - end - return new{F,T,O}(operator, x₀, maxiter, num_field, orth) - end -end - -# Is there a better way to get the type of the output of inner product? What about global variable? -function BlockLanczosIterator(operator::F, - x₀::AbstractVector{T}, - maxiter::Int, - num_field::Type, - orth::O=ModifiedGramSchmidt2()) where {F,T,O<:Orthogonalizer} - if orth != ModifiedGramSchmidt2() - @error "BlockLanczosIterator only supports ModifiedGramSchmidt2 orthogonalizer" - end - return BlockLanczosIterator{F,T,O}(operator, x₀, maxiter, num_field, orth) -end -function BlockLanczosIterator(operator::F, - x₀::AbstractVector{T}, - maxiter::Int, - orth::O=ModifiedGramSchmidt2()) where {F,T,O<:Orthogonalizer} - S = typeof(inner(x₀[1], x₀[1])) - return BlockLanczosIterator{F,T,O}(operator, x₀, maxiter, S, orth) -end - - -#= -I save these 2 functions for future use. But I have not tested them. -function Base.iterate(iter::BlockLanczosIterator) - state = initialize(iter) - return state, state -end - -function Base.iterate(iter::BlockLanczosIterator, state::BlockLanczosFactorization) - nr = normres(state) - if nr < eps(typeof(nr)) - return nothing - else - state = expand!(iter, deepcopy(state)) - return state, state - end -end -=# - -function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.verbosity[]) - x₀_vec = iter.x₀ - iszero(norm(x₀_vec)) && throw(ArgumentError("initial vector should not have norm zero")) - - maxiter = iter.maxiter - bs_now = length(x₀_vec) # block size now - A = iter.operator - S = iter.num_field - - V_basis = similar(x₀_vec, bs_now * (maxiter + 1)) - R = [similar(x₀_vec[i]) for i in 1:bs_now] - TDB = zeros(S, bs_now * (maxiter + 1), bs_now * (maxiter + 1)) - - X₁_view = view(V_basis, 1:bs_now) - copyto!(X₁_view, x₀_vec) - - abstract_qr!(X₁_view,S) - - Ax₀ = [apply(A, x) for x in X₁_view] - M₁_view = view(TDB, 1:bs_now, 1:bs_now) - inner!(M₁_view, X₁_view, Ax₀) - - symmetrize!(M₁_view) - - # We have to write it as a form of matrix multiplication. Get R1 - residual = mul!(Ax₀, X₁_view, - M₁_view) - - # QR decomposition of residual to get the next basis. Get X2 and B1 - B₁, good_idx = abstract_qr!(residual,S) - bs_next = length(good_idx) - X₂_view = view(V_basis, bs_now+1:bs_now+bs_next) - copyto!(X₂_view, residual[good_idx]) - B₁_view = view(TDB, bs_now+1:bs_now+bs_next, 1:bs_now) - copyto!(B₁_view, B₁) - copyto!(view(TDB, 1:bs_now, bs_now+1:bs_now+bs_next), B₁_view') - - # Calculate the next block - Ax₂ = [apply(A, x) for x in X₂_view] - M₂_view = view(TDB, bs_now+1:bs_now+bs_next, bs_now+1:bs_now+bs_next) - inner!(M₂_view, X₂_view, Ax₂) - - symmetrize!(M₂_view) - - # Calculate the new residual. Get R2 - compute_residual!(R, Ax₂, X₂_view, M₂_view, X₁_view, B₁_view) - tmp = Matrix{S}(undef, (maxiter+1)*bs_now, bs_next) - tmp_view = view(tmp, 1:bs_now+bs_next, 1:bs_next) - ortho_basis!(R, view(V_basis, 1:bs_now+bs_next), tmp_view) - - normR = norm(R) - - if verbosity > EACHITERATION_LEVEL - @info "Block Lanczos initiation at dimension 2: subspace normres = $(normres2string(normR))" - end - - - return BlockLanczosFactorization(bs_now+bs_next, - OrthonormalBasis(V_basis), - TDB, - OrthonormalBasis(R), - bs_next, - normR, - tmp) -end - -function expand!(iter::BlockLanczosIterator, state::BlockLanczosFactorization; - verbosity::Int=KrylovDefaults.verbosity[]) - all_size = state.all_size - @show all_size - Rₖ = state.R.basis[1:state.R_size] - S = iter.num_field - bs_now = length(Rₖ) - - # Get the current residual as the initial value of the new basis. Get Xnext - Bₖ, good_idx = abstract_qr!(Rₖ,S) - bs_next = length(good_idx) - Xnext_view = view(state.V.basis, all_size+1:all_size+bs_next) - copyto!(Xnext_view, Rₖ[good_idx]) - - # Calculate the connection matrix - Bₖ_view = view(state.TDB, all_size+1:all_size+bs_next, all_size-bs_now+1:all_size) - copyto!(Bₖ_view, Bₖ) - copyto!(view(state.TDB, all_size-bs_now+1:all_size, all_size+1:all_size+bs_next), Bₖ_view') - - # Apply the operator and calculate the M. Get Mnext - Axₖnext = [apply(iter.operator, x) for x in Xnext_view] - Mnext_view = view(state.TDB, all_size+1:all_size+bs_next, all_size+1:all_size+bs_next) - inner!(Mnext_view, Xnext_view, Axₖnext) - symmetrize!(Mnext_view) - - # Calculate the new residual. Get Rnext - - Axnext_h = hcat(Axₖnext...) - Xnext_h = hcat(Xnext_view...) - Xk_h = hcat(state.V.basis[all_size-bs_now+1:all_size]...) - V_h = hcat(state.V.basis[1:all_size+bs_next]...) - Rnext_h = Axnext_h - Xnext_h * Mnext_view - Xk_h * Bₖ_view - @show norm(Rnext_h' * Xnext_h) - - compute_residual!(Rₖ, Axₖnext, Xnext_view, Mnext_view, state.V.basis[all_size-bs_now+1:all_size], Bₖ_view) - @show norm(blockinner(Xnext_view, Rₖ)) - tmp_view = view(state.tmp, 1:(all_size+bs_next), 1:bs_next) - ortho_basis!(Rₖ, view(state.V.basis, 1:all_size+bs_next), tmp_view) - state.normR = norm(state.R) - @show state.normR - @show norm(blockinner(state.V.basis[1:(all_size+bs_next)], Rₖ)) - state.all_size += bs_next - state.R_size = bs_next - - if verbosity > EACHITERATION_LEVEL - orthogonality_error = maximum(abs(inner(u,v)-(i==j)) - for (i,u) in enumerate(state.V.basis[1:(all_size+bs_next)]), - (j,v) in enumerate(state.V.basis[1:(all_size+bs_next)])) - - @info "Block Lanczos expansion to dimension $(state.all_size): orthogonality error = $orthogonality_error, normres = $(normres2string(state.normR))" - end -end - -function compute_residual!(R::AbstractVector{T}, A_X::AbstractVector{T}, X::AbstractVector{T}, M::AbstractMatrix, X_prev::AbstractVector{T}, B_prev::AbstractMatrix) where T - @inbounds for j in 1:length(X) - r_j = R[j] - copyto!(r_j, A_X[j]) - @simd for i in 1:length(X) - axpy!(- M[i,j], X[i], r_j) - end - @simd for i in 1:length(X_prev) - axpy!(- B_prev[j,i]', X_prev[i], r_j) - end - end - return R -end - -function ortho_basis!(basis_new::AbstractVector{T}, basis_sofar::AbstractVector{T}, tmp::AbstractMatrix) where T - inner!(tmp, basis_sofar, basis_new) - mul!(basis_new, basis_sofar, - tmp) - return basis_new -end - -function symmetrize!(A::AbstractMatrix) - n = size(A, 1) - @inbounds for j in 1:n - A[j,j] = real(A[j,j]) - @simd for i in j+1:n - avg = (A[i,j] + A[j,i]) / 2 - A[i,j] = A[j,i] = avg - end - end - return A -end From 3292f8db484747f7528a8692a25ac1bbc947d81e Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Sat, 5 Apr 2025 19:40:22 +0800 Subject: [PATCH 07/82] add test for abstract_qr --- Project.toml | 3 +- src/eigsolve/lanczos.jl | 4 +- src/factorizations/lanczos.jl | 17 +- src/orthonormal.jl | 3 +- test/eigsolve.jl | 382 +++++++++++++++++++++++++++------- test/orthonormal.jl | 16 ++ 6 files changed, 348 insertions(+), 77 deletions(-) diff --git a/Project.toml b/Project.toml index c17275ab..f46ff94e 100644 --- a/Project.toml +++ b/Project.toml @@ -8,6 +8,7 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" PackageExtensionCompat = "65ce6f38-6b18-4e1d-a461-8949797d7930" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" [weakdeps] @@ -38,10 +39,10 @@ ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" ChainRulesTestUtils = "cdddcdb0-9152-4a09-a978-84456f9df70a" FiniteDifferences = "26cc04aa-876d-5657-8c51-4c34ba976000" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" +SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" -SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" [targets] test = ["Test", "Aqua", "Logging", "TestExtras", "ChainRulesTestUtils", "ChainRulesCore", "FiniteDifferences", "Zygote", "SparseArrays"] diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index 5217a619..c8231f5d 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -243,9 +243,7 @@ function _residual!(fact::BlockLanczosFactorization, A, howmany::Int, tol::Real, values = D[1:howmany_actual] basis_sofar_view = view(V, 1:all_size) - - # TODO: the slowest part - @time @inbounds for i in 1:howmany_actual + @inbounds for i in 1:howmany_actual copyto!(vectors[i], basis_sofar_view[1]) for j in 2:all_size axpy!(U[j,i], basis_sofar_view[j], vectors[i]) diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index 484d84d2..ab9733d6 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -368,7 +368,22 @@ end # block lanczos -# TODO: do I need block_size field? + +#= Now what I implement is block lanczos with mutable block size. But I'm still confused is it neccesary. That is to say, Can we asseert + the iteration would end with size shrink? + +Mathematically: +For a set of initial abstract vectors X₀ = {x₁,..,xₚ}, where A is a hermitian operator, if + +Sₖ = {x ∈ AʲX₀:j=0,..,k-1} + +is linear dependent, can we assert that Rₖ ∈ span(A^{k-2}X₀,A^{k-1}X₀) or at least in span(Sₖ)? +For vectors in F^d I believe it's right. But in a abstract inner product space, it's obviouly much more complicated. + +What ever, mutable block size is at least undoubtedly useful for non-hermitian operator so I implement it. +https://www.netlib.org/utk/people/JackDongarra/etemplates/node252.html#ABLEsection +=# + mutable struct BlockLanczosFactorization{T,S,SR<:Real} <: BlockKrylovFactorization{T,S,SR} all_size::Int const V::OrthonormalBasis{T} # Block Lanczos Basis diff --git a/src/orthonormal.jl b/src/orthonormal.jl index d441a082..9a8ea13d 100644 --- a/src/orthonormal.jl +++ b/src/orthonormal.jl @@ -577,9 +577,8 @@ and its concrete subtypes [`ClassicalGramSchmidt`](@ref), [`ModifiedGramSchmidt` """ orthonormalize, orthonormalize!! -# TODO : Test function abstract_qr!(block::AbstractVector{T}, S::Type; - tol::Real = 1e4 * eps(S)) where {T} + tol::Real = 1e4 * eps(real(S))) where {T} n = length(block) rank_shrink = false idx = ones(Int64,n) diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 9e3bbf7b..4689c85c 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -360,70 +360,67 @@ end @test_logs (:info,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=2)) end -using Test, SparseArrays, Random, LinearAlgebra, Profile - -function toric_code_strings(m::Int, n::Int) - li = LinearIndices((m, n)) - bottom(i, j) = li[mod1(i, m), mod1(j, n)] + m * n - right(i, j) = li[mod1(i, m), mod1(j, n)] - xstrings = Vector{Int}[] - zstrings = Vector{Int}[] - for i in 1:m, j in 1:n - # face center - push!(xstrings, [bottom(i, j - 1), right(i, j), bottom(i, j), right(i - 1, j)]) - # cross - push!(zstrings, [right(i, j), bottom(i, j), right(i, j + 1), bottom(i + 1, j)]) - end - return xstrings, zstrings -end +@testset "Block Lanczos - eigsolve for large sparse matrix and map input" begin -function pauli_kron(n::Int, ops::Pair{Int,Char}...) - mat = sparse(1.0I, 2^n, 2^n) - for (pos, op) in ops - if op == 'X' - σ = sparse([0 1; 1 0]) - elseif op == 'Y' - σ = sparse([0 -im; im 0]) - elseif op == 'Z' - σ = sparse([1 0; 0 -1]) - elseif op == 'I' - σ = sparse(1.0I, 2, 2) - else - error("Unknown Pauli operator $op") + function toric_code_strings(m::Int, n::Int) + li = LinearIndices((m, n)) + bottom(i, j) = li[mod1(i, m), mod1(j, n)] + m * n + right(i, j) = li[mod1(i, m), mod1(j, n)] + xstrings = Vector{Int}[] + zstrings = Vector{Int}[] + for i in 1:m, j in 1:n + # face center + push!(xstrings, [bottom(i, j - 1), right(i, j), bottom(i, j), right(i - 1, j)]) + # cross + push!(zstrings, [right(i, j), bottom(i, j), right(i, j + 1), bottom(i + 1, j)]) end - - left = sparse(1.0I, 2^(pos - 1), 2^(pos - 1)) - right = sparse(1.0I, 2^(n - pos), 2^(n - pos)) - mat = kron(left, kron(σ, right)) * mat + return xstrings, zstrings end - return mat -end - -# define the function to construct the Hamiltonian matrix -function toric_code_hamiltonian_matrix(m::Int, n::Int) - xstrings, zstrings = toric_code_strings(m, n) - N = 2 * m * n # total number of qubits - - # initialize the Hamiltonian matrix as a zero matrix - H = spzeros(2^N, 2^N) - - # add the X-type operator terms - for xs in xstrings[1:(end - 1)] - ops = [i => 'X' for i in xs] - H += pauli_kron(N, ops...) + + function pauli_kron(n::Int, ops::Pair{Int,Char}...) + mat = sparse(1.0I, 2^n, 2^n) + for (pos, op) in ops + if op == 'X' + σ = sparse([0 1; 1 0]) + elseif op == 'Y' + σ = sparse([0 -im; im 0]) + elseif op == 'Z' + σ = sparse([1 0; 0 -1]) + elseif op == 'I' + σ = sparse(1.0I, 2, 2) + else + error("Unknown Pauli operator $op") + end + + left = sparse(1.0I, 2^(pos - 1), 2^(pos - 1)) + right = sparse(1.0I, 2^(n - pos), 2^(n - pos)) + mat = kron(left, kron(σ, right)) * mat + end + return mat end - - for zs in zstrings[1:(end - 1)] - ops = [i => 'Z' for i in zs] - H += pauli_kron(N, ops...) + + # define the function to construct the Hamiltonian matrix + function toric_code_hamiltonian_matrix(m::Int, n::Int) + xstrings, zstrings = toric_code_strings(m, n) + N = 2 * m * n # total number of qubits + + # initialize the Hamiltonian matrix as a zero matrix + H = spzeros(2^N, 2^N) + + # add the X-type operator terms + for xs in xstrings[1:(end - 1)] + ops = [i => 'X' for i in xs] + H += pauli_kron(N, ops...) + end + + for zs in zstrings[1:(end - 1)] + ops = [i => 'Z' for i in zs] + H += pauli_kron(N, ops...) + end + + return H end - - return H -end - - -using KrylovKit -@testset "Block Lanczos - eigsolve for large sparse matrix and map input" begin + Random.seed!(4) sites_num = 3 p = 5 # block size @@ -433,26 +430,271 @@ using KrylovKit h_mat = toric_code_hamiltonian_matrix(sites_num, sites_num) # matrix input - #= - Profile.clear() - local D, U, info - @profile D, U, info = eigsolve(-h_mat, X1, get_value_num, :SR, - Lanczos(; block_size=p, maxiter=20, tol=tol)) - Profile.print(format=:flat, sortedby=:count) - =# D, U, info = eigsolve(-h_mat, X1, get_value_num, :SR, Lanczos(; maxiter=20, tol=tol)) @show D[1:get_value_num] @test count(x -> abs(x + 16.0) < 2.0 - tol, D[1:get_value_num]) == 4 @test count(x -> abs(x + 16.0) < tol, D[1:get_value_num]) == 4 - #= map input + # map input D, U, info = eigsolve(x -> -h_mat * x, X1, get_value_num, :SR, - Lanczos(; block_size=p, maxiter=20, tol=tol)) + Lanczos(; maxiter=20, tol=tol)) @show D[1:get_value_num] @test count(x -> abs(x + 16.0) < 1.9, D[1:get_value_num]) == 4 @test count(x -> abs(x + 16.0) < 1e-8, D[1:get_value_num]) == 4 - =# + +end + +using Test, SparseArrays, Random, LinearAlgebra, Profile +using KrylovKit + +@testset "Block Lanczos - eigsolve full ($mode)" for mode in (:vector, :inplace, :outplace) + scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : + (ComplexF64,) + orths = mode === :vector ? (mgs2,) : (mgs2,) # Block Lanczos 只支持 mgs2 + @testset for T in scalartypes + @testset for orth in orths + A = rand(T, (n, n)) .- one(T) / 2 + A = (A + A') / 2 # 确保矩阵是对称/厄密的 + + # 创建初始块向量(矩阵形式,每列是一个向量) + block_size = 2 # 块大小 + X = rand(T, (n, block_size)) + + # 如果是 :vector 模式,将矩阵转换为向量数组 + x₀ = mode === :vector ? [X[:, i] for i in 1:block_size] : X + + n1 = div(n, 2) # 要求解的特征值数量 + + # 创建 Block Lanczos 算法配置 + alg = Lanczos(; orth=orth, krylovdim=n, maxiter=5, tol=tolerance(T), + verbosity=2) + + # 执行特征值求解 + D1, V1, info = @test_logs (:info,) eigsolve(wrapop(A, Val(mode)), + wrapvec(x₀, Val(mode)), n1, :SR, alg) + + # 测试不同详细级别的日志输出 + alg = Lanczos(; orth=orth, krylovdim=n, maxiter=5, tol=tolerance(T), + verbosity=1) + @test_logs eigsolve(wrapop(A, Val(mode)), + wrapvec(x₀, Val(mode)), n1, :SR, alg) + + # 测试 Krylov 维度较小时的警告 + alg = Lanczos(; orth=orth, krylovdim=n1 + 1, maxiter=5, tol=tolerance(T), + verbosity=1) + @test_logs (:warn,) eigsolve(wrapop(A, Val(mode)), + wrapvec(x₀, Val(mode)), n1, :SR, alg) + + # 测试详细级别为 2 的日志输出 + alg = Lanczos(; orth=orth, krylovdim=n, maxiter=5, tol=tolerance(T), + verbosity=2) + @test_logs (:info,) eigsolve(wrapop(A, Val(mode)), + wrapvec(x₀, Val(mode)), n1, :SR, alg) + + # 测试求解少量特征值时的日志 + alg = Lanczos(; orth=orth, krylovdim=n1, maxiter=3, tol=tolerance(T), + verbosity=3) + @test_logs((:info,), (:info,), (:info,), (:warn,), + eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), 1, :SR, alg)) + + # 计算剩余的特征值 + n2 = n - n1 + alg = Lanczos(; krylovdim=2 * n, maxiter=5, tol=tolerance(T)) + D2, V2, info = @constinferred eigsolve(wrapop(A, Val(mode)), + wrapvec(x₀, Val(mode)), + n2, :LR, alg) + + # 验证计算的特征值与直接求解的特征值匹配 + @test vcat(D1[1:n1], reverse(D2[1:n2])) ≊ eigvals(A) + + # 测试特征向量的正交性 + # 对于块向量,我们需要特殊处理 + if mode === :vector + # 将向量数组转换为矩阵 + U1 = hcat([unwrapvec(v) for v in V1]...) + U2 = hcat([unwrapvec(v) for v in V2]...) + else + U1 = stack(unwrapvec, V1) + U2 = stack(unwrapvec, V2) + end + + @test U1' * U1 ≈ I + @test U2' * U2 ≈ I + + # 验证特征值和特征向量满足 Av = λv + @test A * U1 ≈ U1 * Diagonal(D1) + @test A * U2 ≈ U2 * Diagonal(D2) + + # 测试请求过多特征值时的警告 + alg = Lanczos(; orth=orth, krylovdim=2n, maxiter=5, tol=tolerance(T), + verbosity=1) + @test_logs (:warn,) (:warn,) eigsolve(wrapop(A, Val(mode)), + wrapvec(x₀, Val(mode)), n + 1, :LM, alg) + end + end +end + +@testset "Block Lanczos - eigsolve iteratively ($mode)" for mode in (:vector, :inplace, :outplace) + scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : + (ComplexF64,) + orths = mode === :vector ? (mgs2,) : (mgs2,) # Block Lanczos 只支持 mgs2 + @testset for T in scalartypes + @testset for orth in orths + # 创建大型矩阵,用于测试迭代求解 + A = rand(T, (N, N)) .- one(T) / 2 + A = (A + A') / 2 # 确保矩阵是对称/厄密的 + + # 创建初始块向量 + block_size = 2 # 块大小 + X = rand(T, (N, block_size)) + + # 如果是 :vector 模式,将矩阵转换为向量数组 + x₀ = mode === :vector ? [X[:, i] for i in 1:block_size] : X + + # 创建 Block Lanczos 算法配置,启用 eager 模式加速收敛 + alg = Lanczos(; krylovdim=2 * n, maxiter=10, + tol=tolerance(T), eager=true, verbosity=0) + + # 求解最小实部特征值 + D1, V1, info1 = @constinferred eigsolve(wrapop(A, Val(mode)), + wrapvec(x₀, Val(mode)), n, :SR, alg) + + # 求解最大实部特征值 + D2, V2, info2 = eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n, :LR, + alg) + + # 提取收敛的特征值数量 + l1 = info1.converged + l2 = info2.converged + + # 验证至少有部分特征值收敛 + @test l1 > 0 + @test l2 > 0 + + # 验证计算的特征值与直接求解的特征值匹配 + @test D1[1:l1] ≈ eigvals(A)[1:l1] + @test D2[1:l2] ≈ eigvals(A)[N:-1:(N - l2 + 1)] + + # 测试特征向量的正交性 + if mode === :vector + # 将向量数组转换为矩阵 + U1 = hcat([unwrapvec(v) for v in V1]...) + U2 = hcat([unwrapvec(v) for v in V2]...) + + # 转换残差向量 + R1 = hcat([unwrapvec(r) for r in info1.residual]...) + R2 = hcat([unwrapvec(r) for r in info2.residual]...) + else + U1 = stack(unwrapvec, V1) + U2 = stack(unwrapvec, V2) + + R1 = stack(unwrapvec, info1.residual) + R2 = stack(unwrapvec, info2.residual) + end + + @test U1' * U1 ≈ I + @test U2' * U2 ≈ I + + # 验证特征值方程 A*v = λ*v + r 含残差项 + @test A * U1 ≈ U1 * Diagonal(D1) + R1 + @test A * U2 ≈ U2 * Diagonal(D2) + R2 + end + end +end + +# 测试块Lanczos在稀疏矩阵上的性能和准确性 +@testset "Block Lanczos - eigsolve for sparse matrices" begin + # 创建一个稀疏的对称矩阵 + N = 100 + A = spzeros(Float64, N, N) + + # 生成带对角线结构的稀疏矩阵 + for i in 1:N + A[i, i] = i # 对角线元素 + if i < N + A[i, i+1] = A[i+1, i] = 0.5 # 次对角线元素 + end + if i < N-5 + A[i, i+5] = A[i+5, i] = 0.1 # 远离对角线的元素 + end + end + + # 创建多列初始矩阵(块向量) + block_size = 3 + X = rand(Float64, (N, block_size)) + x₀ = [X[:, i] for i in 1:block_size] + + # 使用Block Lanczos求解10个最小特征值 + howmany = 10 + alg = Lanczos(; krylovdim=30, maxiter=20, tol=1e-8, verbosity=1) + + # 求解并验证 + D, V, info = eigsolve(A, x₀, howmany, :SR, alg) + + # 与直接求解的特征值比较 + exact_eigs = eigvals(Array(A))[1:howmany] + @test D[1:howmany] ≈ exact_eigs + + # 验证收敛的特征值数量 + @test info.converged >= howmany/2 # 至少一半应该收敛 + + # 验证残差范数 + @test all(info.normres[1:info.converged] .< 1e-7) + + # 验证特征向量满足特征值方程 + U = hcat([v for v in V]...) + @test norm(A * U - U * Diagonal(D)) < 1e-6 end -# TODO: test complex numbers \ No newline at end of file +# 测试块Lanczos与标准Lanczos的性能比较 +@testset "Block Lanczos vs Standard Lanczos - Performance" begin + # 创建一个中等大小的矩阵用于测试 + N = 50 + A = rand(Float64, (N, N)) + A = (A + A') / 2 # 确保对称性 + + # 单向量初始条件(标准Lanczos) + v = rand(Float64, N) + + # 多向量初始条件(块Lanczos) + block_size = 3 + X = rand(Float64, (N, block_size)) + x₀ = [X[:, i] for i in 1:block_size] + + # 共同的算法参数 + howmany = 5 + krylovdim = 20 + maxiter = 10 + tol = 1e-8 + + # 创建两种算法实例 + std_alg = Lanczos(; krylovdim=krylovdim, maxiter=maxiter, tol=tol, verbosity=0) + block_alg = Lanczos(; krylovdim=krylovdim, maxiter=maxiter, tol=tol, verbosity=0) + + # 执行求解并计时 + std_time = @elapsed begin + std_D, std_V, std_info = eigsolve(A, v, howmany, :SR, std_alg) + end + + block_time = @elapsed begin + block_D, block_V, block_info = eigsolve(A, x₀, howmany, :SR, block_alg) + end + + # 验证结果的正确性 + @test std_D[1:howmany] ≈ block_D[1:howmany] + + # 打印性能比较结果(可选) + @info "Performance comparison:" standard_lanczos=std_time block_lanczos=block_time ratio=block_time/std_time + + # 验证块Lanczos的收敛速度(以迭代次数衡量)通常更快 + if block_info.numiter < std_info.numiter + @info "Block Lanczos converged in fewer iterations" block_iter=block_info.numiter std_iter=std_info.numiter + end + + # 如果块Lanczos计算的更快,我们应该验证这一点 + # 注意:这个测试可能不稳定,取决于系统负载和实现细节 + # @test block_time <= 1.5 * std_time # 允许50%的性能波动 +end + + diff --git a/test/orthonormal.jl b/test/orthonormal.jl index e69de29b..7f9247c6 100644 --- a/test/orthonormal.jl +++ b/test/orthonormal.jl @@ -0,0 +1,16 @@ +@testset "abstract_qr! for non-full vectors $T" for T in (Float32, Float64, ComplexF32, ComplexF64) + n = 1000 + m = 10 + A = rand(T, n, m) + B = copy(A) + Av = [A[:, i] for i in 1:size(A, 2)] + # A is a non-full rank matrix + Av[m÷2] = sum(Av[m÷2+1:end] .* rand(T, m - m ÷ 2)) + Bv = copy(Av) + R, gi = KrylovKit.abstract_qr!(Av, T) + @test length(gi) < m + @test eltype(R) == eltype(eltype(A)) == T + @test isapprox(hcat(Av[gi]...) * R, hcat(Bv...); atol = 1e4 * eps(real(T))) + @test isapprox(hcat(Av[gi]...)' * hcat(Av[gi]...), I; atol = 1e4 * eps(real(T))) +end + From 040577d4233f49e883b661a85a30fa976859e9f5 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Mon, 7 Apr 2025 03:49:14 +0800 Subject: [PATCH 08/82] the last test of block lanczos facorizations to do --- src/algorithms.jl | 9 +- src/eigsolve/lanczos.jl | 40 +-- src/factorizations/lanczos.jl | 54 ++-- src/innerproductvec.jl | 24 +- test/eigsolve.jl | 563 +++++++++++++--------------------- test/innerproductvec.jl | 40 +++ test/orthonormal.jl | 48 ++- test/testsetup.jl | 6 + 8 files changed, 365 insertions(+), 419 deletions(-) diff --git a/src/algorithms.jl b/src/algorithms.jl index 91f6f214..d877d62b 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -107,6 +107,9 @@ Use `Arnoldi` for non-symmetric or non-Hermitian linear operators. See also: `factorize`, `eigsolve`, `exponentiate`, `Arnoldi`, `Orthogonalizer` """ + +# I add blockmode explicitly here because I find it difficult to judge +# whether use block lanczos or not only by the kind of x₀ struct Lanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm orth::O krylovdim::Int @@ -114,6 +117,7 @@ struct Lanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm tol::S eager::Bool verbosity::Int + blockmode::Bool end function Lanczos(; krylovdim::Int=KrylovDefaults.krylovdim[], @@ -121,8 +125,9 @@ function Lanczos(; tol::Real=KrylovDefaults.tol[], orth::Orthogonalizer=KrylovDefaults.orth, eager::Bool=false, - verbosity::Int=KrylovDefaults.verbosity[]) - return Lanczos(orth, krylovdim, maxiter, tol, eager, verbosity) + verbosity::Int=KrylovDefaults.verbosity[], + blockmode::Bool=false) + return Lanczos(orth, krylovdim, maxiter, tol, eager, verbosity, blockmode) end """ diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index c8231f5d..c6cb3a00 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -4,7 +4,7 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::Lanczos; maxiter=alg.maxiter, eager=alg.eager, orth=alg.orth)) - if (typeof(x₀) <: AbstractMatrix && size(x₀,2)>1)||eltype(x₀) <: Union{InnerProductVec,AbstractVector} + if alg.blockmode return block_lanczos_reortho(A, x₀, howmany, which, alg) end krylovdim = alg.krylovdim @@ -156,7 +156,7 @@ end function block_lanczos_reortho(A, x₀, howmany::Int, which::Selector, alg::Lanczos) - @assert (typeof(x₀) <: AbstractMatrix && size(x₀,2)>1)||eltype(x₀) <: Union{InnerProductVec,AbstractVector} + @assert alg.blockmode == true maxiter = alg.maxiter tol = alg.tol verbosity = alg.verbosity @@ -173,8 +173,8 @@ function block_lanczos_reortho(A, x₀, howmany::Int, which::Selector, converge_check = max(1, 100 ÷ bs_now) # Periodic check for convergence - local values, residuals, normresiduals, num_converged vectors = [similar(x₀_vec[1]) for _ in 1:howmany] + values = Vector{real(eltype(fact.TDB))}(undef, howmany) converged = false for numiter in 2:maxiter @@ -183,9 +183,9 @@ function block_lanczos_reortho(A, x₀, howmany::Int, which::Selector, # Although norm(Rk) is not our convergence condition, when norm(Rk) is to small, we may lose too much precision and orthogonalization. if (numiter % converge_check == 0) || (fact.normR < tol) || (fact.R_size < 2) - values, vectors, residuals, normresiduals, num_converged = _residual!(fact, A, + values, vectors, howmany_actual, residuals, normresiduals, num_converged = _residual!(fact, A, howmany, tol, - which,vectors) + which,vectors, values) if verbosity >= EACHITERATION_LEVEL @info "Block Lanczos eigsolve in iteration $numiter: $num_converged values converged, normres = $(normres2string(normresiduals[1:min(howmany, length(normresiduals))]))" @@ -200,8 +200,8 @@ function block_lanczos_reortho(A, x₀, howmany::Int, which::Selector, end if !converged - values, vectors, residuals, normresiduals, num_converged = _residual(fact, A, howmany, - tol, which) + values, vectors, howmany_actual, residuals, normresiduals, num_converged = _residual!(fact, A, howmany, + tol, which, vectors, values) end if (fact.all_size > alg.krylovdim) @@ -222,43 +222,45 @@ function block_lanczos_reortho(A, x₀, howmany::Int, which::Selector, * number of operations = $numops""" end - return values, - vectors, + return values[1:howmany], + vectors[1:howmany], ConvergenceInfo(num_converged, residuals, normresiduals, fact.all_size, numops) end -function _residual!(fact::BlockLanczosFactorization, A, howmany::Int, tol::Real, which::Selector, vectors) +function _residual!(fact::BlockLanczosFactorization, A, howmany::Int, tol::Real, which::Selector, vectors::AbstractVector, values::AbstractVector{<:Number}) all_size = fact.all_size TDB = view(fact.TDB, 1:all_size, 1:all_size) - D, U = eigen(Hermitian((TDB + TDB') / 2)) # TODO: use keyword sortby + D, U = eigen(Hermitian((TDB + TDB') / 2)) by, rev = eigsort(which) p = sortperm(D; by=by, rev=rev) D = D[p] U = U[:, p] - V = fact.V.basis - T = eltype(V) + basis_sofar_view = view(fact.V.basis, 1:all_size) + T = eltype(basis_sofar_view) S = eltype(TDB) howmany_actual = min(howmany, length(D)) - values = D[1:howmany_actual] + if howmany_actual < howmany + @warn "The number of converged eigenvalues is less than the requested number of eigenvalues." + end + copyto!(values, D[1:howmany_actual]) - basis_sofar_view = view(V, 1:all_size) @inbounds for i in 1:howmany_actual - copyto!(vectors[i], basis_sofar_view[1]) + copyto!(vectors[i], basis_sofar_view[1]*U[1,i]) for j in 2:all_size axpy!(U[j,i], basis_sofar_view[j], vectors[i]) end end residuals = Vector{T}(undef, howmany_actual) - normresiduals = Vector{S}(undef, howmany_actual) + normresiduals = Vector{real(S)}(undef, howmany_actual) for i in 1:howmany_actual residuals[i] = apply(A, vectors[i]) axpy!(-values[i], vectors[i], residuals[i]) # residuals[i] -= values[i] * vectors[i] + #TODO: Does norm need to be modified when residuals is complex? normresiduals[i] = norm(residuals[i]) end - num_converged = count(nr -> nr <= tol, normresiduals) - return values, vectors, residuals, normresiduals, num_converged + return values, vectors, howmany_actual, residuals, normresiduals, num_converged end \ No newline at end of file diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index ab9733d6..8059db0d 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -369,8 +369,9 @@ end # block lanczos -#= Now what I implement is block lanczos with mutable block size. But I'm still confused is it neccesary. That is to say, Can we asseert - the iteration would end with size shrink? +#= +Now what I implement is block lanczos with mutable block size. But I'm still confused is it neccesary. That is to say, Can we asseert +the iteration would end with size shrink? Mathematically: For a set of initial abstract vectors X₀ = {x₁,..,xₚ}, where A is a hermitian operator, if @@ -395,23 +396,24 @@ mutable struct BlockLanczosFactorization{T,S,SR<:Real} <: BlockKrylovFactorizati const tmp:: AbstractMatrix{S} # temporary matrix for ortho_basis! end +#= I don't use these functions. But to keep the style, I keep them temporarily. Base.length(F::BlockLanczosFactorization) = F.all_size -#= where use eltype? Base.eltype(F::BlockLanczosFactorization) = eltype(typeof(F)) Base.eltype(::Type{<:BlockLanczosFactorization{T,<:Any}}) where {T} = T -=# - basis(F::BlockLanczosFactorization) = F.V residual(F::BlockLanczosFactorization) = F.R normres(F::BlockLanczosFactorization) = F.normR +=# -# Now our orthogonalizer is only ModifiedGramSchmidt2. -# Dimension of Krylov subspace in BlockLanczosIterator is usually much bigger than lanczos. -# So ClassicalGramSchmidt and ModifiedGramSchmidt1 is numerically unstable. -# I don't add IR orthogonalizer because I find it sometimes unstable and I am studying it. -# Householder reorthogonalization is theoretically stable and saves memory, but the algorithm I implemented is not stable. -# In the future, I will add IR and Householder orthogonalizer. +#= +Now our orthogonalizer is only ModifiedGramSchmidt2. +Dimension of Krylov subspace in BlockLanczosIterator is usually much bigger than lanczos. +So ClassicalGramSchmidt and ModifiedGramSchmidt1 is numerically unstable. +I don't add IR orthogonalizer because I find it sometimes unstable and I am studying it. +Householder reorthogonalization is theoretically stable and saves memory, but the algorithm I implemented is not stable. +In the future, I will add IR and Householder orthogonalizer. +=# struct BlockLanczosIterator{F,T,O<:Orthogonalizer} <: KrylovIterator{F,T} operator::F x₀::Vector{T} @@ -423,7 +425,7 @@ struct BlockLanczosIterator{F,T,O<:Orthogonalizer} <: KrylovIterator{F,T} maxiter::Int, num_field::Type, orth::O) where {F,T,O<:Orthogonalizer} - if length(x₀) < 2 || norm(x₀) < 1e4 * eps(num_field) + if length(x₀) < 2 || norm(x₀) < 1e4 * eps(real(num_field)) error("initial vector should not have norm zero") end return new{F,T,O}(operator, x₀, maxiter, num_field, orth) @@ -488,15 +490,13 @@ function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.ve copyto!.(X₁_view, x₀_vec) abstract_qr!(X₁_view,S) - - Ax₀ = [apply(A, x) for x in X₁_view] + Ax₁ = [apply(A, x) for x in X₁_view] M₁_view = view(TDB, 1:bs_now, 1:bs_now) - inner!(M₁_view, X₁_view, Ax₀) - - symmetrize!(M₁_view) + inner!(M₁_view, X₁_view, Ax₁) + M₁_view = (M₁_view + M₁_view') / 2 # We have to write it as a form of matrix multiplication. Get R1 - residual = mul!(Ax₀, X₁_view, - M₁_view) + residual = mul!(Ax₁, X₁_view, - M₁_view) # QR decomposition of residual to get the next basis. Get X2 and B1 B₁, good_idx = abstract_qr!(residual,S) @@ -512,7 +512,7 @@ function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.ve M₂_view = view(TDB, bs_now+1:bs_now+bs_next, bs_now+1:bs_now+bs_next) inner!(M₂_view, X₂_view, Ax₂) - symmetrize!(M₂_view) + M₂_view = (M₂_view + M₂_view') / 2 # Calculate the new residual. Get R2 compute_residual!(R, Ax₂, X₂_view, M₂_view, X₁_view, B₁_view) @@ -526,7 +526,6 @@ function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.ve @info "Block Lanczos initiation at dimension 2: subspace normres = $(normres2string(normR))" end - return BlockLanczosFactorization(bs_now+bs_next, OrthonormalBasis(V_basis), TDB, @@ -539,7 +538,6 @@ end function expand!(iter::BlockLanczosIterator, state::BlockLanczosFactorization; verbosity::Int=KrylovDefaults.verbosity[]) all_size = state.all_size - @show all_size Rₖ = view(state.R.basis, 1:state.R_size) S = iter.num_field bs_now = length(Rₖ) @@ -549,6 +547,7 @@ function expand!(iter::BlockLanczosIterator, state::BlockLanczosFactorization; bs_next = length(good_idx) Xnext_view = view(state.V.basis, all_size+1:all_size+bs_next) copyto!.(Xnext_view, Rₖ[good_idx]) + # Calculate the connection matrix Bₖ_view = view(state.TDB, all_size+1:all_size+bs_next, all_size-bs_now+1:all_size) copyto!(Bₖ_view, Bₖ) @@ -558,7 +557,7 @@ function expand!(iter::BlockLanczosIterator, state::BlockLanczosFactorization; Axₖnext = [apply(iter.operator, x) for x in Xnext_view] Mnext_view = view(state.TDB, all_size+1:all_size+bs_next, all_size+1:all_size+bs_next) inner!(Mnext_view, Xnext_view, Axₖnext) - symmetrize!(Mnext_view) + Mnext_view = (Mnext_view + Mnext_view') / 2 # Calculate the new residual. Get Rnext Xnow_view = view(state.V.basis, all_size-bs_now+1:all_size) @@ -600,14 +599,3 @@ function ortho_basis!(basis_new::AbstractVector{T}, basis_sofar::AbstractVector{ return basis_new end -function symmetrize!(A::AbstractMatrix) - n = size(A, 1) - @inbounds for j in 1:n - A[j,j] = real(A[j,j]) - @simd for i in j+1:n - avg = (A[i,j] + A[j,i]) / 2 - A[i,j] = A[j,i] = avg - end - end - return A -end diff --git a/src/innerproductvec.jl b/src/innerproductvec.jl index 54522d91..1cabf0a4 100644 --- a/src/innerproductvec.jl +++ b/src/innerproductvec.jl @@ -24,6 +24,7 @@ Base.:-(v::InnerProductVec) = InnerProductVec(-v.vec, v.dotf) function Base.:+(v::InnerProductVec{F}, w::InnerProductVec{F}) where {F} return InnerProductVec(v.vec + w.vec, v.dotf) end +# TODO: function Base.sum(v::AbstractVector{InnerProductVec}) @assert length(v) > 0 res = copy(v[1]) @@ -39,6 +40,8 @@ end Base.:*(v::InnerProductVec, a::Number) = InnerProductVec(v.vec * a, v.dotf) Base.:*(a::Number, v::InnerProductVec) = InnerProductVec(a * v.vec, v.dotf) + +# TODO: Base.:*(v::AbstractVector{InnerProductVec}, a::Number) = [v[i] * a for i in 1:length(v)] Base.:*(a::Number, v::AbstractVector{InnerProductVec}) = [a * v[i] for i in 1:length(v)] function Base.:*(v::AbstractVector, V::AbstractVector) @@ -47,6 +50,7 @@ end # It's in fact a kind of Linear map Base.:/(v::InnerProductVec, a::Number) = InnerProductVec(v.vec / a, v.dotf) +# TODO: Base.:/(v::AbstractVector{InnerProductVec}, a::Number) = [v[i] / a for i in 1:length(v)] Base.:\(a::Number, v::InnerProductVec) = InnerProductVec(a \ v.vec, v.dotf) # I can't understand well why the last function exists so I don't implement it's block version. @@ -166,20 +170,18 @@ function inner!(M::AbstractMatrix, end VectorInterface.norm(v::InnerProductVec) = sqrt(real(inner(v, v))) -function blockinner(v::AbstractVector, w::AbstractVector) - M = Matrix{Float64}(undef, length(v), length(w)) + +# used for debugging +function blockinner(v::AbstractVector, w::AbstractVector;S::Type = Float64) + M = Matrix{S}(undef, length(v), length(w)) inner!(M, v, w) return M end -#= -n=10000000; -m=10; -x = [rand(n) for i in 1:m]; -y = [rand(n) for i in 1:m]; -M = blockinner(x,y); -Mh = hcat(x...)' * hcat(y...); +function Base.copyto!(x::InnerProductVec, y::InnerProductVec) + @assert x.dotf == y.dotf "Dot functions must match" + copyto!(x.vec, y.vec) + return x +end -norm(M - Mh) -=# \ No newline at end of file diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 4689c85c..e3384c67 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -8,41 +8,41 @@ A = (A + A') / 2 v = rand(T, (n,)) n1 = div(n, 2) - alg = Lanczos(; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), - verbosity=2) + alg = Lanczos(; orth = orth, krylovdim = n, maxiter = 1, tol = tolerance(T), + verbosity = 2) D1, V1, info = @test_logs (:info,) eigsolve(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), n1, :SR, alg) - alg = Lanczos(; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), - verbosity=1) + wrapvec(v, Val(mode)), n1, :SR, alg) + alg = Lanczos(; orth = orth, krylovdim = n, maxiter = 1, tol = tolerance(T), + verbosity = 1) @test_logs eigsolve(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), n1, :SR, alg) - alg = Lanczos(; orth=orth, krylovdim=n1 + 1, maxiter=1, tol=tolerance(T), - verbosity=1) + wrapvec(v, Val(mode)), n1, :SR, alg) + alg = Lanczos(; orth = orth, krylovdim = n1 + 1, maxiter = 1, tol = tolerance(T), + verbosity = 1) @test_logs (:warn,) eigsolve(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), n1, :SR, alg) - alg = Lanczos(; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), - verbosity=2) + wrapvec(v, Val(mode)), n1, :SR, alg) + alg = Lanczos(; orth = orth, krylovdim = n, maxiter = 1, tol = tolerance(T), + verbosity = 2) @test_logs (:info,) eigsolve(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), n1, :SR, alg) - alg = Lanczos(; orth=orth, krylovdim=n1, maxiter=3, tol=tolerance(T), - verbosity=3) + wrapvec(v, Val(mode)), n1, :SR, alg) + alg = Lanczos(; orth = orth, krylovdim = n1, maxiter = 3, tol = tolerance(T), + verbosity = 3) @test_logs((:info,), (:info,), (:info,), (:warn,), - eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), 1, :SR, alg)) - alg = Lanczos(; orth=orth, krylovdim=4, maxiter=1, tol=tolerance(T), - verbosity=4) + eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), 1, :SR, alg)) + alg = Lanczos(; orth = orth, krylovdim = 4, maxiter = 1, tol = tolerance(T), + verbosity = 4) # since it is impossible to know exactly the size of the Krylov subspace after shrinking, # we only know the output for a sigle iteration @test_logs((:info,), (:info,), (:info,), (:info,), (:info,), (:warn,), - eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), 1, :SR, alg)) + eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), 1, :SR, alg)) - @test KrylovKit.eigselector(wrapop(A, Val(mode)), scalartype(v); krylovdim=n, - maxiter=1, - tol=tolerance(T), ishermitian=true) isa Lanczos + @test KrylovKit.eigselector(wrapop(A, Val(mode)), scalartype(v); krylovdim = n, + maxiter = 1, + tol = tolerance(T), ishermitian = true) isa Lanczos n2 = n - n1 - alg = Lanczos(; krylovdim=2 * n, maxiter=1, tol=tolerance(T)) + alg = Lanczos(; krylovdim = 2 * n, maxiter = 1, tol = tolerance(T)) D2, V2, info = @constinferred eigsolve(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), - n2, :LR, alg) + wrapvec(v, Val(mode)), + n2, :LR, alg) @test vcat(D1[1:n1], reverse(D2[1:n2])) ≊ eigvals(A) U1 = stack(unwrapvec, V1) @@ -53,10 +53,10 @@ @test A * U1 ≈ U1 * Diagonal(D1) @test A * U2 ≈ U2 * Diagonal(D2) - alg = Lanczos(; orth=orth, krylovdim=2n, maxiter=1, tol=tolerance(T), - verbosity=1) + alg = Lanczos(; orth = orth, krylovdim = 2n, maxiter = 1, tol = tolerance(T), + verbosity = 1) @test_logs (:warn,) (:warn,) eigsolve(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), n + 1, :LM, alg) + wrapvec(v, Val(mode)), n + 1, :LM, alg) end end end @@ -70,19 +70,19 @@ end A = rand(T, (N, N)) .- one(T) / 2 A = (A + A') / 2 v = rand(T, (N,)) - alg = Lanczos(; krylovdim=2 * n, maxiter=10, - tol=tolerance(T), eager=true, verbosity=0) + alg = Lanczos(; krylovdim = 2 * n, maxiter = 10, + tol = tolerance(T), eager = true, verbosity = 0) D1, V1, info1 = @constinferred eigsolve(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), n, :SR, alg) + wrapvec(v, Val(mode)), n, :SR, alg) D2, V2, info2 = eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n, :LR, - alg) + alg) l1 = info1.converged l2 = info2.converged @test l1 > 0 @test l2 > 0 @test D1[1:l1] ≈ eigvals(A)[1:l1] - @test D2[1:l2] ≈ eigvals(A)[N:-1:(N - l2 + 1)] + @test D2[1:l2] ≈ eigvals(A)[N:-1:(N-l2+1)] U1 = stack(unwrapvec, V1) U2 = stack(unwrapvec, V2) @@ -106,45 +106,45 @@ end A = rand(T, (n, n)) .- one(T) / 2 v = rand(T, (n,)) n1 = div(n, 2) - alg = Arnoldi(; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T)) + alg = Arnoldi(; orth = orth, krylovdim = n, maxiter = 1, tol = tolerance(T)) D1, V1, info1 = @constinferred eigsolve(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), n1, :SR, alg) + wrapvec(v, Val(mode)), n1, :SR, alg) - alg = Arnoldi(; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), - verbosity=0) + alg = Arnoldi(; orth = orth, krylovdim = n, maxiter = 1, tol = tolerance(T), + verbosity = 0) @test_logs eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n1, :SR, alg) - alg = Arnoldi(; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), - verbosity=1) + alg = Arnoldi(; orth = orth, krylovdim = n, maxiter = 1, tol = tolerance(T), + verbosity = 1) @test_logs eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n1, :SR, alg) - alg = Arnoldi(; orth=orth, krylovdim=n1 + 2, maxiter=1, tol=tolerance(T), - verbosity=1) + alg = Arnoldi(; orth = orth, krylovdim = n1 + 2, maxiter = 1, tol = tolerance(T), + verbosity = 1) @test_logs (:warn,) eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n1, - :SR, alg) - alg = Arnoldi(; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), - verbosity=2) + :SR, alg) + alg = Arnoldi(; orth = orth, krylovdim = n, maxiter = 1, tol = tolerance(T), + verbosity = 2) @test_logs (:info,) eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n1, - :SR, alg) - alg = Arnoldi(; orth=orth, krylovdim=n1, maxiter=3, tol=tolerance(T), - verbosity=3) + :SR, alg) + alg = Arnoldi(; orth = orth, krylovdim = n1, maxiter = 3, tol = tolerance(T), + verbosity = 3) @test_logs((:info,), (:info,), (:info,), (:warn,), - eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), 1, :SR, alg)) - alg = Arnoldi(; orth=orth, krylovdim=4, maxiter=1, tol=tolerance(T), - verbosity=4) + eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), 1, :SR, alg)) + alg = Arnoldi(; orth = orth, krylovdim = 4, maxiter = 1, tol = tolerance(T), + verbosity = 4) # since it is impossible to know exactly the size of the Krylov subspace after shrinking, # we only know the output for a sigle iteration @test_logs((:info,), (:info,), (:info,), (:info,), (:info,), (:warn,), - eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), 1, :SR, alg)) + eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), 1, :SR, alg)) - @test KrylovKit.eigselector(wrapop(A, Val(mode)), eltype(v); orth=orth, - krylovdim=n, maxiter=1, - tol=tolerance(T)) isa Arnoldi + @test KrylovKit.eigselector(wrapop(A, Val(mode)), eltype(v); orth = orth, + krylovdim = n, maxiter = 1, + tol = tolerance(T)) isa Arnoldi n2 = n - n1 - alg = Arnoldi(; orth=orth, krylovdim=2 * n, maxiter=1, tol=tolerance(T)) + alg = Arnoldi(; orth = orth, krylovdim = 2 * n, maxiter = 1, tol = tolerance(T)) D2, V2, info2 = @constinferred eigsolve(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), n2, :LR, alg) - D = sort(sort(eigvals(A); by=imag, rev=true); alg=MergeSort, by=real) - D2′ = sort(sort(D2; by=imag, rev=true); alg=MergeSort, by=real) - @test vcat(D1[1:n1], D2′[(end - n2 + 1):end]) ≈ D + wrapvec(v, Val(mode)), n2, :LR, alg) + D = sort(sort(eigvals(A); by = imag, rev = true); alg = MergeSort, by = real) + D2′ = sort(sort(D2; by = imag, rev = true); alg = MergeSort, by = real) + @test vcat(D1[1:n1], D2′[(end-n2+1):end]) ≈ D U1 = stack(unwrapvec, V1) U2 = stack(unwrapvec, V2) @@ -154,13 +154,13 @@ end if T <: Complex n1 = div(n, 2) D1, V1, info = eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n1, - :SI, - alg) + :SI, + alg) n2 = n - n1 D2, V2, info = eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n2, - :LI, - alg) - D = sort(eigvals(A); by=imag) + :LI, + alg) + D = sort(eigvals(A); by = imag) @test vcat(D1[1:n1], reverse(D2[1:n2])) ≊ D @@ -170,10 +170,10 @@ end @test A * U2 ≈ U2 * Diagonal(D2) end - alg = Arnoldi(; orth=orth, krylovdim=2n, maxiter=1, tol=tolerance(T), - verbosity=1) + alg = Arnoldi(; orth = orth, krylovdim = 2n, maxiter = 1, tol = tolerance(T), + verbosity = 1) @test_logs (:warn,) (:warn,) eigsolve(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), n + 1, :LM, alg) + wrapvec(v, Val(mode)), n + 1, :LM, alg) end end end @@ -186,15 +186,15 @@ end @testset for orth in orths A = rand(T, (N, N)) .- one(T) / 2 v = rand(T, (N,)) - alg = Arnoldi(; krylovdim=3 * n, maxiter=20, - tol=tolerance(T), eager=true, verbosity=0) + alg = Arnoldi(; krylovdim = 3 * n, maxiter = 20, + tol = tolerance(T), eager = true, verbosity = 0) D1, V1, info1 = @constinferred eigsolve(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), n, :SR, alg) + wrapvec(v, Val(mode)), n, :SR, alg) D2, V2, info2 = eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n, :LR, - alg) + alg) D3, V3, info3 = eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n, :LM, - alg) - D = sort(eigvals(A); by=imag, rev=true) + alg) + D = sort(eigvals(A); by = imag, rev = true) l1 = info1.converged l2 = info2.converged @@ -202,11 +202,11 @@ end @test l1 > 0 @test l2 > 0 @test l3 > 0 - @test D1[1:l1] ≊ sort(D; alg=MergeSort, by=real)[1:l1] - @test D2[1:l2] ≊ sort(D; alg=MergeSort, by=real, rev=true)[1:l2] + @test D1[1:l1] ≊ sort(D; alg = MergeSort, by = real)[1:l1] + @test D2[1:l2] ≊ sort(D; alg = MergeSort, by = real, rev = true)[1:l2] # sorting by abs does not seem very reliable if two distinct eigenvalues are close # in absolute value, so we perform a second sort afterwards using the real part - @test D3[1:l3] ≊ sort(D; by=abs, rev=true)[1:l3] + @test D3[1:l3] ≊ sort(D; by = abs, rev = true)[1:l3] U1 = stack(unwrapvec, V1) U2 = stack(unwrapvec, V2) @@ -220,17 +220,17 @@ end if T <: Complex D1, V1, info1 = eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n, - :SI, alg) + :SI, alg) D2, V2, info2 = eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n, - :LI, alg) + :LI, alg) D = eigvals(A) l1 = info1.converged l2 = info2.converged @test l1 > 0 @test l2 > 0 - @test D1[1:l1] ≈ sort(D; by=imag)[1:l1] - @test D2[1:l2] ≈ sort(D; by=imag, rev=true)[1:l2] + @test D1[1:l1] ≈ sort(D; by = imag)[1:l1] + @test D2[1:l2] ≈ sort(D; by = imag, rev = true)[1:l2] U1 = stack(unwrapvec, V1) U2 = stack(unwrapvec, V2) @@ -253,27 +253,27 @@ end D = randn(T, N) A = V * Diagonal(D) / V v = rand(T, (N,)) - alg = Arnoldi(; krylovdim=3 * n, maxiter=20, - tol=tolerance(T), eager=true, verbosity=0) + alg = Arnoldi(; krylovdim = 3 * n, maxiter = 20, + tol = tolerance(T), eager = true, verbosity = 0) D1, V1, info1 = @constinferred realeigsolve(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), n, :SR, alg) + wrapvec(v, Val(mode)), n, :SR, alg) D2, V2, info2 = realeigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n, - :LR, - alg) + :LR, + alg) D3, V3, info3 = realeigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n, - :LM, - alg) + :LM, + alg) l1 = info1.converged l2 = info2.converged l3 = info3.converged @test l1 > 0 @test l2 > 0 @test l3 > 0 - @test D1[1:l1] ≊ sort(D; alg=MergeSort)[1:l1] - @test D2[1:l2] ≊ sort(D; alg=MergeSort, rev=true)[1:l2] + @test D1[1:l1] ≊ sort(D; alg = MergeSort)[1:l1] + @test D2[1:l2] ≊ sort(D; alg = MergeSort, rev = true)[1:l2] # sorting by abs does not seem very reliable if two distinct eigenvalues are close # in absolute value, so we perform a second sort afterwards using the real part - @test D3[1:l3] ≊ sort(D; by=abs, rev=true)[1:l3] + @test D3[1:l3] ≊ sort(D; by = abs, rev = true)[1:l3] @test eltype(D1) == T @test eltype(D2) == T @@ -297,12 +297,12 @@ end J = [Z -I; I Z] Ar1 = (Ar - J * Ar * J) / 2 Ar2 = (Ar + J * Ar * J) / 2 - A = complex.(Ar1[1:N, 1:N], -Ar1[1:N, (N + 1):end]) - B = complex.(Ar2[1:N, 1:N], +Ar2[1:N, (N + 1):end]) + A = complex.(Ar1[1:N, 1:N], -Ar1[1:N, (N+1):end]) + B = complex.(Ar2[1:N, 1:N], +Ar2[1:N, (N+1):end]) f = buildrealmap(A, B) v = rand(complex(T), (N,)) - alg = Arnoldi(; krylovdim=3 * n, maxiter=20, - tol=tolerance(T), eager=true, verbosity=0) + alg = Arnoldi(; krylovdim = 3 * n, maxiter = 20, + tol = tolerance(T), eager = true, verbosity = 0) D1, V1, info1 = @constinferred realeigsolve(f, v, n, :SR, alg) D2, V2, info2 = realeigsolve(f, v, n, :LR, alg) D3, V3, info3 = realeigsolve(f, v, n, :LM, alg) @@ -313,11 +313,11 @@ end @test l1 > 0 @test l2 > 0 @test l3 > 0 - @test D1[1:l1] ≊ sort(D; alg=MergeSort)[1:l1] - @test D2[1:l2] ≊ sort(D; alg=MergeSort, rev=true)[1:l2] + @test D1[1:l1] ≊ sort(D; alg = MergeSort)[1:l1] + @test D2[1:l2] ≊ sort(D; alg = MergeSort, rev = true)[1:l2] # sorting by abs does not seem very reliable if two distinct eigenvalues are close # in absolute value, so we perform a second sort afterwards using the real part - @test D3[1:l3] ≊ sort(D; by=abs, rev=true)[1:l3] + @test D3[1:l3] ≊ sort(D; by = abs, rev = true)[1:l3] @test eltype(D1) == T @test eltype(D2) == T @@ -342,12 +342,12 @@ end A[2, 1] = 1e-9 A[1, 2] = -1e-9 v = ones(Float64, size(A, 1)) - @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-8, verbosity=0)) - @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-8, verbosity=1)) - @test_logs (:info,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-8, verbosity=2)) - @test_logs (:warn,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-10, verbosity=1)) + @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol = 1e-8, verbosity = 0)) + @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol = 1e-8, verbosity = 1)) + @test_logs (:info,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol = 1e-8, verbosity = 2)) + @test_logs (:warn,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol = 1e-10, verbosity = 1)) @test_logs (:warn,) (:info,) realeigsolve(A, v, 1, :LM, - Arnoldi(; tol=1e-10, verbosity=2)) + Arnoldi(; tol = 1e-10, verbosity = 2)) # this should not trigger a warning A[1, 2] = A[2, 1] = 0 @@ -355,9 +355,9 @@ end A[2, 2] = A[3, 3] = 0.99 A[3, 2] = 1e-6 A[2, 3] = -1e-6 - @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=0)) - @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=1)) - @test_logs (:info,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=2)) + @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol = 1e-12, verbosity = 0)) + @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol = 1e-12, verbosity = 1)) + @test_logs (:info,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol = 1e-12, verbosity = 2)) end @testset "Block Lanczos - eigsolve for large sparse matrix and map input" begin @@ -376,8 +376,8 @@ end end return xstrings, zstrings end - - function pauli_kron(n::Int, ops::Pair{Int,Char}...) + + function pauli_kron(n::Int, ops::Pair{Int, Char}...) mat = sparse(1.0I, 2^n, 2^n) for (pos, op) in ops if op == 'X' @@ -391,310 +391,175 @@ end else error("Unknown Pauli operator $op") end - + left = sparse(1.0I, 2^(pos - 1), 2^(pos - 1)) right = sparse(1.0I, 2^(n - pos), 2^(n - pos)) mat = kron(left, kron(σ, right)) * mat end return mat end - + # define the function to construct the Hamiltonian matrix function toric_code_hamiltonian_matrix(m::Int, n::Int) xstrings, zstrings = toric_code_strings(m, n) N = 2 * m * n # total number of qubits - + # initialize the Hamiltonian matrix as a zero matrix H = spzeros(2^N, 2^N) - + # add the X-type operator terms - for xs in xstrings[1:(end - 1)] + for xs in xstrings[1:(end-1)] ops = [i => 'X' for i in xs] H += pauli_kron(N, ops...) end - - for zs in zstrings[1:(end - 1)] + + for zs in zstrings[1:(end-1)] ops = [i => 'Z' for i in zs] H += pauli_kron(N, ops...) end - + return H end - + Random.seed!(4) sites_num = 3 p = 5 # block size - X1 = Matrix(qr(rand(2^(2*sites_num^2), p)).Q) + X1 = Matrix(qr(rand(2^(2 * sites_num^2), p)).Q) get_value_num = 10 tol = 1e-6 h_mat = toric_code_hamiltonian_matrix(sites_num, sites_num) # matrix input D, U, info = eigsolve(-h_mat, X1, get_value_num, :SR, - Lanczos(; maxiter=20, tol=tol)) + Lanczos(; maxiter = 20, tol = tol, blockmode = true)) @show D[1:get_value_num] @test count(x -> abs(x + 16.0) < 2.0 - tol, D[1:get_value_num]) == 4 @test count(x -> abs(x + 16.0) < tol, D[1:get_value_num]) == 4 # map input D, U, info = eigsolve(x -> -h_mat * x, X1, get_value_num, :SR, - Lanczos(; maxiter=20, tol=tol)) + Lanczos(; maxiter = 20, tol = tol, blockmode = true)) @show D[1:get_value_num] @test count(x -> abs(x + 16.0) < 1.9, D[1:get_value_num]) == 4 @test count(x -> abs(x + 16.0) < 1e-8, D[1:get_value_num]) == 4 - -end -using Test, SparseArrays, Random, LinearAlgebra, Profile -using KrylovKit +end -@testset "Block Lanczos - eigsolve full ($mode)" for mode in (:vector, :inplace, :outplace) - scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : - (ComplexF64,) - orths = mode === :vector ? (mgs2,) : (mgs2,) # Block Lanczos 只支持 mgs2 - @testset for T in scalartypes - @testset for orth in orths - A = rand(T, (n, n)) .- one(T) / 2 - A = (A + A') / 2 # 确保矩阵是对称/厄密的 - - # 创建初始块向量(矩阵形式,每列是一个向量) - block_size = 2 # 块大小 - X = rand(T, (n, block_size)) - - # 如果是 :vector 模式,将矩阵转换为向量数组 - x₀ = mode === :vector ? [X[:, i] for i in 1:block_size] : X - - n1 = div(n, 2) # 要求解的特征值数量 - - # 创建 Block Lanczos 算法配置 - alg = Lanczos(; orth=orth, krylovdim=n, maxiter=5, tol=tolerance(T), - verbosity=2) - - # 执行特征值求解 - D1, V1, info = @test_logs (:info,) eigsolve(wrapop(A, Val(mode)), - wrapvec(x₀, Val(mode)), n1, :SR, alg) - - # 测试不同详细级别的日志输出 - alg = Lanczos(; orth=orth, krylovdim=n, maxiter=5, tol=tolerance(T), - verbosity=1) - @test_logs eigsolve(wrapop(A, Val(mode)), - wrapvec(x₀, Val(mode)), n1, :SR, alg) - - # 测试 Krylov 维度较小时的警告 - alg = Lanczos(; orth=orth, krylovdim=n1 + 1, maxiter=5, tol=tolerance(T), - verbosity=1) - @test_logs (:warn,) eigsolve(wrapop(A, Val(mode)), - wrapvec(x₀, Val(mode)), n1, :SR, alg) - - # 测试详细级别为 2 的日志输出 - alg = Lanczos(; orth=orth, krylovdim=n, maxiter=5, tol=tolerance(T), - verbosity=2) - @test_logs (:info,) eigsolve(wrapop(A, Val(mode)), - wrapvec(x₀, Val(mode)), n1, :SR, alg) - - # 测试求解少量特征值时的日志 - alg = Lanczos(; orth=orth, krylovdim=n1, maxiter=3, tol=tolerance(T), - verbosity=3) - @test_logs((:info,), (:info,), (:info,), (:warn,), - eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), 1, :SR, alg)) - - # 计算剩余的特征值 +#= +Why don’t I use the "vector", "inplace", or "outplace" modes here? +In the ordinary Lanczos algorithm, each iteration generates a single Lanczos vector, which is then added to the Lanczos basis. +However, in the block version, I can’t pre-allocate arrays when using the WrapOp type. If I stick with the WrapOp approach, +I would need to wrap each vector in a block individually as [v], which is significantly slower compared to pre-allocating. + +Therefore, instead of using the "vector", "inplace", or "outplace" modes defined in testsetup.jl, +I directly use matrices and define the map function manually for better performance. + +Currently, I only test the out-of-place map because testing the in-place version requires solving the pre-allocation issue. +In the future, I plan to add a non-Hermitian eigenvalue solver and implement more abstract types to improve efficiency. +As a result, I’ve decided to postpone dealing with the in-place test issue for now. +=# + +@testset "Block Lanczos - eigsolve full" begin + @testset for T in [Float32, Float64, ComplexF32, ComplexF64] + Random.seed!(1234) + A0 = rand(T, (n, n)) .- one(T) / 2 + A0 = (A0 + A0') / 2 + block_size = 2 + x₀m = Matrix(qr(rand(T, n, block_size)).Q) + x₀ = [x₀m[:, i] for i in 1:block_size] + n1 = div(n, 2) # eigenvalues to solve + eigvalsA = eigvals(A0) + # Different from Lanczos, we don't set maxiter =1 here because the iteration times in Lanczos + # in in fact in the control of deminsion of Krylov subspace. And we Don't use it. + @testset for A in [A0, x -> A0 * x] + alg = Lanczos(; krylovdim = n, maxiter = 4, tol = tolerance(T), verbosity = 2, blockmode = true) + D1, V1, info = @test_logs (:info,) eigsolve(A, x₀, n1, :SR, alg) + alg = Lanczos(; krylovdim = n, maxiter = 4, tol = tolerance(T), verbosity = 1, blockmode = true) + @test_logs eigsolve(A, x₀, n1, :SR, alg) + alg = Lanczos(; krylovdim = n1 + 1, maxiter = 4, tol = tolerance(T), verbosity = 1, blockmode = true) + @test_logs (:warn,) eigsolve(A, x₀, n1, :SR, alg) + alg = Lanczos(; krylovdim = n, maxiter = 4, tol = tolerance(T), verbosity = 2, blockmode = true) + @test_logs (:info,) eigsolve(A, x₀, n1, :SR, alg) + alg = Lanczos(; krylovdim = n1, maxiter = 4, tol = tolerance(T), verbosity = 3, blockmode = true) + @test_logs((:info,), (:warn,), (:info,), eigsolve(A, x₀, 1, :SR, alg)) + # Because of the _residual! function, I can't make sure the stability of types temporarily. + # So I ignore the test of @constinferred n2 = n - n1 - alg = Lanczos(; krylovdim=2 * n, maxiter=5, tol=tolerance(T)) - D2, V2, info = @constinferred eigsolve(wrapop(A, Val(mode)), - wrapvec(x₀, Val(mode)), - n2, :LR, alg) - - # 验证计算的特征值与直接求解的特征值匹配 - @test vcat(D1[1:n1], reverse(D2[1:n2])) ≊ eigvals(A) - - # 测试特征向量的正交性 - # 对于块向量,我们需要特殊处理 - if mode === :vector - # 将向量数组转换为矩阵 - U1 = hcat([unwrapvec(v) for v in V1]...) - U2 = hcat([unwrapvec(v) for v in V2]...) - else - U1 = stack(unwrapvec, V1) - U2 = stack(unwrapvec, V2) - end - + alg = Lanczos(; krylovdim = 2 * n, maxiter = 4, tol = tolerance(T), blockmode = true) + D2, V2, info = eigsolve(A, x₀, n2, :LR, alg) + D2[1:n2] + @test vcat(D1[1:n1], reverse(D2[1:n2])) ≊ eigvalsA + + U1 = hcat(V1...) + U2 = hcat(V2...) + @test U1' * U1 ≈ I @test U2' * U2 ≈ I - - # 验证特征值和特征向量满足 Av = λv - @test A * U1 ≈ U1 * Diagonal(D1) - @test A * U2 ≈ U2 * Diagonal(D2) - - # 测试请求过多特征值时的警告 - alg = Lanczos(; orth=orth, krylovdim=2n, maxiter=5, tol=tolerance(T), - verbosity=1) - @test_logs (:warn,) (:warn,) eigsolve(wrapop(A, Val(mode)), - wrapvec(x₀, Val(mode)), n + 1, :LM, alg) + + @test (x -> KrylovKit.apply(A, x)).(V1) ≈ D1 .* V1 + @test (x -> KrylovKit.apply(A, x)).(V2) ≈ D2 .* V2 + + alg = Lanczos(; krylovdim = 2n, maxiter = 5, tol = tolerance(T), verbosity = 1, blockmode = true) + @test_logs (:warn,) (:warn,) eigsolve(A, x₀, n + 1, :LM, alg) end end end +# krylovdim is not used in block Lanczos so I don't add eager mode. @testset "Block Lanczos - eigsolve iteratively ($mode)" for mode in (:vector, :inplace, :outplace) - scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : - (ComplexF64,) - orths = mode === :vector ? (mgs2,) : (mgs2,) # Block Lanczos 只支持 mgs2 - @testset for T in scalartypes - @testset for orth in orths - # 创建大型矩阵,用于测试迭代求解 - A = rand(T, (N, N)) .- one(T) / 2 - A = (A + A') / 2 # 确保矩阵是对称/厄密的 - - # 创建初始块向量 - block_size = 2 # 块大小 - X = rand(T, (N, block_size)) - - # 如果是 :vector 模式,将矩阵转换为向量数组 - x₀ = mode === :vector ? [X[:, i] for i in 1:block_size] : X - - # 创建 Block Lanczos 算法配置,启用 eager 模式加速收敛 - alg = Lanczos(; krylovdim=2 * n, maxiter=10, - tol=tolerance(T), eager=true, verbosity=0) - - # 求解最小实部特征值 - D1, V1, info1 = @constinferred eigsolve(wrapop(A, Val(mode)), - wrapvec(x₀, Val(mode)), n, :SR, alg) - - # 求解最大实部特征值 - D2, V2, info2 = eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n, :LR, - alg) - - # 提取收敛的特征值数量 + @testset for T in [Float32, Float64, ComplexF32, ComplexF64] + T = ComplexF32 + A0 = rand(T, (N, N)) .- one(T) / 2 + A0 = (A0 + A0') / 2 + block_size = 5 + x₀m = Matrix(qr(rand(T, N, block_size)).Q) + x₀ = [x₀m[:, i] for i in 1:block_size] + @testset for A in [A0, x -> A0 * x] + A = copy(A0) + + alg = Lanczos(; maxiter = 20, tol = tolerance(T), blockmode = true) + D1, V1, info1 = eigsolve(A, x₀, n, :SR, alg) + D2, V2, info2 = eigsolve(A, x₀, n, :LR, alg) + l1 = info1.converged l2 = info2.converged - - # 验证至少有部分特征值收敛 + @test l1 > 0 @test l2 > 0 - - # 验证计算的特征值与直接求解的特征值匹配 @test D1[1:l1] ≈ eigvals(A)[1:l1] - @test D2[1:l2] ≈ eigvals(A)[N:-1:(N - l2 + 1)] - - # 测试特征向量的正交性 - if mode === :vector - # 将向量数组转换为矩阵 - U1 = hcat([unwrapvec(v) for v in V1]...) - U2 = hcat([unwrapvec(v) for v in V2]...) - - # 转换残差向量 - R1 = hcat([unwrapvec(r) for r in info1.residual]...) - R2 = hcat([unwrapvec(r) for r in info2.residual]...) - else - U1 = stack(unwrapvec, V1) - U2 = stack(unwrapvec, V2) - - R1 = stack(unwrapvec, info1.residual) - R2 = stack(unwrapvec, info2.residual) - end - + @test D2[1:l2] ≈ eigvals(A)[N:-1:(N-l2+1)] + + U1 = hcat(V1[1:l1]...) + U2 = hcat(V2[1:l2]...) + R1 = hcat(info1.residual[1:l1]...) + R2 = hcat(info2.residual[1:l2]...) + @test U1' * U1 ≈ I @test U2' * U2 ≈ I - - # 验证特征值方程 A*v = λ*v + r 含残差项 - @test A * U1 ≈ U1 * Diagonal(D1) + R1 - @test A * U2 ≈ U2 * Diagonal(D2) + R2 - end - end -end + @test hcat([KrylovKit.apply(A, U1[:, i]) for i in 1:l1]...) ≈ U1 * Diagonal(D1) + R1 + @test hcat([KrylovKit.apply(A, U2[:, i]) for i in 1:l2]...) ≈ U2 * Diagonal(D2) + R2 -# 测试块Lanczos在稀疏矩阵上的性能和准确性 -@testset "Block Lanczos - eigsolve for sparse matrices" begin - # 创建一个稀疏的对称矩阵 - N = 100 - A = spzeros(Float64, N, N) - - # 生成带对角线结构的稀疏矩阵 - for i in 1:N - A[i, i] = i # 对角线元素 - if i < N - A[i, i+1] = A[i+1, i] = 0.5 # 次对角线元素 - end - if i < N-5 - A[i, i+5] = A[i+5, i] = 0.1 # 远离对角线的元素 end end - - # 创建多列初始矩阵(块向量) - block_size = 3 - X = rand(Float64, (N, block_size)) - x₀ = [X[:, i] for i in 1:block_size] - - # 使用Block Lanczos求解10个最小特征值 - howmany = 10 - alg = Lanczos(; krylovdim=30, maxiter=20, tol=1e-8, verbosity=1) - - # 求解并验证 - D, V, info = eigsolve(A, x₀, howmany, :SR, alg) - - # 与直接求解的特征值比较 - exact_eigs = eigvals(Array(A))[1:howmany] - @test D[1:howmany] ≈ exact_eigs - - # 验证收敛的特征值数量 - @test info.converged >= howmany/2 # 至少一半应该收敛 - - # 验证残差范数 - @test all(info.normres[1:info.converged] .< 1e-7) - - # 验证特征向量满足特征值方程 - U = hcat([v for v in V]...) - @test norm(A * U - U * Diagonal(D)) < 1e-6 end -# 测试块Lanczos与标准Lanczos的性能比较 -@testset "Block Lanczos vs Standard Lanczos - Performance" begin - # 创建一个中等大小的矩阵用于测试 - N = 50 - A = rand(Float64, (N, N)) - A = (A + A') / 2 # 确保对称性 - - # 单向量初始条件(标准Lanczos) - v = rand(Float64, N) - - # 多向量初始条件(块Lanczos) - block_size = 3 - X = rand(Float64, (N, block_size)) - x₀ = [X[:, i] for i in 1:block_size] - - # 共同的算法参数 - howmany = 5 - krylovdim = 20 - maxiter = 10 - tol = 1e-8 - - # 创建两种算法实例 - std_alg = Lanczos(; krylovdim=krylovdim, maxiter=maxiter, tol=tol, verbosity=0) - block_alg = Lanczos(; krylovdim=krylovdim, maxiter=maxiter, tol=tol, verbosity=0) - - # 执行求解并计时 - std_time = @elapsed begin - std_D, std_V, std_info = eigsolve(A, v, howmany, :SR, std_alg) - end - - block_time = @elapsed begin - block_D, block_V, block_info = eigsolve(A, x₀, howmany, :SR, block_alg) - end - - # 验证结果的正确性 - @test std_D[1:howmany] ≈ block_D[1:howmany] - - # 打印性能比较结果(可选) - @info "Performance comparison:" standard_lanczos=std_time block_lanczos=block_time ratio=block_time/std_time - - # 验证块Lanczos的收敛速度(以迭代次数衡量)通常更快 - if block_info.numiter < std_info.numiter - @info "Block Lanczos converged in fewer iterations" block_iter=block_info.numiter std_iter=std_info.numiter - end - - # 如果块Lanczos计算的更快,我们应该验证这一点 - # 注意:这个测试可能不稳定,取决于系统负载和实现细节 - # @test block_time <= 1.5 * std_time # 允许50%的性能波动 +# linear operator A must satisfies that A'H = HA. it means it's a self-adjoint operator. +@testset "Block Lanczos - eigsolve for abstract type" begin + T = ComplexF64 + H = rand(T, (n, n)) + H = H' * H + I + block_size = 2 + eig_num = 2 + Hip(x::Vector, y::Vector) = x' * H * y + x₀ = [InnerProductVec(rand(T, n), Hip) for i in 1:block_size] + Aip(x::InnerProductVec) = InnerProductVec(H * x.vec, Hip) + D, V, info = eigsolve(Aip, x₀, eig_num, :SR, Lanczos(; krylovdim = n, maxiter = 10, tol = tolerance(T), + verbosity = 0, blockmode = true)) + D_true = eigvals(H) + @test D ≈ D_true[1:eig_num] + @test KrylovKit.blockinner(V, V; S = T) ≈ I + @test findmax([norm(Aip(V[i]) - D[i] * V[i]) for i in 1:eig_num])[1] < tolerance(T) end + diff --git a/test/innerproductvec.jl b/test/innerproductvec.jl index e69de29b..842a928a 100644 --- a/test/innerproductvec.jl +++ b/test/innerproductvec.jl @@ -0,0 +1,40 @@ +#= +inner!(M,x,y): +M[i,j] = inner(x[i],y[j]) +=# +@testset "inner! for non-full vectors $T" for T in (Float32, Float64, ComplexF32, ComplexF64) + A = [rand(T, N) for _ in 1:n] + B = [rand(T, N) for _ in 1:n] + M = Matrix{T}(undef, n, n) + KrylovKit.inner!(M, A, B) + M0 = hcat(A...)' * hcat(B...) + @test eltype(M) == T + @test isapprox(M, M0; atol = 1e4 * eps(real(T))) +end + +@testset "inner! for abstract inner product" begin + T = ComplexF64 + H = rand(T, N, N); + H = H'*H + I; + H = (H + H')/2; + ip(x,y) = x'*H*y + X₁ = InnerProductVec(rand(T, N), ip); + X = [similar(X₁) for _ in 1:n]; + X[1] = X₁; + for i in 2:n + X[i] = InnerProductVec(rand(T, N), ip) + end + Y₁ = InnerProductVec(rand(T, N), ip); + Y = [similar(Y₁) for _ in 1:n]; + Y[1] = Y₁; + for i in 2:n + Y[i] = InnerProductVec(rand(T, N), ip) + end + M = Matrix{T}(undef, n, n); + KrylovKit.inner!(M, X, Y); + Xm = hcat([X[i].vec for i in 1:n]...); + Ym = hcat([Y[i].vec for i in 1:n]...); + M0 = Xm' * H * Ym; + @test eltype(M) == T + @test isapprox(M, M0; atol = eps(real(T))^(0.5)) +end \ No newline at end of file diff --git a/test/orthonormal.jl b/test/orthonormal.jl index 7f9247c6..2d83c163 100644 --- a/test/orthonormal.jl +++ b/test/orthonormal.jl @@ -1,16 +1,54 @@ @testset "abstract_qr! for non-full vectors $T" for T in (Float32, Float64, ComplexF32, ComplexF64) - n = 1000 - m = 10 - A = rand(T, n, m) + T = ComplexF32 + A = rand(T, N, n) B = copy(A) Av = [A[:, i] for i in 1:size(A, 2)] # A is a non-full rank matrix - Av[m÷2] = sum(Av[m÷2+1:end] .* rand(T, m - m ÷ 2)) + Av[n÷2] = sum(Av[n÷2+1:end] .* rand(T, n - n ÷ 2)) Bv = copy(Av) R, gi = KrylovKit.abstract_qr!(Av, T) - @test length(gi) < m + @test length(gi) < n @test eltype(R) == eltype(eltype(A)) == T @test isapprox(hcat(Av[gi]...) * R, hcat(Bv...); atol = 1e4 * eps(real(T))) @test isapprox(hcat(Av[gi]...)' * hcat(Av[gi]...), I; atol = 1e4 * eps(real(T))) end +@testset "abstract_qr! for abstract inner product" begin + T = ComplexF64 + H = rand(T, N, N); + H = H'*H + I; + H = (H + H')/2; + ip(x,y) = x'*H*y + X₁ = InnerProductVec(rand(T, N), ip) + X = [similar(X₁) for _ in 1:n]; + X[1] = X₁ + for i in 2:n-1 + X[i] = InnerProductVec(rand(T, N), ip) + end + + # Make sure X is not full rank + X[end] = sum(X[1:end-1] .* rand(T, n-1)) + Xcopy = deepcopy(X) + R, gi = KrylovKit.abstract_qr!(X, T) + + @test length(gi) < n + @test eltype(R) == T + @test isapprox(KrylovKit.blockinner(X[gi],X[gi],S = T), I; atol=1e4*eps(real(T))) + ΔX = norm.(mul_test(X[gi],R) - Xcopy) + @test isapprox(norm(ΔX), T(0); atol=1e4*eps(real(T))) +end + +@testset "ortho_basis! for abstract inner product" begin + T = ComplexF64 + H = rand(T, N, N); + H = H'*H + I; + H = (H + H')/2; + ip(x,y) = x'*H*y + x₀ = [InnerProductVec(rand(T, N), ip) for i in 1:n] + x₁ = [InnerProductVec(rand(T, N), ip) for i in 1:2*n] + tmp = zeros(T, 2*n, n) + KrylovKit.abstract_qr!(x₁, T) + KrylovKit.ortho_basis!(x₀, x₁, tmp) + @test norm(KrylovKit.blockinner(x₀, x₁; S = T)) < eps(real(T))^0.5 +end + diff --git a/test/testsetup.jl b/test/testsetup.jl index 57f88b8f..af5437be 100644 --- a/test/testsetup.jl +++ b/test/testsetup.jl @@ -78,6 +78,12 @@ function wrapop(A, ::Val{mode}) where {mode} end end +# block operations +# ---------------- +function mul_test(v::AbstractVector, A::AbstractMatrix) + return [sum(v .* A[:,i]) for i in axes(A,2)] +end + if VERSION < v"1.9" stack(f, itr) = mapreduce(f, hcat, itr) stack(itr) = reduce(hcat, itr) From 61569850f6fe58ebc55b0fa1b64eea40516637b5 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Mon, 7 Apr 2025 04:07:49 +0800 Subject: [PATCH 09/82] format --- src/innerproductvec.jl | 28 ++++++++--------- src/orthonormal.jl | 68 ++++++++++++++++++++++-------------------- 2 files changed, 49 insertions(+), 47 deletions(-) diff --git a/src/innerproductvec.jl b/src/innerproductvec.jl index 1cabf0a4..a1c53384 100644 --- a/src/innerproductvec.jl +++ b/src/innerproductvec.jl @@ -67,15 +67,15 @@ function Base.copy!(w::InnerProductVec{F}, v::InnerProductVec{F}) where {F} end function LinearAlgebra.mul!(w::InnerProductVec{F}, - a::Number, - v::InnerProductVec{F}) where {F} + a::Number, + v::InnerProductVec{F}) where {F} mul!(w.vec, a, v.vec) return w end function LinearAlgebra.mul!(w::InnerProductVec{F}, - v::InnerProductVec{F}, - a::Number) where {F} + v::InnerProductVec{F}, + a::Number) where {F} mul!(w.vec, v.vec, a) return w end @@ -94,15 +94,15 @@ function LinearAlgebra.mul!(A::AbstractVector{T},B::AbstractVector{T},M::Abstrac end function LinearAlgebra.axpy!(a::Number, - v::InnerProductVec{F}, - w::InnerProductVec{F}) where {F} + v::InnerProductVec{F}, + w::InnerProductVec{F}) where {F} axpy!(a, v.vec, w.vec) return w end function LinearAlgebra.axpby!(a::Number, - v::InnerProductVec{F}, - b, - w::InnerProductVec{F}) where {F} + v::InnerProductVec{F}, + b, + w::InnerProductVec{F}) where {F} axpby!(a, v.vec, b, w.vec) return w end @@ -128,25 +128,25 @@ function VectorInterface.scale!(v::InnerProductVec, a::Number) return v end function VectorInterface.scale!!(w::InnerProductVec{F}, v::InnerProductVec{F}, - a::Number) where {F} + a::Number) where {F} return InnerProductVec(scale!!(w.vec, v.vec, a), w.dotf) end function VectorInterface.scale!(w::InnerProductVec{F}, v::InnerProductVec{F}, - a::Number) where {F} + a::Number) where {F} scale!(w.vec, v.vec, a) return w end function VectorInterface.add(v::InnerProductVec{F}, w::InnerProductVec{F}, a::Number, - b::Number) where {F} + b::Number) where {F} return InnerProductVec(add(v.vec, w.vec, a, b), v.dotf) end function VectorInterface.add!!(v::InnerProductVec{F}, w::InnerProductVec{F}, a::Number, - b::Number) where {F} + b::Number) where {F} return InnerProductVec(add!!(v.vec, w.vec, a, b), v.dotf) end function VectorInterface.add!(v::InnerProductVec{F}, w::InnerProductVec{F}, a::Number, - b::Number) where {F} + b::Number) where {F} add!(v.vec, w.vec, a, b) return v end diff --git a/src/orthonormal.jl b/src/orthonormal.jl index 9a8ea13d..ec678944 100644 --- a/src/orthonormal.jl +++ b/src/orthonormal.jl @@ -67,7 +67,7 @@ const BLOCKSIZE = 4096 # this uses functionality beyond VectorInterface, but can be faster _use_multithreaded_array_kernel(y) = _use_multithreaded_array_kernel(typeof(y)) _use_multithreaded_array_kernel(::Type) = false -function _use_multithreaded_array_kernel(::Type{<:Array{T}}) where {T <: Number} +function _use_multithreaded_array_kernel(::Type{<:Array{T}}) where {T<:Number} return isbitstype(T) && get_num_threads() > 1 end function _use_multithreaded_array_kernel(::Type{<:OrthonormalBasis{T}}) where {T} @@ -88,11 +88,11 @@ projecting the vector `x` onto the subspace spanned by `b`; more specifically th for all ``j ∈ r``. """ function project!!(y::AbstractVector, - b::OrthonormalBasis, - x, - α::Number = true, - β::Number = false, - r = Base.OneTo(length(b))) + b::OrthonormalBasis, + x, + α::Number = true, + β::Number = false, + r = Base.OneTo(length(b))) # no specialized routine for IndexLinear x because reduction dimension is large dimension length(y) == length(r) || throw(DimensionMismatch()) if get_num_threads() > 1 @@ -134,11 +134,11 @@ this computes ``` """ function unproject!!(y, - b::OrthonormalBasis, - x::AbstractVector, - α::Number = true, - β::Number = false, - r = Base.OneTo(length(b))) + b::OrthonormalBasis, + x::AbstractVector, + α::Number = true, + β::Number = false, + r = Base.OneTo(length(b))) if _use_multithreaded_array_kernel(y) return unproject_linear_multithreaded!(y, b, x, α, β, r) end @@ -155,11 +155,12 @@ function unproject!!(y, return y end function unproject_linear_multithreaded!(y::AbstractArray, - b::OrthonormalBasis{<:AbstractArray}, - x::AbstractVector, - α::Number = true, - β::Number = false, - r = Base.OneTo(length(b))) + +b::OrthonormalBasis{<:AbstractArray}, + x::AbstractVector, + α::Number = true, + β::Number = false, + r = Base.OneTo(length(b))) # multi-threaded implementation, similar to BLAS level 2 matrix vector multiplication m = length(y) n = length(r) @@ -180,12 +181,13 @@ function unproject_linear_multithreaded!(y::AbstractArray, return y end function unproject_linear_kernel!(y::AbstractArray, - b::OrthonormalBasis{<:AbstractArray}, - x::AbstractVector, - I, - α::Number, - β::Number, - r) + +b::OrthonormalBasis{<:AbstractArray}, + x::AbstractVector, + I, + α::Number, + β::Number, + r) @inbounds begin if β == 0 @simd for i in I @@ -219,11 +221,11 @@ Perform a rank 1 update of a basis `b`, i.e. update the basis vectors as It is the user's responsibility to make sure that the result is still an orthonormal basis. """ @fastmath function rank1update!(b::OrthonormalBasis, - y, - x::AbstractVector, - α::Number = true, - β::Number = true, - r = Base.OneTo(length(b))) + y, +x::AbstractVector, + α::Number=true, + β::Number=true, + r = Base.OneTo(length(b))) if _use_multithreaded_array_kernel(y) return rank1update_linear_multithreaded!(b, y, x, α, β, r) end @@ -241,11 +243,11 @@ It is the user's responsibility to make sure that the result is still an orthono return b end @fastmath function rank1update_linear_multithreaded!(b::OrthonormalBasis{<:AbstractArray}, - y::AbstractArray, - x::AbstractVector, - α::Number, - β::Number, - r) + y::AbstractArray, +x::AbstractVector, + α::Number, + β::Number, + r) # multi-threaded implementation, similar to BLAS level 2 matrix vector multiplication m = length(y) n = length(r) @@ -274,7 +276,7 @@ end end if I + blocksize - 1 <= m @simd for i in Base.OneTo(blocksize) - Vj[I-1+i] += y[I-1+i] * xj + Vj[I - 1 + i] += y[I - 1 + i] * xj end else @simd for i in I:m From e4c2d64ef100a28f1b7ab2ef2461fee791ef68fd Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Mon, 7 Apr 2025 04:15:50 +0800 Subject: [PATCH 10/82] revise format --- src/orthonormal.jl | 98 +++++++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 50 deletions(-) diff --git a/src/orthonormal.jl b/src/orthonormal.jl index ec678944..13ebdb7c 100644 --- a/src/orthonormal.jl +++ b/src/orthonormal.jl @@ -90,9 +90,9 @@ for all ``j ∈ r``. function project!!(y::AbstractVector, b::OrthonormalBasis, x, - α::Number = true, - β::Number = false, - r = Base.OneTo(length(b))) + α::Number=true, + β::Number=false, + r=Base.OneTo(length(b))) # no specialized routine for IndexLinear x because reduction dimension is large dimension length(y) == length(r) || throw(DimensionMismatch()) if get_num_threads() > 1 @@ -136,9 +136,9 @@ this computes function unproject!!(y, b::OrthonormalBasis, x::AbstractVector, - α::Number = true, - β::Number = false, - r = Base.OneTo(length(b))) + α::Number=true, + β::Number=false, + r=Base.OneTo(length(b))) if _use_multithreaded_array_kernel(y) return unproject_linear_multithreaded!(y, b, x, α, β, r) end @@ -154,13 +154,12 @@ function unproject!!(y, end return y end -function unproject_linear_multithreaded!(y::AbstractArray, - -b::OrthonormalBasis{<:AbstractArray}, +function unproject_linear_multithreaded!(y::AbstractArray, + b::OrthonormalBasis{<:AbstractArray}, x::AbstractVector, - α::Number = true, - β::Number = false, - r = Base.OneTo(length(b))) + α::Number=true, + β::Number=false, + r=Base.OneTo(length(b))) # multi-threaded implementation, similar to BLAS level 2 matrix vector multiplication m = length(y) n = length(r) @@ -180,9 +179,8 @@ b::OrthonormalBasis{<:AbstractArray}, end return y end -function unproject_linear_kernel!(y::AbstractArray, - -b::OrthonormalBasis{<:AbstractArray}, +function unproject_linear_kernel!(y::AbstractArray, + b::OrthonormalBasis{<:AbstractArray}, x::AbstractVector, I, α::Number, @@ -222,10 +220,10 @@ It is the user's responsibility to make sure that the result is still an orthono """ @fastmath function rank1update!(b::OrthonormalBasis, y, -x::AbstractVector, + x::AbstractVector, α::Number=true, β::Number=true, - r = Base.OneTo(length(b))) + r=Base.OneTo(length(b))) if _use_multithreaded_array_kernel(y) return rank1update_linear_multithreaded!(b, y, x, α, β, r) end @@ -244,7 +242,7 @@ x::AbstractVector, end @fastmath function rank1update_linear_multithreaded!(b::OrthonormalBasis{<:AbstractArray}, y::AbstractArray, -x::AbstractVector, + x::AbstractVector, α::Number, β::Number, r) @@ -338,7 +336,7 @@ function basistransform!(b::OrthonormalBasis{T}, U::AbstractMatrix) where {T} # end function basistransform_linear_multithreaded!(b::OrthonormalBasis{<:AbstractArray}, - U::AbstractMatrix) # U should be unitary or isometric + U::AbstractMatrix) # U should be unitary or isometric m, n = size(U) m == length(b) || throw(DimensionMismatch()) K = length(b[1]) @@ -392,17 +390,17 @@ function orthogonalize!!(v::T, b::OrthonormalBasis{T}, alg::Orthogonalizer) wher end function orthogonalize!!(v::T, - b::OrthonormalBasis{T}, - x::AbstractVector, - ::ClassicalGramSchmidt) where {T} + b::OrthonormalBasis{T}, + x::AbstractVector, + ::ClassicalGramSchmidt) where {T} x = project!!(x, b, v) v = unproject!!(v, b, x, -1, 1) return (v, x) end function reorthogonalize!!(v::T, - b::OrthonormalBasis{T}, - x::AbstractVector, - ::ClassicalGramSchmidt) where {T} + b::OrthonormalBasis{T}, + x::AbstractVector, + ::ClassicalGramSchmidt) where {T} s = similar(x) ## EXTRA ALLOCATION s = project!!(s, b, v) v = unproject!!(v, b, s, -1, 1) @@ -410,16 +408,16 @@ function reorthogonalize!!(v::T, return (v, x) end function orthogonalize!!(v::T, - b::OrthonormalBasis{T}, - x::AbstractVector, - ::ClassicalGramSchmidt2) where {T} + b::OrthonormalBasis{T}, + x::AbstractVector, + ::ClassicalGramSchmidt2) where {T} (v, x) = orthogonalize!!(v, b, x, ClassicalGramSchmidt()) return reorthogonalize!!(v, b, x, ClassicalGramSchmidt()) end function orthogonalize!!(v::T, - b::OrthonormalBasis{T}, - x::AbstractVector, - alg::ClassicalGramSchmidtIR) where {T} + b::OrthonormalBasis{T}, + x::AbstractVector, + alg::ClassicalGramSchmidtIR) where {T} nold = norm(v) (v, x) = orthogonalize!!(v, b, x, ClassicalGramSchmidt()) nnew = norm(v) @@ -432,9 +430,9 @@ function orthogonalize!!(v::T, end function orthogonalize!!(v::T, - b::OrthonormalBasis{T}, - x::AbstractVector, - ::ModifiedGramSchmidt) where {T} + b::OrthonormalBasis{T}, + x::AbstractVector, + ::ModifiedGramSchmidt) where {T} for (i, q) in enumerate(b) s = inner(q, v) v = add!!(v, q, -s) @@ -443,9 +441,9 @@ function orthogonalize!!(v::T, return (v, x) end function reorthogonalize!!(v::T, - b::OrthonormalBasis{T}, - x::AbstractVector, - ::ModifiedGramSchmidt) where {T} + b::OrthonormalBasis{T}, + x::AbstractVector, + ::ModifiedGramSchmidt) where {T} for (i, q) in enumerate(b) s = inner(q, v) v = add!!(v, q, -s) @@ -454,16 +452,16 @@ function reorthogonalize!!(v::T, return (v, x) end function orthogonalize!!(v::T, - b::OrthonormalBasis{T}, - x::AbstractVector, - ::ModifiedGramSchmidt2) where {T} + b::OrthonormalBasis{T}, + x::AbstractVector, + ::ModifiedGramSchmidt2) where {T} (v, x) = orthogonalize!!(v, b, x, ModifiedGramSchmidt()) return reorthogonalize!!(v, b, x, ModifiedGramSchmidt()) end function orthogonalize!!(v::T, - b::OrthonormalBasis{T}, - x::AbstractVector, - alg::ModifiedGramSchmidtIR) where {T} + b::OrthonormalBasis{T}, + x::AbstractVector, + alg::ModifiedGramSchmidtIR) where {T} nold = norm(v) (v, x) = orthogonalize!!(v, b, x, ModifiedGramSchmidt()) nnew = norm(v) @@ -480,15 +478,15 @@ orthogonalize!!(v::T, q::T, alg::Orthogonalizer) where {T} = _orthogonalize!!(v, # avoid method ambiguity on Julia 1.0 according to Aqua.jl function _orthogonalize!!(v::T, - q::T, - alg::Union{ClassicalGramSchmidt, ModifiedGramSchmidt}) where {T} + q::T, + alg::Union{ClassicalGramSchmidt, ModifiedGramSchmidt}) where {T} s = inner(q, v) v = add!!(v, q, -s) return (v, s) end function _orthogonalize!!(v::T, - q::T, - alg::Union{ClassicalGramSchmidt2, ModifiedGramSchmidt2}) where {T} + q::T, + alg::Union{ClassicalGramSchmidt2, ModifiedGramSchmidt2}) where {T} s = inner(q, v) v = add!!(v, q, -s) ds = inner(q, v) @@ -496,8 +494,8 @@ function _orthogonalize!!(v::T, return (v, s + ds) end function _orthogonalize!!(v::T, - q::T, - alg::Union{ClassicalGramSchmidtIR, ModifiedGramSchmidtIR}) where {T} + q::T, + alg::Union{ClassicalGramSchmidtIR, ModifiedGramSchmidtIR}) where {T} nold = norm(v) s = inner(q, v) v = add!!(v, q, -s) @@ -580,7 +578,7 @@ and its concrete subtypes [`ClassicalGramSchmidt`](@ref), [`ModifiedGramSchmidt` orthonormalize, orthonormalize!! function abstract_qr!(block::AbstractVector{T}, S::Type; - tol::Real = 1e4 * eps(real(S))) where {T} + tol::Real = 1e4 * eps(real(S))) where {T} n = length(block) rank_shrink = false idx = ones(Int64,n) From 6ea00005c1a3c2155b1985948e6000cb92b893ea Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Mon, 7 Apr 2025 04:18:11 +0800 Subject: [PATCH 11/82] format revise --- src/orthonormal.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/orthonormal.jl b/src/orthonormal.jl index 13ebdb7c..4f9216cf 100644 --- a/src/orthonormal.jl +++ b/src/orthonormal.jl @@ -154,7 +154,7 @@ function unproject!!(y, end return y end -function unproject_linear_multithreaded!(y::AbstractArray, +function unproject_linear_multithreaded!(y::AbstractArray, b::OrthonormalBasis{<:AbstractArray}, x::AbstractVector, α::Number=true, @@ -179,7 +179,7 @@ function unproject_linear_multithreaded!(y::AbstractArray, end return y end -function unproject_linear_kernel!(y::AbstractArray, +function unproject_linear_kernel!(y::AbstractArray, b::OrthonormalBasis{<:AbstractArray}, x::AbstractVector, I, @@ -479,14 +479,14 @@ orthogonalize!!(v::T, q::T, alg::Orthogonalizer) where {T} = _orthogonalize!!(v, function _orthogonalize!!(v::T, q::T, - alg::Union{ClassicalGramSchmidt, ModifiedGramSchmidt}) where {T} + alg::Union{ClassicalGramSchmidt,ModifiedGramSchmidt}) where {T} s = inner(q, v) v = add!!(v, q, -s) return (v, s) end function _orthogonalize!!(v::T, q::T, - alg::Union{ClassicalGramSchmidt2, ModifiedGramSchmidt2}) where {T} + alg::Union{ClassicalGramSchmidt2,ModifiedGramSchmidt2}) where {T} s = inner(q, v) v = add!!(v, q, -s) ds = inner(q, v) @@ -495,7 +495,7 @@ function _orthogonalize!!(v::T, end function _orthogonalize!!(v::T, q::T, - alg::Union{ClassicalGramSchmidtIR, ModifiedGramSchmidtIR}) where {T} + alg::Union{ClassicalGramSchmidtIR,ModifiedGramSchmidtIR}) where {T} nold = norm(v) s = inner(q, v) v = add!!(v, q, -s) From 0a10ca602bb3e45f099fb329dceb8ce121a51dbc Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Mon, 7 Apr 2025 19:16:50 +0800 Subject: [PATCH 12/82] save --- func_list.jl | 7 -- src/factorizations/lanczos.jl | 41 ++------- src/innerproductvec.jl | 20 ----- src/orthonormal.jl | 7 +- test/eigsolve.jl | 16 ++-- test/factorize.jl | 156 ++++++++++++++++++++++++++-------- 6 files changed, 138 insertions(+), 109 deletions(-) delete mode 100644 func_list.jl diff --git a/func_list.jl b/func_list.jl deleted file mode 100644 index f970c07d..00000000 --- a/func_list.jl +++ /dev/null @@ -1,7 +0,0 @@ -# mul!(A,B,M,a,b) = A .= (B .* M) * a .+ b * A - -# axpy!(a,x,y) = y .= a .* x .+ y - -# axpby!(a,x,b,y) = y .= a .* x .+ b .* y - - diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index 8059db0d..fd0234e3 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -385,7 +385,7 @@ What ever, mutable block size is at least undoubtedly useful for non-hermitian o https://www.netlib.org/utk/people/JackDongarra/etemplates/node252.html#ABLEsection =# -mutable struct BlockLanczosFactorization{T,S,SR<:Real} <: BlockKrylovFactorization{T,S,SR} +mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: BlockKrylovFactorization{T,S,SR} all_size::Int const V::OrthonormalBasis{T} # Block Lanczos Basis const TDB::AbstractMatrix{S} # TDB matrix, S is the matrix type @@ -396,16 +396,6 @@ mutable struct BlockLanczosFactorization{T,S,SR<:Real} <: BlockKrylovFactorizati const tmp:: AbstractMatrix{S} # temporary matrix for ortho_basis! end -#= I don't use these functions. But to keep the style, I keep them temporarily. -Base.length(F::BlockLanczosFactorization) = F.all_size -Base.eltype(F::BlockLanczosFactorization) = eltype(typeof(F)) -Base.eltype(::Type{<:BlockLanczosFactorization{T,<:Any}}) where {T} = T -basis(F::BlockLanczosFactorization) = F.V -residual(F::BlockLanczosFactorization) = F.R -normres(F::BlockLanczosFactorization) = F.normR -=# - - #= Now our orthogonalizer is only ModifiedGramSchmidt2. Dimension of Krylov subspace in BlockLanczosIterator is usually much bigger than lanczos. @@ -449,26 +439,7 @@ function BlockLanczosIterator(operator::F, orth::O=ModifiedGramSchmidt2()) where {F,T,O<:Orthogonalizer} S = typeof(inner(x₀[1], x₀[1])) return BlockLanczosIterator{F,T,O}(operator, x₀, maxiter, S, orth) -end - - -#= -I save these 2 functions for future use. But I have not tested them. -function Base.iterate(iter::BlockLanczosIterator) - state = initialize(iter) - return state, state -end - -function Base.iterate(iter::BlockLanczosIterator, state::BlockLanczosFactorization) - nr = normres(state) - if nr < eps(typeof(nr)) - return nothing - else - state = expand!(iter, deepcopy(state)) - return state, state - end -end -=# +end function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.verbosity[]) x₀_vec = iter.x₀ @@ -493,6 +464,7 @@ function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.ve Ax₁ = [apply(A, x) for x in X₁_view] M₁_view = view(TDB, 1:bs_now, 1:bs_now) inner!(M₁_view, X₁_view, Ax₁) + verbosity >= WARN_LEVEL && warn_nonhermitian(M₁_view) M₁_view = (M₁_view + M₁_view') / 2 # We have to write it as a form of matrix multiplication. Get R1 @@ -511,7 +483,6 @@ function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.ve Ax₂ = [apply(A, x) for x in X₂_view] M₂_view = view(TDB, bs_now+1:bs_now+bs_next, bs_now+1:bs_now+bs_next) inner!(M₂_view, X₂_view, Ax₂) - M₂_view = (M₂_view + M₂_view') / 2 # Calculate the new residual. Get R2 @@ -557,6 +528,7 @@ function expand!(iter::BlockLanczosIterator, state::BlockLanczosFactorization; Axₖnext = [apply(iter.operator, x) for x in Xnext_view] Mnext_view = view(state.TDB, all_size+1:all_size+bs_next, all_size+1:all_size+bs_next) inner!(Mnext_view, Xnext_view, Axₖnext) + verbosity >= WARN_LEVEL && warn_nonhermitian(Mnext_view) Mnext_view = (Mnext_view + Mnext_view') / 2 # Calculate the new residual. Get Rnext @@ -599,3 +571,8 @@ function ortho_basis!(basis_new::AbstractVector{T}, basis_sofar::AbstractVector{ return basis_new end +function warn_nonhermitian(M::AbstractMatrix) + if norm(M - M') > eps(real(eltype(M)))*1e4 + @warn "Enforce Hermiticity on the triangular diagonal blocks matrix, even though the operator may not be Hermitian." + end +end diff --git a/src/innerproductvec.jl b/src/innerproductvec.jl index a1c53384..05807669 100644 --- a/src/innerproductvec.jl +++ b/src/innerproductvec.jl @@ -24,15 +24,6 @@ Base.:-(v::InnerProductVec) = InnerProductVec(-v.vec, v.dotf) function Base.:+(v::InnerProductVec{F}, w::InnerProductVec{F}) where {F} return InnerProductVec(v.vec + w.vec, v.dotf) end -# TODO: -function Base.sum(v::AbstractVector{InnerProductVec}) - @assert length(v) > 0 - res = copy(v[1]) - @inbounds for i in 2:length(v) - res += v[i] - end - return res -end function Base.:-(v::InnerProductVec{F}, w::InnerProductVec{F}) where {F} return InnerProductVec(v.vec - w.vec, v.dotf) @@ -41,19 +32,8 @@ end Base.:*(v::InnerProductVec, a::Number) = InnerProductVec(v.vec * a, v.dotf) Base.:*(a::Number, v::InnerProductVec) = InnerProductVec(a * v.vec, v.dotf) -# TODO: -Base.:*(v::AbstractVector{InnerProductVec}, a::Number) = [v[i] * a for i in 1:length(v)] -Base.:*(a::Number, v::AbstractVector{InnerProductVec}) = [a * v[i] for i in 1:length(v)] -function Base.:*(v::AbstractVector, V::AbstractVector) - return sum(v .* V) -end -# It's in fact a kind of Linear map - Base.:/(v::InnerProductVec, a::Number) = InnerProductVec(v.vec / a, v.dotf) -# TODO: -Base.:/(v::AbstractVector{InnerProductVec}, a::Number) = [v[i] / a for i in 1:length(v)] Base.:\(a::Number, v::InnerProductVec) = InnerProductVec(a \ v.vec, v.dotf) -# I can't understand well why the last function exists so I don't implement it's block version. function Base.similar(v::InnerProductVec, ::Type{T}=scalartype(v)) where {T} return InnerProductVec(similar(v.vec), v.dotf) diff --git a/src/orthonormal.jl b/src/orthonormal.jl index 4f9216cf..0aa75774 100644 --- a/src/orthonormal.jl +++ b/src/orthonormal.jl @@ -601,9 +601,4 @@ function abstract_qr!(block::AbstractVector{T}, S::Type; end good_idx = findall(idx .> 0) return R[good_idx,:], good_idx -end - - - - - +end \ No newline at end of file diff --git a/test/eigsolve.jl b/test/eigsolve.jl index e3384c67..b2275d0f 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -506,14 +506,14 @@ As a result, I’ve decided to postpone dealing with the in-place test issue for end # krylovdim is not used in block Lanczos so I don't add eager mode. -@testset "Block Lanczos - eigsolve iteratively ($mode)" for mode in (:vector, :inplace, :outplace) +@testset "Block Lanczos - eigsolve iteratively" begin @testset for T in [Float32, Float64, ComplexF32, ComplexF64] - T = ComplexF32 A0 = rand(T, (N, N)) .- one(T) / 2 A0 = (A0 + A0') / 2 block_size = 5 x₀m = Matrix(qr(rand(T, N, block_size)).Q) x₀ = [x₀m[:, i] for i in 1:block_size] + eigvalsA = eigvals(A0) @testset for A in [A0, x -> A0 * x] A = copy(A0) @@ -526,13 +526,13 @@ end @test l1 > 0 @test l2 > 0 - @test D1[1:l1] ≈ eigvals(A)[1:l1] - @test D2[1:l2] ≈ eigvals(A)[N:-1:(N-l2+1)] + @test D1[1:l1] ≈ eigvalsA[1:l1] + @test D2[1:l2] ≈ eigvalsA[N:-1:(N-l2+1)] - U1 = hcat(V1[1:l1]...) - U2 = hcat(V2[1:l2]...) - R1 = hcat(info1.residual[1:l1]...) - R2 = hcat(info2.residual[1:l2]...) + U1 = hcat(V1[1:l1]...); + U2 = hcat(V2[1:l2]...); + R1 = hcat(info1.residual[1:l1]...); + R2 = hcat(info2.residual[1:l2]...); @test U1' * U1 ≈ I @test U2' * U2 ≈ I diff --git a/test/factorize.jl b/test/factorize.jl index 8419a520..afcd11c5 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -13,15 +13,15 @@ iter = LanczosIterator(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), orth) fact = @constinferred initialize(iter) @constinferred expand!(iter, fact) - @test_logs initialize(iter; verbosity=EACHITERATION_LEVEL) - @test_logs (:info,) initialize(iter; verbosity=EACHITERATION_LEVEL + 1) + @test_logs initialize(iter; verbosity = EACHITERATION_LEVEL) + @test_logs (:info,) initialize(iter; verbosity = EACHITERATION_LEVEL + 1) verbosity = EACHITERATION_LEVEL + 1 while length(fact) < n if verbosity == EACHITERATION_LEVEL + 1 - @test_logs (:info,) expand!(iter, fact; verbosity=verbosity) + @test_logs (:info,) expand!(iter, fact; verbosity = verbosity) verbosity = EACHITERATION_LEVEL else - @test_logs expand!(iter, fact; verbosity=verbosity) + @test_logs expand!(iter, fact; verbosity = verbosity) verbosity = EACHITERATION_LEVEL + 1 end end @@ -35,28 +35,28 @@ @test rayleighquotient(last(states)) ≈ H @constinferred shrink!(fact, n - 1) - @test_logs (:info,) shrink!(fact, n - 2; verbosity=EACHITERATION_LEVEL + 1) - @test_logs shrink!(fact, n - 3; verbosity=EACHITERATION_LEVEL) + @test_logs (:info,) shrink!(fact, n - 2; verbosity = EACHITERATION_LEVEL + 1) + @test_logs shrink!(fact, n - 3; verbosity = EACHITERATION_LEVEL) @constinferred initialize!(iter, deepcopy(fact)) - @test_logs initialize!(iter, deepcopy(fact); verbosity=EACHITERATION_LEVEL) + @test_logs initialize!(iter, deepcopy(fact); verbosity = EACHITERATION_LEVEL) @test_logs (:info,) initialize!(iter, deepcopy(fact); - verbosity=EACHITERATION_LEVEL + 1) + verbosity = EACHITERATION_LEVEL + 1) if T <: Complex A = rand(T, (n, n)) # test warnings for non-hermitian matrices v = rand(T, (n,)) iter = LanczosIterator(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), orth) - fact = @constinferred initialize(iter; verbosity=0) - @constinferred expand!(iter, fact; verbosity=0) - @test_logs initialize(iter; verbosity=0) + fact = @constinferred initialize(iter; verbosity = 0) + @constinferred expand!(iter, fact; verbosity = 0) + @test_logs initialize(iter; verbosity = 0) @test_logs (:warn,) initialize(iter) verbosity = 1 while length(fact) < n if verbosity == 1 - @test_logs (:warn,) expand!(iter, fact; verbosity=verbosity) + @test_logs (:warn,) expand!(iter, fact; verbosity = verbosity) verbosity = 0 else - @test_logs expand!(iter, fact; verbosity=verbosity) + @test_logs expand!(iter, fact; verbosity = verbosity) verbosity = 1 end end @@ -78,15 +78,15 @@ end iter = ArnoldiIterator(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), orth) fact = @constinferred initialize(iter) @constinferred expand!(iter, fact) - @test_logs initialize(iter; verbosity=EACHITERATION_LEVEL) - @test_logs (:info,) initialize(iter; verbosity=EACHITERATION_LEVEL + 1) + @test_logs initialize(iter; verbosity = EACHITERATION_LEVEL) + @test_logs (:info,) initialize(iter; verbosity = EACHITERATION_LEVEL + 1) verbosity = EACHITERATION_LEVEL + 1 while length(fact) < n if verbosity == EACHITERATION_LEVEL + 1 - @test_logs (:info,) expand!(iter, fact; verbosity=verbosity) + @test_logs (:info,) expand!(iter, fact; verbosity = verbosity) verbosity = EACHITERATION_LEVEL else - @test_logs expand!(iter, fact; verbosity=verbosity) + @test_logs expand!(iter, fact; verbosity = verbosity) verbosity = EACHITERATION_LEVEL + 1 end end @@ -101,12 +101,12 @@ end @test rayleighquotient(last(states)) ≈ H @constinferred shrink!(fact, n - 1) - @test_logs (:info,) shrink!(fact, n - 2; verbosity=EACHITERATION_LEVEL + 1) - @test_logs shrink!(fact, n - 3; verbosity=EACHITERATION_LEVEL) + @test_logs (:info,) shrink!(fact, n - 2; verbosity = EACHITERATION_LEVEL + 1) + @test_logs shrink!(fact, n - 3; verbosity = EACHITERATION_LEVEL) @constinferred initialize!(iter, deepcopy(fact)) - @test_logs initialize!(iter, deepcopy(fact); verbosity=EACHITERATION_LEVEL) + @test_logs initialize!(iter, deepcopy(fact); verbosity = EACHITERATION_LEVEL) @test_logs (:info,) initialize!(iter, deepcopy(fact); - verbosity=EACHITERATION_LEVEL + 1) + verbosity = EACHITERATION_LEVEL + 1) end end end @@ -129,8 +129,8 @@ end end A = (A + A') iter = @constinferred LanczosIterator(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), - orth) + wrapvec(v, Val(mode)), + orth) krylovdim = n fact = @constinferred initialize(iter) while normres(fact) > eps(float(real(T))) && length(fact) < krylovdim @@ -173,7 +173,7 @@ end v = rand(T, (N,)) end iter = @constinferred ArnoldiIterator(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), orth) + wrapvec(v, Val(mode)), orth) krylovdim = 3 * n fact = @constinferred initialize(iter) while normres(fact) > eps(float(real(T))) && length(fact) < krylovdim @@ -202,7 +202,7 @@ end # Test complete Golub-Kahan-Lanczos factorization @testset "Complete Golub-Kahan-Lanczos factorization ($mode)" for mode in (:vector, :inplace, - :outplace, :mixed) + :outplace, :mixed) scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) orths = mode === :vector ? (cgs2, mgs2, cgsr, mgsr) : (mgsr,) @@ -213,15 +213,15 @@ end iter = GKLIterator(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), orth) fact = @constinferred initialize(iter) @constinferred expand!(iter, fact) - @test_logs initialize(iter; verbosity=EACHITERATION_LEVEL) - @test_logs (:info,) initialize(iter; verbosity=EACHITERATION_LEVEL + 1) + @test_logs initialize(iter; verbosity = EACHITERATION_LEVEL) + @test_logs (:info,) initialize(iter; verbosity = EACHITERATION_LEVEL + 1) verbosity = EACHITERATION_LEVEL + 1 while length(fact) < n if verbosity == EACHITERATION_LEVEL + 1 - @test_logs (:info,) expand!(iter, fact; verbosity=verbosity) + @test_logs (:info,) expand!(iter, fact; verbosity = verbosity) verbosity = EACHITERATION_LEVEL else - @test_logs expand!(iter, fact; verbosity=verbosity) + @test_logs expand!(iter, fact; verbosity = verbosity) verbosity = EACHITERATION_LEVEL + 1 end end @@ -238,12 +238,12 @@ end @test rayleighquotient(last(states)) ≈ B @constinferred shrink!(fact, n - 1) - @test_logs (:info,) shrink!(fact, n - 2; verbosity=EACHITERATION_LEVEL + 1) - @test_logs shrink!(fact, n - 3; verbosity=EACHITERATION_LEVEL) + @test_logs (:info,) shrink!(fact, n - 2; verbosity = EACHITERATION_LEVEL + 1) + @test_logs shrink!(fact, n - 3; verbosity = EACHITERATION_LEVEL) @constinferred initialize!(iter, deepcopy(fact)) - @test_logs initialize!(iter, deepcopy(fact); verbosity=EACHITERATION_LEVEL) + @test_logs initialize!(iter, deepcopy(fact); verbosity = EACHITERATION_LEVEL) @test_logs (:info,) initialize!(iter, deepcopy(fact); - verbosity=EACHITERATION_LEVEL + 1) + verbosity = EACHITERATION_LEVEL + 1) end end end @@ -251,7 +251,7 @@ end # Test incomplete Golub-Kahan-Lanczos factorization @testset "Incomplete Golub-Kahan-Lanczos factorization ($mode)" for mode in (:vector, :inplace, - :outplace, :mixed) + :outplace, :mixed) scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) orths = mode === :vector ? (cgs2, mgs2, cgsr, mgsr) : (mgsr,) @@ -265,7 +265,7 @@ end v = rand(T, (N,)) end iter = @constinferred GKLIterator(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), - orth) + orth) krylovdim = 3 * n fact = @constinferred initialize(iter) while normres(fact) > eps(float(real(T))) && length(fact) < krylovdim @@ -297,5 +297,89 @@ end end end -# TODO: add more tests for block lanczos +# Test complete Lanczos factorization +@testset "Complete Block Lanczos factorization " begin + using KrylovKit: EACHITERATION_LEVEL + @testset for T in [Float32, Float64, ComplexF32, ComplexF64] + A0 = rand(T, (N, N)) .- one(T) / 2 + A0 = (A0 + A0') / 2 + block_size = 5 + x₀m = Matrix(qr(rand(T, N, block_size)).Q) + x₀ = [x₀m[:, i] for i in 1:block_size] + eigvalsA = eigvals(A0) + @testset for A in [A0, x -> A0 * x] + iter = KrylovKit.BlockLanczosIterator(A, x₀, 4) + # TODO: Why type unstable? + # fact = @constinferred initialize(iter) + fact = initialize(iter) + @constinferred expand!(iter, fact) + @test_logs initialize(iter; verbosity = EACHITERATION_LEVEL) + @test_logs (:info,) initialize(iter; verbosity = EACHITERATION_LEVEL + 1) + verbosity = EACHITERATION_LEVEL + 1 + while fact.all_size < n + if verbosity == EACHITERATION_LEVEL + 1 + @test_logs (:info,) expand!(iter, fact; verbosity = verbosity) + verbosity = EACHITERATION_LEVEL + else + @test_logs expand!(iter, fact; verbosity = verbosity) + verbosity = EACHITERATION_LEVEL + 1 + end + end + # Information about V has been tested in eigsolve.jl + # And Block Lanczos has no "rayleighquotient" function + + # Block Lanczos has no "shrink!" function, as well as "initialize!(iter, fact)" + end + + if T <: Complex + B = rand(T, (n, n)) # test warnings for non-hermitian matrices + bs = 2 + v₀m = Matrix(qr(rand(T, n, bs)).Q) + v₀ = [v₀m[:, i] for i in 1:bs] + iter = KrylovKit.BlockLanczosIterator(B, v₀, 4) + fact = initialize(iter) + @constinferred expand!(iter, fact; verbosity = 0) + @test_logs initialize(iter; verbosity = 0) + @test_logs (:warn,) initialize(iter) + verbosity = 1 + while fact.all_size < n + if verbosity == 1 + @test_logs (:warn,) expand!(iter, fact; verbosity = verbosity) + verbosity = 0 + else + @test_logs expand!(iter, fact; verbosity = verbosity) + verbosity = 1 + end + end + end + end +end + +# Test incomplete Lanczos factorization +@testset "Incomplete Lanczos factorization " begin + @testset for T in [Float32, Float64, ComplexF32, ComplexF64] + A0 = rand(T, (N, N)) + A0 = (A0 + A0') / 2 + block_size = 5 + x₀m = Matrix(qr(rand(T, N, block_size)).Q) + x₀ = [x₀m[:, i] for i in 1:block_size] + @testset for A in [A0, x -> A0 * x] + iter = @constinferred KrylovKit.BlockLanczosIterator(A, x₀, 4) + krylovdim = n + fact = initialize(iter) + while fact.normR > eps(float(real(T))) && fact.all_size < krylovdim + @constinferred expand!(iter, fact) + Ṽ, H, r̃, β, e = fact + V = stack(unwrapvec, Ṽ) + r = unwrapvec(r̃) + @test V' * V ≈ I + @test norm(r) ≈ β + @test A * V ≈ V * H + r * e' + end + + # Block Lanczos has no "shrink!" function + # The V and residual have been tested in eigsolve.jl + end + end +end From a2febc654edb219a1fea76f763cee395efa60421 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Mon, 7 Apr 2025 19:29:34 +0800 Subject: [PATCH 13/82] format --- src/innerproductvec.jl | 6 +--- src/orthonormal.jl | 2 -- test/factorize.jl | 66 +++++++++++++++++++++--------------------- 3 files changed, 34 insertions(+), 40 deletions(-) diff --git a/src/innerproductvec.jl b/src/innerproductvec.jl index 05807669..a58e135b 100644 --- a/src/innerproductvec.jl +++ b/src/innerproductvec.jl @@ -24,14 +24,11 @@ Base.:-(v::InnerProductVec) = InnerProductVec(-v.vec, v.dotf) function Base.:+(v::InnerProductVec{F}, w::InnerProductVec{F}) where {F} return InnerProductVec(v.vec + w.vec, v.dotf) end - function Base.:-(v::InnerProductVec{F}, w::InnerProductVec{F}) where {F} return InnerProductVec(v.vec - w.vec, v.dotf) end - Base.:*(v::InnerProductVec, a::Number) = InnerProductVec(v.vec * a, v.dotf) Base.:*(a::Number, v::InnerProductVec) = InnerProductVec(a * v.vec, v.dotf) - Base.:/(v::InnerProductVec, a::Number) = InnerProductVec(v.vec / a, v.dotf) Base.:\(a::Number, v::InnerProductVec) = InnerProductVec(a \ v.vec, v.dotf) @@ -135,7 +132,6 @@ function VectorInterface.inner(v::InnerProductVec{F}, w::InnerProductVec{F}) whe return v.dotf(v.vec, w.vec) end -# TODO: Add tests function inner!(M::AbstractMatrix, x::AbstractVector, y::AbstractVector) @@ -148,8 +144,8 @@ function inner!(M::AbstractMatrix, end return M end -VectorInterface.norm(v::InnerProductVec) = sqrt(real(inner(v, v))) +VectorInterface.norm(v::InnerProductVec) = sqrt(real(inner(v, v))) # used for debugging function blockinner(v::AbstractVector, w::AbstractVector;S::Type = Float64) diff --git a/src/orthonormal.jl b/src/orthonormal.jl index 0aa75774..3f0cb254 100644 --- a/src/orthonormal.jl +++ b/src/orthonormal.jl @@ -33,8 +33,6 @@ Base.IteratorSize(::Type{<:OrthonormalBasis}) = Base.HasLength() Base.IteratorEltype(::Type{<:OrthonormalBasis}) = Base.HasEltype() Base.length(b::OrthonormalBasis) = length(b.basis) -# blocksize(b::OrthonormalBasis) does have the same logic with Base.length(b::OrthonormalBasis), but I make sense for block lanczos. -blocksize(b::OrthonormalBasis) = length(b.basis) Base.eltype(b::OrthonormalBasis{T}) where {T} = T Base.iterate(b::OrthonormalBasis) = Base.iterate(b.basis) diff --git a/test/factorize.jl b/test/factorize.jl index afcd11c5..60d1bc04 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -13,15 +13,15 @@ iter = LanczosIterator(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), orth) fact = @constinferred initialize(iter) @constinferred expand!(iter, fact) - @test_logs initialize(iter; verbosity = EACHITERATION_LEVEL) - @test_logs (:info,) initialize(iter; verbosity = EACHITERATION_LEVEL + 1) + @test_logs initialize(iter; verbosity=EACHITERATION_LEVEL) + @test_logs (:info,) initialize(iter; verbosity=EACHITERATION_LEVEL + 1) verbosity = EACHITERATION_LEVEL + 1 while length(fact) < n if verbosity == EACHITERATION_LEVEL + 1 - @test_logs (:info,) expand!(iter, fact; verbosity = verbosity) + @test_logs (:info,) expand!(iter, fact; verbosity=verbosity) verbosity = EACHITERATION_LEVEL else - @test_logs expand!(iter, fact; verbosity = verbosity) + @test_logs expand!(iter, fact; verbosity=verbosity) verbosity = EACHITERATION_LEVEL + 1 end end @@ -35,28 +35,28 @@ @test rayleighquotient(last(states)) ≈ H @constinferred shrink!(fact, n - 1) - @test_logs (:info,) shrink!(fact, n - 2; verbosity = EACHITERATION_LEVEL + 1) - @test_logs shrink!(fact, n - 3; verbosity = EACHITERATION_LEVEL) + @test_logs (:info,) shrink!(fact, n - 2; verbosity=EACHITERATION_LEVEL + 1) + @test_logs shrink!(fact, n - 3; verbosity=EACHITERATION_LEVEL) @constinferred initialize!(iter, deepcopy(fact)) - @test_logs initialize!(iter, deepcopy(fact); verbosity = EACHITERATION_LEVEL) + @test_logs initialize!(iter, deepcopy(fact); verbosity=EACHITERATION_LEVEL) @test_logs (:info,) initialize!(iter, deepcopy(fact); - verbosity = EACHITERATION_LEVEL + 1) + verbosity=EACHITERATION_LEVEL + 1) if T <: Complex A = rand(T, (n, n)) # test warnings for non-hermitian matrices v = rand(T, (n,)) iter = LanczosIterator(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), orth) - fact = @constinferred initialize(iter; verbosity = 0) - @constinferred expand!(iter, fact; verbosity = 0) - @test_logs initialize(iter; verbosity = 0) + fact = @constinferred initialize(iter; verbosity=0) + @constinferred expand!(iter, fact; verbosity=0) + @test_logs initialize(iter; verbosity=0) @test_logs (:warn,) initialize(iter) verbosity = 1 while length(fact) < n if verbosity == 1 - @test_logs (:warn,) expand!(iter, fact; verbosity = verbosity) + @test_logs (:warn,) expand!(iter, fact; verbosity=verbosity) verbosity = 0 else - @test_logs expand!(iter, fact; verbosity = verbosity) + @test_logs expand!(iter, fact; verbosity=verbosity) verbosity = 1 end end @@ -78,7 +78,7 @@ end iter = ArnoldiIterator(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), orth) fact = @constinferred initialize(iter) @constinferred expand!(iter, fact) - @test_logs initialize(iter; verbosity = EACHITERATION_LEVEL) + @test_logs initialize(iter; verbosity=EACHITERATION_LEVEL) @test_logs (:info,) initialize(iter; verbosity = EACHITERATION_LEVEL + 1) verbosity = EACHITERATION_LEVEL + 1 while length(fact) < n @@ -86,7 +86,7 @@ end @test_logs (:info,) expand!(iter, fact; verbosity = verbosity) verbosity = EACHITERATION_LEVEL else - @test_logs expand!(iter, fact; verbosity = verbosity) + @test_logs expand!(iter, fact; verbosity=verbosity) verbosity = EACHITERATION_LEVEL + 1 end end @@ -101,12 +101,12 @@ end @test rayleighquotient(last(states)) ≈ H @constinferred shrink!(fact, n - 1) - @test_logs (:info,) shrink!(fact, n - 2; verbosity = EACHITERATION_LEVEL + 1) - @test_logs shrink!(fact, n - 3; verbosity = EACHITERATION_LEVEL) + @test_logs (:info,) shrink!(fact, n - 2; verbosity=EACHITERATION_LEVEL + 1) + @test_logs shrink!(fact, n - 3; verbosity=EACHITERATION_LEVEL) @constinferred initialize!(iter, deepcopy(fact)) - @test_logs initialize!(iter, deepcopy(fact); verbosity = EACHITERATION_LEVEL) + @test_logs initialize!(iter, deepcopy(fact); verbosity=EACHITERATION_LEVEL) @test_logs (:info,) initialize!(iter, deepcopy(fact); - verbosity = EACHITERATION_LEVEL + 1) + verbosity=EACHITERATION_LEVEL + 1) end end end @@ -129,8 +129,8 @@ end end A = (A + A') iter = @constinferred LanczosIterator(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), - orth) + wrapvec(v, Val(mode)), + orth) krylovdim = n fact = @constinferred initialize(iter) while normres(fact) > eps(float(real(T))) && length(fact) < krylovdim @@ -173,7 +173,7 @@ end v = rand(T, (N,)) end iter = @constinferred ArnoldiIterator(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), orth) + wrapvec(v, Val(mode)), orth) krylovdim = 3 * n fact = @constinferred initialize(iter) while normres(fact) > eps(float(real(T))) && length(fact) < krylovdim @@ -202,7 +202,7 @@ end # Test complete Golub-Kahan-Lanczos factorization @testset "Complete Golub-Kahan-Lanczos factorization ($mode)" for mode in (:vector, :inplace, - :outplace, :mixed) + :outplace, :mixed) scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) orths = mode === :vector ? (cgs2, mgs2, cgsr, mgsr) : (mgsr,) @@ -213,15 +213,15 @@ end iter = GKLIterator(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), orth) fact = @constinferred initialize(iter) @constinferred expand!(iter, fact) - @test_logs initialize(iter; verbosity = EACHITERATION_LEVEL) - @test_logs (:info,) initialize(iter; verbosity = EACHITERATION_LEVEL + 1) + @test_logs initialize(iter; verbosity=EACHITERATION_LEVEL) + @test_logs (:info,) initialize(iter; verbosity=EACHITERATION_LEVEL + 1) verbosity = EACHITERATION_LEVEL + 1 while length(fact) < n if verbosity == EACHITERATION_LEVEL + 1 - @test_logs (:info,) expand!(iter, fact; verbosity = verbosity) + @test_logs (:info,) expand!(iter, fact; verbosity=verbosity) verbosity = EACHITERATION_LEVEL else - @test_logs expand!(iter, fact; verbosity = verbosity) + @test_logs expand!(iter, fact; verbosity=verbosity) verbosity = EACHITERATION_LEVEL + 1 end end @@ -238,12 +238,12 @@ end @test rayleighquotient(last(states)) ≈ B @constinferred shrink!(fact, n - 1) - @test_logs (:info,) shrink!(fact, n - 2; verbosity = EACHITERATION_LEVEL + 1) - @test_logs shrink!(fact, n - 3; verbosity = EACHITERATION_LEVEL) + @test_logs (:info,) shrink!(fact, n - 2; verbosity=EACHITERATION_LEVEL + 1) + @test_logs shrink!(fact, n - 3; verbosity=EACHITERATION_LEVEL) @constinferred initialize!(iter, deepcopy(fact)) - @test_logs initialize!(iter, deepcopy(fact); verbosity = EACHITERATION_LEVEL) + @test_logs initialize!(iter, deepcopy(fact); verbosity=EACHITERATION_LEVEL) @test_logs (:info,) initialize!(iter, deepcopy(fact); - verbosity = EACHITERATION_LEVEL + 1) + verbosity = EACHITERATION_LEVEL + 1) end end end @@ -251,7 +251,7 @@ end # Test incomplete Golub-Kahan-Lanczos factorization @testset "Incomplete Golub-Kahan-Lanczos factorization ($mode)" for mode in (:vector, :inplace, - :outplace, :mixed) + :outplace, :mixed) scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) orths = mode === :vector ? (cgs2, mgs2, cgsr, mgsr) : (mgsr,) @@ -265,7 +265,7 @@ end v = rand(T, (N,)) end iter = @constinferred GKLIterator(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), - orth) + orth) krylovdim = 3 * n fact = @constinferred initialize(iter) while normres(fact) > eps(float(real(T))) && length(fact) < krylovdim From fbaf863428862503a2ab1486b220bc57eddf6967 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Mon, 7 Apr 2025 19:48:30 +0800 Subject: [PATCH 14/82] format --- test/factorize.jl | 6 +++--- test/runtests.jl | 3 +++ test/testsetup.jl | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/test/factorize.jl b/test/factorize.jl index 60d1bc04..8ed26371 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -79,11 +79,11 @@ end fact = @constinferred initialize(iter) @constinferred expand!(iter, fact) @test_logs initialize(iter; verbosity=EACHITERATION_LEVEL) - @test_logs (:info,) initialize(iter; verbosity = EACHITERATION_LEVEL + 1) + @test_logs (:info,) initialize(iter; verbosity=EACHITERATION_LEVEL + 1) verbosity = EACHITERATION_LEVEL + 1 while length(fact) < n if verbosity == EACHITERATION_LEVEL + 1 - @test_logs (:info,) expand!(iter, fact; verbosity = verbosity) + @test_logs (:info,) expand!(iter, fact; verbosity=verbosity) verbosity = EACHITERATION_LEVEL else @test_logs expand!(iter, fact; verbosity=verbosity) @@ -243,7 +243,7 @@ end @constinferred initialize!(iter, deepcopy(fact)) @test_logs initialize!(iter, deepcopy(fact); verbosity=EACHITERATION_LEVEL) @test_logs (:info,) initialize!(iter, deepcopy(fact); - verbosity = EACHITERATION_LEVEL + 1) + verbosity=EACHITERATION_LEVEL + 1) end end end diff --git a/test/runtests.jl b/test/runtests.jl index be3a60a1..e8c1102b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -65,6 +65,9 @@ end @testset "Orthonormal" verbose = true begin include("orthonormal.jl") end +@testset "Inner product vector" verbose = true begin + include("innerproductvec.jl") +end t = time() - t # Issues diff --git a/test/testsetup.jl b/test/testsetup.jl index af5437be..898d7c28 100644 --- a/test/testsetup.jl +++ b/test/testsetup.jl @@ -2,6 +2,7 @@ module TestSetup export tolerance, ≊, MinimalVec, isinplace, stack export wrapop, wrapvec, unwrapvec, buildrealmap +export mul_test import VectorInterface as VI using VectorInterface From d5a46180e64997d84d227565e7c85472eb1e9269 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Mon, 7 Apr 2025 20:15:45 +0800 Subject: [PATCH 15/82] revise project.toml --- Project.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Project.toml b/Project.toml index f46ff94e..6e207ef8 100644 --- a/Project.toml +++ b/Project.toml @@ -8,7 +8,6 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" PackageExtensionCompat = "65ce6f38-6b18-4e1d-a461-8949797d7930" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" [weakdeps] From ac130a2015909e9d5323e5f4ec54562eb35403e5 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Mon, 7 Apr 2025 22:25:27 +0800 Subject: [PATCH 16/82] I am ready --- src/algorithms.jl | 19 +++++++++++++++---- src/eigsolve/lanczos.jl | 7 +------ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/algorithms.jl b/src/algorithms.jl index d877d62b..2e33a6db 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -108,8 +108,8 @@ Use `Arnoldi` for non-symmetric or non-Hermitian linear operators. See also: `factorize`, `eigsolve`, `exponentiate`, `Arnoldi`, `Orthogonalizer` """ -# I add blockmode explicitly here because I find it difficult to judge -# whether use block lanczos or not only by the kind of x₀ +# Add BlockLanczos may seem ugly, but to keep the type stable to pass +# tests in test/eigsolve.jl, I have to do this. struct Lanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm orth::O krylovdim::Int @@ -117,7 +117,14 @@ struct Lanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm tol::S eager::Bool verbosity::Int - blockmode::Bool +end +struct BlockLanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm + orth::O + krylovdim::Int + maxiter::Int + tol::S + eager::Bool + verbosity::Int end function Lanczos(; krylovdim::Int=KrylovDefaults.krylovdim[], @@ -127,7 +134,11 @@ function Lanczos(; eager::Bool=false, verbosity::Int=KrylovDefaults.verbosity[], blockmode::Bool=false) - return Lanczos(orth, krylovdim, maxiter, tol, eager, verbosity, blockmode) + if blockmode + return BlockLanczos(orth, krylovdim, maxiter, tol, eager, verbosity) + else + return Lanczos(orth, krylovdim, maxiter, tol, eager, verbosity) + end end """ diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index c6cb3a00..d2a25f7b 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -4,9 +4,6 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::Lanczos; maxiter=alg.maxiter, eager=alg.eager, orth=alg.orth)) - if alg.blockmode - return block_lanczos_reortho(A, x₀, howmany, which, alg) - end krylovdim = alg.krylovdim maxiter = alg.maxiter if howmany > krylovdim @@ -154,9 +151,7 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::Lanczos; ConvergenceInfo(converged, residuals, normresiduals, numiter, numops) end -function block_lanczos_reortho(A, x₀, howmany::Int, which::Selector, - alg::Lanczos) - @assert alg.blockmode == true +function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) maxiter = alg.maxiter tol = alg.tol verbosity = alg.verbosity From bb7ea72363ec115d29f6ae231871c517573e4d3d Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Tue, 8 Apr 2025 15:53:28 +0800 Subject: [PATCH 17/82] firmat --- test/eigsolve.jl | 209 ++++++++++++++++++++++++----------------------- 1 file changed, 105 insertions(+), 104 deletions(-) diff --git a/test/eigsolve.jl b/test/eigsolve.jl index b2275d0f..636e79c7 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -8,41 +8,41 @@ A = (A + A') / 2 v = rand(T, (n,)) n1 = div(n, 2) - alg = Lanczos(; orth = orth, krylovdim = n, maxiter = 1, tol = tolerance(T), - verbosity = 2) + alg = Lanczos(; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), + verbosity=2) D1, V1, info = @test_logs (:info,) eigsolve(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), n1, :SR, alg) - alg = Lanczos(; orth = orth, krylovdim = n, maxiter = 1, tol = tolerance(T), - verbosity = 1) + wrapvec(v, Val(mode)), n1, :SR, alg) + alg = Lanczos(; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), + verbosity=1) @test_logs eigsolve(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), n1, :SR, alg) - alg = Lanczos(; orth = orth, krylovdim = n1 + 1, maxiter = 1, tol = tolerance(T), - verbosity = 1) + wrapvec(v, Val(mode)), n1, :SR, alg) + alg = Lanczos(; orth=orth, krylovdim=n1 + 1, maxiter=1, tol=tolerance(T), + verbosity=1) @test_logs (:warn,) eigsolve(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), n1, :SR, alg) - alg = Lanczos(; orth = orth, krylovdim = n, maxiter = 1, tol = tolerance(T), - verbosity = 2) + wrapvec(v, Val(mode)), n1, :SR, alg) + alg = Lanczos(; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), + verbosity=2) @test_logs (:info,) eigsolve(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), n1, :SR, alg) - alg = Lanczos(; orth = orth, krylovdim = n1, maxiter = 3, tol = tolerance(T), - verbosity = 3) + wrapvec(v, Val(mode)), n1, :SR, alg) + alg = Lanczos(; orth=orth, krylovdim=n1, maxiter=3, tol=tolerance(T), + verbosity=3) @test_logs((:info,), (:info,), (:info,), (:warn,), - eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), 1, :SR, alg)) - alg = Lanczos(; orth = orth, krylovdim = 4, maxiter = 1, tol = tolerance(T), - verbosity = 4) + eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), 1, :SR, alg)) + alg = Lanczos(; orth=orth, krylovdim=4, maxiter=1, tol=tolerance(T), + verbosity=4) # since it is impossible to know exactly the size of the Krylov subspace after shrinking, # we only know the output for a sigle iteration @test_logs((:info,), (:info,), (:info,), (:info,), (:info,), (:warn,), - eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), 1, :SR, alg)) + eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), 1, :SR, alg)) - @test KrylovKit.eigselector(wrapop(A, Val(mode)), scalartype(v); krylovdim = n, - maxiter = 1, - tol = tolerance(T), ishermitian = true) isa Lanczos + @test KrylovKit.eigselector(wrapop(A, Val(mode)), scalartype(v); krylovdim=n, + maxiter=1, + tol=tolerance(T), ishermitian=true) isa Lanczos n2 = n - n1 - alg = Lanczos(; krylovdim = 2 * n, maxiter = 1, tol = tolerance(T)) + alg = Lanczos(; krylovdim=2 * n, maxiter=1, tol=tolerance(T)) D2, V2, info = @constinferred eigsolve(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), - n2, :LR, alg) + wrapvec(v, Val(mode)), + n2, :LR, alg) @test vcat(D1[1:n1], reverse(D2[1:n2])) ≊ eigvals(A) U1 = stack(unwrapvec, V1) @@ -53,10 +53,10 @@ @test A * U1 ≈ U1 * Diagonal(D1) @test A * U2 ≈ U2 * Diagonal(D2) - alg = Lanczos(; orth = orth, krylovdim = 2n, maxiter = 1, tol = tolerance(T), - verbosity = 1) + alg = Lanczos(; orth=orth, krylovdim=2n, maxiter=1, tol=tolerance(T), + verbosity=1) @test_logs (:warn,) (:warn,) eigsolve(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), n + 1, :LM, alg) + wrapvec(v, Val(mode)), n + 1, :LM, alg) end end end @@ -70,19 +70,19 @@ end A = rand(T, (N, N)) .- one(T) / 2 A = (A + A') / 2 v = rand(T, (N,)) - alg = Lanczos(; krylovdim = 2 * n, maxiter = 10, - tol = tolerance(T), eager = true, verbosity = 0) + alg = Lanczos(; krylovdim=2 * n, maxiter=10, + tol=tolerance(T), eager=true, verbosity=0) D1, V1, info1 = @constinferred eigsolve(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), n, :SR, alg) + wrapvec(v, Val(mode)), n, :SR, alg) D2, V2, info2 = eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n, :LR, - alg) + alg) l1 = info1.converged l2 = info2.converged @test l1 > 0 @test l2 > 0 @test D1[1:l1] ≈ eigvals(A)[1:l1] - @test D2[1:l2] ≈ eigvals(A)[N:-1:(N-l2+1)] + @test D2[1:l2] ≈ eigvals(A)[N:-1:(N - l2 + 1)] U1 = stack(unwrapvec, V1) U2 = stack(unwrapvec, V2) @@ -106,45 +106,45 @@ end A = rand(T, (n, n)) .- one(T) / 2 v = rand(T, (n,)) n1 = div(n, 2) - alg = Arnoldi(; orth = orth, krylovdim = n, maxiter = 1, tol = tolerance(T)) + alg = Arnoldi(; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T)) D1, V1, info1 = @constinferred eigsolve(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), n1, :SR, alg) + wrapvec(v, Val(mode)), n1, :SR, alg) - alg = Arnoldi(; orth = orth, krylovdim = n, maxiter = 1, tol = tolerance(T), - verbosity = 0) + alg = Arnoldi(; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), + verbosity=0) @test_logs eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n1, :SR, alg) - alg = Arnoldi(; orth = orth, krylovdim = n, maxiter = 1, tol = tolerance(T), - verbosity = 1) + alg = Arnoldi(; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), + verbosity=1) @test_logs eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n1, :SR, alg) - alg = Arnoldi(; orth = orth, krylovdim = n1 + 2, maxiter = 1, tol = tolerance(T), - verbosity = 1) + alg = Arnoldi(; orth=orth, krylovdim=n1 + 2, maxiter=1, tol=tolerance(T), + verbosity=1) @test_logs (:warn,) eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n1, - :SR, alg) - alg = Arnoldi(; orth = orth, krylovdim = n, maxiter = 1, tol = tolerance(T), - verbosity = 2) + :SR, alg) + alg = Arnoldi(; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), + verbosity=2) @test_logs (:info,) eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n1, - :SR, alg) - alg = Arnoldi(; orth = orth, krylovdim = n1, maxiter = 3, tol = tolerance(T), - verbosity = 3) + :SR, alg) + alg = Arnoldi(; orth=orth, krylovdim=n1, maxiter=3, tol=tolerance(T), + verbosity=3) @test_logs((:info,), (:info,), (:info,), (:warn,), - eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), 1, :SR, alg)) - alg = Arnoldi(; orth = orth, krylovdim = 4, maxiter = 1, tol = tolerance(T), - verbosity = 4) + eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), 1, :SR, alg)) + alg = Arnoldi(; orth=orth, krylovdim=4, maxiter=1, tol=tolerance(T), + verbosity=4) # since it is impossible to know exactly the size of the Krylov subspace after shrinking, # we only know the output for a sigle iteration @test_logs((:info,), (:info,), (:info,), (:info,), (:info,), (:warn,), - eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), 1, :SR, alg)) + eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), 1, :SR, alg)) - @test KrylovKit.eigselector(wrapop(A, Val(mode)), eltype(v); orth = orth, - krylovdim = n, maxiter = 1, - tol = tolerance(T)) isa Arnoldi + @test KrylovKit.eigselector(wrapop(A, Val(mode)), eltype(v); orth=orth, + krylovdim=n, maxiter=1, + tol=tolerance(T)) isa Arnoldi n2 = n - n1 - alg = Arnoldi(; orth = orth, krylovdim = 2 * n, maxiter = 1, tol = tolerance(T)) + alg = Arnoldi(; orth=orth, krylovdim=2 * n, maxiter=1, tol=tolerance(T)) D2, V2, info2 = @constinferred eigsolve(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), n2, :LR, alg) - D = sort(sort(eigvals(A); by = imag, rev = true); alg = MergeSort, by = real) - D2′ = sort(sort(D2; by = imag, rev = true); alg = MergeSort, by = real) - @test vcat(D1[1:n1], D2′[(end-n2+1):end]) ≈ D + wrapvec(v, Val(mode)), n2, :LR, alg) + D = sort(sort(eigvals(A); by=imag, rev=true); alg=MergeSort, by=real) + D2′ = sort(sort(D2; by=imag, rev=true); alg=MergeSort, by=real) + @test vcat(D1[1:n1], D2′[(end - n2 + 1):end]) ≈ D U1 = stack(unwrapvec, V1) U2 = stack(unwrapvec, V2) @@ -154,13 +154,13 @@ end if T <: Complex n1 = div(n, 2) D1, V1, info = eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n1, - :SI, - alg) + :SI, + alg) n2 = n - n1 D2, V2, info = eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n2, - :LI, - alg) - D = sort(eigvals(A); by = imag) + :LI, + alg) + D = sort(eigvals(A); by=imag) @test vcat(D1[1:n1], reverse(D2[1:n2])) ≊ D @@ -170,10 +170,10 @@ end @test A * U2 ≈ U2 * Diagonal(D2) end - alg = Arnoldi(; orth = orth, krylovdim = 2n, maxiter = 1, tol = tolerance(T), - verbosity = 1) + alg = Arnoldi(; orth=orth, krylovdim=2n, maxiter=1, tol=tolerance(T), + verbosity=1) @test_logs (:warn,) (:warn,) eigsolve(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), n + 1, :LM, alg) + wrapvec(v, Val(mode)), n + 1, :LM, alg) end end end @@ -186,15 +186,15 @@ end @testset for orth in orths A = rand(T, (N, N)) .- one(T) / 2 v = rand(T, (N,)) - alg = Arnoldi(; krylovdim = 3 * n, maxiter = 20, - tol = tolerance(T), eager = true, verbosity = 0) + alg = Arnoldi(; krylovdim=3 * n, maxiter=20, + tol=tolerance(T), eager=true, verbosity=0) D1, V1, info1 = @constinferred eigsolve(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), n, :SR, alg) + wrapvec(v, Val(mode)), n, :SR, alg) D2, V2, info2 = eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n, :LR, - alg) + alg) D3, V3, info3 = eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n, :LM, - alg) - D = sort(eigvals(A); by = imag, rev = true) + alg) + D = sort(eigvals(A); by=imag, rev=true) l1 = info1.converged l2 = info2.converged @@ -202,11 +202,11 @@ end @test l1 > 0 @test l2 > 0 @test l3 > 0 - @test D1[1:l1] ≊ sort(D; alg = MergeSort, by = real)[1:l1] - @test D2[1:l2] ≊ sort(D; alg = MergeSort, by = real, rev = true)[1:l2] + @test D1[1:l1] ≊ sort(D; alg=MergeSort, by=real)[1:l1] + @test D2[1:l2] ≊ sort(D; alg=MergeSort, by=real, rev=true)[1:l2] # sorting by abs does not seem very reliable if two distinct eigenvalues are close # in absolute value, so we perform a second sort afterwards using the real part - @test D3[1:l3] ≊ sort(D; by = abs, rev = true)[1:l3] + @test D3[1:l3] ≊ sort(D; by=abs, rev=true)[1:l3] U1 = stack(unwrapvec, V1) U2 = stack(unwrapvec, V2) @@ -220,17 +220,17 @@ end if T <: Complex D1, V1, info1 = eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n, - :SI, alg) + :SI, alg) D2, V2, info2 = eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n, - :LI, alg) + :LI, alg) D = eigvals(A) l1 = info1.converged l2 = info2.converged @test l1 > 0 @test l2 > 0 - @test D1[1:l1] ≈ sort(D; by = imag)[1:l1] - @test D2[1:l2] ≈ sort(D; by = imag, rev = true)[1:l2] + @test D1[1:l1] ≈ sort(D; by=imag)[1:l1] + @test D2[1:l2] ≈ sort(D; by=imag, rev=true)[1:l2] U1 = stack(unwrapvec, V1) U2 = stack(unwrapvec, V2) @@ -253,27 +253,27 @@ end D = randn(T, N) A = V * Diagonal(D) / V v = rand(T, (N,)) - alg = Arnoldi(; krylovdim = 3 * n, maxiter = 20, - tol = tolerance(T), eager = true, verbosity = 0) + alg = Arnoldi(; krylovdim=3 * n, maxiter=20, + tol=tolerance(T), eager=true, verbosity=0) D1, V1, info1 = @constinferred realeigsolve(wrapop(A, Val(mode)), - wrapvec(v, Val(mode)), n, :SR, alg) + wrapvec(v, Val(mode)), n, :SR, alg) D2, V2, info2 = realeigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n, - :LR, - alg) + :LR, + alg) D3, V3, info3 = realeigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n, - :LM, - alg) + :LM, + alg) l1 = info1.converged l2 = info2.converged l3 = info3.converged @test l1 > 0 @test l2 > 0 @test l3 > 0 - @test D1[1:l1] ≊ sort(D; alg = MergeSort)[1:l1] - @test D2[1:l2] ≊ sort(D; alg = MergeSort, rev = true)[1:l2] + @test D1[1:l1] ≊ sort(D; alg=MergeSort)[1:l1] + @test D2[1:l2] ≊ sort(D; alg=MergeSort, rev=true)[1:l2] # sorting by abs does not seem very reliable if two distinct eigenvalues are close # in absolute value, so we perform a second sort afterwards using the real part - @test D3[1:l3] ≊ sort(D; by = abs, rev = true)[1:l3] + @test D3[1:l3] ≊ sort(D; by=abs, rev=true)[1:l3] @test eltype(D1) == T @test eltype(D2) == T @@ -297,12 +297,12 @@ end J = [Z -I; I Z] Ar1 = (Ar - J * Ar * J) / 2 Ar2 = (Ar + J * Ar * J) / 2 - A = complex.(Ar1[1:N, 1:N], -Ar1[1:N, (N+1):end]) - B = complex.(Ar2[1:N, 1:N], +Ar2[1:N, (N+1):end]) + A = complex.(Ar1[1:N, 1:N], -Ar1[1:N, (N + 1):end]) + B = complex.(Ar2[1:N, 1:N], +Ar2[1:N, (N + 1):end]) f = buildrealmap(A, B) v = rand(complex(T), (N,)) - alg = Arnoldi(; krylovdim = 3 * n, maxiter = 20, - tol = tolerance(T), eager = true, verbosity = 0) + alg = Arnoldi(; krylovdim=3 * n, maxiter=20, + tol=tolerance(T), eager=true, verbosity=0) D1, V1, info1 = @constinferred realeigsolve(f, v, n, :SR, alg) D2, V2, info2 = realeigsolve(f, v, n, :LR, alg) D3, V3, info3 = realeigsolve(f, v, n, :LM, alg) @@ -313,11 +313,11 @@ end @test l1 > 0 @test l2 > 0 @test l3 > 0 - @test D1[1:l1] ≊ sort(D; alg = MergeSort)[1:l1] - @test D2[1:l2] ≊ sort(D; alg = MergeSort, rev = true)[1:l2] + @test D1[1:l1] ≊ sort(D; alg=MergeSort)[1:l1] + @test D2[1:l2] ≊ sort(D; alg=MergeSort, rev=true)[1:l2] # sorting by abs does not seem very reliable if two distinct eigenvalues are close # in absolute value, so we perform a second sort afterwards using the real part - @test D3[1:l3] ≊ sort(D; by = abs, rev = true)[1:l3] + @test D3[1:l3] ≊ sort(D; by=abs, rev=true)[1:l3] @test eltype(D1) == T @test eltype(D2) == T @@ -342,12 +342,12 @@ end A[2, 1] = 1e-9 A[1, 2] = -1e-9 v = ones(Float64, size(A, 1)) - @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol = 1e-8, verbosity = 0)) - @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol = 1e-8, verbosity = 1)) - @test_logs (:info,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol = 1e-8, verbosity = 2)) - @test_logs (:warn,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol = 1e-10, verbosity = 1)) + @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-8, verbosity=0)) + @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-8, verbosity=1)) + @test_logs (:info,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-8, verbosity=2)) + @test_logs (:warn,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-10, verbosity=1)) @test_logs (:warn,) (:info,) realeigsolve(A, v, 1, :LM, - Arnoldi(; tol = 1e-10, verbosity = 2)) + Arnoldi(; tol=1e-10, verbosity=2)) # this should not trigger a warning A[1, 2] = A[2, 1] = 0 @@ -355,11 +355,12 @@ end A[2, 2] = A[3, 3] = 0.99 A[3, 2] = 1e-6 A[2, 3] = -1e-6 - @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol = 1e-12, verbosity = 0)) - @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol = 1e-12, verbosity = 1)) - @test_logs (:info,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol = 1e-12, verbosity = 2)) + @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=0)) + @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=1)) + @test_logs (:info,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=2)) end + @testset "Block Lanczos - eigsolve for large sparse matrix and map input" begin function toric_code_strings(m::Int, n::Int) From a9f0d137e05042b13ff3fce5324b5105811f2fdb Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Fri, 11 Apr 2025 15:27:33 +0800 Subject: [PATCH 18/82] add interface for single vector (element in an abstract inner produnct space) --- Project.toml | 1 + src/algorithms.jl | 9 ++++- src/eigsolve/lanczos.jl | 5 ++- src/factorizations/lanczos.jl | 8 ++-- src/innerproductvec.jl | 37 ++++++++++++++++++- test/eigsolve.jl | 69 +++++++++++++++++++++++++++++++++++ test/innerproductvec.jl | 25 ++++++++++--- 7 files changed, 140 insertions(+), 14 deletions(-) diff --git a/Project.toml b/Project.toml index 6e207ef8..f46ff94e 100644 --- a/Project.toml +++ b/Project.toml @@ -8,6 +8,7 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" PackageExtensionCompat = "65ce6f38-6b18-4e1d-a461-8949797d7930" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" [weakdeps] diff --git a/src/algorithms.jl b/src/algorithms.jl index 2e33a6db..901f201f 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -125,6 +125,8 @@ struct BlockLanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm tol::S eager::Bool verbosity::Int + blocksize::Int + init_generator::Bool end function Lanczos(; krylovdim::Int=KrylovDefaults.krylovdim[], @@ -133,9 +135,12 @@ function Lanczos(; orth::Orthogonalizer=KrylovDefaults.orth, eager::Bool=false, verbosity::Int=KrylovDefaults.verbosity[], - blockmode::Bool=false) + blockmode::Bool=false, + blocksize::Int=-1, + init_generator::Bool=false) if blockmode - return BlockLanczos(orth, krylovdim, maxiter, tol, eager, verbosity) + init_generator && blocksize < 1 && error("blocksize must be greater than 1") + return BlockLanczos(orth, krylovdim, maxiter, tol, eager, verbosity, blocksize, init_generator) else return Lanczos(orth, krylovdim, maxiter, tol, eager, verbosity) end diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index d2a25f7b..12a7bd38 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -155,7 +155,10 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) maxiter = alg.maxiter tol = alg.tol verbosity = alg.verbosity - if typeof(x₀) <: AbstractMatrix + + if alg.init_generator + x₀_vec = push!(similar_rand(x₀, alg.blocksize-1), x₀) + elseif typeof(x₀) <: AbstractMatrix x₀_vec = [x₀[:,i] for i in 1:size(x₀,2)] else x₀_vec = x₀ diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index fd0234e3..d1a3c094 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -463,7 +463,7 @@ function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.ve abstract_qr!(X₁_view,S) Ax₁ = [apply(A, x) for x in X₁_view] M₁_view = view(TDB, 1:bs_now, 1:bs_now) - inner!(M₁_view, X₁_view, Ax₁) + blockinner!(M₁_view, X₁_view, Ax₁) verbosity >= WARN_LEVEL && warn_nonhermitian(M₁_view) M₁_view = (M₁_view + M₁_view') / 2 @@ -482,7 +482,7 @@ function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.ve # Calculate the next block Ax₂ = [apply(A, x) for x in X₂_view] M₂_view = view(TDB, bs_now+1:bs_now+bs_next, bs_now+1:bs_now+bs_next) - inner!(M₂_view, X₂_view, Ax₂) + blockinner!(M₂_view, X₂_view, Ax₂) M₂_view = (M₂_view + M₂_view') / 2 # Calculate the new residual. Get R2 @@ -527,7 +527,7 @@ function expand!(iter::BlockLanczosIterator, state::BlockLanczosFactorization; # Apply the operator and calculate the M. Get Mnext Axₖnext = [apply(iter.operator, x) for x in Xnext_view] Mnext_view = view(state.TDB, all_size+1:all_size+bs_next, all_size+1:all_size+bs_next) - inner!(Mnext_view, Xnext_view, Axₖnext) + blockinner!(Mnext_view, Xnext_view, Axₖnext) verbosity >= WARN_LEVEL && warn_nonhermitian(Mnext_view) Mnext_view = (Mnext_view + Mnext_view') / 2 @@ -566,7 +566,7 @@ function compute_residual!(R::AbstractVector{T}, A_X::AbstractVector{T}, X::Abst end function ortho_basis!(basis_new::AbstractVector{T}, basis_sofar::AbstractVector{T}, tmp::AbstractMatrix) where T - inner!(tmp, basis_sofar, basis_new) + blockinner!(tmp, basis_sofar, basis_new) mul!(basis_new, basis_sofar, - tmp) return basis_new end diff --git a/src/innerproductvec.jl b/src/innerproductvec.jl index a58e135b..4a473ce2 100644 --- a/src/innerproductvec.jl +++ b/src/innerproductvec.jl @@ -36,6 +36,39 @@ function Base.similar(v::InnerProductVec, ::Type{T}=scalartype(v)) where {T} return InnerProductVec(similar(v.vec), v.dotf) end +function similar_rand(v::InnerProductVec, n::Int) + @assert n >0 + k = length(v.vec) + res = [similar(v) for _ in 1:n] + T = eltype(v.vec) + try + res[1].vec .+= rand(T,k) + catch + error("Please make sure you have implemented rand operation for your abstract vector type") + end + for i in 2:n + res[i].vec .+= rand(T, k) + end + return res +end + +function similar_rand(v::AbstractVector,n::Int) + @assert n >0 + k = length(v) + res = [similar(v) for _ in 1:n] + T = eltype(v) + try + res[1] .+= rand(T,k) + catch + error("Please make sure you have implemented rand operation for your abstract vector type") + end + for i in 2:n + res[i] .+= rand(T, k) + end + return res +end + + Base.getindex(v::InnerProductVec) = v.vec function Base.copy!(w::InnerProductVec{F}, v::InnerProductVec{F}) where {F} @@ -132,7 +165,7 @@ function VectorInterface.inner(v::InnerProductVec{F}, w::InnerProductVec{F}) whe return v.dotf(v.vec, w.vec) end -function inner!(M::AbstractMatrix, +function blockinner!(M::AbstractMatrix, x::AbstractVector, y::AbstractVector) @assert size(M) == (length(x), length(y)) "Matrix dimensions must match" @@ -150,7 +183,7 @@ VectorInterface.norm(v::InnerProductVec) = sqrt(real(inner(v, v))) # used for debugging function blockinner(v::AbstractVector, w::AbstractVector;S::Type = Float64) M = Matrix{S}(undef, length(v), length(w)) - inner!(M, v, w) + blockinner!(M, v, w) return M end diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 636e79c7..a80c4ef9 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -562,5 +562,74 @@ end @test findmax([norm(Aip(V[i]) - D[i] * V[i]) for i in 1:eig_num])[1] < tolerance(T) end +# For user interface, we should give one vector input block lanczos method. +@testset "Block Lanczos for init_generator - eigsolve full" begin + @testset for T in [Float32, Float64, ComplexF32, ComplexF64] + T = ComplexF64 + Random.seed!(1234) + A0 = rand(T, (n, n)) .- one(T) / 2 + A0 = (A0 + A0') / 2 + block_size = 2 + x₀ = rand(T, n) + n1 = div(n, 2) # eigenvalues to solve + eigvalsA = eigvals(A0) + # Different from Lanczos, we don't set maxiter =1 here because the iteration times in Lanczos + # in in fact in the control of deminsion of Krylov subspace. And we Don't use it. + @testset for A in [A0, x -> A0 * x] + A = copy(A0) + alg = Lanczos(; krylovdim = n, maxiter = 4, tol = tolerance(T), verbosity = 2, blockmode = true, + init_generator = true, blocksize = block_size) + D1, V1, info = @test_logs (:info,) eigsolve(A, x₀, n1, :SR, alg) + alg = Lanczos(; krylovdim = n, maxiter = 4, tol = tolerance(T), verbosity = 1, blockmode = true, + init_generator = true, blocksize = block_size) + @test_logs eigsolve(A, x₀, n1, :SR, alg) + alg = Lanczos(; krylovdim = n1 + 1, maxiter = 4, tol = tolerance(T), verbosity = 1, blockmode = true, + init_generator = true, blocksize = block_size) + @test_logs (:warn,) eigsolve(A, x₀, n1, :SR, alg) + alg = Lanczos(; krylovdim = n, maxiter = 4, tol = tolerance(T), verbosity = 2, blockmode = true, + init_generator = true, blocksize = block_size) + @test_logs (:info,) eigsolve(A, x₀, n1, :SR, alg) + alg = Lanczos(; krylovdim = n1, maxiter = 4, tol = tolerance(T), verbosity = 3, blockmode = true, + init_generator = true, blocksize = block_size) + @test_logs((:info,), (:warn,), (:info,), eigsolve(A, x₀, 1, :SR, alg)) + # Because of the _residual! function, I can't make sure the stability of types temporarily. + # So I ignore the test of @constinferred + n2 = n - n1 + alg = Lanczos(; krylovdim = 2 * n, maxiter = 4, tol = tolerance(T), blockmode = true, + init_generator = true, blocksize = block_size) + D2, V2, info = eigsolve(A, x₀, n2, :LR, alg) + D2[1:n2] + @test vcat(D1[1:n1], reverse(D2[1:n2])) ≊ eigvalsA + + U1 = hcat(V1...) + U2 = hcat(V2...) + + @test U1' * U1 ≈ I + @test U2' * U2 ≈ I + + @test (x -> KrylovKit.apply(A, x)).(V1) ≈ D1 .* V1 + @test (x -> KrylovKit.apply(A, x)).(V2) ≈ D2 .* V2 + alg = Lanczos(; krylovdim = 2n, maxiter = 5, tol = tolerance(T), verbosity = 1, blockmode = true, + init_generator = true, blocksize = block_size) + @test_logs (:warn,) (:warn,) eigsolve(A, x₀, n + 1, :LM, alg) + end + end +end +@testset "Block Lanczos for init_generator - eigsolve for abstract type" begin + T = ComplexF64 + H = rand(T, (n, n)) + H = H' * H + I + block_size = 2 + eig_num = 2 + Hip(x::Vector, y::Vector) = x' * H * y + x₀ = InnerProductVec(rand(T, n), Hip) + Aip(x::InnerProductVec) = InnerProductVec(H * x.vec, Hip) + D, V, info = eigsolve(Aip, x₀, eig_num, :SR, Lanczos(; krylovdim = n, maxiter = 10, tol = tolerance(T), + verbosity = 0, blockmode = true, init_generator = true, blocksize = block_size)) + D_true = eigvals(H) + @test D ≈ D_true[1:eig_num] + @test KrylovKit.blockinner(V, V; S = T) ≈ I + @test findmax([norm(Aip(V[i]) - D[i] * V[i]) for i in 1:eig_num])[1] < tolerance(T) +end diff --git a/test/innerproductvec.jl b/test/innerproductvec.jl index 842a928a..c4d9a46e 100644 --- a/test/innerproductvec.jl +++ b/test/innerproductvec.jl @@ -1,18 +1,18 @@ #= -inner!(M,x,y): +blockinner!(M,x,y): M[i,j] = inner(x[i],y[j]) =# -@testset "inner! for non-full vectors $T" for T in (Float32, Float64, ComplexF32, ComplexF64) +@testset "blockinner! for non-full vectors $T" for T in (Float32, Float64, ComplexF32, ComplexF64) A = [rand(T, N) for _ in 1:n] B = [rand(T, N) for _ in 1:n] M = Matrix{T}(undef, n, n) - KrylovKit.inner!(M, A, B) + KrylovKit.blockinner!(M, A, B) M0 = hcat(A...)' * hcat(B...) @test eltype(M) == T @test isapprox(M, M0; atol = 1e4 * eps(real(T))) end -@testset "inner! for abstract inner product" begin +@testset "blockinner! for abstract inner product" begin T = ComplexF64 H = rand(T, N, N); H = H'*H + I; @@ -31,10 +31,25 @@ end Y[i] = InnerProductVec(rand(T, N), ip) end M = Matrix{T}(undef, n, n); - KrylovKit.inner!(M, X, Y); + KrylovKit.blockinner!(M, X, Y); Xm = hcat([X[i].vec for i in 1:n]...); Ym = hcat([Y[i].vec for i in 1:n]...); M0 = Xm' * H * Ym; @test eltype(M) == T @test isapprox(M, M0; atol = eps(real(T))^(0.5)) +end + +@testset "similar_rand" begin + @testset for T in [Float32,Float64,ComplexF32,ComplexF64] + v = InnerProductVec(rand(T, n), x -> x'*x) + sv = KrylovKit.similar_rand(v, n) + @test length(sv) == n + @test eltype(sv[2].vec) == T + @test sv[2].dotf == v.dotf + + u = rand(T, n) + su = KrylovKit.similar_rand(u, n) + @test length(su) == n + @test eltype(su[2]) == T + end end \ No newline at end of file From 72d7dbdc6cd69d2a2ecdbc46ba4bbb5c350566e2 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Fri, 11 Apr 2025 15:30:47 +0800 Subject: [PATCH 19/82] revise Project.toml --- Project.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Project.toml b/Project.toml index f46ff94e..6e207ef8 100644 --- a/Project.toml +++ b/Project.toml @@ -8,7 +8,6 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" PackageExtensionCompat = "65ce6f38-6b18-4e1d-a461-8949797d7930" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" [weakdeps] From 706f7c8a9034d47578dd809d7f3cd962ec2d704c Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Mon, 14 Apr 2025 02:50:16 +0800 Subject: [PATCH 20/82] add references --- src/factorizations/lanczos.jl | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index d1a3c094..1170e791 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -370,19 +370,21 @@ end # block lanczos #= +The basic theory of the Block Lanczos algorithm can be referred to : +Golub, G. H., & Van Loan, C. F. (2013). Matrix computations (4th ed., pp. 566–569). Johns Hopkins University Press. + Now what I implement is block lanczos with mutable block size. But I'm still confused is it neccesary. That is to say, Can we asseert the iteration would end with size shrink? - -Mathematically: -For a set of initial abstract vectors X₀ = {x₁,..,xₚ}, where A is a hermitian operator, if - +Mathematically: for a set of initial abstract vectors X₀ = {x₁,..,xₚ}, where A is a hermitian operator, if Sₖ = {x ∈ AʲX₀:j=0,..,k-1} - is linear dependent, can we assert that Rₖ ∈ span(A^{k-2}X₀,A^{k-1}X₀) or at least in span(Sₖ)? For vectors in F^d I believe it's right. But in a abstract inner product space, it's obviouly much more complicated. What ever, mutable block size is at least undoubtedly useful for non-hermitian operator so I implement it. https://www.netlib.org/utk/people/JackDongarra/etemplates/node252.html#ABLEsection + +I would like to thank Professor Jinguo Liu for his insightful discussions, which greatly helped me understand and implement the Block Lanczos method. +I am also grateful to Dr. Jutho for his patience with my messy code and for his valuable suggestions for improvement. =# mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: BlockKrylovFactorization{T,S,SR} From 5755e92b04577bb347bc663e79b93412011749e0 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Mon, 14 Apr 2025 09:43:15 +0800 Subject: [PATCH 21/82] revise reference --- src/factorizations/lanczos.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index 1170e791..8171fc11 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -382,9 +382,6 @@ For vectors in F^d I believe it's right. But in a abstract inner product space, What ever, mutable block size is at least undoubtedly useful for non-hermitian operator so I implement it. https://www.netlib.org/utk/people/JackDongarra/etemplates/node252.html#ABLEsection - -I would like to thank Professor Jinguo Liu for his insightful discussions, which greatly helped me understand and implement the Block Lanczos method. -I am also grateful to Dr. Jutho for his patience with my messy code and for his valuable suggestions for improvement. =# mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: BlockKrylovFactorization{T,S,SR} From 2c0320ede18473107d24109aa01e3e6f4807b835 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Fri, 18 Apr 2025 23:08:27 +0800 Subject: [PATCH 22/82] update --- Project.toml | 1 + docs/src/man/implementation.md | 2 +- src/algorithms.jl | 8 +-- src/eigsolve/lanczos.jl | 9 +-- src/factorizations/lanczos.jl | 43 +++++++++--- src/innerproductvec.jl | 74 +++++++-------------- src/orthonormal.jl | 28 +------- test/eigsolve.jl | 118 ++++++--------------------------- test/innerproductvec.jl | 39 ++++++++--- test/orthonormal.jl | 11 ++- 10 files changed, 122 insertions(+), 211 deletions(-) diff --git a/Project.toml b/Project.toml index 6e207ef8..f46ff94e 100644 --- a/Project.toml +++ b/Project.toml @@ -8,6 +8,7 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" PackageExtensionCompat = "65ce6f38-6b18-4e1d-a461-8949797d7930" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" [weakdeps] diff --git a/docs/src/man/implementation.md b/docs/src/man/implementation.md index 70be79c0..1079b8b3 100644 --- a/docs/src/man/implementation.md +++ b/docs/src/man/implementation.md @@ -10,7 +10,7 @@ KrylovKit.Basis Many Krylov based algorithms use an orthogonal basis to parameterize the Krylov subspace. In that case, the specific implementation `OrthonormalBasis{T}` can be used: ```@docs -KrylovKit.OrthonormalBasis +KrylovKit.Orthonormal ``` We can orthogonalize or orthonormalize a given vector to another vector (assumed normalized) diff --git a/src/algorithms.jl b/src/algorithms.jl index 901f201f..fe4f7ed9 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -126,7 +126,6 @@ struct BlockLanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm eager::Bool verbosity::Int blocksize::Int - init_generator::Bool end function Lanczos(; krylovdim::Int=KrylovDefaults.krylovdim[], @@ -136,11 +135,10 @@ function Lanczos(; eager::Bool=false, verbosity::Int=KrylovDefaults.verbosity[], blockmode::Bool=false, - blocksize::Int=-1, - init_generator::Bool=false) + blocksize::Int=-1) if blockmode - init_generator && blocksize < 1 && error("blocksize must be greater than 1") - return BlockLanczos(orth, krylovdim, maxiter, tol, eager, verbosity, blocksize, init_generator) + blocksize <= 1 && error("blocksize must be greater than 1") + return BlockLanczos(orth, krylovdim, maxiter, tol, eager, verbosity, blocksize) else return Lanczos(orth, krylovdim, maxiter, tol, eager, verbosity) end diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index 12a7bd38..f3bd27f3 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -155,14 +155,7 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) maxiter = alg.maxiter tol = alg.tol verbosity = alg.verbosity - - if alg.init_generator - x₀_vec = push!(similar_rand(x₀, alg.blocksize-1), x₀) - elseif typeof(x₀) <: AbstractMatrix - x₀_vec = [x₀[:,i] for i in 1:size(x₀,2)] - else - x₀_vec = x₀ - end + x₀_vec = push!(block_randn_like(x₀, alg.blocksize-1), x₀) bs_now = length(x₀_vec) iter = BlockLanczosIterator(A, x₀_vec, maxiter, alg.orth) diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index 8171fc11..bbb88bdc 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -459,18 +459,18 @@ function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.ve X₁_view = view(V_basis, 1:bs_now) copyto!.(X₁_view, x₀_vec) - abstract_qr!(X₁_view,S) + abstract_qr!(S, X₁_view; tol = 1e4 * eps(real(S))) Ax₁ = [apply(A, x) for x in X₁_view] M₁_view = view(TDB, 1:bs_now, 1:bs_now) - blockinner!(M₁_view, X₁_view, Ax₁) + block_inner!(M₁_view, X₁_view, Ax₁) verbosity >= WARN_LEVEL && warn_nonhermitian(M₁_view) M₁_view = (M₁_view + M₁_view') / 2 # We have to write it as a form of matrix multiplication. Get R1 - residual = mul!(Ax₁, X₁_view, - M₁_view) + residual = block_mul!(Ax₁, X₁_view, - M₁_view, 1.0, 1.0) # QR decomposition of residual to get the next basis. Get X2 and B1 - B₁, good_idx = abstract_qr!(residual,S) + B₁, good_idx = abstract_qr!(S, residual; tol = 1e4 * eps(real(S))) bs_next = length(good_idx) X₂_view = view(V_basis, bs_now+1:bs_now+bs_next) copyto!.(X₂_view, residual[good_idx]) @@ -481,7 +481,7 @@ function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.ve # Calculate the next block Ax₂ = [apply(A, x) for x in X₂_view] M₂_view = view(TDB, bs_now+1:bs_now+bs_next, bs_now+1:bs_now+bs_next) - blockinner!(M₂_view, X₂_view, Ax₂) + block_inner!(M₂_view, X₂_view, Ax₂) M₂_view = (M₂_view + M₂_view') / 2 # Calculate the new residual. Get R2 @@ -513,7 +513,7 @@ function expand!(iter::BlockLanczosIterator, state::BlockLanczosFactorization; bs_now = length(Rₖ) # Get the current residual as the initial value of the new basis. Get Xnext - Bₖ, good_idx = abstract_qr!(Rₖ,S) + Bₖ, good_idx = abstract_qr!(S, Rₖ; tol = 1e4 * eps(real(S))) bs_next = length(good_idx) Xnext_view = view(state.V.basis, all_size+1:all_size+bs_next) copyto!.(Xnext_view, Rₖ[good_idx]) @@ -526,7 +526,7 @@ function expand!(iter::BlockLanczosIterator, state::BlockLanczosFactorization; # Apply the operator and calculate the M. Get Mnext Axₖnext = [apply(iter.operator, x) for x in Xnext_view] Mnext_view = view(state.TDB, all_size+1:all_size+bs_next, all_size+1:all_size+bs_next) - blockinner!(Mnext_view, Xnext_view, Axₖnext) + block_inner!(Mnext_view, Xnext_view, Axₖnext) verbosity >= WARN_LEVEL && warn_nonhermitian(Mnext_view) Mnext_view = (Mnext_view + Mnext_view') / 2 @@ -565,8 +565,8 @@ function compute_residual!(R::AbstractVector{T}, A_X::AbstractVector{T}, X::Abst end function ortho_basis!(basis_new::AbstractVector{T}, basis_sofar::AbstractVector{T}, tmp::AbstractMatrix) where T - blockinner!(tmp, basis_sofar, basis_new) - mul!(basis_new, basis_sofar, - tmp) + block_inner!(tmp, basis_sofar, basis_new) + block_mul!(basis_new, basis_sofar, - tmp, 1.0, 1.0) return basis_new end @@ -575,3 +575,28 @@ function warn_nonhermitian(M::AbstractMatrix) @warn "Enforce Hermiticity on the triangular diagonal blocks matrix, even though the operator may not be Hermitian." end end + +function abstract_qr!(::Type{S}, block::AbstractVector{T}; tol::Real) where {T,S} + n = length(block) + rank_shrink = false + idx = ones(Int64,n) + R = zeros(S, n, n) + @inbounds for j in 1:n + αⱼ = block[j] + for i in 1:j-1 + R[i, j] = inner(block[i], αⱼ) + αⱼ -= R[i, j] * block[i] + end + β = norm(αⱼ) + if !(β ≤ tol) + R[j, j] = β + block[j] = αⱼ / β + else + block[j] *= S(0) + rank_shrink = true + idx[j] = 0 + end + end + good_idx = findall(idx .> 0) + return R[good_idx,:], good_idx +end \ No newline at end of file diff --git a/src/innerproductvec.jl b/src/innerproductvec.jl index 4a473ce2..23d0173b 100644 --- a/src/innerproductvec.jl +++ b/src/innerproductvec.jl @@ -36,38 +36,7 @@ function Base.similar(v::InnerProductVec, ::Type{T}=scalartype(v)) where {T} return InnerProductVec(similar(v.vec), v.dotf) end -function similar_rand(v::InnerProductVec, n::Int) - @assert n >0 - k = length(v.vec) - res = [similar(v) for _ in 1:n] - T = eltype(v.vec) - try - res[1].vec .+= rand(T,k) - catch - error("Please make sure you have implemented rand operation for your abstract vector type") - end - for i in 2:n - res[i].vec .+= rand(T, k) - end - return res -end - -function similar_rand(v::AbstractVector,n::Int) - @assert n >0 - k = length(v) - res = [similar(v) for _ in 1:n] - T = eltype(v) - try - res[1] .+= rand(T,k) - catch - error("Please make sure you have implemented rand operation for your abstract vector type") - end - for i in 2:n - res[i] .+= rand(T, k) - end - return res -end - +Random.randn!(v::InnerProductVec) = (randn!(v.vec); v) Base.getindex(v::InnerProductVec) = v.vec @@ -94,14 +63,6 @@ function LinearAlgebra.rmul!(v::InnerProductVec, a::Number) rmul!(v.vec, a) return v end -function LinearAlgebra.mul!(A::AbstractVector{T},B::AbstractVector{T},M::AbstractMatrix) where T - @inbounds for i in eachindex(A) - @simd for j in eachindex(B) - A[i] += B[j] * M[j,i] - end - end - return A -end function LinearAlgebra.axpy!(a::Number, v::InnerProductVec{F}, @@ -165,7 +126,9 @@ function VectorInterface.inner(v::InnerProductVec{F}, w::InnerProductVec{F}) whe return v.dotf(v.vec, w.vec) end -function blockinner!(M::AbstractMatrix, +VectorInterface.norm(v::InnerProductVec) = sqrt(real(inner(v, v))) + +function block_inner!(M::AbstractMatrix, x::AbstractVector, y::AbstractVector) @assert size(M) == (length(x), length(y)) "Matrix dimensions must match" @@ -178,19 +141,30 @@ function blockinner!(M::AbstractMatrix, return M end -VectorInterface.norm(v::InnerProductVec) = sqrt(real(inner(v, v))) - -# used for debugging -function blockinner(v::AbstractVector, w::AbstractVector;S::Type = Float64) - M = Matrix{S}(undef, length(v), length(w)) - blockinner!(M, v, w) - return M -end - function Base.copyto!(x::InnerProductVec, y::InnerProductVec) @assert x.dotf == y.dotf "Dot functions must match" copyto!(x.vec, y.vec) return x end +block_randn_like(v, n::Int) = [randn!(similar(v)) for _ in 1:n] + +function block_mul!(A::AbstractVector, B::AbstractVector, M::AbstractMatrix, alpha::Number, beta::Number) + @assert (length(B) == size(M, 1)) && (length(A) == size(M, 2)) "Matrix dimensions must match" + @inbounds for i in 1:length(A) + A[i] = beta * A[i] + for j in 1:length(B) + A[i] += alpha * B[j] * M[j, i] + end + end + return A +end + +# used for debugging +function block_inner(v::AbstractVector, w::AbstractVector;S::Type = Float64) + M = Matrix{S}(undef, length(v), length(w)) + block_inner!(M, v, w) + return M +end + diff --git a/src/orthonormal.jl b/src/orthonormal.jl index 3f0cb254..693f16aa 100644 --- a/src/orthonormal.jl +++ b/src/orthonormal.jl @@ -573,30 +573,4 @@ and its concrete subtypes [`ClassicalGramSchmidt`](@ref), [`ModifiedGramSchmidt` [`ClassicalGramSchmidt2`](@ref), [`ModifiedGramSchmidt2`](@ref), [`ClassicalGramSchmidtIR`](@ref) and [`ModifiedGramSchmidtIR`](@ref). """ -orthonormalize, orthonormalize!! - -function abstract_qr!(block::AbstractVector{T}, S::Type; - tol::Real = 1e4 * eps(real(S))) where {T} - n = length(block) - rank_shrink = false - idx = ones(Int64,n) - R = zeros(S, n, n) - @inbounds for j in 1:n - αⱼ = block[j] - for i in 1:j-1 - R[i, j] = inner(block[i], αⱼ) - αⱼ -= R[i, j] * block[i] - end - β = norm(αⱼ) - if !(β ≤ tol) - R[j, j] = β - block[j] = αⱼ / β - else - block[j] *= S(0) - rank_shrink = true - idx[j] = 0 - end - end - good_idx = findall(idx .> 0) - return R[good_idx,:], good_idx -end \ No newline at end of file +orthonormalize, orthonormalize!! \ No newline at end of file diff --git a/test/eigsolve.jl b/test/eigsolve.jl index a80c4ef9..299ed804 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -425,21 +425,20 @@ end Random.seed!(4) sites_num = 3 p = 5 # block size - X1 = Matrix(qr(rand(2^(2 * sites_num^2), p)).Q) + x₀ = rand(2^(2 * sites_num^2)) get_value_num = 10 tol = 1e-6 h_mat = toric_code_hamiltonian_matrix(sites_num, sites_num) # matrix input - D, U, info = eigsolve(-h_mat, X1, get_value_num, :SR, - Lanczos(; maxiter = 20, tol = tol, blockmode = true)) + alg = Lanczos(; maxiter = 20, tol = tol, blockmode = true, blocksize = p) + D, U, info = eigsolve(-h_mat, x₀, get_value_num, :SR, alg) @show D[1:get_value_num] @test count(x -> abs(x + 16.0) < 2.0 - tol, D[1:get_value_num]) == 4 @test count(x -> abs(x + 16.0) < tol, D[1:get_value_num]) == 4 # map input - D, U, info = eigsolve(x -> -h_mat * x, X1, get_value_num, :SR, - Lanczos(; maxiter = 20, tol = tol, blockmode = true)) + D, U, info = eigsolve(x -> -h_mat * x, x₀, get_value_num, :SR, alg) @show D[1:get_value_num] @test count(x -> abs(x + 16.0) < 1.9, D[1:get_value_num]) == 4 @test count(x -> abs(x + 16.0) < 1e-8, D[1:get_value_num]) == 4 @@ -460,33 +459,33 @@ In the future, I plan to add a non-Hermitian eigenvalue solver and implement mor As a result, I’ve decided to postpone dealing with the in-place test issue for now. =# +# For user interface, we should give one vector input block lanczos method. @testset "Block Lanczos - eigsolve full" begin @testset for T in [Float32, Float64, ComplexF32, ComplexF64] - Random.seed!(1234) + Random.seed!(6) A0 = rand(T, (n, n)) .- one(T) / 2 A0 = (A0 + A0') / 2 block_size = 2 - x₀m = Matrix(qr(rand(T, n, block_size)).Q) - x₀ = [x₀m[:, i] for i in 1:block_size] + x₀ = rand(T, n) n1 = div(n, 2) # eigenvalues to solve eigvalsA = eigvals(A0) # Different from Lanczos, we don't set maxiter =1 here because the iteration times in Lanczos # in in fact in the control of deminsion of Krylov subspace. And we Don't use it. - @testset for A in [A0, x -> A0 * x] - alg = Lanczos(; krylovdim = n, maxiter = 4, tol = tolerance(T), verbosity = 2, blockmode = true) + for A in [A0, x -> A0 * x] + alg = Lanczos(; krylovdim = n, maxiter = 4, tol = tolerance(T), verbosity = 2, blockmode = true, blocksize = block_size) D1, V1, info = @test_logs (:info,) eigsolve(A, x₀, n1, :SR, alg) - alg = Lanczos(; krylovdim = n, maxiter = 4, tol = tolerance(T), verbosity = 1, blockmode = true) + alg = Lanczos(; krylovdim = n, maxiter = 4, tol = tolerance(T), verbosity = 1, blockmode = true, blocksize = block_size) @test_logs eigsolve(A, x₀, n1, :SR, alg) - alg = Lanczos(; krylovdim = n1 + 1, maxiter = 4, tol = tolerance(T), verbosity = 1, blockmode = true) + alg = Lanczos(; krylovdim = n1 + 1, maxiter = 4, tol = tolerance(T), verbosity = 1, blockmode = true, blocksize = block_size) @test_logs (:warn,) eigsolve(A, x₀, n1, :SR, alg) - alg = Lanczos(; krylovdim = n, maxiter = 4, tol = tolerance(T), verbosity = 2, blockmode = true) + alg = Lanczos(; krylovdim = n, maxiter = 4, tol = tolerance(T), verbosity = 2, blockmode = true, blocksize = block_size) @test_logs (:info,) eigsolve(A, x₀, n1, :SR, alg) - alg = Lanczos(; krylovdim = n1, maxiter = 4, tol = tolerance(T), verbosity = 3, blockmode = true) + alg = Lanczos(; krylovdim = n1, maxiter = 4, tol = tolerance(T), verbosity = 3, blockmode = true, blocksize = block_size) @test_logs((:info,), (:warn,), (:info,), eigsolve(A, x₀, 1, :SR, alg)) # Because of the _residual! function, I can't make sure the stability of types temporarily. # So I ignore the test of @constinferred n2 = n - n1 - alg = Lanczos(; krylovdim = 2 * n, maxiter = 4, tol = tolerance(T), blockmode = true) + alg = Lanczos(; krylovdim = 2 * n, maxiter = 4, tol = tolerance(T), blockmode = true, blocksize = block_size) D2, V2, info = eigsolve(A, x₀, n2, :LR, alg) D2[1:n2] @test vcat(D1[1:n1], reverse(D2[1:n2])) ≊ eigvalsA @@ -500,7 +499,7 @@ As a result, I’ve decided to postpone dealing with the in-place test issue for @test (x -> KrylovKit.apply(A, x)).(V1) ≈ D1 .* V1 @test (x -> KrylovKit.apply(A, x)).(V2) ≈ D2 .* V2 - alg = Lanczos(; krylovdim = 2n, maxiter = 5, tol = tolerance(T), verbosity = 1, blockmode = true) + alg = Lanczos(; krylovdim = 2n, maxiter = 5, tol = tolerance(T), verbosity = 1, blockmode = true, blocksize = block_size) @test_logs (:warn,) (:warn,) eigsolve(A, x₀, n + 1, :LM, alg) end end @@ -509,16 +508,14 @@ end # krylovdim is not used in block Lanczos so I don't add eager mode. @testset "Block Lanczos - eigsolve iteratively" begin @testset for T in [Float32, Float64, ComplexF32, ComplexF64] + Random.seed!(6) A0 = rand(T, (N, N)) .- one(T) / 2 A0 = (A0 + A0') / 2 block_size = 5 - x₀m = Matrix(qr(rand(T, N, block_size)).Q) - x₀ = [x₀m[:, i] for i in 1:block_size] + x₀ = rand(T, N) eigvalsA = eigvals(A0) - @testset for A in [A0, x -> A0 * x] - A = copy(A0) - - alg = Lanczos(; maxiter = 20, tol = tolerance(T), blockmode = true) + for A in [A0, x -> A0 * x] + alg = Lanczos(; maxiter = 20, tol = tolerance(T), blockmode = true, blocksize = block_size) D1, V1, info1 = eigsolve(A, x₀, n, :SR, alg) D2, V2, info2 = eigsolve(A, x₀, n, :LR, alg) @@ -544,80 +541,7 @@ end end end -# linear operator A must satisfies that A'H = HA. it means it's a self-adjoint operator. @testset "Block Lanczos - eigsolve for abstract type" begin - T = ComplexF64 - H = rand(T, (n, n)) - H = H' * H + I - block_size = 2 - eig_num = 2 - Hip(x::Vector, y::Vector) = x' * H * y - x₀ = [InnerProductVec(rand(T, n), Hip) for i in 1:block_size] - Aip(x::InnerProductVec) = InnerProductVec(H * x.vec, Hip) - D, V, info = eigsolve(Aip, x₀, eig_num, :SR, Lanczos(; krylovdim = n, maxiter = 10, tol = tolerance(T), - verbosity = 0, blockmode = true)) - D_true = eigvals(H) - @test D ≈ D_true[1:eig_num] - @test KrylovKit.blockinner(V, V; S = T) ≈ I - @test findmax([norm(Aip(V[i]) - D[i] * V[i]) for i in 1:eig_num])[1] < tolerance(T) -end - -# For user interface, we should give one vector input block lanczos method. -@testset "Block Lanczos for init_generator - eigsolve full" begin - @testset for T in [Float32, Float64, ComplexF32, ComplexF64] - T = ComplexF64 - Random.seed!(1234) - A0 = rand(T, (n, n)) .- one(T) / 2 - A0 = (A0 + A0') / 2 - block_size = 2 - x₀ = rand(T, n) - n1 = div(n, 2) # eigenvalues to solve - eigvalsA = eigvals(A0) - # Different from Lanczos, we don't set maxiter =1 here because the iteration times in Lanczos - # in in fact in the control of deminsion of Krylov subspace. And we Don't use it. - @testset for A in [A0, x -> A0 * x] - A = copy(A0) - alg = Lanczos(; krylovdim = n, maxiter = 4, tol = tolerance(T), verbosity = 2, blockmode = true, - init_generator = true, blocksize = block_size) - D1, V1, info = @test_logs (:info,) eigsolve(A, x₀, n1, :SR, alg) - alg = Lanczos(; krylovdim = n, maxiter = 4, tol = tolerance(T), verbosity = 1, blockmode = true, - init_generator = true, blocksize = block_size) - @test_logs eigsolve(A, x₀, n1, :SR, alg) - alg = Lanczos(; krylovdim = n1 + 1, maxiter = 4, tol = tolerance(T), verbosity = 1, blockmode = true, - init_generator = true, blocksize = block_size) - @test_logs (:warn,) eigsolve(A, x₀, n1, :SR, alg) - alg = Lanczos(; krylovdim = n, maxiter = 4, tol = tolerance(T), verbosity = 2, blockmode = true, - init_generator = true, blocksize = block_size) - @test_logs (:info,) eigsolve(A, x₀, n1, :SR, alg) - alg = Lanczos(; krylovdim = n1, maxiter = 4, tol = tolerance(T), verbosity = 3, blockmode = true, - init_generator = true, blocksize = block_size) - @test_logs((:info,), (:warn,), (:info,), eigsolve(A, x₀, 1, :SR, alg)) - # Because of the _residual! function, I can't make sure the stability of types temporarily. - # So I ignore the test of @constinferred - n2 = n - n1 - alg = Lanczos(; krylovdim = 2 * n, maxiter = 4, tol = tolerance(T), blockmode = true, - init_generator = true, blocksize = block_size) - D2, V2, info = eigsolve(A, x₀, n2, :LR, alg) - D2[1:n2] - @test vcat(D1[1:n1], reverse(D2[1:n2])) ≊ eigvalsA - - U1 = hcat(V1...) - U2 = hcat(V2...) - - @test U1' * U1 ≈ I - @test U2' * U2 ≈ I - - @test (x -> KrylovKit.apply(A, x)).(V1) ≈ D1 .* V1 - @test (x -> KrylovKit.apply(A, x)).(V2) ≈ D2 .* V2 - - alg = Lanczos(; krylovdim = 2n, maxiter = 5, tol = tolerance(T), verbosity = 1, blockmode = true, - init_generator = true, blocksize = block_size) - @test_logs (:warn,) (:warn,) eigsolve(A, x₀, n + 1, :LM, alg) - end - end -end - -@testset "Block Lanczos for init_generator - eigsolve for abstract type" begin T = ComplexF64 H = rand(T, (n, n)) H = H' * H + I @@ -627,9 +551,9 @@ end x₀ = InnerProductVec(rand(T, n), Hip) Aip(x::InnerProductVec) = InnerProductVec(H * x.vec, Hip) D, V, info = eigsolve(Aip, x₀, eig_num, :SR, Lanczos(; krylovdim = n, maxiter = 10, tol = tolerance(T), - verbosity = 0, blockmode = true, init_generator = true, blocksize = block_size)) + verbosity = 0, blockmode = true, blocksize = block_size)) D_true = eigvals(H) @test D ≈ D_true[1:eig_num] - @test KrylovKit.blockinner(V, V; S = T) ≈ I + @test KrylovKit.block_inner(V, V; S = T) ≈ I @test findmax([norm(Aip(V[i]) - D[i] * V[i]) for i in 1:eig_num])[1] < tolerance(T) end diff --git a/test/innerproductvec.jl b/test/innerproductvec.jl index c4d9a46e..b4819424 100644 --- a/test/innerproductvec.jl +++ b/test/innerproductvec.jl @@ -1,18 +1,18 @@ #= -blockinner!(M,x,y): +block_inner!(M,x,y): M[i,j] = inner(x[i],y[j]) =# -@testset "blockinner! for non-full vectors $T" for T in (Float32, Float64, ComplexF32, ComplexF64) +@testset "block_inner! for non-full vectors $T" for T in (Float32, Float64, ComplexF32, ComplexF64) A = [rand(T, N) for _ in 1:n] B = [rand(T, N) for _ in 1:n] M = Matrix{T}(undef, n, n) - KrylovKit.blockinner!(M, A, B) + KrylovKit.block_inner!(M, A, B) M0 = hcat(A...)' * hcat(B...) @test eltype(M) == T @test isapprox(M, M0; atol = 1e4 * eps(real(T))) end -@testset "blockinner! for abstract inner product" begin +@testset "block_inner! for abstract inner product" begin T = ComplexF64 H = rand(T, N, N); H = H'*H + I; @@ -31,7 +31,7 @@ end Y[i] = InnerProductVec(rand(T, N), ip) end M = Matrix{T}(undef, n, n); - KrylovKit.blockinner!(M, X, Y); + KrylovKit.block_inner!(M, X, Y); Xm = hcat([X[i].vec for i in 1:n]...); Ym = hcat([Y[i].vec for i in 1:n]...); M0 = Xm' * H * Ym; @@ -39,17 +39,40 @@ end @test isapprox(M, M0; atol = eps(real(T))^(0.5)) end -@testset "similar_rand" begin +@testset "block_randn_like" begin @testset for T in [Float32,Float64,ComplexF32,ComplexF64] v = InnerProductVec(rand(T, n), x -> x'*x) - sv = KrylovKit.similar_rand(v, n) + sv = KrylovKit.block_randn_like(v, n) @test length(sv) == n @test eltype(sv[2].vec) == T @test sv[2].dotf == v.dotf u = rand(T, n) - su = KrylovKit.similar_rand(u, n) + su = KrylovKit.block_randn_like(u, n) @test length(su) == n @test eltype(su[2]) == T end +end + +@testset "copyto! for InnerProductVec" begin + T = ComplexF64 + f = x -> x'*x + v = InnerProductVec(rand(T, n), f) + w = InnerProductVec(rand(T, n), f) + KrylovKit.copyto!(v, w) + @test v.vec == w.vec +end + +@testset "block_mul!" begin + T = ComplexF64 + f = x -> x'*x + A = [InnerProductVec(rand(T, N), f) for _ in 1:n] + Acopy = [InnerProductVec(rand(T, N), f) for _ in 1:n] + KrylovKit.copyto!(Acopy, A) + B = [InnerProductVec(rand(T, N), f) for _ in 1:n] + M = rand(T, n, n) + alpha = rand(T) + beta = rand(T) + KrylovKit.block_mul!(A, B, M, alpha, beta) + @test isapprox(hcat([A[i].vec for i in 1:n]...), beta * hcat([Acopy[i].vec for i in 1:n]...) + alpha * hcat([B[i].vec for i in 1:n]...) * M) end \ No newline at end of file diff --git a/test/orthonormal.jl b/test/orthonormal.jl index 2d83c163..42e677ae 100644 --- a/test/orthonormal.jl +++ b/test/orthonormal.jl @@ -1,12 +1,11 @@ @testset "abstract_qr! for non-full vectors $T" for T in (Float32, Float64, ComplexF32, ComplexF64) - T = ComplexF32 A = rand(T, N, n) B = copy(A) Av = [A[:, i] for i in 1:size(A, 2)] # A is a non-full rank matrix Av[n÷2] = sum(Av[n÷2+1:end] .* rand(T, n - n ÷ 2)) Bv = copy(Av) - R, gi = KrylovKit.abstract_qr!(Av, T) + R, gi = KrylovKit.abstract_qr!(T, Av; tol = 1e4 * eps(real(T))) @test length(gi) < n @test eltype(R) == eltype(eltype(A)) == T @test isapprox(hcat(Av[gi]...) * R, hcat(Bv...); atol = 1e4 * eps(real(T))) @@ -29,11 +28,11 @@ end # Make sure X is not full rank X[end] = sum(X[1:end-1] .* rand(T, n-1)) Xcopy = deepcopy(X) - R, gi = KrylovKit.abstract_qr!(X, T) + R, gi = KrylovKit.abstract_qr!(T, X; tol = 1e4 * eps(real(T))) @test length(gi) < n @test eltype(R) == T - @test isapprox(KrylovKit.blockinner(X[gi],X[gi],S = T), I; atol=1e4*eps(real(T))) + @test isapprox(KrylovKit.block_inner(X[gi],X[gi],S = T), I; atol=1e4*eps(real(T))) ΔX = norm.(mul_test(X[gi],R) - Xcopy) @test isapprox(norm(ΔX), T(0); atol=1e4*eps(real(T))) end @@ -47,8 +46,8 @@ end x₀ = [InnerProductVec(rand(T, N), ip) for i in 1:n] x₁ = [InnerProductVec(rand(T, N), ip) for i in 1:2*n] tmp = zeros(T, 2*n, n) - KrylovKit.abstract_qr!(x₁, T) + KrylovKit.abstract_qr!(T, x₁; tol = 1e4 * eps(real(T))) KrylovKit.ortho_basis!(x₀, x₁, tmp) - @test norm(KrylovKit.blockinner(x₀, x₁; S = T)) < eps(real(T))^0.5 + @test norm(KrylovKit.block_inner(x₀, x₁; S = T)) < eps(real(T))^0.5 end From 696aa686d1a99426c51b13d4b177c95d93d23bb2 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Fri, 18 Apr 2025 23:28:20 +0800 Subject: [PATCH 23/82] update --- Project.toml | 1 - docs/src/man/implementation.md | 2 +- src/orthonormal.jl | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index f46ff94e..6e207ef8 100644 --- a/Project.toml +++ b/Project.toml @@ -8,7 +8,6 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" PackageExtensionCompat = "65ce6f38-6b18-4e1d-a461-8949797d7930" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" [weakdeps] diff --git a/docs/src/man/implementation.md b/docs/src/man/implementation.md index 1079b8b3..70be79c0 100644 --- a/docs/src/man/implementation.md +++ b/docs/src/man/implementation.md @@ -10,7 +10,7 @@ KrylovKit.Basis Many Krylov based algorithms use an orthogonal basis to parameterize the Krylov subspace. In that case, the specific implementation `OrthonormalBasis{T}` can be used: ```@docs -KrylovKit.Orthonormal +KrylovKit.OrthonormalBasis ``` We can orthogonalize or orthonormalize a given vector to another vector (assumed normalized) diff --git a/src/orthonormal.jl b/src/orthonormal.jl index 693f16aa..f34764a7 100644 --- a/src/orthonormal.jl +++ b/src/orthonormal.jl @@ -573,4 +573,4 @@ and its concrete subtypes [`ClassicalGramSchmidt`](@ref), [`ModifiedGramSchmidt` [`ClassicalGramSchmidt2`](@ref), [`ModifiedGramSchmidt2`](@ref), [`ClassicalGramSchmidtIR`](@ref) and [`ModifiedGramSchmidtIR`](@ref). """ -orthonormalize, orthonormalize!! \ No newline at end of file +orthonormalize, orthonormalize!! \ No newline at end of file From aa7da7a2fd8dfd8d696ba2560265e624f4c18f61 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Sat, 19 Apr 2025 20:34:01 +0800 Subject: [PATCH 24/82] to add restart --- Project.toml | 1 + src/algorithms.jl | 8 +- src/eigsolve/lanczos.jl | 8 +- src/factorizations/lanczos.jl | 138 ++++++++++++++++------------------ src/innerproductvec.jl | 24 +++--- src/orthonormal.jl | 11 ++- test/eigsolve.jl | 3 +- test/factorize.jl | 13 ++-- test/innerproductvec.jl | 31 ++++---- test/orthonormal.jl | 22 +++--- test/testsetup.jl | 4 +- 11 files changed, 135 insertions(+), 128 deletions(-) diff --git a/Project.toml b/Project.toml index 6e207ef8..f46ff94e 100644 --- a/Project.toml +++ b/Project.toml @@ -8,6 +8,7 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" PackageExtensionCompat = "65ce6f38-6b18-4e1d-a461-8949797d7930" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" [weakdeps] diff --git a/src/algorithms.jl b/src/algorithms.jl index fe4f7ed9..b6c435fb 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -126,7 +126,10 @@ struct BlockLanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm eager::Bool verbosity::Int blocksize::Int + qr_tol::Real end +# qr_tol is the tolerance that we think a vector is non-zero in abstract_qr! +# This qr_tol will also be used in other zero_chcking in block Lanczos. function Lanczos(; krylovdim::Int=KrylovDefaults.krylovdim[], maxiter::Int=KrylovDefaults.maxiter[], @@ -135,10 +138,11 @@ function Lanczos(; eager::Bool=false, verbosity::Int=KrylovDefaults.verbosity[], blockmode::Bool=false, - blocksize::Int=-1) + blocksize::Int=-1, + qr_tol::Real=-1.0) if blockmode blocksize <= 1 && error("blocksize must be greater than 1") - return BlockLanczos(orth, krylovdim, maxiter, tol, eager, verbosity, blocksize) + return BlockLanczos(orth, krylovdim, maxiter, tol, eager, verbosity, blocksize, qr_tol) else return Lanczos(orth, krylovdim, maxiter, tol, eager, verbosity) end diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index f3bd27f3..66b9ba29 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -158,7 +158,7 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) x₀_vec = push!(block_randn_like(x₀, alg.blocksize-1), x₀) bs_now = length(x₀_vec) - iter = BlockLanczosIterator(A, x₀_vec, maxiter, alg.orth) + iter = BlockLanczosIterator(A, x₀_vec, maxiter, alg.qr_tol, alg.orth) fact = initialize(iter; verbosity=verbosity) numops = 2 # how many times we apply A @@ -173,7 +173,7 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) numops += 1 # Although norm(Rk) is not our convergence condition, when norm(Rk) is to small, we may lose too much precision and orthogonalization. - if (numiter % converge_check == 0) || (fact.normR < tol) || (fact.R_size < 2) + if (numiter % converge_check == 0) || (fact.norm_r < tol) || (fact.r_size < 2) values, vectors, howmany_actual, residuals, normresiduals, num_converged = _residual!(fact, A, howmany, tol, which,vectors, values) @@ -183,7 +183,7 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) end # This convergence condition refers to https://www.netlib.org/utk/people/JackDongarra/etemplates/node251.html - if num_converged >= howmany || fact.normR < tol + if num_converged >= howmany || fact.norm_r < tol converged = true break end @@ -237,7 +237,7 @@ function _residual!(fact::BlockLanczosFactorization, A, howmany::Int, tol::Real, copyto!(values, D[1:howmany_actual]) @inbounds for i in 1:howmany_actual - copyto!(vectors[i], basis_sofar_view[1]*U[1,i]) + copy!(vectors[i], basis_sofar_view[1]*U[1,i]) for j in 2:all_size axpy!(U[j,i], basis_sofar_view[j], vectors[i]) end diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index bbb88bdc..f794b07d 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -388,11 +388,9 @@ mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: BlockKrylovFac all_size::Int const V::OrthonormalBasis{T} # Block Lanczos Basis const TDB::AbstractMatrix{S} # TDB matrix, S is the matrix type - const R::OrthonormalBasis{T} # residual block - R_size::Int - normR::SR - - const tmp:: AbstractMatrix{S} # temporary matrix for ortho_basis! + const r::OrthonormalBasis{T} # residual block + r_size::Int + norm_r::SR end #= @@ -403,41 +401,37 @@ I don't add IR orthogonalizer because I find it sometimes unstable and I am stud Householder reorthogonalization is theoretically stable and saves memory, but the algorithm I implemented is not stable. In the future, I will add IR and Householder orthogonalizer. =# + +#= The only orthogonalization method we use in block lanczos is ModifiedGramSchmidt2. So we don't need to +provide "keepvecs" because we have to reverse all krylove vectors. +=# struct BlockLanczosIterator{F,T,O<:Orthogonalizer} <: KrylovIterator{F,T} operator::F x₀::Vector{T} maxiter::Int num_field::Type orth::O + qr_tol::Real function BlockLanczosIterator{F,T,O}(operator::F, x₀::Vector{T}, maxiter::Int, num_field::Type, - orth::O) where {F,T,O<:Orthogonalizer} - if length(x₀) < 2 || norm(x₀) < 1e4 * eps(real(num_field)) - error("initial vector should not have norm zero") - end - return new{F,T,O}(operator, x₀, maxiter, num_field, orth) - end -end - -# Is there a better way to get the type of the output of inner product? What about global variable? -function BlockLanczosIterator(operator::F, - x₀::AbstractVector{T}, - maxiter::Int, - num_field::Type, - orth::O=ModifiedGramSchmidt2()) where {F,T,O<:Orthogonalizer} - if orth != ModifiedGramSchmidt2() - @error "BlockLanczosIterator only supports ModifiedGramSchmidt2 orthogonalizer" + orth::O, + qr_tol::Real) where {F,T,O<:Orthogonalizer} + return new{F,T,O}(operator, x₀, maxiter, num_field, orth, qr_tol) end - return BlockLanczosIterator{F,T,O}(operator, x₀, maxiter, num_field, orth) end function BlockLanczosIterator(operator::F, x₀::AbstractVector{T}, maxiter::Int, + qr_tol::Real, orth::O=ModifiedGramSchmidt2()) where {F,T,O<:Orthogonalizer} S = typeof(inner(x₀[1], x₀[1])) - return BlockLanczosIterator{F,T,O}(operator, x₀, maxiter, S, orth) + qr_tol < 0 && (qr_tol = 1e4 * eps(real(S))) + length(x₀) < 2 && @error "initial vector should not have norm zero" + norm(x₀) < qr_tol && @error "initial vector should not have norm zero" + orth != ModifiedGramSchmidt2() && @error "BlockLanczosIterator only supports ModifiedGramSchmidt2 orthogonalizer" + return BlockLanczosIterator{F,T,O}(operator, x₀, maxiter, S, orth, qr_tol) end function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.verbosity[]) @@ -449,31 +443,27 @@ function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.ve A = iter.operator S = iter.num_field - V_basis = similar(x₀_vec, bs_now * (maxiter + 1)) - for i in 1:length(V_basis) - V_basis[i] = similar(x₀_vec[1]) - end - R = [similar(x₀_vec[i]) for i in 1:bs_now] + V_basis = [similar(x₀_vec[1]) for i in 1:bs_now * (maxiter + 1)] + r = [similar(x₀_vec[i]) for i in 1:bs_now] TDB = zeros(S, bs_now * (maxiter + 1), bs_now * (maxiter + 1)) X₁_view = view(V_basis, 1:bs_now) - copyto!.(X₁_view, x₀_vec) - - abstract_qr!(S, X₁_view; tol = 1e4 * eps(real(S))) + copy!.(X₁_view, x₀_vec) + # Orthogonalization of the initial block + abstract_qr!(BlockVec(X₁_view, S), iter.qr_tol) Ax₁ = [apply(A, x) for x in X₁_view] M₁_view = view(TDB, 1:bs_now, 1:bs_now) - block_inner!(M₁_view, X₁_view, Ax₁) - verbosity >= WARN_LEVEL && warn_nonhermitian(M₁_view) + block_inner!(M₁_view, BlockVec(X₁_view, S), BlockVec(Ax₁, S)) + verbosity >= WARN_LEVEL && warn_nonhermitian(M₁_view, iter.qr_tol) M₁_view = (M₁_view + M₁_view') / 2 - - # We have to write it as a form of matrix multiplication. Get R1 - residual = block_mul!(Ax₁, X₁_view, - M₁_view, 1.0, 1.0) + + residual = block_mul!(BlockVec(Ax₁, S), BlockVec(X₁_view, S), - M₁_view, 1.0, 1.0).vec # QR decomposition of residual to get the next basis. Get X2 and B1 - B₁, good_idx = abstract_qr!(S, residual; tol = 1e4 * eps(real(S))) + B₁, good_idx = abstract_qr!(BlockVec(residual, S), iter.qr_tol) bs_next = length(good_idx) X₂_view = view(V_basis, bs_now+1:bs_now+bs_next) - copyto!.(X₂_view, residual[good_idx]) + copy!.(X₂_view, residual[good_idx]) B₁_view = view(TDB, bs_now+1:bs_now+bs_next, 1:bs_now) copyto!(B₁_view, B₁) copyto!(view(TDB, 1:bs_now, bs_now+1:bs_now+bs_next), B₁_view') @@ -481,42 +471,39 @@ function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.ve # Calculate the next block Ax₂ = [apply(A, x) for x in X₂_view] M₂_view = view(TDB, bs_now+1:bs_now+bs_next, bs_now+1:bs_now+bs_next) - block_inner!(M₂_view, X₂_view, Ax₂) + block_inner!(M₂_view, BlockVec(X₂_view, S), BlockVec(Ax₂, S)) M₂_view = (M₂_view + M₂_view') / 2 - # Calculate the new residual. Get R2 - compute_residual!(R, Ax₂, X₂_view, M₂_view, X₁_view, B₁_view) - tmp = Matrix{S}(undef, (maxiter+1)*bs_now, bs_next) - tmp_view = view(tmp, 1:bs_now+bs_next, 1:bs_next) - ortho_basis!(R, view(V_basis, 1:bs_now+bs_next), tmp_view) + # Calculate the new residual. Get r₂ + compute_residual!(BlockVec(r, S), BlockVec(Ax₂, S), BlockVec(X₂_view, S), M₂_view, BlockVec(X₁_view, S), B₁_view) + ortho_basis!(BlockVec(r, S), BlockVec(view(V_basis, 1:bs_now+bs_next), S)) - normR = norm(R) + norm_r = norm(r) if verbosity > EACHITERATION_LEVEL - @info "Block Lanczos initiation at dimension 2: subspace normres = $(normres2string(normR))" + @info "Block Lanczos initiation at dimension 2: subspace normres = $(normres2string(norm_r))" end return BlockLanczosFactorization(bs_now+bs_next, OrthonormalBasis(V_basis), TDB, - OrthonormalBasis(R), + OrthonormalBasis(r), bs_next, - normR, - tmp) + norm_r) end function expand!(iter::BlockLanczosIterator, state::BlockLanczosFactorization; verbosity::Int=KrylovDefaults.verbosity[]) all_size = state.all_size - Rₖ = view(state.R.basis, 1:state.R_size) + rₖ = view(state.r.basis, 1:state.r_size) S = iter.num_field - bs_now = length(Rₖ) + bs_now = length(rₖ) # Get the current residual as the initial value of the new basis. Get Xnext - Bₖ, good_idx = abstract_qr!(S, Rₖ; tol = 1e4 * eps(real(S))) + Bₖ, good_idx = abstract_qr!(BlockVec(rₖ, S), iter.qr_tol) bs_next = length(good_idx) Xnext_view = view(state.V.basis, all_size+1:all_size+bs_next) - copyto!.(Xnext_view, Rₖ[good_idx]) + copy!.(Xnext_view, rₖ[good_idx]) # Calculate the connection matrix Bₖ_view = view(state.TDB, all_size+1:all_size+bs_next, all_size-bs_now+1:all_size) @@ -526,57 +513,62 @@ function expand!(iter::BlockLanczosIterator, state::BlockLanczosFactorization; # Apply the operator and calculate the M. Get Mnext Axₖnext = [apply(iter.operator, x) for x in Xnext_view] Mnext_view = view(state.TDB, all_size+1:all_size+bs_next, all_size+1:all_size+bs_next) - block_inner!(Mnext_view, Xnext_view, Axₖnext) - verbosity >= WARN_LEVEL && warn_nonhermitian(Mnext_view) + block_inner!(Mnext_view, BlockVec(Xnext_view, S), BlockVec(Axₖnext, S)) + verbosity >= WARN_LEVEL && warn_nonhermitian(Mnext_view, iter.qr_tol) Mnext_view = (Mnext_view + Mnext_view') / 2 # Calculate the new residual. Get Rnext Xnow_view = view(state.V.basis, all_size-bs_now+1:all_size) - Rₖ[1:bs_next] = Rₖ[good_idx] - Rₖnext_view = view(state.R.basis, 1:bs_next) - compute_residual!(Rₖnext_view, Axₖnext, Xnext_view, Mnext_view, Xnow_view, Bₖ_view) - tmp_view = view(state.tmp, 1:(all_size+bs_next), 1:bs_next) - ortho_basis!(Rₖnext_view, view(state.V.basis, 1:all_size+bs_next), tmp_view) - state.normR = norm(Rₖnext_view) + rₖ[1:bs_next] = rₖ[good_idx] + rₖnext_view = view(state.r.basis, 1:bs_next) + compute_residual!(BlockVec(rₖnext_view, S), BlockVec(Axₖnext, S), BlockVec(Xnext_view, S), Mnext_view, BlockVec(Xnow_view, S), Bₖ_view) + ortho_basis!(BlockVec(rₖnext_view, S), BlockVec(view(state.V.basis, 1:all_size+bs_next), S)) + state.norm_r = norm(rₖnext_view) state.all_size += bs_next - state.R_size = bs_next + state.r_size = bs_next if verbosity > EACHITERATION_LEVEL orthogonality_error = maximum(abs(inner(u,v)-(i==j)) for (i,u) in enumerate(state.V.basis[1:(all_size+bs_next)]), (j,v) in enumerate(state.V.basis[1:(all_size+bs_next)])) - @info "Block Lanczos expansion to dimension $(state.all_size): orthogonality error = $orthogonality_error, normres = $(normres2string(state.normR))" + @info "Block Lanczos expansion to dimension $(state.all_size): orthogonality error = $orthogonality_error, normres = $(normres2string(state.norm_r))" end end -function compute_residual!(R::AbstractVector{T}, A_X::AbstractVector{T}, X::AbstractVector{T}, M::AbstractMatrix, X_prev::AbstractVector{T}, B_prev::AbstractMatrix) where T +function compute_residual!(Block_r::BlockVec{T,S}, Block_A_X::BlockVec{T,S}, Block_X::BlockVec{T,S}, M::AbstractMatrix, Block_X_prev::BlockVec{T,S}, B_prev::AbstractMatrix) where {T,S} + r = Block_r.vec + A_X = Block_A_X.vec + X = Block_X.vec + X_prev = Block_X_prev.vec @inbounds for j in 1:length(X) - r_j = R[j] - copyto!(r_j, A_X[j]) - @simd for i in 1:length(X) + r_j = r[j] + copy!(r_j, A_X[j]) + for i in 1:length(X) axpy!(- M[i,j], X[i], r_j) end - @simd for i in 1:length(X_prev) + for i in 1:length(X_prev) axpy!(- B_prev[i,j], X_prev[i], r_j) end end - return R + return Block_r end -function ortho_basis!(basis_new::AbstractVector{T}, basis_sofar::AbstractVector{T}, tmp::AbstractMatrix) where T +function ortho_basis!(basis_new::BlockVec{T,S}, basis_sofar::BlockVec{T,S}) where {T,S} + tmp = Matrix{S}(undef, length(basis_sofar), length(basis_new)) block_inner!(tmp, basis_sofar, basis_new) block_mul!(basis_new, basis_sofar, - tmp, 1.0, 1.0) return basis_new end -function warn_nonhermitian(M::AbstractMatrix) - if norm(M - M') > eps(real(eltype(M)))*1e4 +function warn_nonhermitian(M::AbstractMatrix, tol::Real) + if norm(M - M') > tol @warn "Enforce Hermiticity on the triangular diagonal blocks matrix, even though the operator may not be Hermitian." end end -function abstract_qr!(::Type{S}, block::AbstractVector{T}; tol::Real) where {T,S} +function abstract_qr!(Block::BlockVec{T,S}, tol::Real) where {T,S} + block = Block.vec n = length(block) rank_shrink = false idx = ones(Int64,n) diff --git a/src/innerproductvec.jl b/src/innerproductvec.jl index 23d0173b..061f92cc 100644 --- a/src/innerproductvec.jl +++ b/src/innerproductvec.jl @@ -129,8 +129,10 @@ end VectorInterface.norm(v::InnerProductVec) = sqrt(real(inner(v, v))) function block_inner!(M::AbstractMatrix, - x::AbstractVector, - y::AbstractVector) + B₁::BlockVec{T,S}, + B₂::BlockVec{T,S}) where {T,S} + x = B₁.vec + y = B₂.vec @assert size(M) == (length(x), length(y)) "Matrix dimensions must match" @inbounds for j in eachindex(y) yj = y[j] @@ -141,15 +143,11 @@ function block_inner!(M::AbstractMatrix, return M end -function Base.copyto!(x::InnerProductVec, y::InnerProductVec) - @assert x.dotf == y.dotf "Dot functions must match" - copyto!(x.vec, y.vec) - return x -end - block_randn_like(v, n::Int) = [randn!(similar(v)) for _ in 1:n] -function block_mul!(A::AbstractVector, B::AbstractVector, M::AbstractMatrix, alpha::Number, beta::Number) +function block_mul!(B₁::BlockVec{T,S}, B₂::BlockVec{T,S}, M::AbstractMatrix, alpha::Number, beta::Number) where {T,S} + A = B₁.vec + B = B₂.vec @assert (length(B) == size(M, 1)) && (length(A) == size(M, 2)) "Matrix dimensions must match" @inbounds for i in 1:length(A) A[i] = beta * A[i] @@ -157,13 +155,13 @@ function block_mul!(A::AbstractVector, B::AbstractVector, M::AbstractMatrix, alp A[i] += alpha * B[j] * M[j, i] end end - return A + return B₁ end # used for debugging -function block_inner(v::AbstractVector, w::AbstractVector;S::Type = Float64) - M = Matrix{S}(undef, length(v), length(w)) - block_inner!(M, v, w) +function block_inner(B₁::BlockVec{T,S}, B₂::BlockVec{T,S}) where {T,S} + M = Matrix{S}(undef, length(B₁.vec), length(B₂.vec)) + block_inner!(M, B₁, B₂) return M end diff --git a/src/orthonormal.jl b/src/orthonormal.jl index f34764a7..b7f31de3 100644 --- a/src/orthonormal.jl +++ b/src/orthonormal.jl @@ -28,6 +28,15 @@ struct OrthonormalBasis{T} <: Basis{T} end OrthonormalBasis{T}() where {T} = OrthonormalBasis{T}(Vector{T}(undef, 0)) +# We use this to store vectors as a block. Although now its fields are same to OrthonormalBasis, +# I plan to develop BlockVec a abstract of vector of number and inner product space, +# and process the block in the latter as a matrix with higher efficiency. +struct BlockVec{T,S} + vec::AbstractVector{T} + S::Type{S} +end +Base.length(b::BlockVec) = length(b.vec) + # Iterator methods for OrthonormalBasis Base.IteratorSize(::Type{<:OrthonormalBasis}) = Base.HasLength() Base.IteratorEltype(::Type{<:OrthonormalBasis}) = Base.HasEltype() @@ -573,4 +582,4 @@ and its concrete subtypes [`ClassicalGramSchmidt`](@ref), [`ModifiedGramSchmidt` [`ClassicalGramSchmidt2`](@ref), [`ModifiedGramSchmidt2`](@ref), [`ClassicalGramSchmidtIR`](@ref) and [`ModifiedGramSchmidtIR`](@ref). """ -orthonormalize, orthonormalize!! \ No newline at end of file +orthonormalize, orthonormalize!! \ No newline at end of file diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 299ed804..447b34c8 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -553,7 +553,8 @@ end D, V, info = eigsolve(Aip, x₀, eig_num, :SR, Lanczos(; krylovdim = n, maxiter = 10, tol = tolerance(T), verbosity = 0, blockmode = true, blocksize = block_size)) D_true = eigvals(H) + BlockV = KrylovKit.BlockVec(V, T) @test D ≈ D_true[1:eig_num] - @test KrylovKit.block_inner(V, V; S = T) ≈ I + @test KrylovKit.block_inner(BlockV, BlockV) ≈ I @test findmax([norm(Aip(V[i]) - D[i] * V[i]) for i in 1:eig_num])[1] < tolerance(T) end diff --git a/test/factorize.jl b/test/factorize.jl index 8ed26371..bdd0f371 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -297,6 +297,7 @@ end end end +# TODO : Add more tests for warn in BlockLanczosIterator # Test complete Lanczos factorization @testset "Complete Block Lanczos factorization " begin using KrylovKit: EACHITERATION_LEVEL @@ -307,8 +308,8 @@ end x₀m = Matrix(qr(rand(T, N, block_size)).Q) x₀ = [x₀m[:, i] for i in 1:block_size] eigvalsA = eigvals(A0) - @testset for A in [A0, x -> A0 * x] - iter = KrylovKit.BlockLanczosIterator(A, x₀, 4) + for A in [A0, x -> A0 * x] + iter = KrylovKit.BlockLanczosIterator(A, x₀, 4, qr_tol(T)) # TODO: Why type unstable? # fact = @constinferred initialize(iter) fact = initialize(iter) @@ -336,7 +337,7 @@ end bs = 2 v₀m = Matrix(qr(rand(T, n, bs)).Q) v₀ = [v₀m[:, i] for i in 1:bs] - iter = KrylovKit.BlockLanczosIterator(B, v₀, 4) + iter = KrylovKit.BlockLanczosIterator(B, v₀, 4, qr_tol(T)) fact = initialize(iter) @constinferred expand!(iter, fact; verbosity = 0) @test_logs initialize(iter; verbosity = 0) @@ -363,11 +364,11 @@ end block_size = 5 x₀m = Matrix(qr(rand(T, N, block_size)).Q) x₀ = [x₀m[:, i] for i in 1:block_size] - @testset for A in [A0, x -> A0 * x] - iter = @constinferred KrylovKit.BlockLanczosIterator(A, x₀, 4) + for A in [A0, x -> A0 * x] + iter = @constinferred KrylovKit.BlockLanczosIterator(A, x₀, 4, qr_tol(T)) krylovdim = n fact = initialize(iter) - while fact.normR > eps(float(real(T))) && fact.all_size < krylovdim + while fact.norm_r > eps(float(real(T))) && fact.all_size < krylovdim @constinferred expand!(iter, fact) Ṽ, H, r̃, β, e = fact V = stack(unwrapvec, Ṽ) diff --git a/test/innerproductvec.jl b/test/innerproductvec.jl index b4819424..3fa2110d 100644 --- a/test/innerproductvec.jl +++ b/test/innerproductvec.jl @@ -6,10 +6,12 @@ M[i,j] = inner(x[i],y[j]) A = [rand(T, N) for _ in 1:n] B = [rand(T, N) for _ in 1:n] M = Matrix{T}(undef, n, n) - KrylovKit.block_inner!(M, A, B) - M0 = hcat(A...)' * hcat(B...) + BlockA = KrylovKit.BlockVec(A, T) + BlockB = KrylovKit.BlockVec(B, T) + KrylovKit.block_inner!(M, BlockA, BlockB) + M0 = hcat(BlockA.vec...)' * hcat(BlockB.vec...) @test eltype(M) == T - @test isapprox(M, M0; atol = 1e4 * eps(real(T))) + @test isapprox(M, M0; atol = relax_tol(T)) end @testset "block_inner! for abstract inner product" begin @@ -31,12 +33,14 @@ end Y[i] = InnerProductVec(rand(T, N), ip) end M = Matrix{T}(undef, n, n); - KrylovKit.block_inner!(M, X, Y); + BlockX = KrylovKit.BlockVec(X, T) + BlockY = KrylovKit.BlockVec(Y, T) + KrylovKit.block_inner!(M, BlockX, BlockY); Xm = hcat([X[i].vec for i in 1:n]...); Ym = hcat([Y[i].vec for i in 1:n]...); M0 = Xm' * H * Ym; @test eltype(M) == T - @test isapprox(M, M0; atol = eps(real(T))^(0.5)) + @test isapprox(M, M0; atol = relax_tol(T)) end @testset "block_randn_like" begin @@ -54,25 +58,18 @@ end end end -@testset "copyto! for InnerProductVec" begin - T = ComplexF64 - f = x -> x'*x - v = InnerProductVec(rand(T, n), f) - w = InnerProductVec(rand(T, n), f) - KrylovKit.copyto!(v, w) - @test v.vec == w.vec -end - @testset "block_mul!" begin T = ComplexF64 f = x -> x'*x A = [InnerProductVec(rand(T, N), f) for _ in 1:n] Acopy = [InnerProductVec(rand(T, N), f) for _ in 1:n] - KrylovKit.copyto!(Acopy, A) + KrylovKit.copy!(Acopy, A) B = [InnerProductVec(rand(T, N), f) for _ in 1:n] M = rand(T, n, n) alpha = rand(T) beta = rand(T) - KrylovKit.block_mul!(A, B, M, alpha, beta) - @test isapprox(hcat([A[i].vec for i in 1:n]...), beta * hcat([Acopy[i].vec for i in 1:n]...) + alpha * hcat([B[i].vec for i in 1:n]...) * M) + BlockA = KrylovKit.BlockVec(A, T) + BlockB = KrylovKit.BlockVec(B, T) + KrylovKit.block_mul!(BlockA, BlockB, M, alpha, beta) + @test isapprox(hcat([BlockA.vec[i].vec for i in 1:n]...), beta * hcat([Acopy[i].vec for i in 1:n]...) + alpha * hcat([BlockB.vec[i].vec for i in 1:n]...) * M; atol = tolerance(T)) end \ No newline at end of file diff --git a/test/orthonormal.jl b/test/orthonormal.jl index 42e677ae..17c85230 100644 --- a/test/orthonormal.jl +++ b/test/orthonormal.jl @@ -5,11 +5,11 @@ # A is a non-full rank matrix Av[n÷2] = sum(Av[n÷2+1:end] .* rand(T, n - n ÷ 2)) Bv = copy(Av) - R, gi = KrylovKit.abstract_qr!(T, Av; tol = 1e4 * eps(real(T))) + R, gi = KrylovKit.abstract_qr!(KrylovKit.BlockVec(Av, T), qr_tol(T)) @test length(gi) < n @test eltype(R) == eltype(eltype(A)) == T - @test isapprox(hcat(Av[gi]...) * R, hcat(Bv...); atol = 1e4 * eps(real(T))) - @test isapprox(hcat(Av[gi]...)' * hcat(Av[gi]...), I; atol = 1e4 * eps(real(T))) + @test isapprox(hcat(Av[gi]...) * R, hcat(Bv...); atol = tolerance(T)) + @test isapprox(hcat(Av[gi]...)' * hcat(Av[gi]...), I; atol = tolerance(T)) end @testset "abstract_qr! for abstract inner product" begin @@ -28,13 +28,14 @@ end # Make sure X is not full rank X[end] = sum(X[1:end-1] .* rand(T, n-1)) Xcopy = deepcopy(X) - R, gi = KrylovKit.abstract_qr!(T, X; tol = 1e4 * eps(real(T))) + R, gi = KrylovKit.abstract_qr!(KrylovKit.BlockVec(X, T), qr_tol(T)) @test length(gi) < n @test eltype(R) == T - @test isapprox(KrylovKit.block_inner(X[gi],X[gi],S = T), I; atol=1e4*eps(real(T))) + BlockX = KrylovKit.BlockVec(X[gi], T) + @test isapprox(KrylovKit.block_inner(BlockX,BlockX), I; atol=tolerance(T)) ΔX = norm.(mul_test(X[gi],R) - Xcopy) - @test isapprox(norm(ΔX), T(0); atol=1e4*eps(real(T))) + @test isapprox(norm(ΔX), T(0); atol=tolerance(T)) end @testset "ortho_basis! for abstract inner product" begin @@ -45,9 +46,10 @@ end ip(x,y) = x'*H*y x₀ = [InnerProductVec(rand(T, N), ip) for i in 1:n] x₁ = [InnerProductVec(rand(T, N), ip) for i in 1:2*n] - tmp = zeros(T, 2*n, n) - KrylovKit.abstract_qr!(T, x₁; tol = 1e4 * eps(real(T))) - KrylovKit.ortho_basis!(x₀, x₁, tmp) - @test norm(KrylovKit.block_inner(x₀, x₁; S = T)) < eps(real(T))^0.5 + Blockx₀ = KrylovKit.BlockVec(x₀, T) + Blockx₁ = KrylovKit.BlockVec(x₁, T) + KrylovKit.abstract_qr!(Blockx₁, qr_tol(T)) + KrylovKit.ortho_basis!(Blockx₀, Blockx₁) + @test norm(KrylovKit.block_inner(Blockx₀, Blockx₁)) < 2* tolerance(T) end diff --git a/test/testsetup.jl b/test/testsetup.jl index 898d7c28..a55ce20c 100644 --- a/test/testsetup.jl +++ b/test/testsetup.jl @@ -2,7 +2,7 @@ module TestSetup export tolerance, ≊, MinimalVec, isinplace, stack export wrapop, wrapvec, unwrapvec, buildrealmap -export mul_test +export mul_test, qr_tol, relax_tol import VectorInterface as VI using VectorInterface @@ -12,6 +12,8 @@ using LinearAlgebra: LinearAlgebra # ----------------- "function for determining the precision of a type" tolerance(T::Type{<:Number}) = eps(real(T))^(2 // 3) +qr_tol(T::Type{<:Number}) = 1e4 * eps(real(T)) +relax_tol(T::Type{<:Number}) = eps(real(T))^(0.5) "function for comparing sets of eigenvalues" function ≊(list1::AbstractVector, list2::AbstractVector) From 61bfa13f0663e1eccd018ce5b1dd9e4dc493327c Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Sun, 20 Apr 2025 04:34:09 +0800 Subject: [PATCH 25/82] to revise test for block lanczos with shrink --- src/algorithms.jl | 13 +++++---- src/eigsolve/lanczos.jl | 53 ++++++++++++++++++----------------- src/factorizations/lanczos.jl | 39 ++++++++++++++++---------- test/eigsolve.jl | 28 ++++++++++++++++-- 4 files changed, 84 insertions(+), 49 deletions(-) diff --git a/src/algorithms.jl b/src/algorithms.jl index b6c435fb..61907102 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -123,7 +123,6 @@ struct BlockLanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm krylovdim::Int maxiter::Int tol::S - eager::Bool verbosity::Int blocksize::Int qr_tol::Real @@ -131,18 +130,20 @@ end # qr_tol is the tolerance that we think a vector is non-zero in abstract_qr! # This qr_tol will also be used in other zero_chcking in block Lanczos. function Lanczos(; - krylovdim::Int=KrylovDefaults.krylovdim[], - maxiter::Int=KrylovDefaults.maxiter[], + krylovdim::Int= -1, + maxiter::Int= -1, tol::Real=KrylovDefaults.tol[], orth::Orthogonalizer=KrylovDefaults.orth, eager::Bool=false, verbosity::Int=KrylovDefaults.verbosity[], blockmode::Bool=false, blocksize::Int=-1, - qr_tol::Real=-1.0) + qr_tol::Real=-1.0) + krylovdim < 0 && (blockmode ? (krylovdim = KrylovDefaults.blockkrylovdim[]) : (krylovdim = KrylovDefaults.krylovdim[])) + maxiter < 0 && (blockmode ? (maxiter = KrylovDefaults.blockmaxiter[]) : (maxiter = KrylovDefaults.maxiter[])) if blockmode blocksize <= 1 && error("blocksize must be greater than 1") - return BlockLanczos(orth, krylovdim, maxiter, tol, eager, verbosity, blocksize, qr_tol) + return BlockLanczos(orth, krylovdim, maxiter, tol, verbosity, blocksize, qr_tol) else return Lanczos(orth, krylovdim, maxiter, tol, eager, verbosity) end @@ -486,6 +487,8 @@ using ..KrylovKit const orth = KrylovKit.ModifiedGramSchmidt2() # conservative choice const krylovdim = Ref(30) const maxiter = Ref(100) +const blockkrylovdim = Ref(100) +const blockmaxiter = Ref(2) const tol = Ref(1e-12) const verbosity = Ref(KrylovKit.WARN_LEVEL) end diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index 66b9ba29..b89eb72c 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -153,54 +153,58 @@ end function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) maxiter = alg.maxiter + krylovdim = alg.krylovdim + if howmany > krylovdim + error("krylov dimension $(krylovdim) too small to compute $howmany eigenvalues") + end tol = alg.tol verbosity = alg.verbosity x₀_vec = push!(block_randn_like(x₀, alg.blocksize-1), x₀) bs_now = length(x₀_vec) - iter = BlockLanczosIterator(A, x₀_vec, maxiter, alg.qr_tol, alg.orth) + iter = BlockLanczosIterator(A, x₀_vec, krylovdim + bs_now, alg.qr_tol, alg.orth) fact = initialize(iter; verbosity=verbosity) numops = 2 # how many times we apply A - - converge_check = max(1, 100 ÷ bs_now) # Periodic check for convergence - + numiter = 1 vectors = [similar(x₀_vec[1]) for _ in 1:howmany] values = Vector{real(eltype(fact.TDB))}(undef, howmany) converged = false + num_converged = 0 + local howmany_actual, residuals, normresiduals - for numiter in 2:maxiter + while true expand!(iter, fact; verbosity=verbosity) numops += 1 - # Although norm(Rk) is not our convergence condition, when norm(Rk) is to small, we may lose too much precision and orthogonalization. - if (numiter % converge_check == 0) || (fact.norm_r < tol) || (fact.r_size < 2) - values, vectors, howmany_actual, residuals, normresiduals, num_converged = _residual!(fact, A, - howmany, tol, - which,vectors, values) - - if verbosity >= EACHITERATION_LEVEL - @info "Block Lanczos eigsolve in iteration $numiter: $num_converged values converged, normres = $(normres2string(normresiduals[1:min(howmany, length(normresiduals))]))" + # When norm(Rk) is to small, we may lose too much precision and orthogonalization. + if fact.all_size > krylovdim || (fact.norm_r < tol) || (fact.r_size < 2) + if fact.norm_r < tol && fact.all_size < howmany && verbosity >= WARN_LEVEL + msg = "Invariant subspace of dimension $(fact.all_size) (up to requested tolerance `tol = $tol`), " + msg *= "which is smaller than the number of requested eigenvalues (i.e. `howmany == $howmany`)." + @warn msg end - + values, vectors, howmany_actual, residuals, normresiduals, num_converged = _residual!(fact, A, + howmany, tol, which,vectors, values) # This convergence condition refers to https://www.netlib.org/utk/people/JackDongarra/etemplates/node251.html if num_converged >= howmany || fact.norm_r < tol converged = true break + elseif verbosity >= EACHITERATION_LEVEL + @info "Block Lanczos eigsolve in iteration $numiter: $num_converged values converged, normres = $(normres2string(normresiduals[1:howmany]))" + end + if fact.all_size > alg.krylovdim + numiter >= maxiter && break + shrink!(fact, div(3*fact.all_size + 2*num_converged, 5); verbosity=verbosity) + numiter += 1 end end end - if !converged + if !converged && numiter < maxiter values, vectors, howmany_actual, residuals, normresiduals, num_converged = _residual!(fact, A, howmany, tol, which, vectors, values) end - if (fact.all_size > alg.krylovdim) - @warn "The real Krylov dimension is $(fact.all_size), which is larger than the maximum allowed dimension $(alg.krylovdim)." - # In this version we don't shrink the factorization because it might cause issues, different from the ordinary Lanczos. - # Why it happens remains to be investigated. - end - if (num_converged < howmany) && verbosity >= WARN_LEVEL @warn """Block Lanczos eigsolve stopped without full convergence after $(fact.all_size) iterations: * $num_converged eigenvalues converged @@ -213,8 +217,8 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) * number of operations = $numops""" end - return values[1:howmany], - vectors[1:howmany], + return values[1:howmany_actual], + vectors[1:howmany_actual], ConvergenceInfo(num_converged, residuals, normresiduals, fact.all_size, numops) end @@ -231,9 +235,6 @@ function _residual!(fact::BlockLanczosFactorization, A, howmany::Int, tol::Real, S = eltype(TDB) howmany_actual = min(howmany, length(D)) - if howmany_actual < howmany - @warn "The number of converged eigenvalues is less than the requested number of eigenvalues." - end copyto!(values, D[1:howmany_actual]) @inbounds for i in 1:howmany_actual diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index f794b07d..363e0107 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -408,22 +408,22 @@ provide "keepvecs" because we have to reverse all krylove vectors. struct BlockLanczosIterator{F,T,O<:Orthogonalizer} <: KrylovIterator{F,T} operator::F x₀::Vector{T} - maxiter::Int + maxdim::Int num_field::Type orth::O qr_tol::Real function BlockLanczosIterator{F,T,O}(operator::F, x₀::Vector{T}, - maxiter::Int, + maxdim::Int, num_field::Type, orth::O, qr_tol::Real) where {F,T,O<:Orthogonalizer} - return new{F,T,O}(operator, x₀, maxiter, num_field, orth, qr_tol) + return new{F,T,O}(operator, x₀, maxdim, num_field, orth, qr_tol) end end function BlockLanczosIterator(operator::F, x₀::AbstractVector{T}, - maxiter::Int, + maxdim::Int, qr_tol::Real, orth::O=ModifiedGramSchmidt2()) where {F,T,O<:Orthogonalizer} S = typeof(inner(x₀[1], x₀[1])) @@ -431,21 +431,19 @@ function BlockLanczosIterator(operator::F, length(x₀) < 2 && @error "initial vector should not have norm zero" norm(x₀) < qr_tol && @error "initial vector should not have norm zero" orth != ModifiedGramSchmidt2() && @error "BlockLanczosIterator only supports ModifiedGramSchmidt2 orthogonalizer" - return BlockLanczosIterator{F,T,O}(operator, x₀, maxiter, S, orth, qr_tol) + return BlockLanczosIterator{F,T,O}(operator, x₀, maxdim, S, orth, qr_tol) end function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.verbosity[]) x₀_vec = iter.x₀ - iszero(norm(x₀_vec)) && throw(ArgumentError("initial vector should not have norm zero")) - - maxiter = iter.maxiter + maxdim = iter.maxdim bs_now = length(x₀_vec) # block size now A = iter.operator S = iter.num_field - V_basis = [similar(x₀_vec[1]) for i in 1:bs_now * (maxiter + 1)] + V_basis = [similar(x₀_vec[1]) for i in 1:bs_now * (maxdim + 1)] r = [similar(x₀_vec[i]) for i in 1:bs_now] - TDB = zeros(S, bs_now * (maxiter + 1), bs_now * (maxiter + 1)) + TDB = zeros(S, bs_now * (maxdim + 1), bs_now * (maxdim + 1)) X₁_view = view(V_basis, 1:bs_now) copy!.(X₁_view, x₀_vec) @@ -528,11 +526,7 @@ function expand!(iter::BlockLanczosIterator, state::BlockLanczosFactorization; state.r_size = bs_next if verbosity > EACHITERATION_LEVEL - orthogonality_error = maximum(abs(inner(u,v)-(i==j)) - for (i,u) in enumerate(state.V.basis[1:(all_size+bs_next)]), - (j,v) in enumerate(state.V.basis[1:(all_size+bs_next)])) - - @info "Block Lanczos expansion to dimension $(state.all_size): orthogonality error = $orthogonality_error, normres = $(normres2string(state.norm_r))" + @info "Block Lanczos expansion to dimension $(state.all_size): subspace normres = $(normres2string(state.norm_r))" end end @@ -591,4 +585,19 @@ function abstract_qr!(Block::BlockVec{T,S}, tol::Real) where {T,S} end good_idx = findall(idx .> 0) return R[good_idx,:], good_idx +end + +function shrink!(state::BlockLanczosFactorization, k; verbosity::Int=KrylovDefaults.verbosity[]) + @show "shrink!" + V = state.V + all_size = state.all_size + V[1:k] = V[all_size-k+1:all_size] + newTDB = zero(state.TDB) + newTDB[1:k,1:k] = state.TDB[all_size-k+1:all_size,all_size-k+1:all_size] + state.all_size = k + state.TDB .= newTDB + if verbosity > EACHITERATION_LEVEL + @info "Lanczos reduction to dimension $k" + end + return state end \ No newline at end of file diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 447b34c8..23338a5c 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -431,7 +431,7 @@ end h_mat = toric_code_hamiltonian_matrix(sites_num, sites_num) # matrix input - alg = Lanczos(; maxiter = 20, tol = tol, blockmode = true, blocksize = p) + alg = Lanczos(;tol = tol, blockmode = true, blocksize = p) D, U, info = eigsolve(-h_mat, x₀, get_value_num, :SR, alg) @show D[1:get_value_num] @test count(x -> abs(x + 16.0) < 2.0 - tol, D[1:get_value_num]) == 4 @@ -442,7 +442,6 @@ end @show D[1:get_value_num] @test count(x -> abs(x + 16.0) < 1.9, D[1:get_value_num]) == 4 @test count(x -> abs(x + 16.0) < 1e-8, D[1:get_value_num]) == 4 - end #= @@ -505,7 +504,6 @@ As a result, I’ve decided to postpone dealing with the in-place test issue for end end -# krylovdim is not used in block Lanczos so I don't add eager mode. @testset "Block Lanczos - eigsolve iteratively" begin @testset for T in [Float32, Float64, ComplexF32, ComplexF64] Random.seed!(6) @@ -558,3 +556,27 @@ end @test KrylovKit.block_inner(BlockV, BlockV) ≈ I @test findmax([norm(Aip(V[i]) - D[i] * V[i]) for i in 1:eig_num])[1] < tolerance(T) end + +#= +# TODO: improve shrink +using KrylovKit,LinearAlgebra,Random,Test +Random.seed!(6) +n = 10 +N = 100 +A = rand(N,N); +A = A' * A + I; +alg = Lanczos(;krylovdim = 10,maxiter = 10,tol = 1e-12); +values,vectors,info = eigsolve(A,rand(N),10,:SR,alg) +=# + +#= +using KrylovKit,LinearAlgebra,Random,Test +Random.seed!(6) +n = 10 +N = 100 +A = rand(N,N); +A = A' * A + I; +x₀ = rand(N); +alg = Lanczos(;tol = 1e-8,blockmode = true,blocksize = 5); +values,vectors,info = eigsolve(A,x₀,10,:SR,alg) +=# \ No newline at end of file From b3f2c89d1db670a84d3f34f340c3c20e6c6a6d89 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Sun, 20 Apr 2025 14:58:38 +0800 Subject: [PATCH 26/82] update --- src/factorizations/lanczos.jl | 1 - test/eigsolve.jl | 17 ++++++++++------- test/factorize.jl | 21 ++++++++++----------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index 363e0107..71762f0c 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -588,7 +588,6 @@ function abstract_qr!(Block::BlockVec{T,S}, tol::Real) where {T,S} end function shrink!(state::BlockLanczosFactorization, k; verbosity::Int=KrylovDefaults.verbosity[]) - @show "shrink!" V = state.V all_size = state.all_size V[1:k] = V[all_size-k+1:all_size] diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 23338a5c..defcb064 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -471,16 +471,19 @@ As a result, I’ve decided to postpone dealing with the in-place test issue for # Different from Lanczos, we don't set maxiter =1 here because the iteration times in Lanczos # in in fact in the control of deminsion of Krylov subspace. And we Don't use it. for A in [A0, x -> A0 * x] - alg = Lanczos(; krylovdim = n, maxiter = 4, tol = tolerance(T), verbosity = 2, blockmode = true, blocksize = block_size) + alg = Lanczos(; krylovdim = n, maxiter = 1, tol = tolerance(T), verbosity = 2, blockmode = true, blocksize = block_size) D1, V1, info = @test_logs (:info,) eigsolve(A, x₀, n1, :SR, alg) - alg = Lanczos(; krylovdim = n, maxiter = 4, tol = tolerance(T), verbosity = 1, blockmode = true, blocksize = block_size) + alg = Lanczos(; krylovdim = n, maxiter = 1, tol = tolerance(T), verbosity = 1, blockmode = true, blocksize = block_size) @test_logs eigsolve(A, x₀, n1, :SR, alg) - alg = Lanczos(; krylovdim = n1 + 1, maxiter = 4, tol = tolerance(T), verbosity = 1, blockmode = true, blocksize = block_size) + alg = Lanczos(; krylovdim = n1 + 1, maxiter = 1, tol = tolerance(T), verbosity = 1, blockmode = true, blocksize = block_size) @test_logs (:warn,) eigsolve(A, x₀, n1, :SR, alg) - alg = Lanczos(; krylovdim = n, maxiter = 4, tol = tolerance(T), verbosity = 2, blockmode = true, blocksize = block_size) + alg = Lanczos(; krylovdim = n, maxiter = 1, tol = tolerance(T), verbosity = 2, blockmode = true, blocksize = block_size) @test_logs (:info,) eigsolve(A, x₀, n1, :SR, alg) - alg = Lanczos(; krylovdim = n1, maxiter = 4, tol = tolerance(T), verbosity = 3, blockmode = true, blocksize = block_size) - @test_logs((:info,), (:warn,), (:info,), eigsolve(A, x₀, 1, :SR, alg)) + alg = Lanczos(; krylovdim = n1, maxiter = 3, tol = tolerance(T), verbosity = 3, blockmode = true, blocksize = block_size) + @test_logs((:info,), (:info,), (:info,), (:warn,), eigsolve(A, x₀, 1, :SR, alg)) + alg = Lanczos(; krylovdim=4, maxiter=1, tol=tolerance(T), verbosity=4, blockmode=true, blocksize=block_size) + @test_logs((:info,), (:info,), (:info,), (:warn,), eigsolve(A, x₀, 1, :SR, alg)) + # To use blockmode, users have to explicitly set blockmode = true, we don't allow them to use eigselector. # Because of the _residual! function, I can't make sure the stability of types temporarily. # So I ignore the test of @constinferred n2 = n - n1 @@ -498,7 +501,7 @@ As a result, I’ve decided to postpone dealing with the in-place test issue for @test (x -> KrylovKit.apply(A, x)).(V1) ≈ D1 .* V1 @test (x -> KrylovKit.apply(A, x)).(V2) ≈ D2 .* V2 - alg = Lanczos(; krylovdim = 2n, maxiter = 5, tol = tolerance(T), verbosity = 1, blockmode = true, blocksize = block_size) + alg = Lanczos(; krylovdim = 2n, maxiter = 1, tol = tolerance(T), verbosity = 1, blockmode = true, blocksize = block_size) @test_logs (:warn,) (:warn,) eigsolve(A, x₀, n + 1, :LM, alg) end end diff --git a/test/factorize.jl b/test/factorize.jl index bdd0f371..0570855e 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -297,8 +297,7 @@ end end end -# TODO : Add more tests for warn in BlockLanczosIterator -# Test complete Lanczos factorization +# Test complete Block Lanczos factorization @testset "Complete Block Lanczos factorization " begin using KrylovKit: EACHITERATION_LEVEL @testset for T in [Float32, Float64, ComplexF32, ComplexF64] @@ -328,8 +327,10 @@ end end # Information about V has been tested in eigsolve.jl # And Block Lanczos has no "rayleighquotient" function - - # Block Lanczos has no "shrink!" function, as well as "initialize!(iter, fact)" + # We also don't have "initialize!(iter, fact)" function + @constinferred shrink!(fact, n - 1) + @test_logs (:info,) shrink!(fact, n - 2; verbosity=EACHITERATION_LEVEL + 1) + @test_logs shrink!(fact, n - 3; verbosity=EACHITERATION_LEVEL) end if T <: Complex @@ -356,8 +357,8 @@ end end end -# Test incomplete Lanczos factorization -@testset "Incomplete Lanczos factorization " begin +# Test incomplete Block Lanczos factorization +@testset "Incomplete Block Lanczos factorization " begin @testset for T in [Float32, Float64, ComplexF32, ComplexF64] A0 = rand(T, (N, N)) A0 = (A0 + A0') / 2 @@ -370,16 +371,14 @@ end fact = initialize(iter) while fact.norm_r > eps(float(real(T))) && fact.all_size < krylovdim @constinferred expand!(iter, fact) - Ṽ, H, r̃, β, e = fact - V = stack(unwrapvec, Ṽ) - r = unwrapvec(r̃) + V, H, r, β, e = fact @test V' * V ≈ I @test norm(r) ≈ β @test A * V ≈ V * H + r * e' end + # The V and residual have been tested in eigsolve.jl, and we don't need some functions tested in Lanczos + fact = @constinferred shrink!(fact, div(n, 2)) - # Block Lanczos has no "shrink!" function - # The V and residual have been tested in eigsolve.jl end end end From 124b6f2cfd14044d1a6751e3bceb4b65a82f479c Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Sun, 20 Apr 2025 15:08:24 +0800 Subject: [PATCH 27/82] update --- Project.toml | 1 - src/orthonormal.jl | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index f46ff94e..6e207ef8 100644 --- a/Project.toml +++ b/Project.toml @@ -8,7 +8,6 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" PackageExtensionCompat = "65ce6f38-6b18-4e1d-a461-8949797d7930" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" [weakdeps] diff --git a/src/orthonormal.jl b/src/orthonormal.jl index b7f31de3..00d0a0d9 100644 --- a/src/orthonormal.jl +++ b/src/orthonormal.jl @@ -582,4 +582,4 @@ and its concrete subtypes [`ClassicalGramSchmidt`](@ref), [`ModifiedGramSchmidt` [`ClassicalGramSchmidt2`](@ref), [`ModifiedGramSchmidt2`](@ref), [`ClassicalGramSchmidtIR`](@ref) and [`ModifiedGramSchmidtIR`](@ref). """ -orthonormalize, orthonormalize!! \ No newline at end of file +orthonormalize, orthonormalize!! From 7e98d85875e523f3c840f59b373dfabe475173bb Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Sun, 20 Apr 2025 15:59:16 +0800 Subject: [PATCH 28/82] save --- test/eigsolve.jl | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/eigsolve.jl b/test/eigsolve.jl index defcb064..c5722017 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -560,6 +560,36 @@ end @test findmax([norm(Aip(V[i]) - D[i] * V[i]) for i in 1:eig_num])[1] < tolerance(T) end +using KrylovKit,LinearAlgebra,Random,Test,BenchmarkTools +n = 10 +N = 100 +@testset "Compare Block Lanczos and Lanczos" begin + @testset for T in [Float64, ComplexF64] + Random.seed!(6) + A0 = rand(T, (N,N)) + A = A0' * A0 + I + x₀ = rand(T, N) + krydim = 100 + maxiter = 10 + block_size = 5 + eig_num = 10 + Atype = [A0, x -> A0 * x] + Atypename = ["Matrix", "LinearOperator"] + for i in eachindex(Atype) + A = Atype[i] + Aname = Atypename[i] + lanczos_alg = Lanczos(; krylovdim = krydim, maxiter = maxiter, tol = tolerance(T), verbosity = 0) + block_alg = Lanczos(; krylovdim = krydim, maxiter = maxiter, tol = tolerance(T), verbosity = 0, blockmode = true, blocksize = block_size) + t1 = time() + eigsolve(A, x₀, eig_num, :SR, lanczos_alg) + t1 = time() - t1 + t2 = time() + eigsolve(A, x₀, eig_num, :SR, block_alg) + t2 = time() - t2 + println("When T = $T, time of $Aname lanczos_alg: $t1, time of $Aname block_alg: $t2") + end + end +end #= # TODO: improve shrink using KrylovKit,LinearAlgebra,Random,Test From b8f2f0f9c22e43f4ac48e86f52d1aba1384f9f37 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Wed, 23 Apr 2025 17:54:21 +0800 Subject: [PATCH 29/82] add shrink --- Project.toml | 1 + src/eigsolve/lanczos.jl | 123 +++++++++++++++++-------------- src/factorizations/lanczos.jl | 131 +++++++++++++++++----------------- src/innerproductvec.jl | 36 ++++------ src/orthonormal.jl | 9 --- test/BlockVec.jl | 67 +++++++++++++++++ test/eigsolve.jl | 62 ++-------------- test/factorize.jl | 29 +++++--- test/innerproductvec.jl | 15 ---- test/runtests.jl | 3 + 10 files changed, 246 insertions(+), 230 deletions(-) create mode 100644 test/BlockVec.jl diff --git a/Project.toml b/Project.toml index 6e207ef8..f46ff94e 100644 --- a/Project.toml +++ b/Project.toml @@ -8,6 +8,7 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" PackageExtensionCompat = "65ce6f38-6b18-4e1d-a461-8949797d7930" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" [weakdeps] diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index b89eb72c..d1385fac 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -159,21 +159,22 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) end tol = alg.tol verbosity = alg.verbosity - x₀_vec = push!(block_randn_like(x₀, alg.blocksize-1), x₀) - bs_now = length(x₀_vec) + x₀_vec = [randn!(similar(x₀)) for _ in 1:alg.blocksize-1] + pushfirst!(x₀_vec, x₀) + bs = length(x₀_vec) - iter = BlockLanczosIterator(A, x₀_vec, krylovdim + bs_now, alg.qr_tol, alg.orth) - fact = initialize(iter; verbosity=verbosity) + iter = BlockLanczosIterator(A, x₀_vec, krylovdim + bs, alg.qr_tol, alg.orth) + fact = initialize(iter; verbosity = verbosity) numops = 2 # how many times we apply A numiter = 1 vectors = [similar(x₀_vec[1]) for _ in 1:howmany] values = Vector{real(eltype(fact.TDB))}(undef, howmany) converged = false num_converged = 0 - local howmany_actual, residuals, normresiduals + local howmany_actual, residuals, normresiduals, D, U while true - expand!(iter, fact; verbosity=verbosity) + expand!(iter, fact; verbosity = verbosity) numops += 1 # When norm(Rk) is to small, we may lose too much precision and orthogonalization. @@ -183,26 +184,79 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) msg *= "which is smaller than the number of requested eigenvalues (i.e. `howmany == $howmany`)." @warn msg end - values, vectors, howmany_actual, residuals, normresiduals, num_converged = _residual!(fact, A, - howmany, tol, which,vectors, values) - # This convergence condition refers to https://www.netlib.org/utk/people/JackDongarra/etemplates/node251.html + + all_size = fact.all_size + TDB = view(fact.TDB, 1:all_size, 1:all_size) + D, U = eigen(Hermitian((TDB + TDB') / 2)) + by, rev = eigsort(which) + p = sortperm(D; by = by, rev = rev) + D = D[p] + U = U[:, p] + T = eltype(fact.V.basis) + S = eltype(TDB) + + howmany_actual = min(howmany, length(D)) + copyto!(values, D[1:howmany_actual]) + + residuals = Vector{T}(undef, howmany_actual) + normresiduals = Vector{real(S)}(undef, howmany_actual) + bs_r = fact.r_size + r = fact.r[1:bs_r] + UU = U[end-bs_r+1:end, :] + for i in 1:howmany_actual + residuals[i] = r[1] * UU[1, i] + for j in 2:bs_r + axpy!(UU[j, i], r[j], residuals[i]) + end + normresiduals[i] = norm(residuals[i]) + end + num_converged = count(nr -> nr <= tol, normresiduals) + if num_converged >= howmany || fact.norm_r < tol converged = true break elseif verbosity >= EACHITERATION_LEVEL @info "Block Lanczos eigsolve in iteration $numiter: $num_converged values converged, normres = $(normres2string(normresiduals[1:howmany]))" end - if fact.all_size > alg.krylovdim + if fact.all_size > krylovdim # begin to shrink dimension numiter >= maxiter && break - shrink!(fact, div(3*fact.all_size + 2*num_converged, 5); verbosity=verbosity) + bsn = max(div(3 * krylovdim + 2 * num_converged, 5) ÷ bs, 1) + if (bsn + 1) * bs > fact.all_size # make sure that we can fetch next block after shrinked dimension as residual + warning("shrinked dimesion is too small and there is no need to shrink") + break + end + keep = bs * bsn + H = zeros(S, (bsn + 1) * bs, bsn * bs) + @inbounds for j in 1:keep + H[j, j] = fact.TDB[j, j] + H[bsn * bs + 1:end, j] = U[bsn * bs + 1:(bsn + 1) * bs, j] + end + @inbounds for j in keep:-1:1 + h, ν = householder(H, j + bs, 1:j, j) + H[j + bs, j] = ν + H[j + bs, 1:(j - 1)] .= zero(eltype(H)) + lmul!(h, H) + rmul!(view(H, 1:j, :), h') + rmul!(U, h') + end + TDB .= S(0) + TDB[1:keep, 1:keep] .= H[1:keep, 1:keep] + + V = fact.V + V = basistransform!(V, view(U, :, 1:keep)) + r_new = OrthonormalBasis(fact.r.vec[1:bs_r]) + basistransform!(r_new, view(U, all_size - bs_r + 1:all_size, keep - bs_r + 1:keep)) + fact.all_size = keep numiter += 1 end end end - - if !converged && numiter < maxiter - values, vectors, howmany_actual, residuals, normresiduals, num_converged = _residual!(fact, A, howmany, - tol, which, vectors, values) + V = view(fact.V.basis, 1:fact.all_size) + @inbounds for i in 1:howmany_actual + copy!(vectors[i], V[1] * U[1, i]) + for j in 2:fact.all_size + axpy!(U[j, i], V[j], vectors[i]) + end end if (num_converged < howmany) && verbosity >= WARN_LEVEL @@ -218,41 +272,6 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) end return values[1:howmany_actual], - vectors[1:howmany_actual], - ConvergenceInfo(num_converged, residuals, normresiduals, fact.all_size, numops) -end - -function _residual!(fact::BlockLanczosFactorization, A, howmany::Int, tol::Real, which::Selector, vectors::AbstractVector, values::AbstractVector{<:Number}) - all_size = fact.all_size - TDB = view(fact.TDB, 1:all_size, 1:all_size) - D, U = eigen(Hermitian((TDB + TDB') / 2)) - by, rev = eigsort(which) - p = sortperm(D; by=by, rev=rev) - D = D[p] - U = U[:, p] - basis_sofar_view = view(fact.V.basis, 1:all_size) - T = eltype(basis_sofar_view) - S = eltype(TDB) - - howmany_actual = min(howmany, length(D)) - copyto!(values, D[1:howmany_actual]) - - @inbounds for i in 1:howmany_actual - copy!(vectors[i], basis_sofar_view[1]*U[1,i]) - for j in 2:all_size - axpy!(U[j,i], basis_sofar_view[j], vectors[i]) - end - end - - residuals = Vector{T}(undef, howmany_actual) - normresiduals = Vector{real(S)}(undef, howmany_actual) - - for i in 1:howmany_actual - residuals[i] = apply(A, vectors[i]) - axpy!(-values[i], vectors[i], residuals[i]) # residuals[i] -= values[i] * vectors[i] - #TODO: Does norm need to be modified when residuals is complex? - normresiduals[i] = norm(residuals[i]) - end - num_converged = count(nr -> nr <= tol, normresiduals) - return values, vectors, howmany_actual, residuals, normresiduals, num_converged + vectors[1:howmany_actual], + ConvergenceInfo(num_converged, residuals, normresiduals, fact.all_size, numops) end \ No newline at end of file diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index 71762f0c..4406ca1d 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -384,11 +384,28 @@ What ever, mutable block size is at least undoubtedly useful for non-hermitian o https://www.netlib.org/utk/people/JackDongarra/etemplates/node252.html#ABLEsection =# +# We use this to store vectors as a block. Although now its fields are same to OrthonormalBasis, +# I plan to develop BlockVec a abstract of vector of number and inner product space, +# and process the block in the latter as a matrix with higher efficiency. +struct BlockVec{T,S<:Number} + vec::Vector{T} + num_field::Type{S} +end +BlockVec(T::Type, S::Type) = BlockVec(Vector{T}(undef, 0), S) +Base.length(b::BlockVec) = length(b.vec) +Base.getindex(b::BlockVec, i::Int) = b.vec[i] +Base.getindex(b::BlockVec, idxs::AbstractVector{Int}) = BlockVec([b.vec[i] for i in idxs], b.num_field) +Base.setindex!(b::BlockVec{T}, v::T, i::Int) where {T} = (b.vec[i] = v) +Base.setindex!(b₁::BlockVec{T}, b₂::BlockVec{T}, idxs::AbstractVector{Int}) where {T} = (b₁.vec[idxs] = b₂.vec; b₁) +Base.copy!(b₁::BlockVec{T,S}, b₂::BlockVec{T,S}) where {T,S} = (copy!.(b₁.vec, b₂.vec); b₁) +LinearAlgebra.norm(b::BlockVec) = norm(b.vec) +apply(f, block::BlockVec) = BlockVec([apply(f, x) for x in block.vec], block.num_field) + mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: BlockKrylovFactorization{T,S,SR} all_size::Int const V::OrthonormalBasis{T} # Block Lanczos Basis const TDB::AbstractMatrix{S} # TDB matrix, S is the matrix type - const r::OrthonormalBasis{T} # residual block + const r::BlockVec{T,S} # residual block r_size::Int norm_r::SR end @@ -405,24 +422,24 @@ In the future, I will add IR and Householder orthogonalizer. #= The only orthogonalization method we use in block lanczos is ModifiedGramSchmidt2. So we don't need to provide "keepvecs" because we have to reverse all krylove vectors. =# -struct BlockLanczosIterator{F,T,O<:Orthogonalizer} <: KrylovIterator{F,T} +struct BlockLanczosIterator{F,T,S,O<:Orthogonalizer} <: KrylovIterator{F,T} operator::F - x₀::Vector{T} + x₀::BlockVec{T,S} maxdim::Int - num_field::Type + num_field::Type{S} orth::O qr_tol::Real - function BlockLanczosIterator{F,T,O}(operator::F, + function BlockLanczosIterator{F,T,S,O}(operator::F, x₀::Vector{T}, maxdim::Int, - num_field::Type, + num_field::Type{S}, orth::O, - qr_tol::Real) where {F,T,O<:Orthogonalizer} - return new{F,T,O}(operator, x₀, maxdim, num_field, orth, qr_tol) + qr_tol::Real) where {F,T,S,O<:Orthogonalizer} + return new{F,T,S,O}(operator, BlockVec(x₀, num_field), maxdim, num_field, orth, qr_tol) end end function BlockLanczosIterator(operator::F, - x₀::AbstractVector{T}, + x₀::Vector{T}, maxdim::Int, qr_tol::Real, orth::O=ModifiedGramSchmidt2()) where {F,T,O<:Orthogonalizer} @@ -431,51 +448,50 @@ function BlockLanczosIterator(operator::F, length(x₀) < 2 && @error "initial vector should not have norm zero" norm(x₀) < qr_tol && @error "initial vector should not have norm zero" orth != ModifiedGramSchmidt2() && @error "BlockLanczosIterator only supports ModifiedGramSchmidt2 orthogonalizer" - return BlockLanczosIterator{F,T,O}(operator, x₀, maxdim, S, orth, qr_tol) + return BlockLanczosIterator{F,T,S,O}(operator, x₀, maxdim, S, orth, qr_tol) end function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.verbosity[]) - x₀_vec = iter.x₀ + X₀ = iter.x₀ maxdim = iter.maxdim - bs_now = length(x₀_vec) # block size now + bs_now = length(X₀) # block size now A = iter.operator S = iter.num_field - V_basis = [similar(x₀_vec[1]) for i in 1:bs_now * (maxdim + 1)] - r = [similar(x₀_vec[i]) for i in 1:bs_now] + V_basis = similar(X₀.vec, bs_now * (maxdim + 1)) + r = BlockVec([similar(X₀[i]) for i in 1:bs_now], S) TDB = zeros(S, bs_now * (maxdim + 1), bs_now * (maxdim + 1)) - X₁_view = view(V_basis, 1:bs_now) - copy!.(X₁_view, x₀_vec) # Orthogonalization of the initial block - abstract_qr!(BlockVec(X₁_view, S), iter.qr_tol) - Ax₁ = [apply(A, x) for x in X₁_view] + X₁ = deepcopy(X₀) + abstract_qr!(X₁, iter.qr_tol) + V_basis[1:bs_now] .= X₁.vec + + AX₁ = apply(A, X₁) M₁_view = view(TDB, 1:bs_now, 1:bs_now) - block_inner!(M₁_view, BlockVec(X₁_view, S), BlockVec(Ax₁, S)) + block_inner!(M₁_view, X₁, AX₁) verbosity >= WARN_LEVEL && warn_nonhermitian(M₁_view, iter.qr_tol) M₁_view = (M₁_view + M₁_view') / 2 - residual = block_mul!(BlockVec(Ax₁, S), BlockVec(X₁_view, S), - M₁_view, 1.0, 1.0).vec - + residual = block_mul!(AX₁, X₁, - M₁_view, S(1), S(1)) # QR decomposition of residual to get the next basis. Get X2 and B1 - B₁, good_idx = abstract_qr!(BlockVec(residual, S), iter.qr_tol) + B₁, good_idx = abstract_qr!(residual, iter.qr_tol) bs_next = length(good_idx) - X₂_view = view(V_basis, bs_now+1:bs_now+bs_next) - copy!.(X₂_view, residual[good_idx]) + X₂ = residual[good_idx] + V_basis[bs_now+1:bs_now+bs_next] .= X₂.vec B₁_view = view(TDB, bs_now+1:bs_now+bs_next, 1:bs_now) copyto!(B₁_view, B₁) copyto!(view(TDB, 1:bs_now, bs_now+1:bs_now+bs_next), B₁_view') # Calculate the next block - Ax₂ = [apply(A, x) for x in X₂_view] + AX₂ = apply(A, X₂) M₂_view = view(TDB, bs_now+1:bs_now+bs_next, bs_now+1:bs_now+bs_next) - block_inner!(M₂_view, BlockVec(X₂_view, S), BlockVec(Ax₂, S)) + block_inner!(M₂_view, X₂, AX₂) M₂_view = (M₂_view + M₂_view') / 2 # Calculate the new residual. Get r₂ - compute_residual!(BlockVec(r, S), BlockVec(Ax₂, S), BlockVec(X₂_view, S), M₂_view, BlockVec(X₁_view, S), B₁_view) - ortho_basis!(BlockVec(r, S), BlockVec(view(V_basis, 1:bs_now+bs_next), S)) - + compute_residual!(r, AX₂, X₂, M₂_view, X₁, B₁) + ortho_basis!(r, BlockVec(V_basis[1:bs_now+bs_next], S)) norm_r = norm(r) if verbosity > EACHITERATION_LEVEL @@ -485,7 +501,7 @@ function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.ve return BlockLanczosFactorization(bs_now+bs_next, OrthonormalBasis(V_basis), TDB, - OrthonormalBasis(r), + r, bs_next, norm_r) end @@ -493,15 +509,16 @@ end function expand!(iter::BlockLanczosIterator, state::BlockLanczosFactorization; verbosity::Int=KrylovDefaults.verbosity[]) all_size = state.all_size - rₖ = view(state.r.basis, 1:state.r_size) S = iter.num_field + rₖ = state.r[1:state.r_size] bs_now = length(rₖ) + V_basis = state.V.basis # Get the current residual as the initial value of the new basis. Get Xnext - Bₖ, good_idx = abstract_qr!(BlockVec(rₖ, S), iter.qr_tol) + Bₖ, good_idx = abstract_qr!(rₖ, iter.qr_tol) + Xnext = deepcopy(rₖ[good_idx]) bs_next = length(good_idx) - Xnext_view = view(state.V.basis, all_size+1:all_size+bs_next) - copy!.(Xnext_view, rₖ[good_idx]) + V_basis[all_size+1:all_size+bs_next] .= Xnext.vec # Calculate the connection matrix Bₖ_view = view(state.TDB, all_size+1:all_size+bs_next, all_size-bs_now+1:all_size) @@ -509,19 +526,19 @@ function expand!(iter::BlockLanczosIterator, state::BlockLanczosFactorization; copyto!(view(state.TDB, all_size-bs_now+1:all_size, all_size+1:all_size+bs_next), Bₖ_view') # Apply the operator and calculate the M. Get Mnext - Axₖnext = [apply(iter.operator, x) for x in Xnext_view] + AXₖnext = apply(iter.operator, Xnext) Mnext_view = view(state.TDB, all_size+1:all_size+bs_next, all_size+1:all_size+bs_next) - block_inner!(Mnext_view, BlockVec(Xnext_view, S), BlockVec(Axₖnext, S)) + block_inner!(Mnext_view, Xnext, AXₖnext) verbosity >= WARN_LEVEL && warn_nonhermitian(Mnext_view, iter.qr_tol) Mnext_view = (Mnext_view + Mnext_view') / 2 # Calculate the new residual. Get Rnext - Xnow_view = view(state.V.basis, all_size-bs_now+1:all_size) - rₖ[1:bs_next] = rₖ[good_idx] - rₖnext_view = view(state.r.basis, 1:bs_next) - compute_residual!(BlockVec(rₖnext_view, S), BlockVec(Axₖnext, S), BlockVec(Xnext_view, S), Mnext_view, BlockVec(Xnow_view, S), Bₖ_view) - ortho_basis!(BlockVec(rₖnext_view, S), BlockVec(view(state.V.basis, 1:all_size+bs_next), S)) - state.norm_r = norm(rₖnext_view) + Xnow = BlockVec(V_basis[all_size-bs_now+1:all_size], S) + rₖnext = BlockVec([similar(V_basis[1]) for _ in 1:bs_next], S) + compute_residual!(rₖnext, AXₖnext, Xnext, Mnext_view, Xnow, Bₖ_view) + ortho_basis!(rₖnext, BlockVec(V_basis[1:all_size+bs_next], S)) + state.r.vec[1:bs_next] .= rₖnext.vec + state.norm_r = norm(rₖnext) state.all_size += bs_next state.r_size = bs_next @@ -530,14 +547,11 @@ function expand!(iter::BlockLanczosIterator, state::BlockLanczosFactorization; end end -function compute_residual!(Block_r::BlockVec{T,S}, Block_A_X::BlockVec{T,S}, Block_X::BlockVec{T,S}, M::AbstractMatrix, Block_X_prev::BlockVec{T,S}, B_prev::AbstractMatrix) where {T,S} - r = Block_r.vec - A_X = Block_A_X.vec - X = Block_X.vec - X_prev = Block_X_prev.vec +function compute_residual!(r::BlockVec{T,S}, AX::BlockVec{T,S}, X::BlockVec{T,S}, M::AbstractMatrix, + X_prev::BlockVec{T,S}, B_prev::AbstractMatrix) where {T,S} @inbounds for j in 1:length(X) r_j = r[j] - copy!(r_j, A_X[j]) + copy!(r_j, AX[j]) for i in 1:length(X) axpy!(- M[i,j], X[i], r_j) end @@ -545,13 +559,13 @@ function compute_residual!(Block_r::BlockVec{T,S}, Block_A_X::BlockVec{T,S}, Blo axpy!(- B_prev[i,j], X_prev[i], r_j) end end - return Block_r + return r end function ortho_basis!(basis_new::BlockVec{T,S}, basis_sofar::BlockVec{T,S}) where {T,S} tmp = Matrix{S}(undef, length(basis_sofar), length(basis_new)) block_inner!(tmp, basis_sofar, basis_new) - block_mul!(basis_new, basis_sofar, - tmp, 1.0, 1.0) + block_mul!(basis_new, basis_sofar, - tmp, S(1), S(1)) return basis_new end @@ -561,8 +575,7 @@ function warn_nonhermitian(M::AbstractMatrix, tol::Real) end end -function abstract_qr!(Block::BlockVec{T,S}, tol::Real) where {T,S} - block = Block.vec +function abstract_qr!(block::BlockVec{T,S}, tol::Real) where {T,S} n = length(block) rank_shrink = false idx = ones(Int64,n) @@ -586,17 +599,3 @@ function abstract_qr!(Block::BlockVec{T,S}, tol::Real) where {T,S} good_idx = findall(idx .> 0) return R[good_idx,:], good_idx end - -function shrink!(state::BlockLanczosFactorization, k; verbosity::Int=KrylovDefaults.verbosity[]) - V = state.V - all_size = state.all_size - V[1:k] = V[all_size-k+1:all_size] - newTDB = zero(state.TDB) - newTDB[1:k,1:k] = state.TDB[all_size-k+1:all_size,all_size-k+1:all_size] - state.all_size = k - state.TDB .= newTDB - if verbosity > EACHITERATION_LEVEL - @info "Lanczos reduction to dimension $k" - end - return state -end \ No newline at end of file diff --git a/src/innerproductvec.jl b/src/innerproductvec.jl index 061f92cc..09533043 100644 --- a/src/innerproductvec.jl +++ b/src/innerproductvec.jl @@ -128,26 +128,7 @@ end VectorInterface.norm(v::InnerProductVec) = sqrt(real(inner(v, v))) -function block_inner!(M::AbstractMatrix, - B₁::BlockVec{T,S}, - B₂::BlockVec{T,S}) where {T,S} - x = B₁.vec - y = B₂.vec - @assert size(M) == (length(x), length(y)) "Matrix dimensions must match" - @inbounds for j in eachindex(y) - yj = y[j] - for i in eachindex(x) - M[i, j] = inner(x[i], yj) - end - end - return M -end - -block_randn_like(v, n::Int) = [randn!(similar(v)) for _ in 1:n] - -function block_mul!(B₁::BlockVec{T,S}, B₂::BlockVec{T,S}, M::AbstractMatrix, alpha::Number, beta::Number) where {T,S} - A = B₁.vec - B = B₂.vec +function block_mul!(A::BlockVec{T,S}, B::BlockVec{T,S}, M::AbstractMatrix, alpha::Number, beta::Number) where {T,S} @assert (length(B) == size(M, 1)) && (length(A) == size(M, 2)) "Matrix dimensions must match" @inbounds for i in 1:length(A) A[i] = beta * A[i] @@ -155,7 +136,20 @@ function block_mul!(B₁::BlockVec{T,S}, B₂::BlockVec{T,S}, M::AbstractMatrix, A[i] += alpha * B[j] * M[j, i] end end - return B₁ + return A +end + +function block_inner!(M::AbstractMatrix, + x::BlockVec{T,S}, + y::BlockVec{T,S}) where {T,S} + @assert size(M) == (length(x), length(y)) "Matrix dimensions must match" + @inbounds for j in 1:length(y) + yj = y[j] + for i in 1:length(x) + M[i, j] = inner(x[i], yj) + end + end + return M end # used for debugging diff --git a/src/orthonormal.jl b/src/orthonormal.jl index 00d0a0d9..a0970f60 100644 --- a/src/orthonormal.jl +++ b/src/orthonormal.jl @@ -28,15 +28,6 @@ struct OrthonormalBasis{T} <: Basis{T} end OrthonormalBasis{T}() where {T} = OrthonormalBasis{T}(Vector{T}(undef, 0)) -# We use this to store vectors as a block. Although now its fields are same to OrthonormalBasis, -# I plan to develop BlockVec a abstract of vector of number and inner product space, -# and process the block in the latter as a matrix with higher efficiency. -struct BlockVec{T,S} - vec::AbstractVector{T} - S::Type{S} -end -Base.length(b::BlockVec) = length(b.vec) - # Iterator methods for OrthonormalBasis Base.IteratorSize(::Type{<:OrthonormalBasis}) = Base.HasLength() Base.IteratorEltype(::Type{<:OrthonormalBasis}) = Base.HasEltype() diff --git a/test/BlockVec.jl b/test/BlockVec.jl new file mode 100644 index 00000000..91d52009 --- /dev/null +++ b/test/BlockVec.jl @@ -0,0 +1,67 @@ +@testset "apply on BlockVec" begin + for T in [Float32, Float64, ComplexF64] + T = Float32 + A0 = rand(T,N,N); + A0 = A0' * A0; + x₀ = KrylovKit.BlockVec([rand(T,N) for _ in 1:n], T) + for A in [A0, x -> A0*x] + y = KrylovKit.apply(A, x₀) + @test isapprox(hcat(y.vec...), A0 * hcat(x₀.vec...); atol=tolerance(T)) + end + end + T = ComplexF64 + A0 = rand(T,N,N); + A0 = A0' * A0 + f(x,y) = x' * A0 * y + A(x::InnerProductVec) = KrylovKit.InnerProductVec(A0 * x[], x.dotf) + x₀ = KrylovKit.BlockVec([InnerProductVec(rand(T,N), f) for _ in 1:n], T) + y = KrylovKit.apply(A, x₀) + @test isapprox(hcat([y[i].vec for i in 1:n]...), A0 * hcat([x₀[i].vec for i in 1:n]...); atol=tolerance(T)) +end + +@testset "copy! for BlockVec" begin + for T in [Float32, Float64, ComplexF32, ComplexF64] + x = KrylovKit.BlockVec([rand(T,N) for _ in 1:n], T) + y = KrylovKit.BlockVec([rand(T,N) for _ in 1:n], T) + KrylovKit.copy!(y, x) + @test isapprox(y.vec, x.vec; atol=tolerance(T)) + end + T = ComplexF64 + f = (x,y) -> x' * y + x = KrylovKit.BlockVec([InnerProductVec(rand(T,N), f) for _ in 1:n], T) + y = KrylovKit.BlockVec([InnerProductVec(rand(T,N), f) for _ in 1:n], T) + KrylovKit.copy!(y, x) + @test isapprox([y.vec[i].vec for i in 1:n], [x.vec[i].vec for i in 1:n]; atol=tolerance(T)) +end + +using KrylovKit,Random,Test,LinearAlgebra +N =10 +n = 5 +Random.seed!(1234) +T = ComplexF64; +A = rand(T,N,N); +A = A' * A; +x₀ = rand(T,N); +alg = Lanczos(; krylovdim = n, maxiter = 10, tol = 1e-10) +eigsolve(A, x₀, n, :SR, alg) + +using KrylovKit,Random,Test,LinearAlgebra +N =100 +Random.seed!(1234) +T = ComplexF64; +A = rand(T,N,N); +A = A' * A; +x₀ = rand(T,N); +alg = Lanczos(; maxiter = 3, tol = 1e-10,blockmode=true, blocksize = 5) +vlues1, vectors1, info1 = eigsolve(A, x₀, 10, :SR, alg) + +Random.seed!(1234) +T = ComplexF64; +A = rand(T,N,N); +A = A' * A; +x₀ = rand(T,N); +alg = Lanczos(; maxiter = 3, tol = 1e-10,blockmode=true, blocksize = 5) +vlues2, vectors2, info2 = eigsolve(A, x₀, 10, :SR, alg) + +norm(vlues1 - vlues2) +norm(vectors1 - vectors2) diff --git a/test/eigsolve.jl b/test/eigsolve.jl index c5722017..fd6c09b4 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -431,7 +431,7 @@ end h_mat = toric_code_hamiltonian_matrix(sites_num, sites_num) # matrix input - alg = Lanczos(;tol = tol, blockmode = true, blocksize = p) + alg = Lanczos(;tol = tol, blockmode = true, blocksize = p,maxiter = 1) D, U, info = eigsolve(-h_mat, x₀, get_value_num, :SR, alg) @show D[1:get_value_num] @test count(x -> abs(x + 16.0) < 2.0 - tol, D[1:get_value_num]) == 4 @@ -479,8 +479,8 @@ As a result, I’ve decided to postpone dealing with the in-place test issue for @test_logs (:warn,) eigsolve(A, x₀, n1, :SR, alg) alg = Lanczos(; krylovdim = n, maxiter = 1, tol = tolerance(T), verbosity = 2, blockmode = true, blocksize = block_size) @test_logs (:info,) eigsolve(A, x₀, n1, :SR, alg) - alg = Lanczos(; krylovdim = n1, maxiter = 3, tol = tolerance(T), verbosity = 3, blockmode = true, blocksize = block_size) - @test_logs((:info,), (:info,), (:info,), (:warn,), eigsolve(A, x₀, 1, :SR, alg)) + alg = Lanczos(; krylovdim = 2, maxiter = 3, tol = tolerance(T), verbosity = 3, blockmode = true, blocksize = block_size) + @test_logs((:info,), (:info,), (:info,), eigsolve(A, x₀, 1, :SR, alg)) alg = Lanczos(; krylovdim=4, maxiter=1, tol=tolerance(T), verbosity=4, blockmode=true, blocksize=block_size) @test_logs((:info,), (:info,), (:info,), (:warn,), eigsolve(A, x₀, 1, :SR, alg)) # To use blockmode, users have to explicitly set blockmode = true, we don't allow them to use eigselector. @@ -558,58 +558,4 @@ end @test D ≈ D_true[1:eig_num] @test KrylovKit.block_inner(BlockV, BlockV) ≈ I @test findmax([norm(Aip(V[i]) - D[i] * V[i]) for i in 1:eig_num])[1] < tolerance(T) -end - -using KrylovKit,LinearAlgebra,Random,Test,BenchmarkTools -n = 10 -N = 100 -@testset "Compare Block Lanczos and Lanczos" begin - @testset for T in [Float64, ComplexF64] - Random.seed!(6) - A0 = rand(T, (N,N)) - A = A0' * A0 + I - x₀ = rand(T, N) - krydim = 100 - maxiter = 10 - block_size = 5 - eig_num = 10 - Atype = [A0, x -> A0 * x] - Atypename = ["Matrix", "LinearOperator"] - for i in eachindex(Atype) - A = Atype[i] - Aname = Atypename[i] - lanczos_alg = Lanczos(; krylovdim = krydim, maxiter = maxiter, tol = tolerance(T), verbosity = 0) - block_alg = Lanczos(; krylovdim = krydim, maxiter = maxiter, tol = tolerance(T), verbosity = 0, blockmode = true, blocksize = block_size) - t1 = time() - eigsolve(A, x₀, eig_num, :SR, lanczos_alg) - t1 = time() - t1 - t2 = time() - eigsolve(A, x₀, eig_num, :SR, block_alg) - t2 = time() - t2 - println("When T = $T, time of $Aname lanczos_alg: $t1, time of $Aname block_alg: $t2") - end - end -end -#= -# TODO: improve shrink -using KrylovKit,LinearAlgebra,Random,Test -Random.seed!(6) -n = 10 -N = 100 -A = rand(N,N); -A = A' * A + I; -alg = Lanczos(;krylovdim = 10,maxiter = 10,tol = 1e-12); -values,vectors,info = eigsolve(A,rand(N),10,:SR,alg) -=# - -#= -using KrylovKit,LinearAlgebra,Random,Test -Random.seed!(6) -n = 10 -N = 100 -A = rand(N,N); -A = A' * A + I; -x₀ = rand(N); -alg = Lanczos(;tol = 1e-8,blockmode = true,blocksize = 5); -values,vectors,info = eigsolve(A,x₀,10,:SR,alg) -=# \ No newline at end of file +end \ No newline at end of file diff --git a/test/factorize.jl b/test/factorize.jl index 0570855e..e82dd418 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -325,12 +325,6 @@ end verbosity = EACHITERATION_LEVEL + 1 end end - # Information about V has been tested in eigsolve.jl - # And Block Lanczos has no "rayleighquotient" function - # We also don't have "initialize!(iter, fact)" function - @constinferred shrink!(fact, n - 1) - @test_logs (:info,) shrink!(fact, n - 2; verbosity=EACHITERATION_LEVEL + 1) - @test_logs shrink!(fact, n - 3; verbosity=EACHITERATION_LEVEL) end if T <: Complex @@ -376,10 +370,27 @@ end @test norm(r) ≈ β @test A * V ≈ V * H + r * e' end - # The V and residual have been tested in eigsolve.jl, and we don't need some functions tested in Lanczos - fact = @constinferred shrink!(fact, div(n, 2)) - end end end +# Test effectiveness of shrink!() in block lanczos +@testset "Test effectiveness of shrink!() in block lanczos" begin + @testset for T in [Float32, Float64, ComplexF32, ComplexF64] + A0 = rand(T, (N, N)) + A0 = (A0 + A0') / 2 + block_size = 5 + x₀m = Matrix(qr(rand(T, N, block_size)).Q) + x₀ = [x₀m[:, i] for i in 1:block_size] + values0 = eigvals(A0)[1:n] + for A in [A0, x -> A0 * x] + alg = KrylovKit.Lanczos(; krylovdim = 3*n÷2, maxiter = 1, tol = 1e-12, blockmode = true, blocksize = block_size) + values, _, _ = eigsolve(A, x₀, n, :SR, alg) + error1 = norm(values - values0) + alg_shrink = KrylovKit.Lanczos(; krylovdim = n, maxiter = 1, tol = 1e-12, blockmode = true, blocksize = block_size) + values_shrink, _, _ = eigsolve(A, x₀, n, :SR, alg_shrink) + error2 = norm(values_shrink - values0) + @test error2 < error1 + end + end +end \ No newline at end of file diff --git a/test/innerproductvec.jl b/test/innerproductvec.jl index 3fa2110d..76dacd03 100644 --- a/test/innerproductvec.jl +++ b/test/innerproductvec.jl @@ -43,21 +43,6 @@ end @test isapprox(M, M0; atol = relax_tol(T)) end -@testset "block_randn_like" begin - @testset for T in [Float32,Float64,ComplexF32,ComplexF64] - v = InnerProductVec(rand(T, n), x -> x'*x) - sv = KrylovKit.block_randn_like(v, n) - @test length(sv) == n - @test eltype(sv[2].vec) == T - @test sv[2].dotf == v.dotf - - u = rand(T, n) - su = KrylovKit.block_randn_like(u, n) - @test length(su) == n - @test eltype(su[2]) == T - end -end - @testset "block_mul!" begin T = ComplexF64 f = x -> x'*x diff --git a/test/runtests.jl b/test/runtests.jl index e8c1102b..9a68a7c7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -68,6 +68,9 @@ end @testset "Inner product vector" verbose = true begin include("innerproductvec.jl") end +@testset "BlockVec" verbose = true begin + include("BlockVec.jl") +end t = time() - t # Issues From 29da1e78b9f71136a2032f9414ce740f65cddb96 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Thu, 24 Apr 2025 06:14:00 +0800 Subject: [PATCH 30/82] add shrink --- src/eigsolve/lanczos.jl | 21 +++++++++++++-------- test/BlockVec.jl | 2 ++ test/eigsolve.jl | 26 +++++++++++++++++++++++--- test/factorize.jl | 10 +++++----- 4 files changed, 43 insertions(+), 16 deletions(-) diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index d1385fac..1b170aa1 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -228,24 +228,29 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) keep = bs * bsn H = zeros(S, (bsn + 1) * bs, bsn * bs) @inbounds for j in 1:keep - H[j, j] = fact.TDB[j, j] - H[bsn * bs + 1:end, j] = U[bsn * bs + 1:(bsn + 1) * bs, j] + H[j, j] = D[j] + H[bsn * bs + 1:end, j] = U[all_size - bs + 1:all_size, j] end @inbounds for j in keep:-1:1 h, ν = householder(H, j + bs, 1:j, j) H[j + bs, j] = ν H[j + bs, 1:(j - 1)] .= zero(eltype(H)) lmul!(h, H) - rmul!(view(H, 1:j, :), h') + rmul!(view(H, 1:j + bs -1, :), h') rmul!(U, h') end TDB .= S(0) TDB[1:keep, 1:keep] .= H[1:keep, 1:keep] - - V = fact.V - V = basistransform!(V, view(U, :, 1:keep)) + + V = OrthonormalBasis(fact.V.basis[1:all_size]) + basistransform!(V, view(U, :, 1:keep)) + fact.V[1:keep] = V[1:keep] + r_new = OrthonormalBasis(fact.r.vec[1:bs_r]) - basistransform!(r_new, view(U, all_size - bs_r + 1:all_size, keep - bs_r + 1:keep)) + view_U = view(U, all_size - bs_r + 1:all_size, keep - bs_r + 1:keep) + basistransform!(r_new, view_U) + fact.r.vec[1:bs_r] = r_new[1:bs_r] + fact.all_size = keep numiter += 1 end @@ -273,5 +278,5 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) return values[1:howmany_actual], vectors[1:howmany_actual], - ConvergenceInfo(num_converged, residuals, normresiduals, fact.all_size, numops) + ConvergenceInfo(num_converged, residuals, normresiduals, numiter, numops) end \ No newline at end of file diff --git a/test/BlockVec.jl b/test/BlockVec.jl index 91d52009..1e41f91b 100644 --- a/test/BlockVec.jl +++ b/test/BlockVec.jl @@ -34,6 +34,7 @@ end @test isapprox([y.vec[i].vec for i in 1:n], [x.vec[i].vec for i in 1:n]; atol=tolerance(T)) end +#= using KrylovKit,Random,Test,LinearAlgebra N =10 n = 5 @@ -65,3 +66,4 @@ vlues2, vectors2, info2 = eigsolve(A, x₀, 10, :SR, alg) norm(vlues1 - vlues2) norm(vectors1 - vectors2) +=# diff --git a/test/eigsolve.jl b/test/eigsolve.jl index fd6c09b4..39c993b3 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -468,8 +468,6 @@ As a result, I’ve decided to postpone dealing with the in-place test issue for x₀ = rand(T, n) n1 = div(n, 2) # eigenvalues to solve eigvalsA = eigvals(A0) - # Different from Lanczos, we don't set maxiter =1 here because the iteration times in Lanczos - # in in fact in the control of deminsion of Krylov subspace. And we Don't use it. for A in [A0, x -> A0 * x] alg = Lanczos(; krylovdim = n, maxiter = 1, tol = tolerance(T), verbosity = 2, blockmode = true, blocksize = block_size) D1, V1, info = @test_logs (:info,) eigsolve(A, x₀, n1, :SR, alg) @@ -480,7 +478,7 @@ As a result, I’ve decided to postpone dealing with the in-place test issue for alg = Lanczos(; krylovdim = n, maxiter = 1, tol = tolerance(T), verbosity = 2, blockmode = true, blocksize = block_size) @test_logs (:info,) eigsolve(A, x₀, n1, :SR, alg) alg = Lanczos(; krylovdim = 2, maxiter = 3, tol = tolerance(T), verbosity = 3, blockmode = true, blocksize = block_size) - @test_logs((:info,), (:info,), (:info,), eigsolve(A, x₀, 1, :SR, alg)) + @test_logs((:info,), (:info,), (:info,), (:warn,), eigsolve(A, x₀, 1, :SR, alg)) alg = Lanczos(; krylovdim=4, maxiter=1, tol=tolerance(T), verbosity=4, blockmode=true, blocksize=block_size) @test_logs((:info,), (:info,), (:info,), (:warn,), eigsolve(A, x₀, 1, :SR, alg)) # To use blockmode, users have to explicitly set blockmode = true, we don't allow them to use eigselector. @@ -558,4 +556,26 @@ end @test D ≈ D_true[1:eig_num] @test KrylovKit.block_inner(BlockV, BlockV) ≈ I @test findmax([norm(Aip(V[i]) - D[i] * V[i]) for i in 1:eig_num])[1] < tolerance(T) +end + +@testset "Complete Lanczos and Block Lanczos" begin + @testset for T in [Float32, Float64, ComplexF32, ComplexF64] + Random.seed!(6) + A0 = rand(T, (2N, 2N)) + A0 = (A0 + A0') / 2 + block_size = 2 + x₀ = rand(T, 2N) + alg1 = Lanczos(;krylovdim = N, maxiter = 10, tol = tolerance(T), verbosity = 1) + alg2 = Lanczos(;krylovdim = N, maxiter = 10, tol = tolerance(T), verbosity = 1, blockmode = true, blocksize = block_size) + for A in [A0, x -> A0 * x] + t1 = time() + _, _, info1 = eigsolve(A, x₀, n, :SR, alg1) + t1 = time() - t1 + t2 = time() + _, _, info2 = eigsolve(A, x₀, n, :SR, alg2) + t2 = time() - t2 + @test t2 < 10 * t1 + @test min(info1.converged, n) <= info2.converged + end + end end \ No newline at end of file diff --git a/test/factorize.jl b/test/factorize.jl index e82dd418..7f647013 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -380,16 +380,16 @@ end A0 = rand(T, (N, N)) A0 = (A0 + A0') / 2 block_size = 5 - x₀m = Matrix(qr(rand(T, N, block_size)).Q) - x₀ = [x₀m[:, i] for i in 1:block_size] + x₀ = rand(T, N) values0 = eigvals(A0)[1:n] + n1 = n ÷ 2 for A in [A0, x -> A0 * x] alg = KrylovKit.Lanczos(; krylovdim = 3*n÷2, maxiter = 1, tol = 1e-12, blockmode = true, blocksize = block_size) values, _, _ = eigsolve(A, x₀, n, :SR, alg) - error1 = norm(values - values0) - alg_shrink = KrylovKit.Lanczos(; krylovdim = n, maxiter = 1, tol = 1e-12, blockmode = true, blocksize = block_size) + error1 = norm(values[1:n1] - values0[1:n1]) + alg_shrink = KrylovKit.Lanczos(; krylovdim = n, maxiter = 2, tol = 1e-12, blockmode = true, blocksize = block_size) values_shrink, _, _ = eigsolve(A, x₀, n, :SR, alg_shrink) - error2 = norm(values_shrink - values0) + error2 = norm(values_shrink[1:n1] - values0[1:n1]) @test error2 < error1 end end From ba12f42fcf907a7b40b89adfadc9c99f73f21fb8 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Fri, 25 Apr 2025 01:54:24 +0800 Subject: [PATCH 31/82] uodate --- Project.toml | 1 - src/eigsolve/lanczos.jl | 3 +-- src/factorizations/lanczos.jl | 4 ++-- test/BlockVec.jl | 32 -------------------------------- test/eigsolve.jl | 22 +++++++++------------- test/factorize.jl | 6 +++--- 6 files changed, 15 insertions(+), 53 deletions(-) diff --git a/Project.toml b/Project.toml index f46ff94e..6e207ef8 100644 --- a/Project.toml +++ b/Project.toml @@ -8,7 +8,6 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" PackageExtensionCompat = "65ce6f38-6b18-4e1d-a461-8949797d7930" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" [weakdeps] diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index 1b170aa1..d10231fe 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -241,11 +241,10 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) end TDB .= S(0) TDB[1:keep, 1:keep] .= H[1:keep, 1:keep] - V = OrthonormalBasis(fact.V.basis[1:all_size]) basistransform!(V, view(U, :, 1:keep)) fact.V[1:keep] = V[1:keep] - + r_new = OrthonormalBasis(fact.r.vec[1:bs_r]) view_U = view(U, all_size - bs_r + 1:all_size, keep - bs_r + 1:keep) basistransform!(r_new, view_U) diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index 4406ca1d..8d6cac62 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -458,9 +458,9 @@ function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.ve A = iter.operator S = iter.num_field - V_basis = similar(X₀.vec, bs_now * (maxdim + 1)) + V_basis = similar(X₀.vec, maxdim) r = BlockVec([similar(X₀[i]) for i in 1:bs_now], S) - TDB = zeros(S, bs_now * (maxdim + 1), bs_now * (maxdim + 1)) + TDB = zeros(S, maxdim, maxdim) # Orthogonalization of the initial block X₁ = deepcopy(X₀) diff --git a/test/BlockVec.jl b/test/BlockVec.jl index 1e41f91b..e33599a7 100644 --- a/test/BlockVec.jl +++ b/test/BlockVec.jl @@ -34,36 +34,4 @@ end @test isapprox([y.vec[i].vec for i in 1:n], [x.vec[i].vec for i in 1:n]; atol=tolerance(T)) end -#= -using KrylovKit,Random,Test,LinearAlgebra -N =10 -n = 5 -Random.seed!(1234) -T = ComplexF64; -A = rand(T,N,N); -A = A' * A; -x₀ = rand(T,N); -alg = Lanczos(; krylovdim = n, maxiter = 10, tol = 1e-10) -eigsolve(A, x₀, n, :SR, alg) -using KrylovKit,Random,Test,LinearAlgebra -N =100 -Random.seed!(1234) -T = ComplexF64; -A = rand(T,N,N); -A = A' * A; -x₀ = rand(T,N); -alg = Lanczos(; maxiter = 3, tol = 1e-10,blockmode=true, blocksize = 5) -vlues1, vectors1, info1 = eigsolve(A, x₀, 10, :SR, alg) - -Random.seed!(1234) -T = ComplexF64; -A = rand(T,N,N); -A = A' * A; -x₀ = rand(T,N); -alg = Lanczos(; maxiter = 3, tol = 1e-10,blockmode=true, blocksize = 5) -vlues2, vectors2, info2 = eigsolve(A, x₀, 10, :SR, alg) - -norm(vlues1 - vlues2) -norm(vectors1 - vectors2) -=# diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 39c993b3..8d28c18d 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -477,9 +477,9 @@ As a result, I’ve decided to postpone dealing with the in-place test issue for @test_logs (:warn,) eigsolve(A, x₀, n1, :SR, alg) alg = Lanczos(; krylovdim = n, maxiter = 1, tol = tolerance(T), verbosity = 2, blockmode = true, blocksize = block_size) @test_logs (:info,) eigsolve(A, x₀, n1, :SR, alg) - alg = Lanczos(; krylovdim = 2, maxiter = 3, tol = tolerance(T), verbosity = 3, blockmode = true, blocksize = block_size) + alg = Lanczos(; krylovdim = 4, maxiter = 3, tol = tolerance(T), verbosity = 3, blockmode = true, blocksize = block_size) @test_logs((:info,), (:info,), (:info,), (:warn,), eigsolve(A, x₀, 1, :SR, alg)) - alg = Lanczos(; krylovdim=4, maxiter=1, tol=tolerance(T), verbosity=4, blockmode=true, blocksize=block_size) + alg = Lanczos(; krylovdim = 4, maxiter=1, tol=tolerance(T), verbosity=4, blockmode=true, blocksize=block_size) @test_logs((:info,), (:info,), (:info,), (:warn,), eigsolve(A, x₀, 1, :SR, alg)) # To use blockmode, users have to explicitly set blockmode = true, we don't allow them to use eigselector. # Because of the _residual! function, I can't make sure the stability of types temporarily. @@ -514,7 +514,7 @@ end x₀ = rand(T, N) eigvalsA = eigvals(A0) for A in [A0, x -> A0 * x] - alg = Lanczos(; maxiter = 20, tol = tolerance(T), blockmode = true, blocksize = block_size) + alg = Lanczos(; maxiter = 1, tol = tolerance(T), blockmode = true, blocksize = block_size) D1, V1, info1 = eigsolve(A, x₀, n, :SR, alg) D2, V2, info2 = eigsolve(A, x₀, n, :LR, alg) @@ -549,7 +549,7 @@ end Hip(x::Vector, y::Vector) = x' * H * y x₀ = InnerProductVec(rand(T, n), Hip) Aip(x::InnerProductVec) = InnerProductVec(H * x.vec, Hip) - D, V, info = eigsolve(Aip, x₀, eig_num, :SR, Lanczos(; krylovdim = n, maxiter = 10, tol = tolerance(T), + D, V, info = eigsolve(Aip, x₀, eig_num, :SR, Lanczos(; krylovdim = n, maxiter = 1, tol = tolerance(T), verbosity = 0, blockmode = true, blocksize = block_size)) D_true = eigvals(H) BlockV = KrylovKit.BlockVec(V, T) @@ -563,19 +563,15 @@ end Random.seed!(6) A0 = rand(T, (2N, 2N)) A0 = (A0 + A0') / 2 - block_size = 2 + block_size = 4 x₀ = rand(T, 2N) alg1 = Lanczos(;krylovdim = N, maxiter = 10, tol = tolerance(T), verbosity = 1) alg2 = Lanczos(;krylovdim = N, maxiter = 10, tol = tolerance(T), verbosity = 1, blockmode = true, blocksize = block_size) for A in [A0, x -> A0 * x] - t1 = time() - _, _, info1 = eigsolve(A, x₀, n, :SR, alg1) - t1 = time() - t1 - t2 = time() - _, _, info2 = eigsolve(A, x₀, n, :SR, alg2) - t2 = time() - t2 - @test t2 < 10 * t1 - @test min(info1.converged, n) <= info2.converged + t1 = @elapsed _, _, info1 = eigsolve(A, x₀, block_size, :SR, alg1) + t2 = @elapsed _, _, info2 = eigsolve(A, x₀, block_size, :SR, alg2) + println("When input type is $T, the time of lanczos and block lanczos is $t1, $t2") + @test min(info1.converged, block_size) <= info2.converged end end end \ No newline at end of file diff --git a/test/factorize.jl b/test/factorize.jl index 7f647013..33953967 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -308,7 +308,7 @@ end x₀ = [x₀m[:, i] for i in 1:block_size] eigvalsA = eigvals(A0) for A in [A0, x -> A0 * x] - iter = KrylovKit.BlockLanczosIterator(A, x₀, 4, qr_tol(T)) + iter = KrylovKit.BlockLanczosIterator(A, x₀, N, qr_tol(T)) # TODO: Why type unstable? # fact = @constinferred initialize(iter) fact = initialize(iter) @@ -332,7 +332,7 @@ end bs = 2 v₀m = Matrix(qr(rand(T, n, bs)).Q) v₀ = [v₀m[:, i] for i in 1:bs] - iter = KrylovKit.BlockLanczosIterator(B, v₀, 4, qr_tol(T)) + iter = KrylovKit.BlockLanczosIterator(B, v₀, N, qr_tol(T)) fact = initialize(iter) @constinferred expand!(iter, fact; verbosity = 0) @test_logs initialize(iter; verbosity = 0) @@ -360,7 +360,7 @@ end x₀m = Matrix(qr(rand(T, N, block_size)).Q) x₀ = [x₀m[:, i] for i in 1:block_size] for A in [A0, x -> A0 * x] - iter = @constinferred KrylovKit.BlockLanczosIterator(A, x₀, 4, qr_tol(T)) + iter = @constinferred KrylovKit.BlockLanczosIterator(A, x₀, N, qr_tol(T)) krylovdim = n fact = initialize(iter) while fact.norm_r > eps(float(real(T))) && fact.all_size < krylovdim From 52d0f6c5ad49a6d1709847e23f3daffad85bb285 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Sun, 27 Apr 2025 16:10:37 +0800 Subject: [PATCH 32/82] update --- src/algorithms.jl | 2 +- src/eigsolve/lanczos.jl | 53 ++++++++++++++++++++--------------- src/factorizations/lanczos.jl | 44 ++++++++++++++--------------- test/BlockVec.jl | 21 +++++++++----- test/eigsolve.jl | 35 +++++++++++++++++++---- test/runtests.jl | 2 +- 6 files changed, 97 insertions(+), 60 deletions(-) diff --git a/src/algorithms.jl b/src/algorithms.jl index 61907102..54e3836a 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -142,7 +142,7 @@ function Lanczos(; krylovdim < 0 && (blockmode ? (krylovdim = KrylovDefaults.blockkrylovdim[]) : (krylovdim = KrylovDefaults.krylovdim[])) maxiter < 0 && (blockmode ? (maxiter = KrylovDefaults.blockmaxiter[]) : (maxiter = KrylovDefaults.maxiter[])) if blockmode - blocksize <= 1 && error("blocksize must be greater than 1") + blocksize < 1 && error("blocksize must be greater than 0") return BlockLanczos(orth, krylovdim, maxiter, tol, verbosity, blocksize, qr_tol) else return Lanczos(orth, krylovdim, maxiter, tol, eager, verbosity) diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index d10231fe..95c61958 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -159,16 +159,22 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) end tol = alg.tol verbosity = alg.verbosity + + # Initialize a block of vectors from the initial vector, randomly generated + # TODO: add a more flexible initialization x₀_vec = [randn!(similar(x₀)) for _ in 1:alg.blocksize-1] pushfirst!(x₀_vec, x₀) bs = length(x₀_vec) iter = BlockLanczosIterator(A, x₀_vec, krylovdim + bs, alg.qr_tol, alg.orth) - fact = initialize(iter; verbosity = verbosity) - numops = 2 # how many times we apply A + fact = initialize(iter; verbosity = verbosity) # Returns a BlockLanczosFactorization + numops = 2 # Number of matrix-vector multiplications (for logging) numiter = 1 + + # Preallocate space for eigenvectors and eigenvalues vectors = [similar(x₀_vec[1]) for _ in 1:howmany] - values = Vector{real(eltype(fact.TDB))}(undef, howmany) + values = Vector{real(eltype(fact.TDB))}(undef, howmany) # TODO: fix + converged = false num_converged = 0 local howmany_actual, residuals, normresiduals, D, U @@ -176,17 +182,16 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) while true expand!(iter, fact; verbosity = verbosity) numops += 1 + K = length(fact) + β = normres(fact) - # When norm(Rk) is to small, we may lose too much precision and orthogonalization. - if fact.all_size > krylovdim || (fact.norm_r < tol) || (fact.r_size < 2) - if fact.norm_r < tol && fact.all_size < howmany && verbosity >= WARN_LEVEL - msg = "Invariant subspace of dimension $(fact.all_size) (up to requested tolerance `tol = $tol`), " - msg *= "which is smaller than the number of requested eigenvalues (i.e. `howmany == $howmany`)." - @warn msg - end - - all_size = fact.all_size - TDB = view(fact.TDB, 1:all_size, 1:all_size) + if β < tol && K < howmany && verbosity >= WARN_LEVEL + msg = "Invariant subspace of dimension $(K) (up to requested tolerance `tol = $tol`), " + msg *= "which is smaller than the number of requested eigenvalues (i.e. `howmany == $howmany`)." + @warn msg + end + if K > krylovdim || β < tol + TDB = view(fact.TDB, 1:K, 1:K) D, U = eigen(Hermitian((TDB + TDB') / 2)) by, rev = eigsort(which) p = sortperm(D; by = by, rev = rev) @@ -212,16 +217,16 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) end num_converged = count(nr -> nr <= tol, normresiduals) - if num_converged >= howmany || fact.norm_r < tol + if num_converged >= howmany || β < tol converged = true break elseif verbosity >= EACHITERATION_LEVEL @info "Block Lanczos eigsolve in iteration $numiter: $num_converged values converged, normres = $(normres2string(normresiduals[1:howmany]))" end - if fact.all_size > krylovdim # begin to shrink dimension + if K > krylovdim # begin to shrink dimension numiter >= maxiter && break bsn = max(div(3 * krylovdim + 2 * num_converged, 5) ÷ bs, 1) - if (bsn + 1) * bs > fact.all_size # make sure that we can fetch next block after shrinked dimension as residual + if (bsn + 1) * bs > K # make sure that we can fetch next block after shrinked dimension as residual warning("shrinked dimesion is too small and there is no need to shrink") break end @@ -229,7 +234,7 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) H = zeros(S, (bsn + 1) * bs, bsn * bs) @inbounds for j in 1:keep H[j, j] = D[j] - H[bsn * bs + 1:end, j] = U[all_size - bs + 1:all_size, j] + H[bsn * bs + 1:end, j] = U[K - bs + 1:K, j] end @inbounds for j in keep:-1:1 h, ν = householder(H, j + bs, 1:j, j) @@ -241,12 +246,12 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) end TDB .= S(0) TDB[1:keep, 1:keep] .= H[1:keep, 1:keep] - V = OrthonormalBasis(fact.V.basis[1:all_size]) + V = OrthonormalBasis(fact.V.basis[1:K]) basistransform!(V, view(U, :, 1:keep)) fact.V[1:keep] = V[1:keep] r_new = OrthonormalBasis(fact.r.vec[1:bs_r]) - view_U = view(U, all_size - bs_r + 1:all_size, keep - bs_r + 1:keep) + view_U = view(U, K - bs_r + 1:K, keep - bs_r + 1:keep) basistransform!(r_new, view_U) fact.r.vec[1:bs_r] = r_new[1:bs_r] @@ -255,21 +260,23 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) end end end - V = view(fact.V.basis, 1:fact.all_size) + + K = length(fact) + V = view(fact.V.basis, 1:K) @inbounds for i in 1:howmany_actual copy!(vectors[i], V[1] * U[1, i]) - for j in 2:fact.all_size + for j in 2:K axpy!(U[j, i], V[j], vectors[i]) end end if (num_converged < howmany) && verbosity >= WARN_LEVEL - @warn """Block Lanczos eigsolve stopped without full convergence after $(fact.all_size) iterations: + @warn """Block Lanczos eigsolve stopped without full convergence after $(K) iterations: * $num_converged eigenvalues converged * norm of residuals = $(normres2string(normresiduals)) * number of operations = $numops""" elseif verbosity >= STARTSTOP_LEVEL - @info """Block Lanczos eigsolve finished after $(fact.all_size) iterations: + @info """Block Lanczos eigsolve finished after $(K) iterations: * $num_converged eigenvalues converged * norm of residuals = $(normres2string(normresiduals)) * number of operations = $numops""" diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index 8d6cac62..12dae4fd 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -389,26 +389,29 @@ https://www.netlib.org/utk/people/JackDongarra/etemplates/node252.html#ABLEsecti # and process the block in the latter as a matrix with higher efficiency. struct BlockVec{T,S<:Number} vec::Vector{T} - num_field::Type{S} + function BlockVec{S}(vec::Vector{T}) where {T,S<:Number} + return new{T,S}(vec) + end end -BlockVec(T::Type, S::Type) = BlockVec(Vector{T}(undef, 0), S) Base.length(b::BlockVec) = length(b.vec) Base.getindex(b::BlockVec, i::Int) = b.vec[i] -Base.getindex(b::BlockVec, idxs::AbstractVector{Int}) = BlockVec([b.vec[i] for i in idxs], b.num_field) +Base.getindex(b::BlockVec{T, S}, idxs::AbstractVector{Int}) where {T, S} = BlockVec{S}([b.vec[i] for i in idxs]) Base.setindex!(b::BlockVec{T}, v::T, i::Int) where {T} = (b.vec[i] = v) Base.setindex!(b₁::BlockVec{T}, b₂::BlockVec{T}, idxs::AbstractVector{Int}) where {T} = (b₁.vec[idxs] = b₂.vec; b₁) Base.copy!(b₁::BlockVec{T,S}, b₂::BlockVec{T,S}) where {T,S} = (copy!.(b₁.vec, b₂.vec); b₁) LinearAlgebra.norm(b::BlockVec) = norm(b.vec) -apply(f, block::BlockVec) = BlockVec([apply(f, x) for x in block.vec], block.num_field) +apply(f, block::BlockVec{T, S}) where {T, S} = BlockVec{S}([apply(f, x) for x in block.vec]) mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: BlockKrylovFactorization{T,S,SR} all_size::Int - const V::OrthonormalBasis{T} # Block Lanczos Basis - const TDB::AbstractMatrix{S} # TDB matrix, S is the matrix type + const V::OrthonormalBasis{T} # Block Lanczos Basis + const TDB::AbstractMatrix{S} # TDB matrix, S is the matrix type const r::BlockVec{T,S} # residual block - r_size::Int - norm_r::SR + r_size::Int # size of the residual block + norm_r::SR # norm of the residual block end +Base.length(fact::BlockLanczosFactorization) = fact.all_size +normres(fact::BlockLanczosFactorization) = fact.norm_r #= Now our orthogonalizer is only ModifiedGramSchmidt2. @@ -426,16 +429,14 @@ struct BlockLanczosIterator{F,T,S,O<:Orthogonalizer} <: KrylovIterator{F,T} operator::F x₀::BlockVec{T,S} maxdim::Int - num_field::Type{S} orth::O qr_tol::Real function BlockLanczosIterator{F,T,S,O}(operator::F, x₀::Vector{T}, maxdim::Int, - num_field::Type{S}, orth::O, qr_tol::Real) where {F,T,S,O<:Orthogonalizer} - return new{F,T,S,O}(operator, BlockVec(x₀, num_field), maxdim, num_field, orth, qr_tol) + return new{F,T,S,O}(operator, BlockVec{S}(x₀), maxdim, orth, qr_tol) end end function BlockLanczosIterator(operator::F, @@ -445,21 +446,19 @@ function BlockLanczosIterator(operator::F, orth::O=ModifiedGramSchmidt2()) where {F,T,O<:Orthogonalizer} S = typeof(inner(x₀[1], x₀[1])) qr_tol < 0 && (qr_tol = 1e4 * eps(real(S))) - length(x₀) < 2 && @error "initial vector should not have norm zero" norm(x₀) < qr_tol && @error "initial vector should not have norm zero" orth != ModifiedGramSchmidt2() && @error "BlockLanczosIterator only supports ModifiedGramSchmidt2 orthogonalizer" - return BlockLanczosIterator{F,T,S,O}(operator, x₀, maxdim, S, orth, qr_tol) + return BlockLanczosIterator{F,T,S,O}(operator, x₀, maxdim, orth, qr_tol) end -function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.verbosity[]) +function initialize(iter::BlockLanczosIterator{F,T,S}; verbosity::Int=KrylovDefaults.verbosity[]) where {F,T,S} X₀ = iter.x₀ maxdim = iter.maxdim bs_now = length(X₀) # block size now A = iter.operator - S = iter.num_field V_basis = similar(X₀.vec, maxdim) - r = BlockVec([similar(X₀[i]) for i in 1:bs_now], S) + r = BlockVec{S}([similar(X₀[i]) for i in 1:bs_now]) TDB = zeros(S, maxdim, maxdim) # Orthogonalization of the initial block @@ -491,7 +490,7 @@ function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.ve # Calculate the new residual. Get r₂ compute_residual!(r, AX₂, X₂, M₂_view, X₁, B₁) - ortho_basis!(r, BlockVec(V_basis[1:bs_now+bs_next], S)) + ortho_basis!(r, BlockVec{S}(V_basis[1:bs_now+bs_next])) norm_r = norm(r) if verbosity > EACHITERATION_LEVEL @@ -506,10 +505,9 @@ function initialize(iter::BlockLanczosIterator; verbosity::Int=KrylovDefaults.ve norm_r) end -function expand!(iter::BlockLanczosIterator, state::BlockLanczosFactorization; - verbosity::Int=KrylovDefaults.verbosity[]) +function expand!(iter::BlockLanczosIterator{F,T,S}, state::BlockLanczosFactorization{T,S,SR}; + verbosity::Int=KrylovDefaults.verbosity[]) where {F,T,S,SR} all_size = state.all_size - S = iter.num_field rₖ = state.r[1:state.r_size] bs_now = length(rₖ) V_basis = state.V.basis @@ -533,10 +531,10 @@ function expand!(iter::BlockLanczosIterator, state::BlockLanczosFactorization; Mnext_view = (Mnext_view + Mnext_view') / 2 # Calculate the new residual. Get Rnext - Xnow = BlockVec(V_basis[all_size-bs_now+1:all_size], S) - rₖnext = BlockVec([similar(V_basis[1]) for _ in 1:bs_next], S) + Xnow = BlockVec{S}(V_basis[all_size-bs_now+1:all_size]) + rₖnext = BlockVec{S}([similar(V_basis[1]) for _ in 1:bs_next]) compute_residual!(rₖnext, AXₖnext, Xnext, Mnext_view, Xnow, Bₖ_view) - ortho_basis!(rₖnext, BlockVec(V_basis[1:all_size+bs_next], S)) + ortho_basis!(rₖnext, BlockVec{S}(V_basis[1:all_size+bs_next])) state.r.vec[1:bs_next] .= rₖnext.vec state.norm_r = norm(rₖnext) state.all_size += bs_next diff --git a/test/BlockVec.jl b/test/BlockVec.jl index e33599a7..ff4b4573 100644 --- a/test/BlockVec.jl +++ b/test/BlockVec.jl @@ -1,9 +1,13 @@ +using KrylovKit, LinearAlgebra, Random, Test + @testset "apply on BlockVec" begin + N = 100 + n = 10 + tolerance(T) = sqrt(eps(real(oneunit(T)))) for T in [Float32, Float64, ComplexF64] - T = Float32 A0 = rand(T,N,N); A0 = A0' * A0; - x₀ = KrylovKit.BlockVec([rand(T,N) for _ in 1:n], T) + x₀ = KrylovKit.BlockVec{T}([rand(T,N) for _ in 1:n]) for A in [A0, x -> A0*x] y = KrylovKit.apply(A, x₀) @test isapprox(hcat(y.vec...), A0 * hcat(x₀.vec...); atol=tolerance(T)) @@ -14,22 +18,25 @@ A0 = A0' * A0 f(x,y) = x' * A0 * y A(x::InnerProductVec) = KrylovKit.InnerProductVec(A0 * x[], x.dotf) - x₀ = KrylovKit.BlockVec([InnerProductVec(rand(T,N), f) for _ in 1:n], T) + x₀ = KrylovKit.BlockVec{T}([InnerProductVec(rand(T,N), f) for _ in 1:n]) y = KrylovKit.apply(A, x₀) @test isapprox(hcat([y[i].vec for i in 1:n]...), A0 * hcat([x₀[i].vec for i in 1:n]...); atol=tolerance(T)) end @testset "copy! for BlockVec" begin + N = 100 + n = 10 + tolerance(T) = sqrt(eps(real(oneunit(T)))) for T in [Float32, Float64, ComplexF32, ComplexF64] - x = KrylovKit.BlockVec([rand(T,N) for _ in 1:n], T) - y = KrylovKit.BlockVec([rand(T,N) for _ in 1:n], T) + x = KrylovKit.BlockVec{T}([rand(T,N) for _ in 1:n]) + y = KrylovKit.BlockVec{T}([rand(T,N) for _ in 1:n]) KrylovKit.copy!(y, x) @test isapprox(y.vec, x.vec; atol=tolerance(T)) end T = ComplexF64 f = (x,y) -> x' * y - x = KrylovKit.BlockVec([InnerProductVec(rand(T,N), f) for _ in 1:n], T) - y = KrylovKit.BlockVec([InnerProductVec(rand(T,N), f) for _ in 1:n], T) + x = KrylovKit.BlockVec{T}([InnerProductVec(rand(T,N), f) for _ in 1:n]) + y = KrylovKit.BlockVec{T}([InnerProductVec(rand(T,N), f) for _ in 1:n]) KrylovKit.copy!(y, x) @test isapprox([y.vec[i].vec for i in 1:n], [x.vec[i].vec for i in 1:n]; atol=tolerance(T)) end diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 8d28c18d..33068561 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -558,20 +558,45 @@ end @test findmax([norm(Aip(V[i]) - D[i] * V[i]) for i in 1:eig_num])[1] < tolerance(T) end +using KrylovKit, Random @testset "Complete Lanczos and Block Lanczos" begin + N = 100 + tolerance(T) = sqrt(eps(real(oneunit(T)))) @testset for T in [Float32, Float64, ComplexF32, ComplexF64] Random.seed!(6) A0 = rand(T, (2N, 2N)) A0 = (A0 + A0') / 2 - block_size = 4 + block_size = 1 x₀ = rand(T, 2N) alg1 = Lanczos(;krylovdim = N, maxiter = 10, tol = tolerance(T), verbosity = 1) alg2 = Lanczos(;krylovdim = N, maxiter = 10, tol = tolerance(T), verbosity = 1, blockmode = true, blocksize = block_size) for A in [A0, x -> A0 * x] - t1 = @elapsed _, _, info1 = eigsolve(A, x₀, block_size, :SR, alg1) - t2 = @elapsed _, _, info2 = eigsolve(A, x₀, block_size, :SR, alg2) - println("When input type is $T, the time of lanczos and block lanczos is $t1, $t2") + t1 = @elapsed evals1, _, info1 = eigsolve(A, x₀, block_size, :SR, alg1) + t2 = @elapsed evals2, _, info2 = eigsolve(A, x₀, block_size, :SR, alg2) + println("When input type is $T, the time of lanczos and block lanczos is $t1, $t2, the rate is $(t2/t1)") @test min(info1.converged, block_size) <= info2.converged + @test evals1[1] ≈ evals2[1] atol = tolerance(T) end end -end \ No newline at end of file +end + + +using Profile +Random.seed!(6) +M = 10N +T = ComplexF64 + A = rand(T, (M, M)) + A = (A + A') / 2 + block_size = 1 + x₀ = normalize(rand(T, M)) + + alg = Lanczos(;krylovdim = N, maxiter = 10, tol = tolerance(T), verbosity = 1, blockmode = true, blocksize = block_size) + Profile.clear() + @profile _, _, info2 = eigsolve(A, x₀, block_size, :SR, alg) + Profile.print(mincount = 10) + + + alg1 = Lanczos(;krylovdim = N, maxiter = 10, tol = tolerance(T), verbosity = 1) + Profile.clear() + @profile _, _, info1 = eigsolve(A, x₀, block_size, :SR, alg1) + Profile.print(mincount = 10) \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 9a68a7c7..57d48aef 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,7 +1,7 @@ using Random Random.seed!(76543210) -using Test, TestExtras, Logging +using Test, Logging using LinearAlgebra, SparseArrays using KrylovKit using VectorInterface From 288549302fd0c6e3f21f850fd6e5d473b2c402fc Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Sun, 27 Apr 2025 17:50:23 +0800 Subject: [PATCH 33/82] save --- src/algorithms.jl | 5 +- src/dense/linalg.jl | 6 +-- src/eigsolve/lanczos.jl | 107 ++++++++++++++++++++-------------------- test/eigsolve.jl | 6 ++- test/factorize.jl | 2 + 5 files changed, 66 insertions(+), 60 deletions(-) diff --git a/src/algorithms.jl b/src/algorithms.jl index 54e3836a..820df733 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -123,9 +123,10 @@ struct BlockLanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm krylovdim::Int maxiter::Int tol::S - verbosity::Int blocksize::Int qr_tol::Real + eager::Bool + verbosity::Int end # qr_tol is the tolerance that we think a vector is non-zero in abstract_qr! # This qr_tol will also be used in other zero_chcking in block Lanczos. @@ -143,7 +144,7 @@ function Lanczos(; maxiter < 0 && (blockmode ? (maxiter = KrylovDefaults.blockmaxiter[]) : (maxiter = KrylovDefaults.maxiter[])) if blockmode blocksize < 1 && error("blocksize must be greater than 0") - return BlockLanczos(orth, krylovdim, maxiter, tol, verbosity, blocksize, qr_tol) + return BlockLanczos(orth, krylovdim, maxiter, tol, blocksize, qr_tol, eager, verbosity) else return Lanczos(orth, krylovdim, maxiter, tol, eager, verbosity) end diff --git a/src/dense/linalg.jl b/src/dense/linalg.jl index 1e80906a..e42c5b9c 100644 --- a/src/dense/linalg.jl +++ b/src/dense/linalg.jl @@ -307,9 +307,9 @@ function schur2eigvecs(T::AbstractMatrix{<:BlasReal}, which::AbstractVector{Int} return _normalizevecs!(VR) end -function permuteeig!(D::AbstractVector{S}, - V::AbstractMatrix{S}, - perm::AbstractVector{Int}) where {S} +function permuteeig!(D::AbstractVector, + V::AbstractMatrix, + perm::AbstractVector{Int}) n = checksquare(V) p = collect(perm) # makes copy cause will be overwritten isperm(p) && length(p) == n || diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index 95c61958..589ec557 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -151,7 +151,7 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::Lanczos; ConvergenceInfo(converged, residuals, normresiduals, numiter, numops) end -function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) +function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) where T maxiter = alg.maxiter krylovdim = alg.krylovdim if howmany > krylovdim @@ -168,20 +168,20 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) iter = BlockLanczosIterator(A, x₀_vec, krylovdim + bs, alg.qr_tol, alg.orth) fact = initialize(iter; verbosity = verbosity) # Returns a BlockLanczosFactorization + S = eltype(fact.TDB) # The element type (Note: can be Complex) of the block tridiagonal matrix numops = 2 # Number of matrix-vector multiplications (for logging) numiter = 1 # Preallocate space for eigenvectors and eigenvalues vectors = [similar(x₀_vec[1]) for _ in 1:howmany] - values = Vector{real(eltype(fact.TDB))}(undef, howmany) # TODO: fix + values = Vector{real(S)}(undef, howmany) # TODO: fix + residuals = [similar(x₀_vec[1]) for _ in 1:howmany] converged = false num_converged = 0 local howmany_actual, residuals, normresiduals, D, U while true - expand!(iter, fact; verbosity = verbosity) - numops += 1 K = length(fact) β = normres(fact) @@ -190,74 +190,75 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) msg *= "which is smaller than the number of requested eigenvalues (i.e. `howmany == $howmany`)." @warn msg end - if K > krylovdim || β < tol + if K >= krylovdim || β < tol || (alg.eager && K >= howmany) + # compute eigenvalues + # Note: Fast eigen solver for block tridiagonal matrices is not implemented yet. TDB = view(fact.TDB, 1:K, 1:K) - D, U = eigen(Hermitian((TDB + TDB') / 2)) + D, U = eigen(Hermitian(TDB)) by, rev = eigsort(which) p = sortperm(D; by = by, rev = rev) - D = D[p] - U = U[:, p] - T = eltype(fact.V.basis) - S = eltype(TDB) + D, U = permuteeig!(D, U, p) howmany_actual = min(howmany, length(D)) copyto!(values, D[1:howmany_actual]) - residuals = Vector{T}(undef, howmany_actual) - normresiduals = Vector{real(S)}(undef, howmany_actual) - bs_r = fact.r_size + # detect convergence by computing the residuals + bs_r = fact.r_size # the block size of the residual (decreases as the iteration goes) r = fact.r[1:bs_r] - UU = U[end-bs_r+1:end, :] - for i in 1:howmany_actual - residuals[i] = r[1] * UU[1, i] + UU = U[end-bs_r+1:end, :] # the last bs_r rows of U, used to compute the residuals + normresiduals = map(1:howmany_actual) do i + mul!(residuals[i], r[1], UU[1, i]) for j in 2:bs_r axpy!(UU[j, i], r[j], residuals[i]) end - normresiduals[i] = norm(residuals[i]) + norm(residuals[i]) end num_converged = count(nr -> nr <= tol, normresiduals) - - if num_converged >= howmany || β < tol + if num_converged >= howmany || β < tol # successfully find enough eigenvalues converged = true break elseif verbosity >= EACHITERATION_LEVEL - @info "Block Lanczos eigsolve in iteration $numiter: $num_converged values converged, normres = $(normres2string(normresiduals[1:howmany]))" + @info "Block Lanczos eigsolve in iteration $numiter: $num_converged values converged, normres = $(normres2string(normresiduals))" end - if K > krylovdim # begin to shrink dimension - numiter >= maxiter && break - bsn = max(div(3 * krylovdim + 2 * num_converged, 5) ÷ bs, 1) - if (bsn + 1) * bs > K # make sure that we can fetch next block after shrinked dimension as residual - warning("shrinked dimesion is too small and there is no need to shrink") - break - end - keep = bs * bsn - H = zeros(S, (bsn + 1) * bs, bsn * bs) - @inbounds for j in 1:keep - H[j, j] = D[j] - H[bsn * bs + 1:end, j] = U[K - bs + 1:K, j] - end - @inbounds for j in keep:-1:1 - h, ν = householder(H, j + bs, 1:j, j) - H[j + bs, j] = ν - H[j + bs, 1:(j - 1)] .= zero(eltype(H)) - lmul!(h, H) - rmul!(view(H, 1:j + bs -1, :), h') - rmul!(U, h') - end - TDB .= S(0) - TDB[1:keep, 1:keep] .= H[1:keep, 1:keep] - V = OrthonormalBasis(fact.V.basis[1:K]) - basistransform!(V, view(U, :, 1:keep)) - fact.V[1:keep] = V[1:keep] - - r_new = OrthonormalBasis(fact.r.vec[1:bs_r]) - view_U = view(U, K - bs_r + 1:K, keep - bs_r + 1:keep) - basistransform!(r_new, view_U) - fact.r.vec[1:bs_r] = r_new[1:bs_r] + end - fact.all_size = keep - numiter += 1 + if K < krylovdim + expand!(iter, fact; verbosity = verbosity) + numops += 1 + else # shrink and restart + numiter >= maxiter && break + bsn = max(div(3 * krylovdim + 2 * num_converged, 5) ÷ bs, 1) + if (bsn + 1) * bs > K # make sure that we can fetch next block after shrinked dimension as residual + @warn "shrinked dimesion is too small and there is no need to shrink" + break end + keep = bs * bsn + H = zeros(S, (bsn + 1) * bs, bsn * bs) + @inbounds for j in 1:keep + H[j, j] = D[j] + H[bsn * bs + 1:end, j] = U[K - bs + 1:K, j] + end + @inbounds for j in keep:-1:1 + h, ν = householder(H, j + bs, 1:j, j) + H[j + bs, j] = ν + H[j + bs, 1:(j - 1)] .= zero(eltype(H)) + lmul!(h, H) + rmul!(view(H, 1:j + bs -1, :), h') + rmul!(U, h') + end + TDB .= S(0) + TDB[1:keep, 1:keep] .= H[1:keep, 1:keep] + V = OrthonormalBasis(fact.V.basis[1:K]) + basistransform!(V, view(U, :, 1:keep)) + fact.V[1:keep] = V[1:keep] + + r_new = OrthonormalBasis(fact.r.vec[1:bs_r]) + view_U = view(U, K - bs_r + 1:K, keep - bs_r + 1:keep) + basistransform!(r_new, view_U) + fact.r.vec[1:bs_r] = r_new[1:bs_r] + + fact.all_size = keep + numiter += 1 end end diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 33068561..0d57e91a 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -558,7 +558,7 @@ end @test findmax([norm(Aip(V[i]) - D[i] * V[i]) for i in 1:eig_num])[1] < tolerance(T) end -using KrylovKit, Random +using KrylovKit, Random, Test, LinearAlgebra @testset "Complete Lanczos and Block Lanczos" begin N = 100 tolerance(T) = sqrt(eps(real(oneunit(T)))) @@ -583,6 +583,7 @@ end using Profile Random.seed!(6) +N = 100 M = 10N T = ComplexF64 A = rand(T, (M, M)) @@ -595,8 +596,9 @@ T = ComplexF64 @profile _, _, info2 = eigsolve(A, x₀, block_size, :SR, alg) Profile.print(mincount = 10) - +tolerance(T) = sqrt(eps(real(oneunit(T)))) alg1 = Lanczos(;krylovdim = N, maxiter = 10, tol = tolerance(T), verbosity = 1) + E,V,info1 = eigsolve(A, x₀, block_size, :SR, alg1) Profile.clear() @profile _, _, info1 = eigsolve(A, x₀, block_size, :SR, alg1) Profile.print(mincount = 10) \ No newline at end of file diff --git a/test/factorize.jl b/test/factorize.jl index 33953967..8a039708 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -376,6 +376,8 @@ end # Test effectiveness of shrink!() in block lanczos @testset "Test effectiveness of shrink!() in block lanczos" begin + n = 10 + N = 100 @testset for T in [Float32, Float64, ComplexF32, ComplexF64] A0 = rand(T, (N, N)) A0 = (A0 + A0') / 2 From 07c2f0387d42ec46529739b29b0b5ccc25d801ec Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Mon, 28 Apr 2025 23:21:18 +0800 Subject: [PATCH 34/82] eager to add --- Project.toml | 1 + src/eigsolve/lanczos.jl | 45 ++++++++++----------- src/factorizations/lanczos.jl | 75 ++++++++++++++--------------------- test/BlockVec.jl | 24 +++++++---- test/eigsolve.jl | 35 ++++++++++++---- test/factorize.jl | 51 +++++++++--------------- test/innerproductvec.jl | 12 +++--- test/orthonormal.jl | 10 ++--- test/runtests.jl | 2 +- 9 files changed, 123 insertions(+), 132 deletions(-) diff --git a/Project.toml b/Project.toml index 6e207ef8..f46ff94e 100644 --- a/Project.toml +++ b/Project.toml @@ -8,6 +8,7 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" PackageExtensionCompat = "65ce6f38-6b18-4e1d-a461-8949797d7930" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" [weakdeps] diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index 589ec557..74fa75f1 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -162,23 +162,20 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) # Initialize a block of vectors from the initial vector, randomly generated # TODO: add a more flexible initialization - x₀_vec = [randn!(similar(x₀)) for _ in 1:alg.blocksize-1] - pushfirst!(x₀_vec, x₀) - bs = length(x₀_vec) + block0 = initialize(x₀, alg.blocksize) + bs = length(block0) - iter = BlockLanczosIterator(A, x₀_vec, krylovdim + bs, alg.qr_tol, alg.orth) + iter = BlockLanczosIterator(A, block0, krylovdim + bs, alg.qr_tol, alg.orth) fact = initialize(iter; verbosity = verbosity) # Returns a BlockLanczosFactorization S = eltype(fact.TDB) # The element type (Note: can be Complex) of the block tridiagonal matrix - numops = 2 # Number of matrix-vector multiplications (for logging) + numops = 1 # Number of matrix-vector multiplications (for logging) numiter = 1 # Preallocate space for eigenvectors and eigenvalues - vectors = [similar(x₀_vec[1]) for _ in 1:howmany] - values = Vector{real(S)}(undef, howmany) # TODO: fix - residuals = [similar(x₀_vec[1]) for _ in 1:howmany] + vectors = [similar(x₀) for _ in 1:howmany] + residuals = [similar(x₀) for _ in 1:howmany] - converged = false - num_converged = 0 + converged = 0 local howmany_actual, residuals, normresiduals, D, U while true @@ -190,17 +187,16 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) msg *= "which is smaller than the number of requested eigenvalues (i.e. `howmany == $howmany`)." @warn msg end - if K >= krylovdim || β < tol || (alg.eager && K >= howmany) + # BlockLanczos can access the case of K = 1 and doesn't need extra processing + if K >= krylovdim || β <= tol || (alg.eager && K >= howmany) # compute eigenvalues # Note: Fast eigen solver for block tridiagonal matrices is not implemented yet. TDB = view(fact.TDB, 1:K, 1:K) D, U = eigen(Hermitian(TDB)) by, rev = eigsort(which) p = sortperm(D; by = by, rev = rev) - D, U = permuteeig!(D, U, p) - + D, U = permuteeig!(D, U, p) howmany_actual = min(howmany, length(D)) - copyto!(values, D[1:howmany_actual]) # detect convergence by computing the residuals bs_r = fact.r_size # the block size of the residual (decreases as the iteration goes) @@ -213,12 +209,11 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) end norm(residuals[i]) end - num_converged = count(nr -> nr <= tol, normresiduals) - if num_converged >= howmany || β < tol # successfully find enough eigenvalues - converged = true + converged = count(nr -> nr <= tol, normresiduals) + if converged >= howmany || β <= tol # successfully find enough eigenvalues break elseif verbosity >= EACHITERATION_LEVEL - @info "Block Lanczos eigsolve in iteration $numiter: $num_converged values converged, normres = $(normres2string(normresiduals))" + @info "Block Lanczos eigsolve in iteration $numiter: $converged values converged, normres = $(normres2string(normresiduals))" end end @@ -227,7 +222,7 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) numops += 1 else # shrink and restart numiter >= maxiter && break - bsn = max(div(3 * krylovdim + 2 * num_converged, 5) ÷ bs, 1) + bsn = max(div(3 * krylovdim + 2 * converged, 5) ÷ bs, 1) if (bsn + 1) * bs > K # make sure that we can fetch next block after shrinked dimension as residual @warn "shrinked dimesion is too small and there is no need to shrink" break @@ -257,7 +252,7 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) basistransform!(r_new, view_U) fact.r.vec[1:bs_r] = r_new[1:bs_r] - fact.all_size = keep + fact.total_size = keep numiter += 1 end end @@ -271,19 +266,19 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) end end - if (num_converged < howmany) && verbosity >= WARN_LEVEL + if (converged < howmany) && verbosity >= WARN_LEVEL @warn """Block Lanczos eigsolve stopped without full convergence after $(K) iterations: - * $num_converged eigenvalues converged + * $converged eigenvalues converged * norm of residuals = $(normres2string(normresiduals)) * number of operations = $numops""" elseif verbosity >= STARTSTOP_LEVEL @info """Block Lanczos eigsolve finished after $(K) iterations: - * $num_converged eigenvalues converged + * $converged eigenvalues converged * norm of residuals = $(normres2string(normresiduals)) * number of operations = $numops""" end - return values[1:howmany_actual], + return D[1:howmany_actual], vectors[1:howmany_actual], - ConvergenceInfo(num_converged, residuals, normresiduals, numiter, numops) + ConvergenceInfo(converged, residuals, normresiduals, numiter, numops) end \ No newline at end of file diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index 12dae4fd..03b00fc4 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -401,16 +401,22 @@ Base.setindex!(b₁::BlockVec{T}, b₂::BlockVec{T}, idxs::AbstractVector{Int}) Base.copy!(b₁::BlockVec{T,S}, b₂::BlockVec{T,S}) where {T,S} = (copy!.(b₁.vec, b₂.vec); b₁) LinearAlgebra.norm(b::BlockVec) = norm(b.vec) apply(f, block::BlockVec{T, S}) where {T, S} = BlockVec{S}([apply(f, x) for x in block.vec]) +function initialize(x₀, size::Int) + S = typeof(inner(x₀, x₀)) + x₀_vec = [randn!(similar(x₀)) for _ in 1:size-1] + pushfirst!(x₀_vec, x₀) + return BlockVec{S}(x₀_vec) +end mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: BlockKrylovFactorization{T,S,SR} - all_size::Int + total_size::Int const V::OrthonormalBasis{T} # Block Lanczos Basis const TDB::AbstractMatrix{S} # TDB matrix, S is the matrix type const r::BlockVec{T,S} # residual block r_size::Int # size of the residual block norm_r::SR # norm of the residual block end -Base.length(fact::BlockLanczosFactorization) = fact.all_size +Base.length(fact::BlockLanczosFactorization) = fact.total_size normres(fact::BlockLanczosFactorization) = fact.norm_r #= @@ -432,19 +438,18 @@ struct BlockLanczosIterator{F,T,S,O<:Orthogonalizer} <: KrylovIterator{F,T} orth::O qr_tol::Real function BlockLanczosIterator{F,T,S,O}(operator::F, - x₀::Vector{T}, + x₀::BlockVec{T,S}, maxdim::Int, orth::O, qr_tol::Real) where {F,T,S,O<:Orthogonalizer} - return new{F,T,S,O}(operator, BlockVec{S}(x₀), maxdim, orth, qr_tol) + return new{F,T,S,O}(operator, x₀, maxdim, orth, qr_tol) end end function BlockLanczosIterator(operator::F, - x₀::Vector{T}, + x₀::BlockVec{T,S}, maxdim::Int, qr_tol::Real, - orth::O=ModifiedGramSchmidt2()) where {F,T,O<:Orthogonalizer} - S = typeof(inner(x₀[1], x₀[1])) + orth::O=ModifiedGramSchmidt2()) where {F,T,S,O<:Orthogonalizer} qr_tol < 0 && (qr_tol = 1e4 * eps(real(S))) norm(x₀) < qr_tol && @error "initial vector should not have norm zero" orth != ModifiedGramSchmidt2() && @error "BlockLanczosIterator only supports ModifiedGramSchmidt2 orthogonalizer" @@ -454,60 +459,38 @@ end function initialize(iter::BlockLanczosIterator{F,T,S}; verbosity::Int=KrylovDefaults.verbosity[]) where {F,T,S} X₀ = iter.x₀ maxdim = iter.maxdim - bs_now = length(X₀) # block size now + bs = length(X₀) # block size now A = iter.operator - V_basis = similar(X₀.vec, maxdim) - r = BlockVec{S}([similar(X₀[i]) for i in 1:bs_now]) TDB = zeros(S, maxdim, maxdim) # Orthogonalization of the initial block X₁ = deepcopy(X₀) abstract_qr!(X₁, iter.qr_tol) - V_basis[1:bs_now] .= X₁.vec + V_basis[1:bs] .= X₁.vec AX₁ = apply(A, X₁) - M₁_view = view(TDB, 1:bs_now, 1:bs_now) + M₁_view = view(TDB, 1:bs, 1:bs) block_inner!(M₁_view, X₁, AX₁) verbosity >= WARN_LEVEL && warn_nonhermitian(M₁_view, iter.qr_tol) M₁_view = (M₁_view + M₁_view') / 2 residual = block_mul!(AX₁, X₁, - M₁_view, S(1), S(1)) - # QR decomposition of residual to get the next basis. Get X2 and B1 - B₁, good_idx = abstract_qr!(residual, iter.qr_tol) - bs_next = length(good_idx) - X₂ = residual[good_idx] - V_basis[bs_now+1:bs_now+bs_next] .= X₂.vec - B₁_view = view(TDB, bs_now+1:bs_now+bs_next, 1:bs_now) - copyto!(B₁_view, B₁) - copyto!(view(TDB, 1:bs_now, bs_now+1:bs_now+bs_next), B₁_view') - - # Calculate the next block - AX₂ = apply(A, X₂) - M₂_view = view(TDB, bs_now+1:bs_now+bs_next, bs_now+1:bs_now+bs_next) - block_inner!(M₂_view, X₂, AX₂) - M₂_view = (M₂_view + M₂_view') / 2 - - # Calculate the new residual. Get r₂ - compute_residual!(r, AX₂, X₂, M₂_view, X₁, B₁) - ortho_basis!(r, BlockVec{S}(V_basis[1:bs_now+bs_next])) - norm_r = norm(r) - + norm_r = norm(residual) if verbosity > EACHITERATION_LEVEL - @info "Block Lanczos initiation at dimension 2: subspace normres = $(normres2string(norm_r))" + @info "Block Lanczos initiation at dimension $bs: subspace normres = $(normres2string(norm_r))" end - - return BlockLanczosFactorization(bs_now+bs_next, + return BlockLanczosFactorization(bs, OrthonormalBasis(V_basis), TDB, - r, - bs_next, + residual, + bs, norm_r) end function expand!(iter::BlockLanczosIterator{F,T,S}, state::BlockLanczosFactorization{T,S,SR}; verbosity::Int=KrylovDefaults.verbosity[]) where {F,T,S,SR} - all_size = state.all_size + total_size = state.total_size rₖ = state.r[1:state.r_size] bs_now = length(rₖ) V_basis = state.V.basis @@ -516,32 +499,32 @@ function expand!(iter::BlockLanczosIterator{F,T,S}, state::BlockLanczosFactoriza Bₖ, good_idx = abstract_qr!(rₖ, iter.qr_tol) Xnext = deepcopy(rₖ[good_idx]) bs_next = length(good_idx) - V_basis[all_size+1:all_size+bs_next] .= Xnext.vec + V_basis[total_size+1:total_size+bs_next] .= Xnext.vec # Calculate the connection matrix - Bₖ_view = view(state.TDB, all_size+1:all_size+bs_next, all_size-bs_now+1:all_size) + Bₖ_view = view(state.TDB, total_size+1:total_size+bs_next, total_size-bs_now+1:total_size) copyto!(Bₖ_view, Bₖ) - copyto!(view(state.TDB, all_size-bs_now+1:all_size, all_size+1:all_size+bs_next), Bₖ_view') + copyto!(view(state.TDB, total_size-bs_now+1:total_size, total_size+1:total_size+bs_next), Bₖ_view') # Apply the operator and calculate the M. Get Mnext AXₖnext = apply(iter.operator, Xnext) - Mnext_view = view(state.TDB, all_size+1:all_size+bs_next, all_size+1:all_size+bs_next) + Mnext_view = view(state.TDB, total_size+1:total_size+bs_next, total_size+1:total_size+bs_next) block_inner!(Mnext_view, Xnext, AXₖnext) verbosity >= WARN_LEVEL && warn_nonhermitian(Mnext_view, iter.qr_tol) Mnext_view = (Mnext_view + Mnext_view') / 2 # Calculate the new residual. Get Rnext - Xnow = BlockVec{S}(V_basis[all_size-bs_now+1:all_size]) + Xnow = BlockVec{S}(V_basis[total_size-bs_now+1:total_size]) rₖnext = BlockVec{S}([similar(V_basis[1]) for _ in 1:bs_next]) compute_residual!(rₖnext, AXₖnext, Xnext, Mnext_view, Xnow, Bₖ_view) - ortho_basis!(rₖnext, BlockVec{S}(V_basis[1:all_size+bs_next])) + ortho_basis!(rₖnext, BlockVec{S}(V_basis[1:total_size+bs_next])) state.r.vec[1:bs_next] .= rₖnext.vec state.norm_r = norm(rₖnext) - state.all_size += bs_next + state.total_size += bs_next state.r_size = bs_next if verbosity > EACHITERATION_LEVEL - @info "Block Lanczos expansion to dimension $(state.all_size): subspace normres = $(normres2string(state.norm_r))" + @info "Block Lanczos expansion to dimension $(state.total_size): subspace normres = $(normres2string(state.norm_r))" end end diff --git a/test/BlockVec.jl b/test/BlockVec.jl index ff4b4573..2f529fc4 100644 --- a/test/BlockVec.jl +++ b/test/BlockVec.jl @@ -1,9 +1,4 @@ -using KrylovKit, LinearAlgebra, Random, Test - @testset "apply on BlockVec" begin - N = 100 - n = 10 - tolerance(T) = sqrt(eps(real(oneunit(T)))) for T in [Float32, Float64, ComplexF64] A0 = rand(T,N,N); A0 = A0' * A0; @@ -24,9 +19,6 @@ using KrylovKit, LinearAlgebra, Random, Test end @testset "copy! for BlockVec" begin - N = 100 - n = 10 - tolerance(T) = sqrt(eps(real(oneunit(T)))) for T in [Float32, Float64, ComplexF32, ComplexF64] x = KrylovKit.BlockVec{T}([rand(T,N) for _ in 1:n]) y = KrylovKit.BlockVec{T}([rand(T,N) for _ in 1:n]) @@ -41,4 +33,20 @@ end @test isapprox([y.vec[i].vec for i in 1:n], [x.vec[i].vec for i in 1:n]; atol=tolerance(T)) end +@testset "initialize for BlockVec" begin + for T in [Float32, Float64, ComplexF32, ComplexF64] + block0 = KrylovKit.initialize(rand(T,N), n) + @test block0 isa KrylovKit.BlockVec + @test length(block0) == n + @test Tuple(typeof(block0).parameters) == (Vector{T},T) + end + # test for abtract type + T = ComplexF64 + f(x,y) = x' * y + x0 = InnerProductVec(rand(T,N), f) + block0 = KrylovKit.initialize(x0, n) + @test block0 isa KrylovKit.BlockVec + @test length(block0) == n + @test Tuple(typeof(block0).parameters) == (typeof(x0),T) +end diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 0d57e91a..28f05add 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -477,10 +477,10 @@ As a result, I’ve decided to postpone dealing with the in-place test issue for @test_logs (:warn,) eigsolve(A, x₀, n1, :SR, alg) alg = Lanczos(; krylovdim = n, maxiter = 1, tol = tolerance(T), verbosity = 2, blockmode = true, blocksize = block_size) @test_logs (:info,) eigsolve(A, x₀, n1, :SR, alg) - alg = Lanczos(; krylovdim = 4, maxiter = 3, tol = tolerance(T), verbosity = 3, blockmode = true, blocksize = block_size) + alg = Lanczos(; krylovdim = 3, maxiter = 3, tol = tolerance(T), verbosity = 3, blockmode = true, blocksize = block_size) @test_logs((:info,), (:info,), (:info,), (:warn,), eigsolve(A, x₀, 1, :SR, alg)) alg = Lanczos(; krylovdim = 4, maxiter=1, tol=tolerance(T), verbosity=4, blockmode=true, blocksize=block_size) - @test_logs((:info,), (:info,), (:info,), (:warn,), eigsolve(A, x₀, 1, :SR, alg)) + @test_logs((:info,), (:info,),(:info,),(:warn,), eigsolve(A, x₀, 1, :SR, alg)) # To use blockmode, users have to explicitly set blockmode = true, we don't allow them to use eigselector. # Because of the _residual! function, I can't make sure the stability of types temporarily. # So I ignore the test of @constinferred @@ -552,16 +552,13 @@ end D, V, info = eigsolve(Aip, x₀, eig_num, :SR, Lanczos(; krylovdim = n, maxiter = 1, tol = tolerance(T), verbosity = 0, blockmode = true, blocksize = block_size)) D_true = eigvals(H) - BlockV = KrylovKit.BlockVec(V, T) + BlockV = KrylovKit.BlockVec{T}(V) @test D ≈ D_true[1:eig_num] @test KrylovKit.block_inner(BlockV, BlockV) ≈ I @test findmax([norm(Aip(V[i]) - D[i] * V[i]) for i in 1:eig_num])[1] < tolerance(T) end -using KrylovKit, Random, Test, LinearAlgebra @testset "Complete Lanczos and Block Lanczos" begin - N = 100 - tolerance(T) = sqrt(eps(real(oneunit(T)))) @testset for T in [Float32, Float64, ComplexF32, ComplexF64] Random.seed!(6) A0 = rand(T, (2N, 2N)) @@ -580,9 +577,30 @@ using KrylovKit, Random, Test, LinearAlgebra end end +# Test effectiveness of shrink!() in block lanczos +@testset "Test effectiveness of shrink!() in block lanczos" begin + @testset for T in [Float32, Float64, ComplexF32, ComplexF64] + A0 = rand(T, (N, N)) + A0 = (A0 + A0') / 2 + block_size = 5 + x₀ = rand(T, N) + values0 = eigvals(A0)[1:n] + n1 = n ÷ 2 + for A in [A0, x -> A0 * x] + alg = KrylovKit.Lanczos(; krylovdim = 3*n÷2, maxiter = 1, tol = 1e-12, blockmode = true, blocksize = block_size) + values, _, _ = eigsolve(A, x₀, n, :SR, alg) + error1 = norm(values[1:n1] - values0[1:n1]) + alg_shrink = KrylovKit.Lanczos(; krylovdim = 3*n÷2, maxiter = 2, tol = 1e-12, blockmode = true, blocksize = block_size) + values_shrink, _, _ = eigsolve(A, x₀, n, :SR, alg_shrink) + error2 = norm(values_shrink[1:n1] - values0[1:n1]) + @test error2 < error1 + end + end +end +#= using Profile -Random.seed!(6) +Randomeed!(6) N = 100 M = 10N T = ComplexF64 @@ -601,4 +619,5 @@ tolerance(T) = sqrt(eps(real(oneunit(T)))) E,V,info1 = eigsolve(A, x₀, block_size, :SR, alg1) Profile.clear() @profile _, _, info1 = eigsolve(A, x₀, block_size, :SR, alg1) - Profile.print(mincount = 10) \ No newline at end of file + Profile.print(mincount = 10) + =# \ No newline at end of file diff --git a/test/factorize.jl b/test/factorize.jl index 8a039708..100041d8 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -305,7 +305,7 @@ end A0 = (A0 + A0') / 2 block_size = 5 x₀m = Matrix(qr(rand(T, N, block_size)).Q) - x₀ = [x₀m[:, i] for i in 1:block_size] + x₀ = KrylovKit.BlockVec{T}([x₀m[:, i] for i in 1:block_size]) eigvalsA = eigvals(A0) for A in [A0, x -> A0 * x] iter = KrylovKit.BlockLanczosIterator(A, x₀, N, qr_tol(T)) @@ -316,7 +316,7 @@ end @test_logs initialize(iter; verbosity = EACHITERATION_LEVEL) @test_logs (:info,) initialize(iter; verbosity = EACHITERATION_LEVEL + 1) verbosity = EACHITERATION_LEVEL + 1 - while fact.all_size < n + while fact.total_size < n if verbosity == EACHITERATION_LEVEL + 1 @test_logs (:info,) expand!(iter, fact; verbosity = verbosity) verbosity = EACHITERATION_LEVEL @@ -331,14 +331,14 @@ end B = rand(T, (n, n)) # test warnings for non-hermitian matrices bs = 2 v₀m = Matrix(qr(rand(T, n, bs)).Q) - v₀ = [v₀m[:, i] for i in 1:bs] + v₀ = KrylovKit.BlockVec{T}([v₀m[:, i] for i in 1:bs]) iter = KrylovKit.BlockLanczosIterator(B, v₀, N, qr_tol(T)) fact = initialize(iter) @constinferred expand!(iter, fact; verbosity = 0) @test_logs initialize(iter; verbosity = 0) @test_logs (:warn,) initialize(iter) verbosity = 1 - while fact.all_size < n + while fact.total_size < n if verbosity == 1 @test_logs (:warn,) expand!(iter, fact; verbosity = verbosity) verbosity = 0 @@ -358,41 +358,26 @@ end A0 = (A0 + A0') / 2 block_size = 5 x₀m = Matrix(qr(rand(T, N, block_size)).Q) - x₀ = [x₀m[:, i] for i in 1:block_size] + x₀ = KrylovKit.BlockVec{T}([x₀m[:, i] for i in 1:block_size]) for A in [A0, x -> A0 * x] iter = @constinferred KrylovKit.BlockLanczosIterator(A, x₀, N, qr_tol(T)) krylovdim = n fact = initialize(iter) - while fact.norm_r > eps(float(real(T))) && fact.all_size < krylovdim + #while fact.norm_r > eps(float(real(T))) && fact.total_size < krylovdim @constinferred expand!(iter, fact) - V, H, r, β, e = fact + k = fact.total_size + rs = fact.r_size + V0 = fact.V[1:k] + r0 = fact.r[1:rs] + H = fact.TDB[1:k, 1:k] + norm_r = fact.norm_r + V = hcat(V0...) + r = hcat(r0.vec...) + e = hcat(zeros(T, rs, k-rs), I) @test V' * V ≈ I - @test norm(r) ≈ β - @test A * V ≈ V * H + r * e' - end - end - end -end - -# Test effectiveness of shrink!() in block lanczos -@testset "Test effectiveness of shrink!() in block lanczos" begin - n = 10 - N = 100 - @testset for T in [Float32, Float64, ComplexF32, ComplexF64] - A0 = rand(T, (N, N)) - A0 = (A0 + A0') / 2 - block_size = 5 - x₀ = rand(T, N) - values0 = eigvals(A0)[1:n] - n1 = n ÷ 2 - for A in [A0, x -> A0 * x] - alg = KrylovKit.Lanczos(; krylovdim = 3*n÷2, maxiter = 1, tol = 1e-12, blockmode = true, blocksize = block_size) - values, _, _ = eigsolve(A, x₀, n, :SR, alg) - error1 = norm(values[1:n1] - values0[1:n1]) - alg_shrink = KrylovKit.Lanczos(; krylovdim = n, maxiter = 2, tol = 1e-12, blockmode = true, blocksize = block_size) - values_shrink, _, _ = eigsolve(A, x₀, n, :SR, alg_shrink) - error2 = norm(values_shrink[1:n1] - values0[1:n1]) - @test error2 < error1 + @test norm(r) ≈ norm_r + @test A0 * V ≈ V * H + r * e + #end end end end \ No newline at end of file diff --git a/test/innerproductvec.jl b/test/innerproductvec.jl index 76dacd03..7d2cb09e 100644 --- a/test/innerproductvec.jl +++ b/test/innerproductvec.jl @@ -6,8 +6,8 @@ M[i,j] = inner(x[i],y[j]) A = [rand(T, N) for _ in 1:n] B = [rand(T, N) for _ in 1:n] M = Matrix{T}(undef, n, n) - BlockA = KrylovKit.BlockVec(A, T) - BlockB = KrylovKit.BlockVec(B, T) + BlockA = KrylovKit.BlockVec{T}(A) + BlockB = KrylovKit.BlockVec{T}(B) KrylovKit.block_inner!(M, BlockA, BlockB) M0 = hcat(BlockA.vec...)' * hcat(BlockB.vec...) @test eltype(M) == T @@ -33,8 +33,8 @@ end Y[i] = InnerProductVec(rand(T, N), ip) end M = Matrix{T}(undef, n, n); - BlockX = KrylovKit.BlockVec(X, T) - BlockY = KrylovKit.BlockVec(Y, T) + BlockX = KrylovKit.BlockVec{T}(X) + BlockY = KrylovKit.BlockVec{T}(Y) KrylovKit.block_inner!(M, BlockX, BlockY); Xm = hcat([X[i].vec for i in 1:n]...); Ym = hcat([Y[i].vec for i in 1:n]...); @@ -53,8 +53,8 @@ end M = rand(T, n, n) alpha = rand(T) beta = rand(T) - BlockA = KrylovKit.BlockVec(A, T) - BlockB = KrylovKit.BlockVec(B, T) + BlockA = KrylovKit.BlockVec{T}(A) + BlockB = KrylovKit.BlockVec{T}(B) KrylovKit.block_mul!(BlockA, BlockB, M, alpha, beta) @test isapprox(hcat([BlockA.vec[i].vec for i in 1:n]...), beta * hcat([Acopy[i].vec for i in 1:n]...) + alpha * hcat([BlockB.vec[i].vec for i in 1:n]...) * M; atol = tolerance(T)) end \ No newline at end of file diff --git a/test/orthonormal.jl b/test/orthonormal.jl index 17c85230..90af243b 100644 --- a/test/orthonormal.jl +++ b/test/orthonormal.jl @@ -5,7 +5,7 @@ # A is a non-full rank matrix Av[n÷2] = sum(Av[n÷2+1:end] .* rand(T, n - n ÷ 2)) Bv = copy(Av) - R, gi = KrylovKit.abstract_qr!(KrylovKit.BlockVec(Av, T), qr_tol(T)) + R, gi = KrylovKit.abstract_qr!(KrylovKit.BlockVec{T}(Av), qr_tol(T)) @test length(gi) < n @test eltype(R) == eltype(eltype(A)) == T @test isapprox(hcat(Av[gi]...) * R, hcat(Bv...); atol = tolerance(T)) @@ -28,11 +28,11 @@ end # Make sure X is not full rank X[end] = sum(X[1:end-1] .* rand(T, n-1)) Xcopy = deepcopy(X) - R, gi = KrylovKit.abstract_qr!(KrylovKit.BlockVec(X, T), qr_tol(T)) + R, gi = KrylovKit.abstract_qr!(KrylovKit.BlockVec{T}(X), qr_tol(T)) @test length(gi) < n @test eltype(R) == T - BlockX = KrylovKit.BlockVec(X[gi], T) + BlockX = KrylovKit.BlockVec{T}(X[gi]) @test isapprox(KrylovKit.block_inner(BlockX,BlockX), I; atol=tolerance(T)) ΔX = norm.(mul_test(X[gi],R) - Xcopy) @test isapprox(norm(ΔX), T(0); atol=tolerance(T)) @@ -46,8 +46,8 @@ end ip(x,y) = x'*H*y x₀ = [InnerProductVec(rand(T, N), ip) for i in 1:n] x₁ = [InnerProductVec(rand(T, N), ip) for i in 1:2*n] - Blockx₀ = KrylovKit.BlockVec(x₀, T) - Blockx₁ = KrylovKit.BlockVec(x₁, T) + Blockx₀ = KrylovKit.BlockVec{T}(x₀) + Blockx₁ = KrylovKit.BlockVec{T}(x₁) KrylovKit.abstract_qr!(Blockx₁, qr_tol(T)) KrylovKit.ortho_basis!(Blockx₀, Blockx₁) @test norm(KrylovKit.block_inner(Blockx₀, Blockx₁)) < 2* tolerance(T) diff --git a/test/runtests.jl b/test/runtests.jl index 57d48aef..9a68a7c7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,7 +1,7 @@ using Random Random.seed!(76543210) -using Test, Logging +using Test, TestExtras, Logging using LinearAlgebra, SparseArrays using KrylovKit using VectorInterface From 693edc38366af86aafed5c56461df45b297a2632 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Tue, 29 Apr 2025 00:29:06 +0800 Subject: [PATCH 35/82] add test for eager --- Project.toml | 1 - src/algorithms.jl | 4 ++-- src/apply.jl | 2 +- src/factorizations/lanczos.jl | 2 +- test/eigsolve.jl | 43 ++++++++--------------------------- 5 files changed, 14 insertions(+), 38 deletions(-) diff --git a/Project.toml b/Project.toml index f46ff94e..6e207ef8 100644 --- a/Project.toml +++ b/Project.toml @@ -8,7 +8,6 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" PackageExtensionCompat = "65ce6f38-6b18-4e1d-a461-8949797d7930" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -TestExtras = "5ed8adda-3752-4e41-b88a-e8b09835ee3a" VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8" [weakdeps] diff --git a/src/algorithms.jl b/src/algorithms.jl index 820df733..7cecadfd 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -108,8 +108,7 @@ Use `Arnoldi` for non-symmetric or non-Hermitian linear operators. See also: `factorize`, `eigsolve`, `exponentiate`, `Arnoldi`, `Orthogonalizer` """ -# Add BlockLanczos may seem ugly, but to keep the type stable to pass -# tests in test/eigsolve.jl, I have to do this. +# Add BlockLanczos to keep the type stable to pass struct Lanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm orth::O krylovdim::Int @@ -492,4 +491,5 @@ const blockkrylovdim = Ref(100) const blockmaxiter = Ref(2) const tol = Ref(1e-12) const verbosity = Ref(KrylovKit.WARN_LEVEL) +const qr_tol(S::Type) = 1e4 * eps(real(S)) end diff --git a/src/apply.jl b/src/apply.jl index deb64228..2a7f9160 100644 --- a/src/apply.jl +++ b/src/apply.jl @@ -1,4 +1,4 @@ -apply(A::AbstractMatrix, x::AbstractVecOrMat) = A * x +apply(A::AbstractMatrix, x::AbstractVector) = A * x apply(f, x) = f(x) function apply(operator, x, α₀, α₁) diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index 03b00fc4..1e3020b3 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -450,7 +450,7 @@ function BlockLanczosIterator(operator::F, maxdim::Int, qr_tol::Real, orth::O=ModifiedGramSchmidt2()) where {F,T,S,O<:Orthogonalizer} - qr_tol < 0 && (qr_tol = 1e4 * eps(real(S))) + qr_tol < 0 && (qr_tol = KrylovDefaults.qr_tol(S)) norm(x₀) < qr_tol && @error "initial vector should not have norm zero" orth != ModifiedGramSchmidt2() && @error "BlockLanczosIterator only supports ModifiedGramSchmidt2 orthogonalizer" return BlockLanczosIterator{F,T,S,O}(operator, x₀, maxdim, orth, qr_tol) diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 28f05add..fcf87b09 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -510,11 +510,13 @@ end Random.seed!(6) A0 = rand(T, (N, N)) .- one(T) / 2 A0 = (A0 + A0') / 2 - block_size = 5 - x₀ = rand(T, N) + block_size = 2 + x₀ = normalize(rand(T, N)) eigvalsA = eigvals(A0) for A in [A0, x -> A0 * x] - alg = Lanczos(; maxiter = 1, tol = tolerance(T), blockmode = true, blocksize = block_size) + alg = Lanczos(; krylovdim = N, maxiter = 10, tol = tolerance(T), + eager = true, verbosity = 0, + blockmode = true, blocksize = block_size) D1, V1, info1 = eigsolve(A, x₀, n, :SR, alg) D2, V2, info2 = eigsolve(A, x₀, n, :LR, alg) @@ -563,16 +565,15 @@ end Random.seed!(6) A0 = rand(T, (2N, 2N)) A0 = (A0 + A0') / 2 - block_size = 1 + block_size = 5 x₀ = rand(T, 2N) alg1 = Lanczos(;krylovdim = N, maxiter = 10, tol = tolerance(T), verbosity = 1) alg2 = Lanczos(;krylovdim = N, maxiter = 10, tol = tolerance(T), verbosity = 1, blockmode = true, blocksize = block_size) for A in [A0, x -> A0 * x] - t1 = @elapsed evals1, _, info1 = eigsolve(A, x₀, block_size, :SR, alg1) - t2 = @elapsed evals2, _, info2 = eigsolve(A, x₀, block_size, :SR, alg2) - println("When input type is $T, the time of lanczos and block lanczos is $t1, $t2, the rate is $(t2/t1)") + evals1, _, info1 = eigsolve(A, x₀, block_size, :SR, alg1) + evals2, _, info2 = eigsolve(A, x₀, block_size, :SR, alg2) @test min(info1.converged, block_size) <= info2.converged - @test evals1[1] ≈ evals2[1] atol = tolerance(T) + @test norm(evals1[1:block_size] - evals2[1:block_size]) < tolerance(T) end end end @@ -596,28 +597,4 @@ end @test error2 < error1 end end -end - -#= -using Profile -Randomeed!(6) -N = 100 -M = 10N -T = ComplexF64 - A = rand(T, (M, M)) - A = (A + A') / 2 - block_size = 1 - x₀ = normalize(rand(T, M)) - - alg = Lanczos(;krylovdim = N, maxiter = 10, tol = tolerance(T), verbosity = 1, blockmode = true, blocksize = block_size) - Profile.clear() - @profile _, _, info2 = eigsolve(A, x₀, block_size, :SR, alg) - Profile.print(mincount = 10) - -tolerance(T) = sqrt(eps(real(oneunit(T)))) - alg1 = Lanczos(;krylovdim = N, maxiter = 10, tol = tolerance(T), verbosity = 1) - E,V,info1 = eigsolve(A, x₀, block_size, :SR, alg1) - Profile.clear() - @profile _, _, info1 = eigsolve(A, x₀, block_size, :SR, alg1) - Profile.print(mincount = 10) - =# \ No newline at end of file +end \ No newline at end of file From 6ddd4673dc6af08ba69ad46f07d9a91080149ec3 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Tue, 29 Apr 2025 20:01:04 +0800 Subject: [PATCH 36/82] when blocksize = 1, the efficiency of lanczos and block lanczos is almostly equal --- src/eigsolve/lanczos.jl | 10 +++-- src/factorizations/lanczos.jl | 73 +++++++++++++++++++++-------------- test/eigsolve.jl | 30 +++++++++----- test/factorize.jl | 40 +++++++++---------- test/orthonormal.jl | 14 ++++--- 5 files changed, 98 insertions(+), 69 deletions(-) diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index 74fa75f1..a110ed08 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -243,16 +243,18 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) end TDB .= S(0) TDB[1:keep, 1:keep] .= H[1:keep, 1:keep] - V = OrthonormalBasis(fact.V.basis[1:K]) - basistransform!(V, view(U, :, 1:keep)) - fact.V[1:keep] = V[1:keep] + B = basis(fact) + basistransform!(B, view(U, :, 1:keep)) r_new = OrthonormalBasis(fact.r.vec[1:bs_r]) view_U = view(U, K - bs_r + 1:K, keep - bs_r + 1:keep) basistransform!(r_new, view_U) fact.r.vec[1:bs_r] = r_new[1:bs_r] - fact.total_size = keep + while length(fact) > keep + pop!(fact.V) + fact.total_size -= 1 + end numiter += 1 end end diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index 1e3020b3..9c3403f0 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -407,6 +407,12 @@ function initialize(x₀, size::Int) pushfirst!(x₀_vec, x₀) return BlockVec{S}(x₀_vec) end +function Base.push!(V::OrthonormalBasis{T}, b::BlockVec{T}) where {T} + for i in 1:length(b) + push!(V, b[i]) + end + return V +end mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: BlockKrylovFactorization{T,S,SR} total_size::Int @@ -418,6 +424,7 @@ mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: BlockKrylovFac end Base.length(fact::BlockLanczosFactorization) = fact.total_size normres(fact::BlockLanczosFactorization) = fact.norm_r +basis(fact::BlockLanczosFactorization) = fact.V #= Now our orthogonalizer is only ModifiedGramSchmidt2. @@ -461,13 +468,12 @@ function initialize(iter::BlockLanczosIterator{F,T,S}; verbosity::Int=KrylovDefa maxdim = iter.maxdim bs = length(X₀) # block size now A = iter.operator - V_basis = similar(X₀.vec, maxdim) TDB = zeros(S, maxdim, maxdim) # Orthogonalization of the initial block X₁ = deepcopy(X₀) abstract_qr!(X₁, iter.qr_tol) - V_basis[1:bs] .= X₁.vec + V = OrthonormalBasis(X₁.vec) AX₁ = apply(A, X₁) M₁_view = view(TDB, 1:bs, 1:bs) @@ -481,7 +487,7 @@ function initialize(iter::BlockLanczosIterator{F,T,S}; verbosity::Int=KrylovDefa @info "Block Lanczos initiation at dimension $bs: subspace normres = $(normres2string(norm_r))" end return BlockLanczosFactorization(bs, - OrthonormalBasis(V_basis), + V, TDB, residual, bs, @@ -490,34 +496,23 @@ end function expand!(iter::BlockLanczosIterator{F,T,S}, state::BlockLanczosFactorization{T,S,SR}; verbosity::Int=KrylovDefaults.verbosity[]) where {F,T,S,SR} - total_size = state.total_size + k = state.total_size rₖ = state.r[1:state.r_size] bs_now = length(rₖ) - V_basis = state.V.basis + V = state.V - # Get the current residual as the initial value of the new basis. Get Xnext + # Calculate the new basis and Bₖ Bₖ, good_idx = abstract_qr!(rₖ, iter.qr_tol) - Xnext = deepcopy(rₖ[good_idx]) bs_next = length(good_idx) - V_basis[total_size+1:total_size+bs_next] .= Xnext.vec + push!(V, rₖ[good_idx]) + state.TDB[k+1:k+bs_next, k-bs_now+1:k] .= Bₖ + state.TDB[k-bs_now+1:k, k+1:k+bs_next] .= Bₖ' - # Calculate the connection matrix - Bₖ_view = view(state.TDB, total_size+1:total_size+bs_next, total_size-bs_now+1:total_size) - copyto!(Bₖ_view, Bₖ) - copyto!(view(state.TDB, total_size-bs_now+1:total_size, total_size+1:total_size+bs_next), Bₖ_view') + # Calculate the new residual and orthogonalize the new basis + rₖnext, Mnext = blocklanczosrecurrence(iter.operator, V, Bₖ, iter.orth) + verbosity >= WARN_LEVEL && warn_nonhermitian(Mnext, iter.qr_tol) - # Apply the operator and calculate the M. Get Mnext - AXₖnext = apply(iter.operator, Xnext) - Mnext_view = view(state.TDB, total_size+1:total_size+bs_next, total_size+1:total_size+bs_next) - block_inner!(Mnext_view, Xnext, AXₖnext) - verbosity >= WARN_LEVEL && warn_nonhermitian(Mnext_view, iter.qr_tol) - Mnext_view = (Mnext_view + Mnext_view') / 2 - - # Calculate the new residual. Get Rnext - Xnow = BlockVec{S}(V_basis[total_size-bs_now+1:total_size]) - rₖnext = BlockVec{S}([similar(V_basis[1]) for _ in 1:bs_next]) - compute_residual!(rₖnext, AXₖnext, Xnext, Mnext_view, Xnow, Bₖ_view) - ortho_basis!(rₖnext, BlockVec{S}(V_basis[1:total_size+bs_next])) + state.TDB[k+1:k+bs_next, k+1:k+bs_next] .= Mnext state.r.vec[1:bs_next] .= rₖnext.vec state.norm_r = norm(rₖnext) state.total_size += bs_next @@ -528,6 +523,22 @@ function expand!(iter::BlockLanczosIterator{F,T,S}, state::BlockLanczosFactoriza end end +function blocklanczosrecurrence(operator, V::OrthonormalBasis, Bₖ::AbstractMatrix, orth::ModifiedGramSchmidt2) + # Apply the operator and calculate the M. Get Xnext and Mnext + bs,bs_last = size(Bₖ) + S = eltype(Bₖ) + k = length(V) + X = BlockVec{S}(V[k-bs+1:k]) + AX = apply(operator, X) + M = block_inner(X, AX) + # Calculate the new residual. Get Rnext + Xlast = BlockVec{S}(V[k-bs_last-bs+1:k-bs]) + rₖnext = BlockVec{S}([similar(X[1]) for _ in 1:bs]) + compute_residual!(rₖnext, AX, X, M, Xlast, Bₖ) + ortho_basis!(rₖnext, V) + return rₖnext, M +end + function compute_residual!(r::BlockVec{T,S}, AX::BlockVec{T,S}, X::BlockVec{T,S}, M::AbstractMatrix, X_prev::BlockVec{T,S}, B_prev::AbstractMatrix) where {T,S} @inbounds for j in 1:length(X) @@ -543,11 +554,15 @@ function compute_residual!(r::BlockVec{T,S}, AX::BlockVec{T,S}, X::BlockVec{T,S} return r end -function ortho_basis!(basis_new::BlockVec{T,S}, basis_sofar::BlockVec{T,S}) where {T,S} - tmp = Matrix{S}(undef, length(basis_sofar), length(basis_new)) - block_inner!(tmp, basis_sofar, basis_new) - block_mul!(basis_new, basis_sofar, - tmp, S(1), S(1)) - return basis_new +# This function is reserved for further improvement on case of vector of number input. +function ortho_basis!(basis::BlockVec{T,S}, basis_sofar::OrthonormalBasis{T}) where {T,S} + for i in 1:length(basis) + w = basis[i] + for q in basis_sofar + orthogonalize!!(w, q, ModifiedGramSchmidt()) + end + end + return basis end function warn_nonhermitian(M::AbstractMatrix, tol::Real) diff --git a/test/eigsolve.jl b/test/eigsolve.jl index fcf87b09..665f6cc8 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -433,13 +433,11 @@ end # matrix input alg = Lanczos(;tol = tol, blockmode = true, blocksize = p,maxiter = 1) D, U, info = eigsolve(-h_mat, x₀, get_value_num, :SR, alg) - @show D[1:get_value_num] @test count(x -> abs(x + 16.0) < 2.0 - tol, D[1:get_value_num]) == 4 @test count(x -> abs(x + 16.0) < tol, D[1:get_value_num]) == 4 # map input D, U, info = eigsolve(x -> -h_mat * x, x₀, get_value_num, :SR, alg) - @show D[1:get_value_num] @test count(x -> abs(x + 16.0) < 1.9, D[1:get_value_num]) == 4 @test count(x -> abs(x + 16.0) < 1e-8, D[1:get_value_num]) == 4 end @@ -560,20 +558,34 @@ end @test findmax([norm(Aip(V[i]) - D[i] * V[i]) for i in 1:eig_num])[1] < tolerance(T) end +# with the same krylovdim, block lanczos has lower accuracy with blocksize >1. @testset "Complete Lanczos and Block Lanczos" begin @testset for T in [Float32, Float64, ComplexF32, ComplexF64] Random.seed!(6) A0 = rand(T, (2N, 2N)) A0 = (A0 + A0') / 2 - block_size = 5 + block_size = 1 + x₀ = rand(T, 2N) + alg1 = Lanczos(;krylovdim = 2n, maxiter = 10, tol = tolerance(T), verbosity = 1) + alg2 = Lanczos(;krylovdim = 2n, maxiter = 10, tol = tolerance(T), verbosity = 1, blockmode = true, blocksize = block_size) + for A in [A0, x -> A0 * x] + evals1, _, info1 = eigsolve(A, x₀, n, :SR, alg1) + evals2, _, info2 = eigsolve(A, x₀, n, :SR, alg2) + @test min(info1.converged, n) == info2.converged + end + end + @testset for T in [Float32, Float64, ComplexF32, ComplexF64] + Random.seed!(6) + A0 = rand(T, (2N, 2N)) + A0 = (A0 + A0') / 2 + block_size = 4 x₀ = rand(T, 2N) - alg1 = Lanczos(;krylovdim = N, maxiter = 10, tol = tolerance(T), verbosity = 1) - alg2 = Lanczos(;krylovdim = N, maxiter = 10, tol = tolerance(T), verbosity = 1, blockmode = true, blocksize = block_size) + alg1 = Lanczos(;krylovdim = 2n, maxiter = 10, tol = tolerance(T), verbosity = 1) + alg2 = Lanczos(;krylovdim = 2n, maxiter = 10, tol = tolerance(T), verbosity = 1, blockmode = true, blocksize = block_size) for A in [A0, x -> A0 * x] - evals1, _, info1 = eigsolve(A, x₀, block_size, :SR, alg1) - evals2, _, info2 = eigsolve(A, x₀, block_size, :SR, alg2) - @test min(info1.converged, block_size) <= info2.converged - @test norm(evals1[1:block_size] - evals2[1:block_size]) < tolerance(T) + evals1, _, info1 = eigsolve(A, x₀, n, :SR, alg1) + evals2, _, info2 = eigsolve(A, x₀, n, :SR, alg2) + @test min(info1.converged, n) >= info2.converged + 1 end end end diff --git a/test/factorize.jl b/test/factorize.jl index 100041d8..adcd9d0f 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -327,25 +327,23 @@ end end end - if T <: Complex - B = rand(T, (n, n)) # test warnings for non-hermitian matrices - bs = 2 - v₀m = Matrix(qr(rand(T, n, bs)).Q) - v₀ = KrylovKit.BlockVec{T}([v₀m[:, i] for i in 1:bs]) - iter = KrylovKit.BlockLanczosIterator(B, v₀, N, qr_tol(T)) - fact = initialize(iter) - @constinferred expand!(iter, fact; verbosity = 0) - @test_logs initialize(iter; verbosity = 0) - @test_logs (:warn,) initialize(iter) - verbosity = 1 - while fact.total_size < n - if verbosity == 1 - @test_logs (:warn,) expand!(iter, fact; verbosity = verbosity) - verbosity = 0 - else - @test_logs expand!(iter, fact; verbosity = verbosity) - verbosity = 1 - end + B = rand(T, (n, n)) # test warnings for non-hermitian matrices + bs = 2 + v₀m = Matrix(qr(rand(T, n, bs)).Q) + v₀ = KrylovKit.BlockVec{T}([v₀m[:, i] for i in 1:bs]) + iter = KrylovKit.BlockLanczosIterator(B, v₀, N, qr_tol(T)) + fact = initialize(iter) + @constinferred expand!(iter, fact; verbosity = 0) + @test_logs initialize(iter; verbosity = 0) + @test_logs (:warn,) initialize(iter) + verbosity = 1 + while fact.total_size < n + if verbosity == 1 + @test_logs (:warn,) expand!(iter, fact; verbosity = verbosity) + verbosity = 0 + else + @test_logs expand!(iter, fact; verbosity = verbosity) + verbosity = 1 end end end @@ -363,7 +361,7 @@ end iter = @constinferred KrylovKit.BlockLanczosIterator(A, x₀, N, qr_tol(T)) krylovdim = n fact = initialize(iter) - #while fact.norm_r > eps(float(real(T))) && fact.total_size < krylovdim + while fact.norm_r > eps(float(real(T))) && fact.total_size < krylovdim @constinferred expand!(iter, fact) k = fact.total_size rs = fact.r_size @@ -377,7 +375,7 @@ end @test V' * V ≈ I @test norm(r) ≈ norm_r @test A0 * V ≈ V * H + r * e - #end + end end end end \ No newline at end of file diff --git a/test/orthonormal.jl b/test/orthonormal.jl index 90af243b..43454aba 100644 --- a/test/orthonormal.jl +++ b/test/orthonormal.jl @@ -44,12 +44,14 @@ end H = H'*H + I; H = (H + H')/2; ip(x,y) = x'*H*y + x₀ = [InnerProductVec(rand(T, N), ip) for i in 1:n] x₁ = [InnerProductVec(rand(T, N), ip) for i in 1:2*n] - Blockx₀ = KrylovKit.BlockVec{T}(x₀) - Blockx₁ = KrylovKit.BlockVec{T}(x₁) - KrylovKit.abstract_qr!(Blockx₁, qr_tol(T)) - KrylovKit.ortho_basis!(Blockx₀, Blockx₁) - @test norm(KrylovKit.block_inner(Blockx₀, Blockx₁)) < 2* tolerance(T) -end + b₀ = KrylovKit.BlockVec{T}(x₀) + b₁ = KrylovKit.BlockVec{T}(x₁) + KrylovKit.abstract_qr!(b₁, qr_tol(T)) + orthobasis_x₁ = KrylovKit.OrthonormalBasis(b₁.vec) + KrylovKit.ortho_basis!(b₀, orthobasis_x₁) + @test norm(KrylovKit.block_inner(b₀, b₁)) < tolerance(T) +end \ No newline at end of file From 83e9b9f55eb7f81fe593b0a6599b62c83eaa388e Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Wed, 30 Apr 2025 05:10:33 +0800 Subject: [PATCH 37/82] revise what review mentions and add annotation --- src/KrylovKit.jl | 4 +- src/algorithms.jl | 46 +++++++------- src/eigsolve/lanczos.jl | 15 +++-- src/factorizations/lanczos.jl | 111 +++++++++++++++++++++++----------- src/innerproductvec.jl | 21 ++----- test/eigsolve.jl | 34 +++++------ test/factorize.jl | 6 +- test/innerproductvec.jl | 11 ++-- 8 files changed, 140 insertions(+), 108 deletions(-) diff --git a/src/KrylovKit.jl b/src/KrylovKit.jl index c43ce082..309ce9f1 100644 --- a/src/KrylovKit.jl +++ b/src/KrylovKit.jl @@ -36,8 +36,8 @@ export basis, rayleighquotient, residual, normres, rayleighextension export initialize, initialize!, expand!, shrink! export ClassicalGramSchmidt, ClassicalGramSchmidt2, ClassicalGramSchmidtIR export ModifiedGramSchmidt, ModifiedGramSchmidt2, ModifiedGramSchmidtIR -export LanczosIterator, ArnoldiIterator, GKLIterator -export CG, GMRES, BiCGStab, Lanczos, Arnoldi, GKL, GolubYe, LSMR +export LanczosIterator, BlockLanczosIterator, ArnoldiIterator, GKLIterator +export CG, GMRES, BiCGStab, Lanczos, BlockLanczos, Arnoldi, GKL, GolubYe, LSMR export KrylovDefaults, EigSorter export RecursiveVec, InnerProductVec diff --git a/src/algorithms.jl b/src/algorithms.jl index 7cecadfd..d9d5b73b 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -107,8 +107,6 @@ Use `Arnoldi` for non-symmetric or non-Hermitian linear operators. See also: `factorize`, `eigsolve`, `exponentiate`, `Arnoldi`, `Orthogonalizer` """ - -# Add BlockLanczos to keep the type stable to pass struct Lanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm orth::O krylovdim::Int @@ -117,6 +115,18 @@ struct Lanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm eager::Bool verbosity::Int end +function Lanczos(; + krylovdim::Int=KrylovDefaults.krylovdim[], + maxiter::Int=KrylovDefaults.maxiter[], + tol::Real=KrylovDefaults.tol[], + orth::Orthogonalizer=KrylovDefaults.orth, + eager::Bool=false, + verbosity::Int=KrylovDefaults.verbosity[]) + return Lanczos(orth, krylovdim, maxiter, tol, eager, verbosity) +end + +# qr_tol is the tolerance that we think a vector is non-zero in abstract_qr! +# This qr_tol will also be used in other zero_chcking in block Lanczos. struct BlockLanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm orth::O krylovdim::Int @@ -127,26 +137,17 @@ struct BlockLanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm eager::Bool verbosity::Int end -# qr_tol is the tolerance that we think a vector is non-zero in abstract_qr! -# This qr_tol will also be used in other zero_chcking in block Lanczos. -function Lanczos(; - krylovdim::Int= -1, - maxiter::Int= -1, - tol::Real=KrylovDefaults.tol[], - orth::Orthogonalizer=KrylovDefaults.orth, - eager::Bool=false, - verbosity::Int=KrylovDefaults.verbosity[], - blockmode::Bool=false, - blocksize::Int=-1, - qr_tol::Real=-1.0) - krylovdim < 0 && (blockmode ? (krylovdim = KrylovDefaults.blockkrylovdim[]) : (krylovdim = KrylovDefaults.krylovdim[])) - maxiter < 0 && (blockmode ? (maxiter = KrylovDefaults.blockmaxiter[]) : (maxiter = KrylovDefaults.maxiter[])) - if blockmode - blocksize < 1 && error("blocksize must be greater than 0") - return BlockLanczos(orth, krylovdim, maxiter, tol, blocksize, qr_tol, eager, verbosity) - else - return Lanczos(orth, krylovdim, maxiter, tol, eager, verbosity) - end +function BlockLanczos(; + krylovdim::Int=KrylovDefaults.blockkrylovdim[], + maxiter::Int=KrylovDefaults.blockmaxiter[], + tol::Real=KrylovDefaults.tol[], + orth::Orthogonalizer=KrylovDefaults.orth, + eager::Bool=false, + verbosity::Int=KrylovDefaults.verbosity[], + blocksize::Int=1, + qr_tol::Real=KrylovDefaults.tol[]) + blocksize < 1 && error("blocksize must be greater than 0") + return BlockLanczos(orth, krylovdim, maxiter, tol, blocksize, qr_tol, eager, verbosity) end """ @@ -491,5 +492,4 @@ const blockkrylovdim = Ref(100) const blockmaxiter = Ref(2) const tol = Ref(1e-12) const verbosity = Ref(KrylovKit.WARN_LEVEL) -const qr_tol(S::Type) = 1e4 * eps(real(S)) end diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index a110ed08..d39eabcd 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -161,7 +161,6 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) verbosity = alg.verbosity # Initialize a block of vectors from the initial vector, randomly generated - # TODO: add a more flexible initialization block0 = initialize(x₀, alg.blocksize) bs = length(block0) @@ -222,17 +221,16 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) numops += 1 else # shrink and restart numiter >= maxiter && break - bsn = max(div(3 * krylovdim + 2 * converged, 5) ÷ bs, 1) - if (bsn + 1) * bs > K # make sure that we can fetch next block after shrinked dimension as residual - @warn "shrinked dimesion is too small and there is no need to shrink" - break - end + bsn = max(div(3 * krylovdim + 2 * converged, 5) ÷ bs, 1) # Divide basis into blocks with the same size keep = bs * bsn H = zeros(S, (bsn + 1) * bs, bsn * bs) + # The last bs rows of U contribute to calculate errors of Ritz values. @inbounds for j in 1:keep H[j, j] = D[j] H[bsn * bs + 1:end, j] = U[K - bs + 1:K, j] end + # Turn diagonal matrix D into a block tridiagonal matrix, and make sure + # the residual of krylov subspace keeps the form of [0,..,0,R] @inbounds for j in keep:-1:1 h, ν = householder(H, j + bs, 1:j, j) H[j + bs, j] = ν @@ -241,6 +239,7 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) rmul!(view(H, 1:j + bs -1, :), h') rmul!(U, h') end + # transform the basis and update the residual and update the TDB. TDB .= S(0) TDB[1:keep, 1:keep] .= H[1:keep, 1:keep] B = basis(fact) @@ -281,6 +280,6 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) end return D[1:howmany_actual], - vectors[1:howmany_actual], - ConvergenceInfo(converged, residuals, normresiduals, numiter, numops) + vectors[1:howmany_actual], + ConvergenceInfo(converged, residuals, normresiduals, numiter, numops) end \ No newline at end of file diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index 9c3403f0..7a84a31b 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -369,22 +369,9 @@ end # block lanczos -#= -The basic theory of the Block Lanczos algorithm can be referred to : -Golub, G. H., & Van Loan, C. F. (2013). Matrix computations (4th ed., pp. 566–569). Johns Hopkins University Press. - -Now what I implement is block lanczos with mutable block size. But I'm still confused is it neccesary. That is to say, Can we asseert -the iteration would end with size shrink? -Mathematically: for a set of initial abstract vectors X₀ = {x₁,..,xₚ}, where A is a hermitian operator, if -Sₖ = {x ∈ AʲX₀:j=0,..,k-1} -is linear dependent, can we assert that Rₖ ∈ span(A^{k-2}X₀,A^{k-1}X₀) or at least in span(Sₖ)? -For vectors in F^d I believe it's right. But in a abstract inner product space, it's obviouly much more complicated. - -What ever, mutable block size is at least undoubtedly useful for non-hermitian operator so I implement it. -https://www.netlib.org/utk/people/JackDongarra/etemplates/node252.html#ABLEsection -=# - -# We use this to store vectors as a block. Although now its fields are same to OrthonormalBasis, +# The basic theory of the Block Lanczos algorithm can be referred to : Golub, G. H., & Van Loan, C. F. (2013). Matrix computations (4th ed., pp. 566–569). Johns Hopkins University Press. + +# We use BlockVec to store vectors as a block. Although now its fields are same to OrthonormalBasis, # I plan to develop BlockVec a abstract of vector of number and inner product space, # and process the block in the latter as a matrix with higher efficiency. struct BlockVec{T,S<:Number} @@ -414,6 +401,29 @@ function Base.push!(V::OrthonormalBasis{T}, b::BlockVec{T}) where {T} return V end +""" + mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: BlockKrylovFactorization{T,S,SR} + +Structure to store a block lanczos factorization of a real symmetric or complex hermitian linear +map `A` of the form + +```julia +A * V = V * B + r * b' +``` + +For a given BlockLanczos factorization `fact` of length `k = length(fact)`, the basis `V` is +obtained via [`basis(fact)`](@ref basis) and is an instance of [`OrthonormalBasis{T}`](@ref +Basis), with also `length(V) == k` and where `T` denotes the type of vector like objects +used in the problem. The block tridiagonal matrix `B` is preallocated in BlockLanczosFactorization +and is of type `Hermitian{S<:Number}` with `size(B) == (k,k)`. The residual `r` is of type `T`. +One can also query [`normres(fact)`](@ref) to obtain `norm(r)`, the norm of the residual. The vector +`b` takes the default value ``e_k``, i.e. the matrix of size `(k,bs)` and an unit matrix in the last +`bs` rows and all zeros in the other rows. `bs` is the size of the last block. + +`BlockLanczosFactorization` is mutable because it can [`expand!`](@ref) or [`shrink!`](@ref). +See also [`BlockLanczosIterator`](@ref) for an iterator that constructs a progressively expanding +BlockLanczos factorizations of a given linear map and a starting vector. +""" mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: BlockKrylovFactorization{T,S,SR} total_size::Int const V::OrthonormalBasis{T} # Block Lanczos Basis @@ -426,18 +436,53 @@ Base.length(fact::BlockLanczosFactorization) = fact.total_size normres(fact::BlockLanczosFactorization) = fact.norm_r basis(fact::BlockLanczosFactorization) = fact.V -#= -Now our orthogonalizer is only ModifiedGramSchmidt2. -Dimension of Krylov subspace in BlockLanczosIterator is usually much bigger than lanczos. -So ClassicalGramSchmidt and ModifiedGramSchmidt1 is numerically unstable. -I don't add IR orthogonalizer because I find it sometimes unstable and I am studying it. +""" + struct BlockLanczosIterator{F,T,O<:Orthogonalizer} <: KrylovIterator{F,T} + BlockLanczosIterator(f, x₀, [orth::Orthogonalizer = KrylovDefaults.orth, keepvecs::Bool = true]) + +Iterator that takes a linear map `f::F` (supposed to be real symmetric or complex hermitian) +and an initial block `x₀::Vector{T}` and generates an expanding `BlockLanczosFactorization` thereof. In +particular, `BlockLanczosIterator` uses the +[Block Lanczos iteration](https://en.wikipedia.org/wiki/Block_Lanczos_algorithm) scheme to build a +successively expanding BlockLanczos factorization. While `f` cannot be tested to be symmetric or +hermitian directly when the linear map is encoded as a general callable object or function, +it is tested whether `block_inner(X, f.(X))` is sufficiently small to be +neglected. + +The argument `f` can be a matrix, or a function accepting a single argument `x`, so that +`f(x)` implements the action of the linear map on the block `x`. + +The optional argument `orth` specifies which [`Orthogonalizer`](@ref) to be used. The +default value in [`KrylovDefaults`](@ref) is to use [`ModifiedGramSchmidt2`](@ref), which +uses reorthogonalization steps in every iteration. +Now our orthogonalizer is only ModifiedGramSchmidt2. So we don't need to provide "keepvecs" because we have to reverse all krylove vectors. +Dimension of Krylov subspace in BlockLanczosIterator is usually much bigger than lanczos.So ClassicalGramSchmidt +and ModifiedGramSchmidt1 is numerically unstable. I don't add IR orthogonalizer because I find it sometimes unstable. Householder reorthogonalization is theoretically stable and saves memory, but the algorithm I implemented is not stable. In the future, I will add IR and Householder orthogonalizer. -=# -#= The only orthogonalization method we use in block lanczos is ModifiedGramSchmidt2. So we don't need to -provide "keepvecs" because we have to reverse all krylove vectors. -=# +When iterating over an instance of `BlockLanczosIterator`, the values being generated are +instances of [`BlockLanczosFactorization`](@ref), which can be destructured. For example as + +```julia +for (V, B, r, nr, b) in BlockLanczosIterator(f, x₀) + # do something + nr < tol && break # a typical stopping criterion +end +``` + +Since the iterator does not know the dimension of the underlying vector space of +objects of type `T`, it keeps expanding the Krylov subspace until the residual norm `nr` +falls below machine precision `eps(typeof(nr))`. + +The internal state of `BlockLanczosIterator` is the same as the return value, i.e. the +corresponding `BlockLanczosFactorization`. + +Here, [`initialize(::KrylovIterator)`](@ref) produces the first Krylov factorization, +and `expand!(::KrylovIterator, ::KrylovFactorization)`(@ref) expands the +factorization in place. See also [`shrink!(::KrylovFactorization, k)`](@ref) to shrink an +existing factorization down to length `k`. +""" struct BlockLanczosIterator{F,T,S,O<:Orthogonalizer} <: KrylovIterator{F,T} operator::F x₀::BlockVec{T,S} @@ -457,7 +502,6 @@ function BlockLanczosIterator(operator::F, maxdim::Int, qr_tol::Real, orth::O=ModifiedGramSchmidt2()) where {F,T,S,O<:Orthogonalizer} - qr_tol < 0 && (qr_tol = KrylovDefaults.qr_tol(S)) norm(x₀) < qr_tol && @error "initial vector should not have norm zero" orth != ModifiedGramSchmidt2() && @error "BlockLanczosIterator only supports ModifiedGramSchmidt2 orthogonalizer" return BlockLanczosIterator{F,T,S,O}(operator, x₀, maxdim, orth, qr_tol) @@ -476,12 +520,11 @@ function initialize(iter::BlockLanczosIterator{F,T,S}; verbosity::Int=KrylovDefa V = OrthonormalBasis(X₁.vec) AX₁ = apply(A, X₁) - M₁_view = view(TDB, 1:bs, 1:bs) - block_inner!(M₁_view, X₁, AX₁) - verbosity >= WARN_LEVEL && warn_nonhermitian(M₁_view, iter.qr_tol) - M₁_view = (M₁_view + M₁_view') / 2 + M₁ = block_inner(X₁, AX₁) + TDB[1:bs, 1:bs] .= M₁ + verbosity >= WARN_LEVEL && warn_nonhermitian(M₁) - residual = block_mul!(AX₁, X₁, - M₁_view, S(1), S(1)) + residual = block_mul!(AX₁, X₁, - M₁, S(1), S(1)) norm_r = norm(residual) if verbosity > EACHITERATION_LEVEL @info "Block Lanczos initiation at dimension $bs: subspace normres = $(normres2string(norm_r))" @@ -510,7 +553,7 @@ function expand!(iter::BlockLanczosIterator{F,T,S}, state::BlockLanczosFactoriza # Calculate the new residual and orthogonalize the new basis rₖnext, Mnext = blocklanczosrecurrence(iter.operator, V, Bₖ, iter.orth) - verbosity >= WARN_LEVEL && warn_nonhermitian(Mnext, iter.qr_tol) + verbosity >= WARN_LEVEL && warn_nonhermitian(Mnext) state.TDB[k+1:k+bs_next, k+1:k+bs_next] .= Mnext state.r.vec[1:bs_next] .= rₖnext.vec @@ -565,8 +608,8 @@ function ortho_basis!(basis::BlockVec{T,S}, basis_sofar::OrthonormalBasis{T}) wh return basis end -function warn_nonhermitian(M::AbstractMatrix, tol::Real) - if norm(M - M') > tol +function warn_nonhermitian(M::AbstractMatrix) + if norm(M - M') > eps(real(eltype(M)))^(2/5) @warn "Enforce Hermiticity on the triangular diagonal blocks matrix, even though the operator may not be Hermitian." end end diff --git a/src/innerproductvec.jl b/src/innerproductvec.jl index 09533043..0de1dc26 100644 --- a/src/innerproductvec.jl +++ b/src/innerproductvec.jl @@ -139,23 +139,14 @@ function block_mul!(A::BlockVec{T,S}, B::BlockVec{T,S}, M::AbstractMatrix, alpha return A end -function block_inner!(M::AbstractMatrix, - x::BlockVec{T,S}, - y::BlockVec{T,S}) where {T,S} - @assert size(M) == (length(x), length(y)) "Matrix dimensions must match" - @inbounds for j in 1:length(y) - yj = y[j] - for i in 1:length(x) - M[i, j] = inner(x[i], yj) - end - end - return M -end - -# used for debugging function block_inner(B₁::BlockVec{T,S}, B₂::BlockVec{T,S}) where {T,S} M = Matrix{S}(undef, length(B₁.vec), length(B₂.vec)) - block_inner!(M, B₁, B₂) + @inbounds for j in 1:length(B₂) + yj = B₂[j] + for i in 1:length(B₁) + M[i, j] = inner(B₁[i], yj) + end + end return M end diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 665f6cc8..04f6fce3 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -431,7 +431,7 @@ end h_mat = toric_code_hamiltonian_matrix(sites_num, sites_num) # matrix input - alg = Lanczos(;tol = tol, blockmode = true, blocksize = p,maxiter = 1) + alg = BlockLanczos(;tol = tol, blocksize = p,maxiter = 1) D, U, info = eigsolve(-h_mat, x₀, get_value_num, :SR, alg) @test count(x -> abs(x + 16.0) < 2.0 - tol, D[1:get_value_num]) == 4 @test count(x -> abs(x + 16.0) < tol, D[1:get_value_num]) == 4 @@ -467,23 +467,23 @@ As a result, I’ve decided to postpone dealing with the in-place test issue for n1 = div(n, 2) # eigenvalues to solve eigvalsA = eigvals(A0) for A in [A0, x -> A0 * x] - alg = Lanczos(; krylovdim = n, maxiter = 1, tol = tolerance(T), verbosity = 2, blockmode = true, blocksize = block_size) + alg = BlockLanczos(; krylovdim = n, maxiter = 1, tol = tolerance(T), verbosity = 2, blocksize = block_size) D1, V1, info = @test_logs (:info,) eigsolve(A, x₀, n1, :SR, alg) - alg = Lanczos(; krylovdim = n, maxiter = 1, tol = tolerance(T), verbosity = 1, blockmode = true, blocksize = block_size) + alg = BlockLanczos(; krylovdim = n, maxiter = 1, tol = tolerance(T), verbosity = 1, blocksize = block_size) @test_logs eigsolve(A, x₀, n1, :SR, alg) - alg = Lanczos(; krylovdim = n1 + 1, maxiter = 1, tol = tolerance(T), verbosity = 1, blockmode = true, blocksize = block_size) + alg = BlockLanczos(; krylovdim = n1 + 1, maxiter = 1, tol = tolerance(T), verbosity = 1, blocksize = block_size) @test_logs (:warn,) eigsolve(A, x₀, n1, :SR, alg) - alg = Lanczos(; krylovdim = n, maxiter = 1, tol = tolerance(T), verbosity = 2, blockmode = true, blocksize = block_size) + alg = BlockLanczos(; krylovdim = n, maxiter = 1, tol = tolerance(T), verbosity = 2, blocksize = block_size) @test_logs (:info,) eigsolve(A, x₀, n1, :SR, alg) - alg = Lanczos(; krylovdim = 3, maxiter = 3, tol = tolerance(T), verbosity = 3, blockmode = true, blocksize = block_size) + alg = BlockLanczos(; krylovdim = 3, maxiter = 3, tol = tolerance(T), verbosity = 3, blocksize = block_size) @test_logs((:info,), (:info,), (:info,), (:warn,), eigsolve(A, x₀, 1, :SR, alg)) - alg = Lanczos(; krylovdim = 4, maxiter=1, tol=tolerance(T), verbosity=4, blockmode=true, blocksize=block_size) + alg = BlockLanczos(; krylovdim = 4, maxiter=1, tol=tolerance(T), verbosity=4, blocksize=block_size) @test_logs((:info,), (:info,),(:info,),(:warn,), eigsolve(A, x₀, 1, :SR, alg)) # To use blockmode, users have to explicitly set blockmode = true, we don't allow them to use eigselector. # Because of the _residual! function, I can't make sure the stability of types temporarily. # So I ignore the test of @constinferred n2 = n - n1 - alg = Lanczos(; krylovdim = 2 * n, maxiter = 4, tol = tolerance(T), blockmode = true, blocksize = block_size) + alg = BlockLanczos(; krylovdim = 2 * n, maxiter = 4, tol = tolerance(T), blocksize = block_size) D2, V2, info = eigsolve(A, x₀, n2, :LR, alg) D2[1:n2] @test vcat(D1[1:n1], reverse(D2[1:n2])) ≊ eigvalsA @@ -497,7 +497,7 @@ As a result, I’ve decided to postpone dealing with the in-place test issue for @test (x -> KrylovKit.apply(A, x)).(V1) ≈ D1 .* V1 @test (x -> KrylovKit.apply(A, x)).(V2) ≈ D2 .* V2 - alg = Lanczos(; krylovdim = 2n, maxiter = 1, tol = tolerance(T), verbosity = 1, blockmode = true, blocksize = block_size) + alg = BlockLanczos(; krylovdim = 2n, maxiter = 1, tol = tolerance(T), verbosity = 1, blocksize = block_size) @test_logs (:warn,) (:warn,) eigsolve(A, x₀, n + 1, :LM, alg) end end @@ -512,9 +512,9 @@ end x₀ = normalize(rand(T, N)) eigvalsA = eigvals(A0) for A in [A0, x -> A0 * x] - alg = Lanczos(; krylovdim = N, maxiter = 10, tol = tolerance(T), + alg = BlockLanczos(; krylovdim = N, maxiter = 10, tol = tolerance(T), eager = true, verbosity = 0, - blockmode = true, blocksize = block_size) + blocksize = block_size) D1, V1, info1 = eigsolve(A, x₀, n, :SR, alg) D2, V2, info2 = eigsolve(A, x₀, n, :LR, alg) @@ -549,8 +549,8 @@ end Hip(x::Vector, y::Vector) = x' * H * y x₀ = InnerProductVec(rand(T, n), Hip) Aip(x::InnerProductVec) = InnerProductVec(H * x.vec, Hip) - D, V, info = eigsolve(Aip, x₀, eig_num, :SR, Lanczos(; krylovdim = n, maxiter = 1, tol = tolerance(T), - verbosity = 0, blockmode = true, blocksize = block_size)) + D, V, info = eigsolve(Aip, x₀, eig_num, :SR, BlockLanczos(; krylovdim = n, maxiter = 1, tol = tolerance(T), + verbosity = 0, blocksize = block_size)) D_true = eigvals(H) BlockV = KrylovKit.BlockVec{T}(V) @test D ≈ D_true[1:eig_num] @@ -567,7 +567,7 @@ end block_size = 1 x₀ = rand(T, 2N) alg1 = Lanczos(;krylovdim = 2n, maxiter = 10, tol = tolerance(T), verbosity = 1) - alg2 = Lanczos(;krylovdim = 2n, maxiter = 10, tol = tolerance(T), verbosity = 1, blockmode = true, blocksize = block_size) + alg2 = BlockLanczos(;krylovdim = 2n, maxiter = 10, tol = tolerance(T), verbosity = 1, blocksize = block_size) for A in [A0, x -> A0 * x] evals1, _, info1 = eigsolve(A, x₀, n, :SR, alg1) evals2, _, info2 = eigsolve(A, x₀, n, :SR, alg2) @@ -581,7 +581,7 @@ end block_size = 4 x₀ = rand(T, 2N) alg1 = Lanczos(;krylovdim = 2n, maxiter = 10, tol = tolerance(T), verbosity = 1) - alg2 = Lanczos(;krylovdim = 2n, maxiter = 10, tol = tolerance(T), verbosity = 1, blockmode = true, blocksize = block_size) + alg2 = BlockLanczos(;krylovdim = 2n, maxiter = 10, tol = tolerance(T), verbosity = 1, blocksize = block_size) for A in [A0, x -> A0 * x] evals1, _, info1 = eigsolve(A, x₀, n, :SR, alg1) evals2, _, info2 = eigsolve(A, x₀, n, :SR, alg2) @@ -600,10 +600,10 @@ end values0 = eigvals(A0)[1:n] n1 = n ÷ 2 for A in [A0, x -> A0 * x] - alg = KrylovKit.Lanczos(; krylovdim = 3*n÷2, maxiter = 1, tol = 1e-12, blockmode = true, blocksize = block_size) + alg = KrylovKit.BlockLanczos(; krylovdim = 3*n÷2, maxiter = 1, tol = 1e-12, blocksize = block_size) values, _, _ = eigsolve(A, x₀, n, :SR, alg) error1 = norm(values[1:n1] - values0[1:n1]) - alg_shrink = KrylovKit.Lanczos(; krylovdim = 3*n÷2, maxiter = 2, tol = 1e-12, blockmode = true, blocksize = block_size) + alg_shrink = KrylovKit.BlockLanczos(; krylovdim = 3*n÷2, maxiter = 2, tol = 1e-12, blocksize = block_size) values_shrink, _, _ = eigsolve(A, x₀, n, :SR, alg_shrink) error2 = norm(values_shrink[1:n1] - values0[1:n1]) @test error2 < error1 diff --git a/test/factorize.jl b/test/factorize.jl index adcd9d0f..94b4c1c5 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -308,7 +308,7 @@ end x₀ = KrylovKit.BlockVec{T}([x₀m[:, i] for i in 1:block_size]) eigvalsA = eigvals(A0) for A in [A0, x -> A0 * x] - iter = KrylovKit.BlockLanczosIterator(A, x₀, N, qr_tol(T)) + iter = BlockLanczosIterator(A, x₀, N, qr_tol(T)) # TODO: Why type unstable? # fact = @constinferred initialize(iter) fact = initialize(iter) @@ -331,7 +331,7 @@ end bs = 2 v₀m = Matrix(qr(rand(T, n, bs)).Q) v₀ = KrylovKit.BlockVec{T}([v₀m[:, i] for i in 1:bs]) - iter = KrylovKit.BlockLanczosIterator(B, v₀, N, qr_tol(T)) + iter = BlockLanczosIterator(B, v₀, N, qr_tol(T)) fact = initialize(iter) @constinferred expand!(iter, fact; verbosity = 0) @test_logs initialize(iter; verbosity = 0) @@ -358,7 +358,7 @@ end x₀m = Matrix(qr(rand(T, N, block_size)).Q) x₀ = KrylovKit.BlockVec{T}([x₀m[:, i] for i in 1:block_size]) for A in [A0, x -> A0 * x] - iter = @constinferred KrylovKit.BlockLanczosIterator(A, x₀, N, qr_tol(T)) + iter = @constinferred BlockLanczosIterator(A, x₀, N, qr_tol(T)) krylovdim = n fact = initialize(iter) while fact.norm_r > eps(float(real(T))) && fact.total_size < krylovdim diff --git a/test/innerproductvec.jl b/test/innerproductvec.jl index 7d2cb09e..e76e2044 100644 --- a/test/innerproductvec.jl +++ b/test/innerproductvec.jl @@ -1,20 +1,20 @@ #= -block_inner!(M,x,y): +block_inner(x,y): M[i,j] = inner(x[i],y[j]) =# -@testset "block_inner! for non-full vectors $T" for T in (Float32, Float64, ComplexF32, ComplexF64) +@testset "block_inner for non-full vectors $T" for T in (Float32, Float64, ComplexF32, ComplexF64) A = [rand(T, N) for _ in 1:n] B = [rand(T, N) for _ in 1:n] M = Matrix{T}(undef, n, n) BlockA = KrylovKit.BlockVec{T}(A) BlockB = KrylovKit.BlockVec{T}(B) - KrylovKit.block_inner!(M, BlockA, BlockB) + M = KrylovKit.block_inner(BlockA, BlockB) M0 = hcat(BlockA.vec...)' * hcat(BlockB.vec...) @test eltype(M) == T @test isapprox(M, M0; atol = relax_tol(T)) end -@testset "block_inner! for abstract inner product" begin +@testset "block_inner for abstract inner product" begin T = ComplexF64 H = rand(T, N, N); H = H'*H + I; @@ -32,10 +32,9 @@ end for i in 2:n Y[i] = InnerProductVec(rand(T, N), ip) end - M = Matrix{T}(undef, n, n); BlockX = KrylovKit.BlockVec{T}(X) BlockY = KrylovKit.BlockVec{T}(Y) - KrylovKit.block_inner!(M, BlockX, BlockY); + M = KrylovKit.block_inner(BlockX, BlockY); Xm = hcat([X[i].vec for i in 1:n]...); Ym = hcat([Y[i].vec for i in 1:n]...); M0 = Xm' * H * Ym; From 4cf09ae2d0a5955f2dec46d71651fb8810dc26b5 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Wed, 30 Apr 2025 18:44:05 +0800 Subject: [PATCH 38/82] Added some comments, modified the parameters of , and formatted the file using the yas style. --- src/algorithms.jl | 17 +++---- src/eigsolve/lanczos.jl | 26 +++++----- src/factorizations/lanczos.jl | 94 ++++++++++++++++++++--------------- src/innerproductvec.jl | 5 +- test/BlockVec.jl | 40 ++++++++------- test/eigsolve.jl | 82 ++++++++++++++++-------------- test/factorize.jl | 20 ++++---- test/innerproductvec.jl | 45 +++++++++-------- test/orthonormal.jl | 41 +++++++-------- test/testsetup.jl | 2 +- 10 files changed, 199 insertions(+), 173 deletions(-) diff --git a/src/algorithms.jl b/src/algorithms.jl index d9d5b73b..a0221dd8 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -137,15 +137,14 @@ struct BlockLanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm eager::Bool verbosity::Int end -function BlockLanczos(; - krylovdim::Int=KrylovDefaults.blockkrylovdim[], - maxiter::Int=KrylovDefaults.blockmaxiter[], - tol::Real=KrylovDefaults.tol[], - orth::Orthogonalizer=KrylovDefaults.orth, - eager::Bool=false, - verbosity::Int=KrylovDefaults.verbosity[], - blocksize::Int=1, - qr_tol::Real=KrylovDefaults.tol[]) +function BlockLanczos(blocksize::Int; + krylovdim::Int=KrylovDefaults.blockkrylovdim[], + maxiter::Int=KrylovDefaults.blockmaxiter[], + tol::Real=KrylovDefaults.tol[], + orth::Orthogonalizer=KrylovDefaults.orth, + eager::Bool=false, + verbosity::Int=KrylovDefaults.verbosity[], + qr_tol::Real=KrylovDefaults.tol[]) blocksize < 1 && error("blocksize must be greater than 0") return BlockLanczos(orth, krylovdim, maxiter, tol, blocksize, qr_tol, eager, verbosity) end diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index d39eabcd..f333f742 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -151,7 +151,7 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::Lanczos; ConvergenceInfo(converged, residuals, normresiduals, numiter, numops) end -function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) where T +function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) where {T} maxiter = alg.maxiter krylovdim = alg.krylovdim if howmany > krylovdim @@ -165,7 +165,7 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) bs = length(block0) iter = BlockLanczosIterator(A, block0, krylovdim + bs, alg.qr_tol, alg.orth) - fact = initialize(iter; verbosity = verbosity) # Returns a BlockLanczosFactorization + fact = initialize(iter; verbosity=verbosity) # Returns a BlockLanczosFactorization S = eltype(fact.TDB) # The element type (Note: can be Complex) of the block tridiagonal matrix numops = 1 # Number of matrix-vector multiplications (for logging) numiter = 1 @@ -193,20 +193,20 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) TDB = view(fact.TDB, 1:K, 1:K) D, U = eigen(Hermitian(TDB)) by, rev = eigsort(which) - p = sortperm(D; by = by, rev = rev) - D, U = permuteeig!(D, U, p) + p = sortperm(D; by=by, rev=rev) + D, U = permuteeig!(D, U, p) howmany_actual = min(howmany, length(D)) - + # detect convergence by computing the residuals bs_r = fact.r_size # the block size of the residual (decreases as the iteration goes) r = fact.r[1:bs_r] - UU = U[end-bs_r+1:end, :] # the last bs_r rows of U, used to compute the residuals + UU = U[(end - bs_r + 1):end, :] # the last bs_r rows of U, used to compute the residuals normresiduals = map(1:howmany_actual) do i mul!(residuals[i], r[1], UU[1, i]) for j in 2:bs_r axpy!(UU[j, i], r[j], residuals[i]) end - norm(residuals[i]) + return norm(residuals[i]) end converged = count(nr -> nr <= tol, normresiduals) if converged >= howmany || β <= tol # successfully find enough eigenvalues @@ -217,7 +217,7 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) end if K < krylovdim - expand!(iter, fact; verbosity = verbosity) + expand!(iter, fact; verbosity=verbosity) numops += 1 else # shrink and restart numiter >= maxiter && break @@ -227,7 +227,7 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) # The last bs rows of U contribute to calculate errors of Ritz values. @inbounds for j in 1:keep H[j, j] = D[j] - H[bsn * bs + 1:end, j] = U[K - bs + 1:K, j] + H[(bsn * bs + 1):end, j] = U[(K - bs + 1):K, j] end # Turn diagonal matrix D into a block tridiagonal matrix, and make sure # the residual of krylov subspace keeps the form of [0,..,0,R] @@ -236,7 +236,7 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) H[j + bs, j] = ν H[j + bs, 1:(j - 1)] .= zero(eltype(H)) lmul!(h, H) - rmul!(view(H, 1:j + bs -1, :), h') + rmul!(view(H, 1:(j + bs - 1), :), h') rmul!(U, h') end # transform the basis and update the residual and update the TDB. @@ -244,9 +244,9 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) TDB[1:keep, 1:keep] .= H[1:keep, 1:keep] B = basis(fact) basistransform!(B, view(U, :, 1:keep)) - + r_new = OrthonormalBasis(fact.r.vec[1:bs_r]) - view_U = view(U, K - bs_r + 1:K, keep - bs_r + 1:keep) + view_U = view(U, (K - bs_r + 1):K, (keep - bs_r + 1):keep) basistransform!(r_new, view_U) fact.r.vec[1:bs_r] = r_new[1:bs_r] @@ -282,4 +282,4 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) return D[1:howmany_actual], vectors[1:howmany_actual], ConvergenceInfo(converged, residuals, normresiduals, numiter, numops) -end \ No newline at end of file +end diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index 7a84a31b..3371f40a 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -366,7 +366,6 @@ function lanczosrecurrence(operator, V::OrthonormalBasis, β, orth::ModifiedGram return w, α, β end - # block lanczos # The basic theory of the Block Lanczos algorithm can be referred to : Golub, G. H., & Van Loan, C. F. (2013). Matrix computations (4th ed., pp. 566–569). Johns Hopkins University Press. @@ -382,15 +381,22 @@ struct BlockVec{T,S<:Number} end Base.length(b::BlockVec) = length(b.vec) Base.getindex(b::BlockVec, i::Int) = b.vec[i] -Base.getindex(b::BlockVec{T, S}, idxs::AbstractVector{Int}) where {T, S} = BlockVec{S}([b.vec[i] for i in idxs]) +function Base.getindex(b::BlockVec{T,S}, idxs::AbstractVector{Int}) where {T,S} + return BlockVec{S}([b.vec[i] for i in idxs]) +end Base.setindex!(b::BlockVec{T}, v::T, i::Int) where {T} = (b.vec[i] = v) -Base.setindex!(b₁::BlockVec{T}, b₂::BlockVec{T}, idxs::AbstractVector{Int}) where {T} = (b₁.vec[idxs] = b₂.vec; b₁) -Base.copy!(b₁::BlockVec{T,S}, b₂::BlockVec{T,S}) where {T,S} = (copy!.(b₁.vec, b₂.vec); b₁) +function Base.setindex!(b₁::BlockVec{T}, b₂::BlockVec{T}, + idxs::AbstractVector{Int}) where {T} + (b₁.vec[idxs] = b₂.vec; + b₁) +end +Base.copy!(b₁::BlockVec{T,S}, b₂::BlockVec{T,S}) where {T,S} = (copy!.(b₁.vec, b₂.vec); + b₁) LinearAlgebra.norm(b::BlockVec) = norm(b.vec) -apply(f, block::BlockVec{T, S}) where {T, S} = BlockVec{S}([apply(f, x) for x in block.vec]) +apply(f, block::BlockVec{T,S}) where {T,S} = BlockVec{S}([apply(f, x) for x in block.vec]) function initialize(x₀, size::Int) S = typeof(inner(x₀, x₀)) - x₀_vec = [randn!(similar(x₀)) for _ in 1:size-1] + x₀_vec = [randn!(similar(x₀)) for _ in 1:(size - 1)] pushfirst!(x₀_vec, x₀) return BlockVec{S}(x₀_vec) end @@ -424,7 +430,8 @@ One can also query [`normres(fact)`](@ref) to obtain `norm(r)`, the norm of the See also [`BlockLanczosIterator`](@ref) for an iterator that constructs a progressively expanding BlockLanczos factorizations of a given linear map and a starting vector. """ -mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: BlockKrylovFactorization{T,S,SR} +mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: + BlockKrylovFactorization{T,S,SR} total_size::Int const V::OrthonormalBasis{T} # Block Lanczos Basis const TDB::AbstractMatrix{S} # TDB matrix, S is the matrix type @@ -490,10 +497,10 @@ struct BlockLanczosIterator{F,T,S,O<:Orthogonalizer} <: KrylovIterator{F,T} orth::O qr_tol::Real function BlockLanczosIterator{F,T,S,O}(operator::F, - x₀::BlockVec{T,S}, - maxdim::Int, - orth::O, - qr_tol::Real) where {F,T,S,O<:Orthogonalizer} + x₀::BlockVec{T,S}, + maxdim::Int, + orth::O, + qr_tol::Real) where {F,T,S,O<:Orthogonalizer} return new{F,T,S,O}(operator, x₀, maxdim, orth, qr_tol) end end @@ -501,13 +508,16 @@ function BlockLanczosIterator(operator::F, x₀::BlockVec{T,S}, maxdim::Int, qr_tol::Real, - orth::O=ModifiedGramSchmidt2()) where {F,T,S,O<:Orthogonalizer} + orth::O=ModifiedGramSchmidt2()) where {F,T,S, + O<:Orthogonalizer} norm(x₀) < qr_tol && @error "initial vector should not have norm zero" - orth != ModifiedGramSchmidt2() && @error "BlockLanczosIterator only supports ModifiedGramSchmidt2 orthogonalizer" + orth != ModifiedGramSchmidt2() && + @error "BlockLanczosIterator only supports ModifiedGramSchmidt2 orthogonalizer" return BlockLanczosIterator{F,T,S,O}(operator, x₀, maxdim, orth, qr_tol) -end +end -function initialize(iter::BlockLanczosIterator{F,T,S}; verbosity::Int=KrylovDefaults.verbosity[]) where {F,T,S} +function initialize(iter::BlockLanczosIterator{F,T,S}; + verbosity::Int=KrylovDefaults.verbosity[]) where {F,T,S} X₀ = iter.x₀ maxdim = iter.maxdim bs = length(X₀) # block size now @@ -523,24 +533,25 @@ function initialize(iter::BlockLanczosIterator{F,T,S}; verbosity::Int=KrylovDefa M₁ = block_inner(X₁, AX₁) TDB[1:bs, 1:bs] .= M₁ verbosity >= WARN_LEVEL && warn_nonhermitian(M₁) - - residual = block_mul!(AX₁, X₁, - M₁, S(1), S(1)) + + residual = block_mul!(AX₁, X₁, -M₁, S(1), S(1)) norm_r = norm(residual) if verbosity > EACHITERATION_LEVEL @info "Block Lanczos initiation at dimension $bs: subspace normres = $(normres2string(norm_r))" end return BlockLanczosFactorization(bs, - V, - TDB, - residual, - bs, - norm_r) + V, + TDB, + residual, + bs, + norm_r) end -function expand!(iter::BlockLanczosIterator{F,T,S}, state::BlockLanczosFactorization{T,S,SR}; +function expand!(iter::BlockLanczosIterator{F,T,S}, + state::BlockLanczosFactorization{T,S,SR}; verbosity::Int=KrylovDefaults.verbosity[]) where {F,T,S,SR} k = state.total_size - rₖ = state.r[1:state.r_size] + rₖ = state.r[1:(state.r_size)] bs_now = length(rₖ) V = state.V @@ -548,14 +559,14 @@ function expand!(iter::BlockLanczosIterator{F,T,S}, state::BlockLanczosFactoriza Bₖ, good_idx = abstract_qr!(rₖ, iter.qr_tol) bs_next = length(good_idx) push!(V, rₖ[good_idx]) - state.TDB[k+1:k+bs_next, k-bs_now+1:k] .= Bₖ - state.TDB[k-bs_now+1:k, k+1:k+bs_next] .= Bₖ' + state.TDB[(k + 1):(k + bs_next), (k - bs_now + 1):k] .= Bₖ + state.TDB[(k - bs_now + 1):k, (k + 1):(k + bs_next)] .= Bₖ' # Calculate the new residual and orthogonalize the new basis rₖnext, Mnext = blocklanczosrecurrence(iter.operator, V, Bₖ, iter.orth) verbosity >= WARN_LEVEL && warn_nonhermitian(Mnext) - state.TDB[k+1:k+bs_next, k+1:k+bs_next] .= Mnext + state.TDB[(k + 1):(k + bs_next), (k + 1):(k + bs_next)] .= Mnext state.r.vec[1:bs_next] .= rₖnext.vec state.norm_r = norm(rₖnext) state.total_size += bs_next @@ -566,32 +577,34 @@ function expand!(iter::BlockLanczosIterator{F,T,S}, state::BlockLanczosFactoriza end end -function blocklanczosrecurrence(operator, V::OrthonormalBasis, Bₖ::AbstractMatrix, orth::ModifiedGramSchmidt2) - # Apply the operator and calculate the M. Get Xnext and Mnext - bs,bs_last = size(Bₖ) +function blocklanczosrecurrence(operator, V::OrthonormalBasis, Bₖ::AbstractMatrix, + orth::ModifiedGramSchmidt2) + # Apply the operator and calculate the M. Get Xnext and Mnext. + bs, bs_last = size(Bₖ) S = eltype(Bₖ) k = length(V) - X = BlockVec{S}(V[k-bs+1:k]) + X = BlockVec{S}(V[(k - bs + 1):k]) AX = apply(operator, X) M = block_inner(X, AX) # Calculate the new residual. Get Rnext - Xlast = BlockVec{S}(V[k-bs_last-bs+1:k-bs]) + Xlast = BlockVec{S}(V[(k - bs_last - bs + 1):(k - bs)]) rₖnext = BlockVec{S}([similar(X[1]) for _ in 1:bs]) compute_residual!(rₖnext, AX, X, M, Xlast, Bₖ) ortho_basis!(rₖnext, V) return rₖnext, M end -function compute_residual!(r::BlockVec{T,S}, AX::BlockVec{T,S}, X::BlockVec{T,S}, M::AbstractMatrix, +function compute_residual!(r::BlockVec{T,S}, AX::BlockVec{T,S}, X::BlockVec{T,S}, + M::AbstractMatrix, X_prev::BlockVec{T,S}, B_prev::AbstractMatrix) where {T,S} @inbounds for j in 1:length(X) - r_j = r[j] + r_j = r[j] copy!(r_j, AX[j]) for i in 1:length(X) - axpy!(- M[i,j], X[i], r_j) + axpy!(-M[i, j], X[i], r_j) end for i in 1:length(X_prev) - axpy!(- B_prev[i,j], X_prev[i], r_j) + axpy!(-B_prev[i, j], X_prev[i], r_j) end end return r @@ -609,19 +622,20 @@ function ortho_basis!(basis::BlockVec{T,S}, basis_sofar::OrthonormalBasis{T}) wh end function warn_nonhermitian(M::AbstractMatrix) - if norm(M - M') > eps(real(eltype(M)))^(2/5) + if norm(M - M') > eps(real(eltype(M)))^(2 / 5) @warn "Enforce Hermiticity on the triangular diagonal blocks matrix, even though the operator may not be Hermitian." end end +# This is for block of abstract vectors and resolving the rank in block lanczos. function abstract_qr!(block::BlockVec{T,S}, tol::Real) where {T,S} n = length(block) rank_shrink = false - idx = ones(Int64,n) + idx = ones(Int64, n) R = zeros(S, n, n) @inbounds for j in 1:n αⱼ = block[j] - for i in 1:j-1 + for i in 1:(j - 1) R[i, j] = inner(block[i], αⱼ) αⱼ -= R[i, j] * block[i] end @@ -636,5 +650,5 @@ function abstract_qr!(block::BlockVec{T,S}, tol::Real) where {T,S} end end good_idx = findall(idx .> 0) - return R[good_idx,:], good_idx + return R[good_idx, :], good_idx end diff --git a/src/innerproductvec.jl b/src/innerproductvec.jl index 0de1dc26..a9abe445 100644 --- a/src/innerproductvec.jl +++ b/src/innerproductvec.jl @@ -128,7 +128,8 @@ end VectorInterface.norm(v::InnerProductVec) = sqrt(real(inner(v, v))) -function block_mul!(A::BlockVec{T,S}, B::BlockVec{T,S}, M::AbstractMatrix, alpha::Number, beta::Number) where {T,S} +function block_mul!(A::BlockVec{T,S}, B::BlockVec{T,S}, M::AbstractMatrix, alpha::Number, + beta::Number) where {T,S} @assert (length(B) == size(M, 1)) && (length(A) == size(M, 2)) "Matrix dimensions must match" @inbounds for i in 1:length(A) A[i] = beta * A[i] @@ -149,5 +150,3 @@ function block_inner(B₁::BlockVec{T,S}, B₂::BlockVec{T,S}) where {T,S} end return M end - - diff --git a/test/BlockVec.jl b/test/BlockVec.jl index 2f529fc4..1afe4cc9 100644 --- a/test/BlockVec.jl +++ b/test/BlockVec.jl @@ -1,52 +1,54 @@ @testset "apply on BlockVec" begin for T in [Float32, Float64, ComplexF64] - A0 = rand(T,N,N); - A0 = A0' * A0; - x₀ = KrylovKit.BlockVec{T}([rand(T,N) for _ in 1:n]) - for A in [A0, x -> A0*x] + A0 = rand(T, N, N) + A0 = A0' * A0 + x₀ = KrylovKit.BlockVec{T}([rand(T, N) for _ in 1:n]) + for A in [A0, x -> A0 * x] y = KrylovKit.apply(A, x₀) @test isapprox(hcat(y.vec...), A0 * hcat(x₀.vec...); atol=tolerance(T)) end end T = ComplexF64 - A0 = rand(T,N,N); + A0 = rand(T, N, N) A0 = A0' * A0 - f(x,y) = x' * A0 * y + f(x, y) = x' * A0 * y A(x::InnerProductVec) = KrylovKit.InnerProductVec(A0 * x[], x.dotf) - x₀ = KrylovKit.BlockVec{T}([InnerProductVec(rand(T,N), f) for _ in 1:n]) + x₀ = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), f) for _ in 1:n]) y = KrylovKit.apply(A, x₀) - @test isapprox(hcat([y[i].vec for i in 1:n]...), A0 * hcat([x₀[i].vec for i in 1:n]...); atol=tolerance(T)) + @test isapprox(hcat([y[i].vec for i in 1:n]...), A0 * hcat([x₀[i].vec for i in 1:n]...); + atol=tolerance(T)) end @testset "copy! for BlockVec" begin for T in [Float32, Float64, ComplexF32, ComplexF64] - x = KrylovKit.BlockVec{T}([rand(T,N) for _ in 1:n]) - y = KrylovKit.BlockVec{T}([rand(T,N) for _ in 1:n]) + x = KrylovKit.BlockVec{T}([rand(T, N) for _ in 1:n]) + y = KrylovKit.BlockVec{T}([rand(T, N) for _ in 1:n]) KrylovKit.copy!(y, x) @test isapprox(y.vec, x.vec; atol=tolerance(T)) end T = ComplexF64 - f = (x,y) -> x' * y - x = KrylovKit.BlockVec{T}([InnerProductVec(rand(T,N), f) for _ in 1:n]) - y = KrylovKit.BlockVec{T}([InnerProductVec(rand(T,N), f) for _ in 1:n]) + f = (x, y) -> x' * y + x = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), f) for _ in 1:n]) + y = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), f) for _ in 1:n]) KrylovKit.copy!(y, x) - @test isapprox([y.vec[i].vec for i in 1:n], [x.vec[i].vec for i in 1:n]; atol=tolerance(T)) + @test isapprox([y.vec[i].vec for i in 1:n], [x.vec[i].vec for i in 1:n]; + atol=tolerance(T)) end @testset "initialize for BlockVec" begin for T in [Float32, Float64, ComplexF32, ComplexF64] - block0 = KrylovKit.initialize(rand(T,N), n) + block0 = KrylovKit.initialize(rand(T, N), n) @test block0 isa KrylovKit.BlockVec @test length(block0) == n - @test Tuple(typeof(block0).parameters) == (Vector{T},T) + @test Tuple(typeof(block0).parameters) == (Vector{T}, T) end # test for abtract type T = ComplexF64 - f(x,y) = x' * y - x0 = InnerProductVec(rand(T,N), f) + f(x, y) = x' * y + x0 = InnerProductVec(rand(T, N), f) block0 = KrylovKit.initialize(x0, n) @test block0 isa KrylovKit.BlockVec @test length(block0) == n - @test Tuple(typeof(block0).parameters) == (typeof(x0),T) + @test Tuple(typeof(block0).parameters) == (typeof(x0), T) end diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 04f6fce3..96556ecd 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -360,9 +360,7 @@ end @test_logs (:info,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=2)) end - @testset "Block Lanczos - eigsolve for large sparse matrix and map input" begin - function toric_code_strings(m::Int, n::Int) li = LinearIndices((m, n)) bottom(i, j) = li[mod1(i, m), mod1(j, n)] + m * n @@ -378,7 +376,7 @@ end return xstrings, zstrings end - function pauli_kron(n::Int, ops::Pair{Int, Char}...) + function pauli_kron(n::Int, ops::Pair{Int,Char}...) mat = sparse(1.0I, 2^n, 2^n) for (pos, op) in ops if op == 'X' @@ -409,12 +407,12 @@ end H = spzeros(2^N, 2^N) # add the X-type operator terms - for xs in xstrings[1:(end-1)] + for xs in xstrings[1:(end - 1)] ops = [i => 'X' for i in xs] H += pauli_kron(N, ops...) end - for zs in zstrings[1:(end-1)] + for zs in zstrings[1:(end - 1)] ops = [i => 'Z' for i in zs] H += pauli_kron(N, ops...) end @@ -431,7 +429,7 @@ end h_mat = toric_code_hamiltonian_matrix(sites_num, sites_num) # matrix input - alg = BlockLanczos(;tol = tol, blocksize = p,maxiter = 1) + alg = BlockLanczos(p; tol=tol, maxiter=1) D, U, info = eigsolve(-h_mat, x₀, get_value_num, :SR, alg) @test count(x -> abs(x + 16.0) < 2.0 - tol, D[1:get_value_num]) == 4 @test count(x -> abs(x + 16.0) < tol, D[1:get_value_num]) == 4 @@ -467,23 +465,29 @@ As a result, I’ve decided to postpone dealing with the in-place test issue for n1 = div(n, 2) # eigenvalues to solve eigvalsA = eigvals(A0) for A in [A0, x -> A0 * x] - alg = BlockLanczos(; krylovdim = n, maxiter = 1, tol = tolerance(T), verbosity = 2, blocksize = block_size) + alg = BlockLanczos(block_size; krylovdim=n, maxiter=1, tol=tolerance(T), + verbosity=2) D1, V1, info = @test_logs (:info,) eigsolve(A, x₀, n1, :SR, alg) - alg = BlockLanczos(; krylovdim = n, maxiter = 1, tol = tolerance(T), verbosity = 1, blocksize = block_size) + alg = BlockLanczos(block_size; krylovdim=n, maxiter=1, tol=tolerance(T), + verbosity=1) @test_logs eigsolve(A, x₀, n1, :SR, alg) - alg = BlockLanczos(; krylovdim = n1 + 1, maxiter = 1, tol = tolerance(T), verbosity = 1, blocksize = block_size) + alg = BlockLanczos(block_size; krylovdim=n1 + 1, maxiter=1, tol=tolerance(T), + verbosity=1) @test_logs (:warn,) eigsolve(A, x₀, n1, :SR, alg) - alg = BlockLanczos(; krylovdim = n, maxiter = 1, tol = tolerance(T), verbosity = 2, blocksize = block_size) + alg = BlockLanczos(block_size; krylovdim=n, maxiter=1, tol=tolerance(T), + verbosity=2) @test_logs (:info,) eigsolve(A, x₀, n1, :SR, alg) - alg = BlockLanczos(; krylovdim = 3, maxiter = 3, tol = tolerance(T), verbosity = 3, blocksize = block_size) + alg = BlockLanczos(block_size; krylovdim=3, maxiter=3, tol=tolerance(T), + verbosity=3) + @test_logs((:info,), (:info,), (:info,), (:warn,), eigsolve(A, x₀, 1, :SR, alg)) + alg = BlockLanczos(block_size; krylovdim=4, maxiter=1, tol=tolerance(T), + verbosity=4) @test_logs((:info,), (:info,), (:info,), (:warn,), eigsolve(A, x₀, 1, :SR, alg)) - alg = BlockLanczos(; krylovdim = 4, maxiter=1, tol=tolerance(T), verbosity=4, blocksize=block_size) - @test_logs((:info,), (:info,),(:info,),(:warn,), eigsolve(A, x₀, 1, :SR, alg)) # To use blockmode, users have to explicitly set blockmode = true, we don't allow them to use eigselector. # Because of the _residual! function, I can't make sure the stability of types temporarily. # So I ignore the test of @constinferred n2 = n - n1 - alg = BlockLanczos(; krylovdim = 2 * n, maxiter = 4, tol = tolerance(T), blocksize = block_size) + alg = BlockLanczos(block_size; krylovdim=2 * n, maxiter=4, tol=tolerance(T)) D2, V2, info = eigsolve(A, x₀, n2, :LR, alg) D2[1:n2] @test vcat(D1[1:n1], reverse(D2[1:n2])) ≊ eigvalsA @@ -497,7 +501,8 @@ As a result, I’ve decided to postpone dealing with the in-place test issue for @test (x -> KrylovKit.apply(A, x)).(V1) ≈ D1 .* V1 @test (x -> KrylovKit.apply(A, x)).(V2) ≈ D2 .* V2 - alg = BlockLanczos(; krylovdim = 2n, maxiter = 1, tol = tolerance(T), verbosity = 1, blocksize = block_size) + alg = BlockLanczos(block_size; krylovdim=2n, maxiter=1, tol=tolerance(T), + verbosity=1) @test_logs (:warn,) (:warn,) eigsolve(A, x₀, n + 1, :LM, alg) end end @@ -512,9 +517,8 @@ end x₀ = normalize(rand(T, N)) eigvalsA = eigvals(A0) for A in [A0, x -> A0 * x] - alg = BlockLanczos(; krylovdim = N, maxiter = 10, tol = tolerance(T), - eager = true, verbosity = 0, - blocksize = block_size) + alg = BlockLanczos(block_size; krylovdim=N, maxiter=10, tol=tolerance(T), + eager=true, verbosity=0) D1, V1, info1 = eigsolve(A, x₀, n, :SR, alg) D2, V2, info2 = eigsolve(A, x₀, n, :LR, alg) @@ -524,18 +528,19 @@ end @test l1 > 0 @test l2 > 0 @test D1[1:l1] ≈ eigvalsA[1:l1] - @test D2[1:l2] ≈ eigvalsA[N:-1:(N-l2+1)] + @test D2[1:l2] ≈ eigvalsA[N:-1:(N - l2 + 1)] - U1 = hcat(V1[1:l1]...); - U2 = hcat(V2[1:l2]...); - R1 = hcat(info1.residual[1:l1]...); - R2 = hcat(info2.residual[1:l2]...); + U1 = hcat(V1[1:l1]...) + U2 = hcat(V2[1:l2]...) + R1 = hcat(info1.residual[1:l1]...) + R2 = hcat(info2.residual[1:l2]...) @test U1' * U1 ≈ I @test U2' * U2 ≈ I - @test hcat([KrylovKit.apply(A, U1[:, i]) for i in 1:l1]...) ≈ U1 * Diagonal(D1) + R1 - @test hcat([KrylovKit.apply(A, U2[:, i]) for i in 1:l2]...) ≈ U2 * Diagonal(D2) + R2 - + @test hcat([KrylovKit.apply(A, U1[:, i]) for i in 1:l1]...) ≈ + U1 * Diagonal(D1) + R1 + @test hcat([KrylovKit.apply(A, U2[:, i]) for i in 1:l2]...) ≈ + U2 * Diagonal(D2) + R2 end end end @@ -549,8 +554,9 @@ end Hip(x::Vector, y::Vector) = x' * H * y x₀ = InnerProductVec(rand(T, n), Hip) Aip(x::InnerProductVec) = InnerProductVec(H * x.vec, Hip) - D, V, info = eigsolve(Aip, x₀, eig_num, :SR, BlockLanczos(; krylovdim = n, maxiter = 1, tol = tolerance(T), - verbosity = 0, blocksize = block_size)) + D, V, info = eigsolve(Aip, x₀, eig_num, :SR, + BlockLanczos(block_size; krylovdim=n, maxiter=1, tol=tolerance(T), + verbosity=0)) D_true = eigvals(H) BlockV = KrylovKit.BlockVec{T}(V) @test D ≈ D_true[1:eig_num] @@ -562,12 +568,13 @@ end @testset "Complete Lanczos and Block Lanczos" begin @testset for T in [Float32, Float64, ComplexF32, ComplexF64] Random.seed!(6) - A0 = rand(T, (2N, 2N)) + A0 = rand(T, (2N, 2N)) A0 = (A0 + A0') / 2 block_size = 1 x₀ = rand(T, 2N) - alg1 = Lanczos(;krylovdim = 2n, maxiter = 10, tol = tolerance(T), verbosity = 1) - alg2 = BlockLanczos(;krylovdim = 2n, maxiter = 10, tol = tolerance(T), verbosity = 1, blocksize = block_size) + alg1 = Lanczos(; krylovdim=2n, maxiter=10, tol=tolerance(T), verbosity=1) + alg2 = BlockLanczos(block_size; krylovdim=2n, maxiter=10, tol=tolerance(T), + verbosity=1) for A in [A0, x -> A0 * x] evals1, _, info1 = eigsolve(A, x₀, n, :SR, alg1) evals2, _, info2 = eigsolve(A, x₀, n, :SR, alg2) @@ -576,12 +583,13 @@ end end @testset for T in [Float32, Float64, ComplexF32, ComplexF64] Random.seed!(6) - A0 = rand(T, (2N, 2N)) + A0 = rand(T, (2N, 2N)) A0 = (A0 + A0') / 2 block_size = 4 x₀ = rand(T, 2N) - alg1 = Lanczos(;krylovdim = 2n, maxiter = 10, tol = tolerance(T), verbosity = 1) - alg2 = BlockLanczos(;krylovdim = 2n, maxiter = 10, tol = tolerance(T), verbosity = 1, blocksize = block_size) + alg1 = Lanczos(; krylovdim=2n, maxiter=10, tol=tolerance(T), verbosity=1) + alg2 = BlockLanczos(block_size; krylovdim=2n, maxiter=10, tol=tolerance(T), + verbosity=1) for A in [A0, x -> A0 * x] evals1, _, info1 = eigsolve(A, x₀, n, :SR, alg1) evals2, _, info2 = eigsolve(A, x₀, n, :SR, alg2) @@ -600,13 +608,13 @@ end values0 = eigvals(A0)[1:n] n1 = n ÷ 2 for A in [A0, x -> A0 * x] - alg = KrylovKit.BlockLanczos(; krylovdim = 3*n÷2, maxiter = 1, tol = 1e-12, blocksize = block_size) + alg = BlockLanczos(block_size; krylovdim=3 * n ÷ 2, maxiter=1, tol=1e-12) values, _, _ = eigsolve(A, x₀, n, :SR, alg) error1 = norm(values[1:n1] - values0[1:n1]) - alg_shrink = KrylovKit.BlockLanczos(; krylovdim = 3*n÷2, maxiter = 2, tol = 1e-12, blocksize = block_size) + alg_shrink = BlockLanczos(block_size; krylovdim=3 * n ÷ 2, maxiter=2, tol=1e-12) values_shrink, _, _ = eigsolve(A, x₀, n, :SR, alg_shrink) error2 = norm(values_shrink[1:n1] - values0[1:n1]) @test error2 < error1 end end -end \ No newline at end of file +end diff --git a/test/factorize.jl b/test/factorize.jl index 94b4c1c5..f567eb49 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -313,15 +313,15 @@ end # fact = @constinferred initialize(iter) fact = initialize(iter) @constinferred expand!(iter, fact) - @test_logs initialize(iter; verbosity = EACHITERATION_LEVEL) - @test_logs (:info,) initialize(iter; verbosity = EACHITERATION_LEVEL + 1) + @test_logs initialize(iter; verbosity=EACHITERATION_LEVEL) + @test_logs (:info,) initialize(iter; verbosity=EACHITERATION_LEVEL + 1) verbosity = EACHITERATION_LEVEL + 1 while fact.total_size < n if verbosity == EACHITERATION_LEVEL + 1 - @test_logs (:info,) expand!(iter, fact; verbosity = verbosity) + @test_logs (:info,) expand!(iter, fact; verbosity=verbosity) verbosity = EACHITERATION_LEVEL else - @test_logs expand!(iter, fact; verbosity = verbosity) + @test_logs expand!(iter, fact; verbosity=verbosity) verbosity = EACHITERATION_LEVEL + 1 end end @@ -333,16 +333,16 @@ end v₀ = KrylovKit.BlockVec{T}([v₀m[:, i] for i in 1:bs]) iter = BlockLanczosIterator(B, v₀, N, qr_tol(T)) fact = initialize(iter) - @constinferred expand!(iter, fact; verbosity = 0) - @test_logs initialize(iter; verbosity = 0) + @constinferred expand!(iter, fact; verbosity=0) + @test_logs initialize(iter; verbosity=0) @test_logs (:warn,) initialize(iter) verbosity = 1 while fact.total_size < n if verbosity == 1 - @test_logs (:warn,) expand!(iter, fact; verbosity = verbosity) + @test_logs (:warn,) expand!(iter, fact; verbosity=verbosity) verbosity = 0 else - @test_logs expand!(iter, fact; verbosity = verbosity) + @test_logs expand!(iter, fact; verbosity=verbosity) verbosity = 1 end end @@ -371,11 +371,11 @@ end norm_r = fact.norm_r V = hcat(V0...) r = hcat(r0.vec...) - e = hcat(zeros(T, rs, k-rs), I) + e = hcat(zeros(T, rs, k - rs), I) @test V' * V ≈ I @test norm(r) ≈ norm_r @test A0 * V ≈ V * H + r * e end end end -end \ No newline at end of file +end diff --git a/test/innerproductvec.jl b/test/innerproductvec.jl index e76e2044..b83ba54f 100644 --- a/test/innerproductvec.jl +++ b/test/innerproductvec.jl @@ -2,7 +2,8 @@ block_inner(x,y): M[i,j] = inner(x[i],y[j]) =# -@testset "block_inner for non-full vectors $T" for T in (Float32, Float64, ComplexF32, ComplexF64) +@testset "block_inner for non-full vectors $T" for T in (Float32, Float64, ComplexF32, + ComplexF64) A = [rand(T, N) for _ in 1:n] B = [rand(T, N) for _ in 1:n] M = Matrix{T}(undef, n, n) @@ -11,40 +12,40 @@ M[i,j] = inner(x[i],y[j]) M = KrylovKit.block_inner(BlockA, BlockB) M0 = hcat(BlockA.vec...)' * hcat(BlockB.vec...) @test eltype(M) == T - @test isapprox(M, M0; atol = relax_tol(T)) + @test isapprox(M, M0; atol=relax_tol(T)) end @testset "block_inner for abstract inner product" begin T = ComplexF64 - H = rand(T, N, N); - H = H'*H + I; - H = (H + H')/2; - ip(x,y) = x'*H*y - X₁ = InnerProductVec(rand(T, N), ip); - X = [similar(X₁) for _ in 1:n]; - X[1] = X₁; + H = rand(T, N, N) + H = H' * H + I + H = (H + H') / 2 + ip(x, y) = x' * H * y + X₁ = InnerProductVec(rand(T, N), ip) + X = [similar(X₁) for _ in 1:n] + X[1] = X₁ for i in 2:n X[i] = InnerProductVec(rand(T, N), ip) end - Y₁ = InnerProductVec(rand(T, N), ip); - Y = [similar(Y₁) for _ in 1:n]; - Y[1] = Y₁; + Y₁ = InnerProductVec(rand(T, N), ip) + Y = [similar(Y₁) for _ in 1:n] + Y[1] = Y₁ for i in 2:n Y[i] = InnerProductVec(rand(T, N), ip) - end + end BlockX = KrylovKit.BlockVec{T}(X) BlockY = KrylovKit.BlockVec{T}(Y) - M = KrylovKit.block_inner(BlockX, BlockY); - Xm = hcat([X[i].vec for i in 1:n]...); - Ym = hcat([Y[i].vec for i in 1:n]...); - M0 = Xm' * H * Ym; + M = KrylovKit.block_inner(BlockX, BlockY) + Xm = hcat([X[i].vec for i in 1:n]...) + Ym = hcat([Y[i].vec for i in 1:n]...) + M0 = Xm' * H * Ym @test eltype(M) == T - @test isapprox(M, M0; atol = relax_tol(T)) + @test isapprox(M, M0; atol=relax_tol(T)) end @testset "block_mul!" begin T = ComplexF64 - f = x -> x'*x + f = x -> x' * x A = [InnerProductVec(rand(T, N), f) for _ in 1:n] Acopy = [InnerProductVec(rand(T, N), f) for _ in 1:n] KrylovKit.copy!(Acopy, A) @@ -55,5 +56,7 @@ end BlockA = KrylovKit.BlockVec{T}(A) BlockB = KrylovKit.BlockVec{T}(B) KrylovKit.block_mul!(BlockA, BlockB, M, alpha, beta) - @test isapprox(hcat([BlockA.vec[i].vec for i in 1:n]...), beta * hcat([Acopy[i].vec for i in 1:n]...) + alpha * hcat([BlockB.vec[i].vec for i in 1:n]...) * M; atol = tolerance(T)) -end \ No newline at end of file + @test isapprox(hcat([BlockA.vec[i].vec for i in 1:n]...), + beta * hcat([Acopy[i].vec for i in 1:n]...) + + alpha * hcat([BlockB.vec[i].vec for i in 1:n]...) * M; atol=tolerance(T)) +end diff --git a/test/orthonormal.jl b/test/orthonormal.jl index 43454aba..afc848b3 100644 --- a/test/orthonormal.jl +++ b/test/orthonormal.jl @@ -1,52 +1,53 @@ -@testset "abstract_qr! for non-full vectors $T" for T in (Float32, Float64, ComplexF32, ComplexF64) +@testset "abstract_qr! for non-full vectors $T" for T in (Float32, Float64, ComplexF32, + ComplexF64) A = rand(T, N, n) B = copy(A) Av = [A[:, i] for i in 1:size(A, 2)] # A is a non-full rank matrix - Av[n÷2] = sum(Av[n÷2+1:end] .* rand(T, n - n ÷ 2)) + Av[n ÷ 2] = sum(Av[(n ÷ 2 + 1):end] .* rand(T, n - n ÷ 2)) Bv = copy(Av) R, gi = KrylovKit.abstract_qr!(KrylovKit.BlockVec{T}(Av), qr_tol(T)) @test length(gi) < n @test eltype(R) == eltype(eltype(A)) == T - @test isapprox(hcat(Av[gi]...) * R, hcat(Bv...); atol = tolerance(T)) - @test isapprox(hcat(Av[gi]...)' * hcat(Av[gi]...), I; atol = tolerance(T)) + @test isapprox(hcat(Av[gi]...) * R, hcat(Bv...); atol=tolerance(T)) + @test isapprox(hcat(Av[gi]...)' * hcat(Av[gi]...), I; atol=tolerance(T)) end @testset "abstract_qr! for abstract inner product" begin T = ComplexF64 - H = rand(T, N, N); - H = H'*H + I; - H = (H + H')/2; - ip(x,y) = x'*H*y + H = rand(T, N, N) + H = H' * H + I + H = (H + H') / 2 + ip(x, y) = x' * H * y X₁ = InnerProductVec(rand(T, N), ip) - X = [similar(X₁) for _ in 1:n]; + X = [similar(X₁) for _ in 1:n] X[1] = X₁ - for i in 2:n-1 + for i in 2:(n - 1) X[i] = InnerProductVec(rand(T, N), ip) end # Make sure X is not full rank - X[end] = sum(X[1:end-1] .* rand(T, n-1)) + X[end] = sum(X[1:(end - 1)] .* rand(T, n - 1)) Xcopy = deepcopy(X) R, gi = KrylovKit.abstract_qr!(KrylovKit.BlockVec{T}(X), qr_tol(T)) @test length(gi) < n @test eltype(R) == T BlockX = KrylovKit.BlockVec{T}(X[gi]) - @test isapprox(KrylovKit.block_inner(BlockX,BlockX), I; atol=tolerance(T)) - ΔX = norm.(mul_test(X[gi],R) - Xcopy) + @test isapprox(KrylovKit.block_inner(BlockX, BlockX), I; atol=tolerance(T)) + ΔX = norm.(mul_test(X[gi], R) - Xcopy) @test isapprox(norm(ΔX), T(0); atol=tolerance(T)) end @testset "ortho_basis! for abstract inner product" begin T = ComplexF64 - H = rand(T, N, N); - H = H'*H + I; - H = (H + H')/2; - ip(x,y) = x'*H*y - + H = rand(T, N, N) + H = H' * H + I + H = (H + H') / 2 + ip(x, y) = x' * H * y + x₀ = [InnerProductVec(rand(T, N), ip) for i in 1:n] - x₁ = [InnerProductVec(rand(T, N), ip) for i in 1:2*n] + x₁ = [InnerProductVec(rand(T, N), ip) for i in 1:(2 * n)] b₀ = KrylovKit.BlockVec{T}(x₀) b₁ = KrylovKit.BlockVec{T}(x₁) KrylovKit.abstract_qr!(b₁, qr_tol(T)) @@ -54,4 +55,4 @@ end orthobasis_x₁ = KrylovKit.OrthonormalBasis(b₁.vec) KrylovKit.ortho_basis!(b₀, orthobasis_x₁) @test norm(KrylovKit.block_inner(b₀, b₁)) < tolerance(T) -end \ No newline at end of file +end diff --git a/test/testsetup.jl b/test/testsetup.jl index a55ce20c..6fdb22f0 100644 --- a/test/testsetup.jl +++ b/test/testsetup.jl @@ -84,7 +84,7 @@ end # block operations # ---------------- function mul_test(v::AbstractVector, A::AbstractMatrix) - return [sum(v .* A[:,i]) for i in axes(A,2)] + return [sum(v .* A[:, i]) for i in axes(A, 2)] end if VERSION < v"1.9" From 41d3a24ac1032cdff059f7c8a28fb77bb8fe66b4 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Wed, 30 Apr 2025 23:47:16 +0800 Subject: [PATCH 39/82] save --- src/factorizations/lanczos.jl | 30 ++++++++++++++++-------------- src/innerproductvec.jl | 12 ------------ test/eigsolve.jl | 11 +++++++++++ test/innerproductvec.jl | 33 +++++++++++++++++++-------------- test/orthonormal.jl | 11 ++++++----- test/testsetup.jl | 4 ++++ 6 files changed, 56 insertions(+), 45 deletions(-) diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index 3371f40a..d6baf32b 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -393,7 +393,7 @@ end Base.copy!(b₁::BlockVec{T,S}, b₂::BlockVec{T,S}) where {T,S} = (copy!.(b₁.vec, b₂.vec); b₁) LinearAlgebra.norm(b::BlockVec) = norm(b.vec) -apply(f, block::BlockVec{T,S}) where {T,S} = BlockVec{S}([apply(f, x) for x in block.vec]) +apply(f, block::BlockVec{T,S}) where {T,S} = BlockVec{S}([apply(f, block[i]) for i in 1:length(block)]) function initialize(x₀, size::Int) S = typeof(inner(x₀, x₀)) x₀_vec = [randn!(similar(x₀)) for _ in 1:(size - 1)] @@ -534,15 +534,20 @@ function initialize(iter::BlockLanczosIterator{F,T,S}; TDB[1:bs, 1:bs] .= M₁ verbosity >= WARN_LEVEL && warn_nonhermitian(M₁) - residual = block_mul!(AX₁, X₁, -M₁, S(1), S(1)) - norm_r = norm(residual) + # Get the first residual + for j in 1:length(X₁) + for i in 1:length(X₁) + add!!(AX₁[j], X₁[i], -M₁[i, j]) + end + end + norm_r = norm(AX₁) if verbosity > EACHITERATION_LEVEL @info "Block Lanczos initiation at dimension $bs: subspace normres = $(normres2string(norm_r))" end return BlockLanczosFactorization(bs, V, TDB, - residual, + AX₁, bs, norm_r) end @@ -588,26 +593,23 @@ function blocklanczosrecurrence(operator, V::OrthonormalBasis, Bₖ::AbstractMat M = block_inner(X, AX) # Calculate the new residual. Get Rnext Xlast = BlockVec{S}(V[(k - bs_last - bs + 1):(k - bs)]) - rₖnext = BlockVec{S}([similar(X[1]) for _ in 1:bs]) - compute_residual!(rₖnext, AX, X, M, Xlast, Bₖ) + rₖnext = compute_residual!(AX, X, M, Xlast, Bₖ) ortho_basis!(rₖnext, V) return rₖnext, M end -function compute_residual!(r::BlockVec{T,S}, AX::BlockVec{T,S}, X::BlockVec{T,S}, +function compute_residual!(AX::BlockVec{T,S}, X::BlockVec{T,S}, M::AbstractMatrix, X_prev::BlockVec{T,S}, B_prev::AbstractMatrix) where {T,S} @inbounds for j in 1:length(X) - r_j = r[j] - copy!(r_j, AX[j]) for i in 1:length(X) - axpy!(-M[i, j], X[i], r_j) + add!!(AX[j], X[i], -M[i, j]) end for i in 1:length(X_prev) - axpy!(-B_prev[i, j], X_prev[i], r_j) + add!!(AX[j], X_prev[i], -B_prev[i, j]) end end - return r + return AX end # This function is reserved for further improvement on case of vector of number input. @@ -637,12 +639,12 @@ function abstract_qr!(block::BlockVec{T,S}, tol::Real) where {T,S} αⱼ = block[j] for i in 1:(j - 1) R[i, j] = inner(block[i], αⱼ) - αⱼ -= R[i, j] * block[i] + add!!(αⱼ, block[i], -R[i, j]) end β = norm(αⱼ) if !(β ≤ tol) R[j, j] = β - block[j] = αⱼ / β + block[j] = scale(αⱼ, 1 / β) else block[j] *= S(0) rank_shrink = true diff --git a/src/innerproductvec.jl b/src/innerproductvec.jl index a9abe445..a4674052 100644 --- a/src/innerproductvec.jl +++ b/src/innerproductvec.jl @@ -128,18 +128,6 @@ end VectorInterface.norm(v::InnerProductVec) = sqrt(real(inner(v, v))) -function block_mul!(A::BlockVec{T,S}, B::BlockVec{T,S}, M::AbstractMatrix, alpha::Number, - beta::Number) where {T,S} - @assert (length(B) == size(M, 1)) && (length(A) == size(M, 2)) "Matrix dimensions must match" - @inbounds for i in 1:length(A) - A[i] = beta * A[i] - for j in 1:length(B) - A[i] += alpha * B[j] * M[j, i] - end - end - return A -end - function block_inner(B₁::BlockVec{T,S}, B₂::BlockVec{T,S}) where {T,S} M = Matrix{S}(undef, length(B₁.vec), length(B₂.vec)) @inbounds for j in 1:length(B₂) diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 96556ecd..5c5f747b 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -618,3 +618,14 @@ end end end end + + +A = rand(ComplexF64, (10, 10)) +A = (A + A') / 2 +x = rand(ComplexF64, 10) +mode = :inplace +alg = BlockLanczos(2; krylovdim=10, maxiter=1, tol=1e-12) +eigsolve(wrapop(A,Val(mode)), wrapvec(x,Val(mode)), 1, :SR, alg) +wx = wrapvec(x,Val(mode)) +similar(wx) +randn!(wx) diff --git a/test/innerproductvec.jl b/test/innerproductvec.jl index b83ba54f..8ad1c921 100644 --- a/test/innerproductvec.jl +++ b/test/innerproductvec.jl @@ -43,20 +43,25 @@ end @test isapprox(M, M0; atol=relax_tol(T)) end -@testset "block_mul!" begin +#@testset "compute_residual! for abstract inner product" begin T = ComplexF64 - f = x -> x' * x - A = [InnerProductVec(rand(T, N), f) for _ in 1:n] - Acopy = [InnerProductVec(rand(T, N), f) for _ in 1:n] - KrylovKit.copy!(Acopy, A) - B = [InnerProductVec(rand(T, N), f) for _ in 1:n] + A = rand(T, N, N) + A = A * A' + I + ip(x,y) = x' * A * y M = rand(T, n, n) - alpha = rand(T) - beta = rand(T) - BlockA = KrylovKit.BlockVec{T}(A) - BlockB = KrylovKit.BlockVec{T}(B) - KrylovKit.block_mul!(BlockA, BlockB, M, alpha, beta) - @test isapprox(hcat([BlockA.vec[i].vec for i in 1:n]...), - beta * hcat([Acopy[i].vec for i in 1:n]...) + - alpha * hcat([BlockB.vec[i].vec for i in 1:n]...) * M; atol=tolerance(T)) + M = M' + M + B = qr(rand(T, n, n)).R + X0 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) + X1 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) + AX1 = KrylovKit.apply(A, X1) + AX1copy = deepcopy(AX1) + KrylovKit.compute_residual!(AX1, X1, M, X0, B) + @test isapprox(hcat([AX1.vec[i] for i in 1:n]...), + hcat([AX1copy.vec[i] for i in 1:n]...) - + M * hcat([X1.vec[i] for i in 1:n]...) - + B * hcat([X0.vec[i] for i in 1:n]...); atol=tolerance(T)) end + +x = InnerProductVec(rand(T, N), ip) +A +KrylovKit.apply(A, x) \ No newline at end of file diff --git a/test/orthonormal.jl b/test/orthonormal.jl index afc848b3..4540289a 100644 --- a/test/orthonormal.jl +++ b/test/orthonormal.jl @@ -1,23 +1,24 @@ @testset "abstract_qr! for non-full vectors $T" for T in (Float32, Float64, ComplexF32, - ComplexF64) - A = rand(T, N, n) + ComplexF64) + Random.seed!(1234) + A = rand(T, n, n) B = copy(A) Av = [A[:, i] for i in 1:size(A, 2)] # A is a non-full rank matrix Av[n ÷ 2] = sum(Av[(n ÷ 2 + 1):end] .* rand(T, n - n ÷ 2)) - Bv = copy(Av) + Bv = deepcopy(Av) R, gi = KrylovKit.abstract_qr!(KrylovKit.BlockVec{T}(Av), qr_tol(T)) @test length(gi) < n @test eltype(R) == eltype(eltype(A)) == T + norm(hcat(Av[gi]...) * R - hcat(Bv...)) @test isapprox(hcat(Av[gi]...) * R, hcat(Bv...); atol=tolerance(T)) @test isapprox(hcat(Av[gi]...)' * hcat(Av[gi]...), I; atol=tolerance(T)) end -@testset "abstract_qr! for abstract inner product" begin +#@testset "abstract_qr! for abstract inner product" begin T = ComplexF64 H = rand(T, N, N) H = H' * H + I - H = (H + H') / 2 ip(x, y) = x' * H * y X₁ = InnerProductVec(rand(T, N), ip) X = [similar(X₁) for _ in 1:n] diff --git a/test/testsetup.jl b/test/testsetup.jl index 6fdb22f0..6dcd1344 100644 --- a/test/testsetup.jl +++ b/test/testsetup.jl @@ -7,6 +7,7 @@ export mul_test, qr_tol, relax_tol import VectorInterface as VI using VectorInterface using LinearAlgebra: LinearAlgebra +using Random # Utility functions # ----------------- @@ -55,6 +56,9 @@ function wrapvec(v, ::Val{mode}) where {mode} mode === :mixed ? MinimalSVec(v) : throw(ArgumentError("invalid mode ($mode)")) end +Base.copy!(v::MinimalVec{M}, w::MinimalVec{M}) where {M} = (copy!(v.vec, w.vec); v) +Base.similar(v::MinimalVec{M}) where {M} = MinimalVec{M}(similar(v.vec)) +Random.randn!(v::MinimalVec) = (randn!(v.vec); v) # For tests of block lanczos function wrapvec2(v, ::Val{mode}) where {mode} return mode === :mixed ? MinimalMVec(v) : wrapvec(v, mode) end From 3611d5b337880cd7a570afff1b13623547c9af20 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Thu, 1 May 2025 00:34:35 +0800 Subject: [PATCH 40/82] Blcok lanczos runs with :inplace --- src/eigsolve/lanczos.jl | 37 +++++++++++++++++------------------ src/factorizations/lanczos.jl | 1 + test/eigsolve.jl | 11 ----------- test/innerproductvec.jl | 17 +++++++--------- test/orthonormal.jl | 2 +- 5 files changed, 27 insertions(+), 41 deletions(-) diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index f333f742..faa1d229 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -170,12 +170,8 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) numops = 1 # Number of matrix-vector multiplications (for logging) numiter = 1 - # Preallocate space for eigenvectors and eigenvalues - vectors = [similar(x₀) for _ in 1:howmany] - residuals = [similar(x₀) for _ in 1:howmany] - converged = 0 - local howmany_actual, residuals, normresiduals, D, U + local normresiduals, D, U while true K = length(fact) @@ -195,19 +191,13 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) by, rev = eigsort(which) p = sortperm(D; by=by, rev=rev) D, U = permuteeig!(D, U, p) - howmany_actual = min(howmany, length(D)) # detect convergence by computing the residuals bs_r = fact.r_size # the block size of the residual (decreases as the iteration goes) - r = fact.r[1:bs_r] + r = residual(fact) UU = U[(end - bs_r + 1):end, :] # the last bs_r rows of U, used to compute the residuals - normresiduals = map(1:howmany_actual) do i - mul!(residuals[i], r[1], UU[1, i]) - for j in 2:bs_r - axpy!(UU[j, i], r[j], residuals[i]) - end - return norm(residuals[i]) - end + normresiduals = diag(UU' * block_inner(r,r) * UU) + normresiduals = sqrt.(real.(normresiduals)) converged = count(nr -> nr <= tol, normresiduals) if converged >= howmany || β <= tol # successfully find enough eigenvalues break @@ -258,12 +248,21 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) end end - K = length(fact) - V = view(fact.V.basis, 1:K) + howmany_actual = howmany + if converged > howmany + howmany_actual = howmany + elseif length(D) < howmany + howmany_actual = length(D) + end + UU = view(U, :, 1:howmany_actual) + vectors = let V = basis(fact) + [V * u for u in cols(UU)] + end + residuals = [zerovector(x₀) for _ in 1:howmany_actual] + V = basis(fact) @inbounds for i in 1:howmany_actual - copy!(vectors[i], V[1] * U[1, i]) - for j in 2:K - axpy!(U[j, i], V[j], vectors[i]) + for j in 1:length(fact) + add!!(residuals[i], V[j], U[j, i]) end end diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index d6baf32b..0cb1fae2 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -442,6 +442,7 @@ end Base.length(fact::BlockLanczosFactorization) = fact.total_size normres(fact::BlockLanczosFactorization) = fact.norm_r basis(fact::BlockLanczosFactorization) = fact.V +residual(fact::BlockLanczosFactorization) = fact.r[1:fact.r_size] """ struct BlockLanczosIterator{F,T,O<:Orthogonalizer} <: KrylovIterator{F,T} diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 5c5f747b..96556ecd 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -618,14 +618,3 @@ end end end end - - -A = rand(ComplexF64, (10, 10)) -A = (A + A') / 2 -x = rand(ComplexF64, 10) -mode = :inplace -alg = BlockLanczos(2; krylovdim=10, maxiter=1, tol=1e-12) -eigsolve(wrapop(A,Val(mode)), wrapvec(x,Val(mode)), 1, :SR, alg) -wx = wrapvec(x,Val(mode)) -similar(wx) -randn!(wx) diff --git a/test/innerproductvec.jl b/test/innerproductvec.jl index 8ad1c921..66c8dcf7 100644 --- a/test/innerproductvec.jl +++ b/test/innerproductvec.jl @@ -43,7 +43,7 @@ end @test isapprox(M, M0; atol=relax_tol(T)) end -#@testset "compute_residual! for abstract inner product" begin +@testset "compute_residual! for abstract inner product" begin T = ComplexF64 A = rand(T, N, N) A = A * A' + I @@ -53,15 +53,12 @@ end B = qr(rand(T, n, n)).R X0 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) X1 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) - AX1 = KrylovKit.apply(A, X1) + AX1 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) AX1copy = deepcopy(AX1) KrylovKit.compute_residual!(AX1, X1, M, X0, B) - @test isapprox(hcat([AX1.vec[i] for i in 1:n]...), - hcat([AX1copy.vec[i] for i in 1:n]...) - - M * hcat([X1.vec[i] for i in 1:n]...) - - B * hcat([X0.vec[i] for i in 1:n]...); atol=tolerance(T)) -end -x = InnerProductVec(rand(T, N), ip) -A -KrylovKit.apply(A, x) \ No newline at end of file + @test isapprox(hcat([AX1.vec[i].vec for i in 1:n]...), + hcat([AX1copy.vec[i].vec for i in 1:n]...) - + hcat([X1.vec[i].vec for i in 1:n]...) * M - + hcat([X0.vec[i].vec for i in 1:n]...) * B; atol=tolerance(T)) +end diff --git a/test/orthonormal.jl b/test/orthonormal.jl index 4540289a..d511dac4 100644 --- a/test/orthonormal.jl +++ b/test/orthonormal.jl @@ -15,7 +15,7 @@ @test isapprox(hcat(Av[gi]...)' * hcat(Av[gi]...), I; atol=tolerance(T)) end -#@testset "abstract_qr! for abstract inner product" begin +@testset "abstract_qr! for abstract inner product" begin T = ComplexF64 H = rand(T, N, N) H = H' * H + I From 0aa7eb1f136c4e87fa1990071178d32ecc2102b7 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Thu, 1 May 2025 01:43:16 +0800 Subject: [PATCH 41/82] run for :inplace and all origin tests pass. Now revise tests --- src/eigsolve/lanczos.jl | 25 +++++++++++++++++++------ test/eigsolve.jl | 24 +++++------------------- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index faa1d229..8e2b9641 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -250,22 +250,35 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) howmany_actual = howmany if converged > howmany - howmany_actual = howmany + howmany_actual = converged elseif length(D) < howmany howmany_actual = length(D) end - UU = view(U, :, 1:howmany_actual) + U1 = view(U, :, 1:howmany_actual) vectors = let V = basis(fact) - [V * u for u in cols(UU)] + [V * u for u in cols(U1)] end + bs_r = fact.r_size + K = length(fact) + U2 = view(U, K-bs_r+1:K, 1:howmany_actual) + R = fact.r residuals = [zerovector(x₀) for _ in 1:howmany_actual] - V = basis(fact) @inbounds for i in 1:howmany_actual - for j in 1:length(fact) - add!!(residuals[i], V[j], U[j, i]) + for j in 1:bs_r + add!(residuals[i], R[j], U2[j,i]) end end + r = fact.r[1:bs_r] + UU = U[(end - bs_r + 1):end, :] # the last bs_r rows of U, used to compute the residuals + normresiduals = map(1:howmany_actual) do i + mul!(residuals[i], r[1], UU[1, i]) + for j in 2:bs_r + axpy!(UU[j, i], r[j], residuals[i]) + end + return norm(residuals[i]) + end + if (converged < howmany) && verbosity >= WARN_LEVEL @warn """Block Lanczos eigsolve stopped without full convergence after $(K) iterations: * $converged eigenvalues converged diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 96556ecd..cc0c1272 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -440,20 +440,6 @@ end @test count(x -> abs(x + 16.0) < 1e-8, D[1:get_value_num]) == 4 end -#= -Why don’t I use the "vector", "inplace", or "outplace" modes here? -In the ordinary Lanczos algorithm, each iteration generates a single Lanczos vector, which is then added to the Lanczos basis. -However, in the block version, I can’t pre-allocate arrays when using the WrapOp type. If I stick with the WrapOp approach, -I would need to wrap each vector in a block individually as [v], which is significantly slower compared to pre-allocating. - -Therefore, instead of using the "vector", "inplace", or "outplace" modes defined in testsetup.jl, -I directly use matrices and define the map function manually for better performance. - -Currently, I only test the out-of-place map because testing the in-place version requires solving the pre-allocation issue. -In the future, I plan to add a non-Hermitian eigenvalue solver and implement more abstract types to improve efficiency. -As a result, I’ve decided to postpone dealing with the in-place test issue for now. -=# - # For user interface, we should give one vector input block lanczos method. @testset "Block Lanczos - eigsolve full" begin @testset for T in [Float32, Float64, ComplexF32, ComplexF64] @@ -527,8 +513,8 @@ end @test l1 > 0 @test l2 > 0 - @test D1[1:l1] ≈ eigvalsA[1:l1] - @test D2[1:l2] ≈ eigvalsA[N:-1:(N - l2 + 1)] + @test D1[1:n] ≈ eigvalsA[1:n] + @test D2[1:n] ≈ eigvalsA[N:-1:(N - n + 1)] U1 = hcat(V1[1:l1]...) U2 = hcat(V2[1:l2]...) @@ -559,7 +545,7 @@ end verbosity=0)) D_true = eigvals(H) BlockV = KrylovKit.BlockVec{T}(V) - @test D ≈ D_true[1:eig_num] + @test D[1:eig_num] ≈ D_true[1:eig_num] @test KrylovKit.block_inner(BlockV, BlockV) ≈ I @test findmax([norm(Aip(V[i]) - D[i] * V[i]) for i in 1:eig_num])[1] < tolerance(T) end @@ -578,7 +564,7 @@ end for A in [A0, x -> A0 * x] evals1, _, info1 = eigsolve(A, x₀, n, :SR, alg1) evals2, _, info2 = eigsolve(A, x₀, n, :SR, alg2) - @test min(info1.converged, n) == info2.converged + @test info1.converged == info2.converged end end @testset for T in [Float32, Float64, ComplexF32, ComplexF64] @@ -593,7 +579,7 @@ end for A in [A0, x -> A0 * x] evals1, _, info1 = eigsolve(A, x₀, n, :SR, alg1) evals2, _, info2 = eigsolve(A, x₀, n, :SR, alg2) - @test min(info1.converged, n) >= info2.converged + 1 + @test info1.converged >= info2.converged + 1 end end end From 040b648200e4e595e14f6d850e225b36ae619fec Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Thu, 1 May 2025 17:53:09 +0800 Subject: [PATCH 42/82] I have changed my code to keep the same style with original Lanczos method, and tests of Block Lanczos all pass for :vector, :inplace and :outplace modes --- src/eigsolve/lanczos.jl | 13 +- src/factorizations/lanczos.jl | 24 ++-- test/BlockVec.jl | 66 +++++----- test/eigsolve.jl | 233 ++++++++++++++++++---------------- test/factorize.jl | 127 +++++++++--------- test/innerproductvec.jl | 60 +++++---- test/orthonormal.jl | 58 ++++++--- test/testsetup.jl | 7 +- 8 files changed, 312 insertions(+), 276 deletions(-) diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index 8e2b9641..8ccc53dd 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -265,19 +265,10 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) residuals = [zerovector(x₀) for _ in 1:howmany_actual] @inbounds for i in 1:howmany_actual for j in 1:bs_r - add!(residuals[i], R[j], U2[j,i]) + residuals[i] = add!!(residuals[i], R[j], U2[j,i]) end end - - r = fact.r[1:bs_r] - UU = U[(end - bs_r + 1):end, :] # the last bs_r rows of U, used to compute the residuals - normresiduals = map(1:howmany_actual) do i - mul!(residuals[i], r[1], UU[1, i]) - for j in 2:bs_r - axpy!(UU[j, i], r[j], residuals[i]) - end - return norm(residuals[i]) - end + normresiduals = normresiduals[1:howmany_actual] if (converged < howmany) && verbosity >= WARN_LEVEL @warn """Block Lanczos eigsolve stopped without full convergence after $(K) iterations: diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index 0cb1fae2..aeec74bd 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -390,8 +390,6 @@ function Base.setindex!(b₁::BlockVec{T}, b₂::BlockVec{T}, (b₁.vec[idxs] = b₂.vec; b₁) end -Base.copy!(b₁::BlockVec{T,S}, b₂::BlockVec{T,S}) where {T,S} = (copy!.(b₁.vec, b₂.vec); - b₁) LinearAlgebra.norm(b::BlockVec) = norm(b.vec) apply(f, block::BlockVec{T,S}) where {T,S} = BlockVec{S}([apply(f, block[i]) for i in 1:length(block)]) function initialize(x₀, size::Int) @@ -406,6 +404,8 @@ function Base.push!(V::OrthonormalBasis{T}, b::BlockVec{T}) where {T} end return V end +Base.iterate(b::BlockVec) = iterate(b.vec) +Base.iterate(b::BlockVec, state) = iterate(b.vec, state) """ mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: BlockKrylovFactorization{T,S,SR} @@ -538,7 +538,7 @@ function initialize(iter::BlockLanczosIterator{F,T,S}; # Get the first residual for j in 1:length(X₁) for i in 1:length(X₁) - add!!(AX₁[j], X₁[i], -M₁[i, j]) + AX₁[j] = add!!(AX₁[j], X₁[i], -M₁[i, j]) end end norm_r = norm(AX₁) @@ -604,10 +604,10 @@ function compute_residual!(AX::BlockVec{T,S}, X::BlockVec{T,S}, X_prev::BlockVec{T,S}, B_prev::AbstractMatrix) where {T,S} @inbounds for j in 1:length(X) for i in 1:length(X) - add!!(AX[j], X[i], -M[i, j]) + AX[j] = add!!(AX[j], X[i], -M[i, j]) end for i in 1:length(X_prev) - add!!(AX[j], X_prev[i], -B_prev[i, j]) + AX[j] = add!!(AX[j], X_prev[i], -B_prev[i, j]) end end return AX @@ -616,9 +616,8 @@ end # This function is reserved for further improvement on case of vector of number input. function ortho_basis!(basis::BlockVec{T,S}, basis_sofar::OrthonormalBasis{T}) where {T,S} for i in 1:length(basis) - w = basis[i] for q in basis_sofar - orthogonalize!!(w, q, ModifiedGramSchmidt()) + basis[i], _ = orthogonalize!!(basis[i], q, ModifiedGramSchmidt()) end end return basis @@ -637,17 +636,16 @@ function abstract_qr!(block::BlockVec{T,S}, tol::Real) where {T,S} idx = ones(Int64, n) R = zeros(S, n, n) @inbounds for j in 1:n - αⱼ = block[j] for i in 1:(j - 1) - R[i, j] = inner(block[i], αⱼ) - add!!(αⱼ, block[i], -R[i, j]) + R[i, j] = inner(block[i], block[j]) + block[j] = add!!(block[j], block[i], -R[i, j]) end - β = norm(αⱼ) + β = norm(block[j]) if !(β ≤ tol) R[j, j] = β - block[j] = scale(αⱼ, 1 / β) + block[j] = scale(block[j], 1 / β) else - block[j] *= S(0) + block[j] = scale!!(block[j], 0) rank_shrink = true idx[j] = 0 end diff --git a/test/BlockVec.jl b/test/BlockVec.jl index 1afe4cc9..0d6334e0 100644 --- a/test/BlockVec.jl +++ b/test/BlockVec.jl @@ -1,46 +1,44 @@ @testset "apply on BlockVec" begin - for T in [Float32, Float64, ComplexF64] - A0 = rand(T, N, N) - A0 = A0' * A0 - x₀ = KrylovKit.BlockVec{T}([rand(T, N) for _ in 1:n]) - for A in [A0, x -> A0 * x] - y = KrylovKit.apply(A, x₀) - @test isapprox(hcat(y.vec...), A0 * hcat(x₀.vec...); atol=tolerance(T)) + for mode in (:vector, :inplace, :outplace) + scalartypes = mode === :vector ? (Float32, Float64, ComplexF64) : + (ComplexF64,) + @testset for T in scalartypes + mode = :inplace + T = ComplexF64 + A = rand(T, N, N) .- one(T) / 2 + A = (A + A') / 2 + wx₀ = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) + wy = KrylovKit.apply(wrapop(A, Val(mode)), wx₀) + y = unwrapvec.(wy) + x₀ = unwrapvec.(wx₀) + @test isapprox(hcat(y...), A * hcat(x₀...); atol=tolerance(T)) end end T = ComplexF64 - A0 = rand(T, N, N) - A0 = A0' * A0 - f(x, y) = x' * A0 * y - A(x::InnerProductVec) = KrylovKit.InnerProductVec(A0 * x[], x.dotf) + A = rand(T, N, N) .- one(T) / 2 + A = (A + A') / 2 + f(x, y) = x' * A * y + Af(x::InnerProductVec) = KrylovKit.InnerProductVec(A * x[], x.dotf) x₀ = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), f) for _ in 1:n]) - y = KrylovKit.apply(A, x₀) - @test isapprox(hcat([y[i].vec for i in 1:n]...), A0 * hcat([x₀[i].vec for i in 1:n]...); - atol=tolerance(T)) -end - -@testset "copy! for BlockVec" begin - for T in [Float32, Float64, ComplexF32, ComplexF64] - x = KrylovKit.BlockVec{T}([rand(T, N) for _ in 1:n]) - y = KrylovKit.BlockVec{T}([rand(T, N) for _ in 1:n]) - KrylovKit.copy!(y, x) - @test isapprox(y.vec, x.vec; atol=tolerance(T)) - end - T = ComplexF64 - f = (x, y) -> x' * y - x = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), f) for _ in 1:n]) - y = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), f) for _ in 1:n]) - KrylovKit.copy!(y, x) - @test isapprox([y.vec[i].vec for i in 1:n], [x.vec[i].vec for i in 1:n]; + y = KrylovKit.apply(Af, x₀) + @test isapprox(hcat([y[i].vec for i in 1:n]...), A * hcat([x₀[i].vec for i in 1:n]...); atol=tolerance(T)) end @testset "initialize for BlockVec" begin - for T in [Float32, Float64, ComplexF32, ComplexF64] - block0 = KrylovKit.initialize(rand(T, N), n) - @test block0 isa KrylovKit.BlockVec - @test length(block0) == n - @test Tuple(typeof(block0).parameters) == (Vector{T}, T) + for mode in (:vector, :inplace, :outplace) + scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : + (ComplexF64,) + @testset for T in scalartypes + block0 = KrylovKit.initialize(wrapvec(rand(T, N), Val(mode)), n) + @test block0 isa KrylovKit.BlockVec + @test length(block0) == n + Tv = mode === :vector ? Vector{T} : + mode === :inplace ? MinimalVec{true,Vector{T}} : + MinimalVec{false,Vector{T}} + + @test Tuple(typeof(block0).parameters) == (Tv, T) + end end # test for abtract type diff --git a/test/eigsolve.jl b/test/eigsolve.jl index cc0c1272..71deae71 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -440,94 +440,102 @@ end @test count(x -> abs(x + 16.0) < 1e-8, D[1:get_value_num]) == 4 end -# For user interface, we should give one vector input block lanczos method. -@testset "Block Lanczos - eigsolve full" begin - @testset for T in [Float32, Float64, ComplexF32, ComplexF64] +# For user interface, input is single vector. +@testset "Block Lanczos - eigsolve full $mode" for mode in (:vector, :inplace, :outplace) + scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : + (ComplexF64,) + @testset for T in scalartypes Random.seed!(6) - A0 = rand(T, (n, n)) .- one(T) / 2 - A0 = (A0 + A0') / 2 + A = rand(T, (n, n)) .- one(T) / 2 + A = (A + A') / 2 block_size = 2 x₀ = rand(T, n) n1 = div(n, 2) # eigenvalues to solve - eigvalsA = eigvals(A0) - for A in [A0, x -> A0 * x] - alg = BlockLanczos(block_size; krylovdim=n, maxiter=1, tol=tolerance(T), - verbosity=2) - D1, V1, info = @test_logs (:info,) eigsolve(A, x₀, n1, :SR, alg) - alg = BlockLanczos(block_size; krylovdim=n, maxiter=1, tol=tolerance(T), - verbosity=1) - @test_logs eigsolve(A, x₀, n1, :SR, alg) - alg = BlockLanczos(block_size; krylovdim=n1 + 1, maxiter=1, tol=tolerance(T), - verbosity=1) - @test_logs (:warn,) eigsolve(A, x₀, n1, :SR, alg) - alg = BlockLanczos(block_size; krylovdim=n, maxiter=1, tol=tolerance(T), - verbosity=2) - @test_logs (:info,) eigsolve(A, x₀, n1, :SR, alg) - alg = BlockLanczos(block_size; krylovdim=3, maxiter=3, tol=tolerance(T), - verbosity=3) - @test_logs((:info,), (:info,), (:info,), (:warn,), eigsolve(A, x₀, 1, :SR, alg)) - alg = BlockLanczos(block_size; krylovdim=4, maxiter=1, tol=tolerance(T), - verbosity=4) - @test_logs((:info,), (:info,), (:info,), (:warn,), eigsolve(A, x₀, 1, :SR, alg)) - # To use blockmode, users have to explicitly set blockmode = true, we don't allow them to use eigselector. - # Because of the _residual! function, I can't make sure the stability of types temporarily. - # So I ignore the test of @constinferred - n2 = n - n1 - alg = BlockLanczos(block_size; krylovdim=2 * n, maxiter=4, tol=tolerance(T)) - D2, V2, info = eigsolve(A, x₀, n2, :LR, alg) - D2[1:n2] - @test vcat(D1[1:n1], reverse(D2[1:n2])) ≊ eigvalsA - - U1 = hcat(V1...) - U2 = hcat(V2...) - - @test U1' * U1 ≈ I - @test U2' * U2 ≈ I - - @test (x -> KrylovKit.apply(A, x)).(V1) ≈ D1 .* V1 - @test (x -> KrylovKit.apply(A, x)).(V2) ≈ D2 .* V2 - - alg = BlockLanczos(block_size; krylovdim=2n, maxiter=1, tol=tolerance(T), - verbosity=1) - @test_logs (:warn,) (:warn,) eigsolve(A, x₀, n + 1, :LM, alg) - end + eigvalsA = eigvals(A) + alg = BlockLanczos(block_size; krylovdim=n, maxiter=1, tol=tolerance(T), + verbosity=2) + D1, V1, info = @test_logs (:info,) eigsolve(wrapop(A, Val(mode)), + wrapvec(x₀, Val(mode)), n1, :SR, alg) + alg = BlockLanczos(block_size; krylovdim=n, maxiter=1, tol=tolerance(T), + verbosity=1) + @test_logs eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n1, :SR, alg) + alg = BlockLanczos(block_size; krylovdim=n1 + 1, maxiter=1, tol=tolerance(T), + verbosity=1) + @test_logs (:warn,) eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n1, :SR, + alg) + alg = BlockLanczos(block_size; krylovdim=n, maxiter=1, tol=tolerance(T), + verbosity=2) + @test_logs (:info,) eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n1, :SR, + alg) + alg = BlockLanczos(block_size; krylovdim=3, maxiter=3, tol=tolerance(T), + verbosity=3) + @test_logs((:info,), (:info,), (:info,), (:warn,), + eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), 1, :SR, alg)) + alg = BlockLanczos(block_size; krylovdim=4, maxiter=1, tol=tolerance(T), + verbosity=4) + @test_logs((:info,), (:info,), (:info,), (:warn,), + eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), 1, :SR, alg)) + # To use blockmode, users have to explicitly set blockmode = true, we don't allow them to use eigselector. + # Because of the _residual! function, I can't make sure the stability of types temporarily. + # So I ignore the test of @constinferred + n2 = n - n1 + alg = BlockLanczos(block_size; krylovdim=2 * n, maxiter=4, tol=tolerance(T)) + D2, V2, info = eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n2, :LR, alg) + D2[1:n2] + @test vcat(D1[1:n1], reverse(D2[1:n2])) ≊ eigvalsA + + U1 = hcat(unwrapvec.(V1)...) + U2 = hcat(unwrapvec.(V2)...) + + @test U1' * U1 ≈ I + @test U2' * U2 ≈ I + + @test (x -> KrylovKit.apply(A, x)).(unwrapvec.(V1)) ≈ D1 .* unwrapvec.(V1) + @test (x -> KrylovKit.apply(A, x)).(unwrapvec.(V2)) ≈ D2 .* unwrapvec.(V2) + + alg = BlockLanczos(block_size; krylovdim=2n, maxiter=1, tol=tolerance(T), + verbosity=1) + @test_logs (:warn,) (:warn,) eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), + n + 1, :LM, alg) end end -@testset "Block Lanczos - eigsolve iteratively" begin - @testset for T in [Float32, Float64, ComplexF32, ComplexF64] +@testset "Block Lanczos - eigsolve iteratively $mode" for mode in + (:vector, :inplace, :outplace) + scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : + (ComplexF64,) + @testset for T in scalartypes Random.seed!(6) - A0 = rand(T, (N, N)) .- one(T) / 2 - A0 = (A0 + A0') / 2 + A = rand(T, (N, N)) .- one(T) / 2 + A = (A + A') / 2 block_size = 2 x₀ = normalize(rand(T, N)) - eigvalsA = eigvals(A0) - for A in [A0, x -> A0 * x] - alg = BlockLanczos(block_size; krylovdim=N, maxiter=10, tol=tolerance(T), - eager=true, verbosity=0) - D1, V1, info1 = eigsolve(A, x₀, n, :SR, alg) - D2, V2, info2 = eigsolve(A, x₀, n, :LR, alg) - - l1 = info1.converged - l2 = info2.converged - - @test l1 > 0 - @test l2 > 0 - @test D1[1:n] ≈ eigvalsA[1:n] - @test D2[1:n] ≈ eigvalsA[N:-1:(N - n + 1)] - - U1 = hcat(V1[1:l1]...) - U2 = hcat(V2[1:l2]...) - R1 = hcat(info1.residual[1:l1]...) - R2 = hcat(info2.residual[1:l2]...) - - @test U1' * U1 ≈ I - @test U2' * U2 ≈ I - @test hcat([KrylovKit.apply(A, U1[:, i]) for i in 1:l1]...) ≈ - U1 * Diagonal(D1) + R1 - @test hcat([KrylovKit.apply(A, U2[:, i]) for i in 1:l2]...) ≈ - U2 * Diagonal(D2) + R2 - end + eigvalsA = eigvals(A) + + alg = BlockLanczos(block_size; krylovdim=N, maxiter=10, tol=tolerance(T), + eager=true, verbosity=0) + D1, V1, info1 = eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n, :SR, alg) + D2, V2, info2 = eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n, :LR, alg) + + l1 = info1.converged + l2 = info2.converged + + @test l1 > 0 + @test l2 > 0 + @test D1[1:n] ≈ eigvalsA[1:n] + @test D2[1:n] ≈ eigvalsA[N:-1:(N - n + 1)] + + U1 = hcat(unwrapvec.(V1[1:l1])...) + U2 = hcat(unwrapvec.(V2[1:l2])...) + R1 = hcat(unwrapvec.(info1.residual[1:l1])...) + R2 = hcat(unwrapvec.(info2.residual[1:l2])...) + + @test U1' * U1 ≈ I + @test U2' * U2 ≈ I + @test hcat([KrylovKit.apply(A, U1[:, i]) for i in 1:l1]...) ≈ + U1 * Diagonal(D1) + R1 + @test hcat([KrylovKit.apply(A, U2[:, i]) for i in 1:l2]...) ≈ + U2 * Diagonal(D2) + R2 end end @@ -551,56 +559,63 @@ end end # with the same krylovdim, block lanczos has lower accuracy with blocksize >1. -@testset "Complete Lanczos and Block Lanczos" begin - @testset for T in [Float32, Float64, ComplexF32, ComplexF64] +@testset "Complete Lanczos and Block Lanczos $mode" for mode in + (:vector, :inplace, :outplace) + scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : + (ComplexF64,) + @testset for T in scalartypes Random.seed!(6) - A0 = rand(T, (2N, 2N)) - A0 = (A0 + A0') / 2 + A = rand(T, (2N, 2N)) + A = (A + A') / 2 block_size = 1 x₀ = rand(T, 2N) alg1 = Lanczos(; krylovdim=2n, maxiter=10, tol=tolerance(T), verbosity=1) alg2 = BlockLanczos(block_size; krylovdim=2n, maxiter=10, tol=tolerance(T), verbosity=1) - for A in [A0, x -> A0 * x] - evals1, _, info1 = eigsolve(A, x₀, n, :SR, alg1) - evals2, _, info2 = eigsolve(A, x₀, n, :SR, alg2) - @test info1.converged == info2.converged - end + evals1, _, info1 = eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n, :SR, + alg1) + evals2, _, info2 = eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n, :SR, + alg2) + @test info1.converged == info2.converged end - @testset for T in [Float32, Float64, ComplexF32, ComplexF64] + @testset for T in scalartypes Random.seed!(6) - A0 = rand(T, (2N, 2N)) - A0 = (A0 + A0') / 2 + A = rand(T, (2N, 2N)) + A = (A + A') / 2 block_size = 4 x₀ = rand(T, 2N) alg1 = Lanczos(; krylovdim=2n, maxiter=10, tol=tolerance(T), verbosity=1) alg2 = BlockLanczos(block_size; krylovdim=2n, maxiter=10, tol=tolerance(T), verbosity=1) - for A in [A0, x -> A0 * x] - evals1, _, info1 = eigsolve(A, x₀, n, :SR, alg1) - evals2, _, info2 = eigsolve(A, x₀, n, :SR, alg2) - @test info1.converged >= info2.converged + 1 - end + evals1, _, info1 = eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n, :SR, + alg1) + evals2, _, info2 = eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n, :SR, + alg2) + @test info1.converged >= info2.converged + 1 end end # Test effectiveness of shrink!() in block lanczos -@testset "Test effectiveness of shrink!() in block lanczos" begin - @testset for T in [Float32, Float64, ComplexF32, ComplexF64] - A0 = rand(T, (N, N)) - A0 = (A0 + A0') / 2 +@testset "Test effectiveness of shrink!() in block lanczos $mode" for mode in + (:vector, :inplace, + :outplace) + scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : + (ComplexF64,) + @testset for T in scalartypes + Random.seed!(6) + A = rand(T, (N, N)) .- one(T) / 2 + A = (A + A') / 2 block_size = 5 x₀ = rand(T, N) - values0 = eigvals(A0)[1:n] + values0 = eigvals(A)[1:n] n1 = n ÷ 2 - for A in [A0, x -> A0 * x] - alg = BlockLanczos(block_size; krylovdim=3 * n ÷ 2, maxiter=1, tol=1e-12) - values, _, _ = eigsolve(A, x₀, n, :SR, alg) - error1 = norm(values[1:n1] - values0[1:n1]) - alg_shrink = BlockLanczos(block_size; krylovdim=3 * n ÷ 2, maxiter=2, tol=1e-12) - values_shrink, _, _ = eigsolve(A, x₀, n, :SR, alg_shrink) - error2 = norm(values_shrink[1:n1] - values0[1:n1]) - @test error2 < error1 - end + alg = BlockLanczos(block_size; krylovdim=3 * n ÷ 2, maxiter=1, tol=1e-12) + values, _, _ = eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n, :SR, alg) + error1 = norm(values[1:n1] - values0[1:n1]) + alg_shrink = BlockLanczos(block_size; krylovdim=3 * n ÷ 2, maxiter=2, tol=1e-12) + values_shrink, _, _ = eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n, :SR, + alg_shrink) + error2 = norm(values_shrink[1:n1] - values0[1:n1]) + @test error2 < error1 end end diff --git a/test/factorize.jl b/test/factorize.jl index f567eb49..595c2304 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -298,84 +298,87 @@ end end # Test complete Block Lanczos factorization -@testset "Complete Block Lanczos factorization " begin +@testset "Complete Block Lanczos factorization " for mode in (:vector, :inplace, :outplace) + scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : + (ComplexF64,) using KrylovKit: EACHITERATION_LEVEL - @testset for T in [Float32, Float64, ComplexF32, ComplexF64] - A0 = rand(T, (N, N)) .- one(T) / 2 - A0 = (A0 + A0') / 2 + @testset for T in scalartypes + A = rand(T, (N, N)) + A = (A + A') / 2 block_size = 5 x₀m = Matrix(qr(rand(T, N, block_size)).Q) - x₀ = KrylovKit.BlockVec{T}([x₀m[:, i] for i in 1:block_size]) - eigvalsA = eigvals(A0) - for A in [A0, x -> A0 * x] - iter = BlockLanczosIterator(A, x₀, N, qr_tol(T)) - # TODO: Why type unstable? - # fact = @constinferred initialize(iter) + x₀ = KrylovKit.BlockVec{T}([wrapvec(x₀m[:, i], Val(mode)) for i in 1:block_size]) + eigvalsA = eigvals(A) + iter = BlockLanczosIterator(wrapop(A, Val(mode)), x₀, N, qr_tol(T)) + # TODO: Why type unstable? + fact = @constinferred initialize(iter) + @constinferred expand!(iter, fact) + @test_logs initialize(iter; verbosity=EACHITERATION_LEVEL) + @test_logs (:info,) initialize(iter; verbosity=EACHITERATION_LEVEL + 1) + verbosity = EACHITERATION_LEVEL + 1 + while fact.total_size < n + if verbosity == EACHITERATION_LEVEL + 1 + @test_logs (:info,) expand!(iter, fact; verbosity=verbosity) + verbosity = EACHITERATION_LEVEL + else + @test_logs expand!(iter, fact; verbosity=verbosity) + verbosity = EACHITERATION_LEVEL + 1 + end + end + + if T <: Complex + B = rand(T, (n, n)) # test warnings for non-hermitian matrices + bs = 2 + v₀m = Matrix(qr(rand(T, n, bs)).Q) + v₀ = KrylovKit.BlockVec{T}([wrapvec(v₀m[:, i], Val(mode)) for i in 1:bs]) + iter = BlockLanczosIterator(wrapop(B, Val(mode)), v₀, N, qr_tol(T)) fact = initialize(iter) - @constinferred expand!(iter, fact) - @test_logs initialize(iter; verbosity=EACHITERATION_LEVEL) - @test_logs (:info,) initialize(iter; verbosity=EACHITERATION_LEVEL + 1) - verbosity = EACHITERATION_LEVEL + 1 + @constinferred expand!(iter, fact; verbosity=0) + @test_logs initialize(iter; verbosity=0) + @test_logs (:warn,) initialize(iter) + verbosity = 1 while fact.total_size < n - if verbosity == EACHITERATION_LEVEL + 1 - @test_logs (:info,) expand!(iter, fact; verbosity=verbosity) - verbosity = EACHITERATION_LEVEL + if verbosity == 1 + @test_logs (:warn,) expand!(iter, fact; verbosity=verbosity) + verbosity = 0 else @test_logs expand!(iter, fact; verbosity=verbosity) - verbosity = EACHITERATION_LEVEL + 1 + verbosity = 1 end end end - - B = rand(T, (n, n)) # test warnings for non-hermitian matrices - bs = 2 - v₀m = Matrix(qr(rand(T, n, bs)).Q) - v₀ = KrylovKit.BlockVec{T}([v₀m[:, i] for i in 1:bs]) - iter = BlockLanczosIterator(B, v₀, N, qr_tol(T)) - fact = initialize(iter) - @constinferred expand!(iter, fact; verbosity=0) - @test_logs initialize(iter; verbosity=0) - @test_logs (:warn,) initialize(iter) - verbosity = 1 - while fact.total_size < n - if verbosity == 1 - @test_logs (:warn,) expand!(iter, fact; verbosity=verbosity) - verbosity = 0 - else - @test_logs expand!(iter, fact; verbosity=verbosity) - verbosity = 1 - end - end end end # Test incomplete Block Lanczos factorization -@testset "Incomplete Block Lanczos factorization " begin - @testset for T in [Float32, Float64, ComplexF32, ComplexF64] - A0 = rand(T, (N, N)) - A0 = (A0 + A0') / 2 +@testset "Incomplete Block Lanczos factorization " for mode in + (:vector, :inplace, :outplace) + scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : + (ComplexF64,) + @testset for T in scalartypes + A = rand(T, (N, N)) .- one(T) / 2 + A = (A + A') / 2 block_size = 5 x₀m = Matrix(qr(rand(T, N, block_size)).Q) - x₀ = KrylovKit.BlockVec{T}([x₀m[:, i] for i in 1:block_size]) - for A in [A0, x -> A0 * x] - iter = @constinferred BlockLanczosIterator(A, x₀, N, qr_tol(T)) - krylovdim = n - fact = initialize(iter) - while fact.norm_r > eps(float(real(T))) && fact.total_size < krylovdim - @constinferred expand!(iter, fact) - k = fact.total_size - rs = fact.r_size - V0 = fact.V[1:k] - r0 = fact.r[1:rs] - H = fact.TDB[1:k, 1:k] - norm_r = fact.norm_r - V = hcat(V0...) - r = hcat(r0.vec...) - e = hcat(zeros(T, rs, k - rs), I) - @test V' * V ≈ I - @test norm(r) ≈ norm_r - @test A0 * V ≈ V * H + r * e - end + x₀ = KrylovKit.BlockVec{T}([wrapvec(x₀m[:, i], Val(mode)) for i in 1:block_size]) + iter = @constinferred BlockLanczosIterator(wrapop(A, Val(mode)), x₀, N, qr_tol(T)) + krylovdim = n + fact = initialize(iter) + while fact.norm_r > eps(float(real(T))) && fact.total_size < krylovdim + @constinferred expand!(iter, fact) + k = fact.total_size + rs = fact.r_size + V0 = fact.V[1:k] + r0 = fact.r[1:rs] + H = fact.TDB[1:k, 1:k] + norm_r = fact.norm_r + V = hcat([unwrapvec(v) for v in V0]...) + r = hcat([unwrapvec(r0[i]) for i in 1:rs]...) + e = hcat(zeros(T, rs, k - rs), I) + norm(V' * V - I) + @test V' * V ≈ I + @test norm(r) ≈ norm_r + @test A * V ≈ V * H + r * e end end end diff --git a/test/innerproductvec.jl b/test/innerproductvec.jl index 66c8dcf7..8afd4973 100644 --- a/test/innerproductvec.jl +++ b/test/innerproductvec.jl @@ -2,17 +2,19 @@ block_inner(x,y): M[i,j] = inner(x[i],y[j]) =# -@testset "block_inner for non-full vectors $T" for T in (Float32, Float64, ComplexF32, - ComplexF64) - A = [rand(T, N) for _ in 1:n] - B = [rand(T, N) for _ in 1:n] - M = Matrix{T}(undef, n, n) - BlockA = KrylovKit.BlockVec{T}(A) - BlockB = KrylovKit.BlockVec{T}(B) - M = KrylovKit.block_inner(BlockA, BlockB) - M0 = hcat(BlockA.vec...)' * hcat(BlockB.vec...) - @test eltype(M) == T - @test isapprox(M, M0; atol=relax_tol(T)) +@testset "block_inner for non-full vectors $mode" for mode in (:vector, :inplace, :outplace) + scalartypes = mode === :vector ? (Float32, Float64, ComplexF64) : + (ComplexF64,) + @testset for T in scalartypes + A = [rand(T, N) for _ in 1:n] + B = [rand(T, N) for _ in 1:n] + M0 = hcat(A...)' * hcat(B...) + BlockA = KrylovKit.BlockVec{T}(wrapvec.(A, Val(mode))) + BlockB = KrylovKit.BlockVec{T}(wrapvec.(B, Val(mode))) + M = KrylovKit.block_inner(BlockA, BlockB) + @test eltype(M) == T + @test isapprox(M, M0; atol=relax_tol(T)) + end end @testset "block_inner for abstract inner product" begin @@ -21,18 +23,8 @@ end H = H' * H + I H = (H + H') / 2 ip(x, y) = x' * H * y - X₁ = InnerProductVec(rand(T, N), ip) - X = [similar(X₁) for _ in 1:n] - X[1] = X₁ - for i in 2:n - X[i] = InnerProductVec(rand(T, N), ip) - end - Y₁ = InnerProductVec(rand(T, N), ip) - Y = [similar(Y₁) for _ in 1:n] - Y[1] = Y₁ - for i in 2:n - Y[i] = InnerProductVec(rand(T, N), ip) - end + X = [InnerProductVec(rand(T, N), ip) for _ in 1:n] + Y = [InnerProductVec(rand(T, N), ip) for _ in 1:n] BlockX = KrylovKit.BlockVec{T}(X) BlockY = KrylovKit.BlockVec{T}(Y) M = KrylovKit.block_inner(BlockX, BlockY) @@ -43,11 +35,31 @@ end @test isapprox(M, M0; atol=relax_tol(T)) end +@testset "compute_residual! $mode" for mode in (:vector, :inplace, :outplace) + scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : + (ComplexF64,) + @testset for T in scalartypes + A = rand(T, N, N) .- one(T) / 2 + A = A + A' + M = rand(T, n, n) + M = M' + M + B = qr(rand(T, n, n)).R + X0 = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) + X1 = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) + AX1 = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) + AX1copy = deepcopy(AX1) + KrylovKit.compute_residual!(AX1, X1, M, X0, B) + _bw2m(X) = hcat(unwrapvec.(X)...) + @test isapprox(_bw2m(AX1), _bw2m(AX1copy) - _bw2m(X1) * M - _bw2m(X0) * B; + atol=tolerance(T)) + end +end + @testset "compute_residual! for abstract inner product" begin T = ComplexF64 A = rand(T, N, N) A = A * A' + I - ip(x,y) = x' * A * y + ip(x, y) = x' * A * y M = rand(T, n, n) M = M' + M B = qr(rand(T, n, n)).R diff --git a/test/orthonormal.jl b/test/orthonormal.jl index d511dac4..a538fc76 100644 --- a/test/orthonormal.jl +++ b/test/orthonormal.jl @@ -1,18 +1,25 @@ -@testset "abstract_qr! for non-full vectors $T" for T in (Float32, Float64, ComplexF32, - ComplexF64) - Random.seed!(1234) - A = rand(T, n, n) - B = copy(A) - Av = [A[:, i] for i in 1:size(A, 2)] - # A is a non-full rank matrix - Av[n ÷ 2] = sum(Av[(n ÷ 2 + 1):end] .* rand(T, n - n ÷ 2)) - Bv = deepcopy(Av) - R, gi = KrylovKit.abstract_qr!(KrylovKit.BlockVec{T}(Av), qr_tol(T)) - @test length(gi) < n - @test eltype(R) == eltype(eltype(A)) == T - norm(hcat(Av[gi]...) * R - hcat(Bv...)) - @test isapprox(hcat(Av[gi]...) * R, hcat(Bv...); atol=tolerance(T)) - @test isapprox(hcat(Av[gi]...)' * hcat(Av[gi]...), I; atol=tolerance(T)) +@testset "abstract_qr! for non-full vectors $mode" for mode in + (:vector, :inplace, :outplace) + scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : + (ComplexF64,) + @testset for T in scalartypes + A = rand(T, n, n) + B = copy(A) + Av = [A[:, i] for i in 1:size(A, 2)] + # A is a non-full rank matrix + Av[n ÷ 2] = sum(Av[(n ÷ 2 + 1):end] .* rand(T, n - n ÷ 2)) + Bv = deepcopy(Av) + wAv = wrapvec.(Av, Val(mode)) + R, gi = KrylovKit.abstract_qr!(KrylovKit.BlockVec{T}(wAv), qr_tol(T)) + Av1 = [unwrapvec(wAv[i]) for i in gi] + @test hcat(Av1...)' * hcat(Av1...) ≈ I + @test length(gi) < n + @test eltype(R) == eltype(eltype(A)) == T + + norm(hcat(Av1...) * R - hcat(Bv...)) + @test isapprox(hcat(Av1...) * R, hcat(Bv...); atol=tolerance(T)) + @test isapprox(hcat(Av1...)' * hcat(Av1...), I; atol=tolerance(T)) + end end @testset "abstract_qr! for abstract inner product" begin @@ -36,10 +43,28 @@ end @test eltype(R) == T BlockX = KrylovKit.BlockVec{T}(X[gi]) @test isapprox(KrylovKit.block_inner(BlockX, BlockX), I; atol=tolerance(T)) - ΔX = norm.(mul_test(X[gi], R) - Xcopy) + ΔX = norm.([sum(X[gi] .* R[:, i]) for i in 1:size(R, 2)] - Xcopy) @test isapprox(norm(ΔX), T(0); atol=tolerance(T)) end +@testset "ortho_basis! for non-full vectors $mode" for mode in + (:vector, :inplace, :outplace) + scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : + (ComplexF64,) + @testset for T in scalartypes + A = rand(T, n, n) .- one(T) / 2 + A = (A + A') / 2 + x₀ = [wrapvec(rand(T, N), Val(mode)) for i in 1:n] + x₁ = [wrapvec(rand(T, N), Val(mode)) for i in 1:(2 * n)] + b₀ = KrylovKit.BlockVec{T}(x₀) + b₁ = KrylovKit.BlockVec{T}(x₁) + KrylovKit.abstract_qr!(b₁, qr_tol(T)) + orthobasis_x₁ = KrylovKit.OrthonormalBasis(b₁.vec) + KrylovKit.ortho_basis!(b₀, orthobasis_x₁) + @test norm(KrylovKit.block_inner(b₀, b₁)) < tolerance(T) + end +end + @testset "ortho_basis! for abstract inner product" begin T = ComplexF64 H = rand(T, N, N) @@ -52,7 +77,6 @@ end b₀ = KrylovKit.BlockVec{T}(x₀) b₁ = KrylovKit.BlockVec{T}(x₁) KrylovKit.abstract_qr!(b₁, qr_tol(T)) - orthobasis_x₁ = KrylovKit.OrthonormalBasis(b₁.vec) KrylovKit.ortho_basis!(b₀, orthobasis_x₁) @test norm(KrylovKit.block_inner(b₀, b₁)) < tolerance(T) diff --git a/test/testsetup.jl b/test/testsetup.jl index 6dcd1344..ef66a1e3 100644 --- a/test/testsetup.jl +++ b/test/testsetup.jl @@ -2,7 +2,7 @@ module TestSetup export tolerance, ≊, MinimalVec, isinplace, stack export wrapop, wrapvec, unwrapvec, buildrealmap -export mul_test, qr_tol, relax_tol +export qr_tol, relax_tol import VectorInterface as VI using VectorInterface @@ -56,7 +56,6 @@ function wrapvec(v, ::Val{mode}) where {mode} mode === :mixed ? MinimalSVec(v) : throw(ArgumentError("invalid mode ($mode)")) end -Base.copy!(v::MinimalVec{M}, w::MinimalVec{M}) where {M} = (copy!(v.vec, w.vec); v) Base.similar(v::MinimalVec{M}) where {M} = MinimalVec{M}(similar(v.vec)) Random.randn!(v::MinimalVec) = (randn!(v.vec); v) # For tests of block lanczos function wrapvec2(v, ::Val{mode}) where {mode} @@ -87,10 +86,6 @@ end # block operations # ---------------- -function mul_test(v::AbstractVector, A::AbstractMatrix) - return [sum(v .* A[:, i]) for i in axes(A, 2)] -end - if VERSION < v"1.9" stack(f, itr) = mapreduce(f, hcat, itr) stack(itr) = reduce(hcat, itr) From c7196b9991e359bf8538c60b2bb27373dae0d55e Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Sat, 3 May 2025 16:17:53 +0800 Subject: [PATCH 43/82] test document --- docs/src/man/implementation.md | 2 ++ src/eigsolve/lanczos.jl | 8 ++++---- src/factorizations/lanczos.jl | 10 ++++++---- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/src/man/implementation.md b/docs/src/man/implementation.md index 70be79c0..9746f5fc 100644 --- a/docs/src/man/implementation.md +++ b/docs/src/man/implementation.md @@ -60,6 +60,7 @@ concrete implementations: ```@docs KrylovKit.KrylovFactorization KrylovKit.LanczosFactorization +KrylovKit.BlockLanczosFactorization KrylovKit.ArnoldiFactorization KrylovKit.GKLFactorization ``` @@ -98,6 +99,7 @@ respectively). These processes are represented as iterators in Julia: ```@docs KrylovKit.KrylovIterator KrylovKit.LanczosIterator +KrylovKit.BlockLanczosIterator KrylovKit.ArnoldiIterator ``` diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index 8ccc53dd..6fdb44e2 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -171,7 +171,7 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) numiter = 1 converged = 0 - local normresiduals, D, U + local normresiduals, D, U while true K = length(fact) @@ -196,7 +196,7 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) bs_r = fact.r_size # the block size of the residual (decreases as the iteration goes) r = residual(fact) UU = U[(end - bs_r + 1):end, :] # the last bs_r rows of U, used to compute the residuals - normresiduals = diag(UU' * block_inner(r,r) * UU) + normresiduals = diag(UU' * block_inner(r, r) * UU) normresiduals = sqrt.(real.(normresiduals)) converged = count(nr -> nr <= tol, normresiduals) if converged >= howmany || β <= tol # successfully find enough eigenvalues @@ -260,12 +260,12 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) end bs_r = fact.r_size K = length(fact) - U2 = view(U, K-bs_r+1:K, 1:howmany_actual) + U2 = view(U, (K - bs_r + 1):K, 1:howmany_actual) R = fact.r residuals = [zerovector(x₀) for _ in 1:howmany_actual] @inbounds for i in 1:howmany_actual for j in 1:bs_r - residuals[i] = add!!(residuals[i], R[j], U2[j,i]) + residuals[i] = add!!(residuals[i], R[j], U2[j, i]) end end normresiduals = normresiduals[1:howmany_actual] diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index aeec74bd..4c37ff8a 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -387,11 +387,13 @@ end Base.setindex!(b::BlockVec{T}, v::T, i::Int) where {T} = (b.vec[i] = v) function Base.setindex!(b₁::BlockVec{T}, b₂::BlockVec{T}, idxs::AbstractVector{Int}) where {T} - (b₁.vec[idxs] = b₂.vec; - b₁) + return (b₁.vec[idxs] = b₂.vec; + b₁) end LinearAlgebra.norm(b::BlockVec) = norm(b.vec) -apply(f, block::BlockVec{T,S}) where {T,S} = BlockVec{S}([apply(f, block[i]) for i in 1:length(block)]) +function apply(f, block::BlockVec{T,S}) where {T,S} + return BlockVec{S}([apply(f, block[i]) for i in 1:length(block)]) +end function initialize(x₀, size::Int) S = typeof(inner(x₀, x₀)) x₀_vec = [randn!(similar(x₀)) for _ in 1:(size - 1)] @@ -442,7 +444,7 @@ end Base.length(fact::BlockLanczosFactorization) = fact.total_size normres(fact::BlockLanczosFactorization) = fact.norm_r basis(fact::BlockLanczosFactorization) = fact.V -residual(fact::BlockLanczosFactorization) = fact.r[1:fact.r_size] +residual(fact::BlockLanczosFactorization) = fact.r[1:(fact.r_size)] """ struct BlockLanczosIterator{F,T,O<:Orthogonalizer} <: KrylovIterator{F,T} From d6abcf67c1b2ee231deb5c18c7cc9dfa11ff1a3a Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Sat, 3 May 2025 16:20:17 +0800 Subject: [PATCH 44/82] test document --- docs/make.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/make.jl b/docs/make.jl index 682fe453..6c4e9e8d 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -16,4 +16,4 @@ makedocs(; modules=[KrylovKit], "man/implementation.md"]], format=Documenter.HTML(; prettyurls=get(ENV, "CI", nothing) == "true")) -deploydocs(; repo="github.com/Jutho/KrylovKit.jl.git") +deploydocs(; repo="https://github.com/yuiyuiui/KrylovKit.jl") From b3e5c87728ecf28026d9714681cea1367398573d Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Sat, 3 May 2025 16:38:10 +0800 Subject: [PATCH 45/82] test document --- docs/make.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/make.jl b/docs/make.jl index 6c4e9e8d..e263c06b 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -16,4 +16,4 @@ makedocs(; modules=[KrylovKit], "man/implementation.md"]], format=Documenter.HTML(; prettyurls=get(ENV, "CI", nothing) == "true")) -deploydocs(; repo="https://github.com/yuiyuiui/KrylovKit.jl") +deploydocs(; repo="github.com/yuiyuiui/KrylovKit.jl.git") From 082e34b1ccfc183428ca9a43b6beae18c5b152f9 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Sat, 3 May 2025 16:42:36 +0800 Subject: [PATCH 46/82] test document --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 286628bc..5c1c926e 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ to vectors. | [![][docs-stable-img]][docs-stable-url] [![][docs-dev-img]][docs-dev-url] | [![][aqua-img]][aqua-url] [![CI][github-img]][github-url] [![][codecov-img]][codecov-url] | [![DOI][doi-img]][doi-url] | [![license][license-img]][license-url] | [docs-dev-img]: https://img.shields.io/badge/docs-dev-blue.svg -[docs-dev-url]: https://jutho.github.io/KrylovKit.jl/latest +[docs-dev-url]: https://yuiyuiui.github.io/KrylovKit.jl/latest [docs-stable-img]: https://img.shields.io/badge/docs-stable-blue.svg [docs-stable-url]: https://jutho.github.io/KrylovKit.jl/stable From 18c5ad05930f797f2486529f492d702ec08c3c6f Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Sat, 3 May 2025 17:34:30 +0800 Subject: [PATCH 47/82] add explanation of BlockLanczos lockLanczosFactorization and BlockLanczosIterator into docs --- README.md | 2 +- docs/make.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5c1c926e..286628bc 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ to vectors. | [![][docs-stable-img]][docs-stable-url] [![][docs-dev-img]][docs-dev-url] | [![][aqua-img]][aqua-url] [![CI][github-img]][github-url] [![][codecov-img]][codecov-url] | [![DOI][doi-img]][doi-url] | [![license][license-img]][license-url] | [docs-dev-img]: https://img.shields.io/badge/docs-dev-blue.svg -[docs-dev-url]: https://yuiyuiui.github.io/KrylovKit.jl/latest +[docs-dev-url]: https://jutho.github.io/KrylovKit.jl/latest [docs-stable-img]: https://img.shields.io/badge/docs-stable-blue.svg [docs-stable-url]: https://jutho.github.io/KrylovKit.jl/stable diff --git a/docs/make.jl b/docs/make.jl index e263c06b..682fe453 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -16,4 +16,4 @@ makedocs(; modules=[KrylovKit], "man/implementation.md"]], format=Documenter.HTML(; prettyurls=get(ENV, "CI", nothing) == "true")) -deploydocs(; repo="github.com/yuiyuiui/KrylovKit.jl.git") +deploydocs(; repo="github.com/Jutho/KrylovKit.jl.git") From f465f9eda8b2cdd1925f77760d96dc6f52042a34 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Tue, 6 May 2025 02:42:33 +0800 Subject: [PATCH 48/82] add document --- docs/src/index.md | 2 +- docs/src/man/algorithms.md | 1 + docs/src/man/eig.md | 2 +- docs/src/man/implementation.md | 10 ++++-- src/algorithms.jl | 22 ++++++++++++ src/factorizations/krylov.jl | 4 --- src/factorizations/lanczos.jl | 63 +++++++++++++++------------------- 7 files changed, 60 insertions(+), 44 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index c9e89734..512e2537 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -91,7 +91,7 @@ The following algorithms are currently implemented * `linsolve`: [`CG`](@ref), [`GMRES`](@ref), [`BiCGStab`](@ref) * `eigsolve`: a Krylov-Schur algorithm (i.e. with tick restarts) for extremal eigenvalues of normal (i.e. not generalized) eigenvalue problems, corresponding to - [`Lanczos`](@ref) for real symmetric or complex hermitian linear maps, and to + [`Lanczos`](@ref) and [`BlockLanczos`](@ref) for real symmetric or complex hermitian linear maps, and to [`Arnoldi`](@ref) for general linear maps. * `geneigsolve`: an customized implementation of the inverse-free algorithm of Golub and Ye for symmetric / hermitian generalized eigenvalue problems with positive definite diff --git a/docs/src/man/algorithms.md b/docs/src/man/algorithms.md index d6c3cd4f..8e948400 100644 --- a/docs/src/man/algorithms.md +++ b/docs/src/man/algorithms.md @@ -14,6 +14,7 @@ ModifiedGramSchmidtIR ## General Krylov algorithms ```@docs Lanczos +BlockLanczos Arnoldi ``` diff --git a/docs/src/man/eig.md b/docs/src/man/eig.md index bedceb3e..323a0a5d 100644 --- a/docs/src/man/eig.md +++ b/docs/src/man/eig.md @@ -25,7 +25,7 @@ using `schursolve`, for which only an 'expert' method call is available schursolve ``` Note that, for symmetric or hermitian linear maps, the eigenvalue and Schur factorization -are equivalent, and one should only use `eigsolve`. There is no `schursolve` using the `Lanczos` algorithm. +are equivalent, and one should only use `eigsolve`. There is no `schursolve` using the `Lanczos` or `BlockLanczos` algorithm. Another example of a possible use case of `schursolve` is if the linear map is known to have a unique eigenvalue of, e.g. largest magnitude. Then, if the linear map is real valued, that diff --git a/docs/src/man/implementation.md b/docs/src/man/implementation.md index 9746f5fc..1787bd13 100644 --- a/docs/src/man/implementation.md +++ b/docs/src/man/implementation.md @@ -46,6 +46,12 @@ the subspace spanned by it, can be computed using KrylovKit.basistransform! ``` +## BlockVec + +```@docs +KrylovKit.BlockVec +``` + ## Dense linear algebra KrylovKit relies on Julia's `LinearAlgebra` module from the standard library for most of its @@ -54,7 +60,7 @@ dense linear algebra dependencies. ## Factorization types The central ingredient in a Krylov based algorithm is a Krylov factorization or decomposition of a linear map. Such partial factorizations are represented as a -`KrylovFactorization`, of which `LanczosFactorization` and `ArnoldiFactorization` are two +`KrylovFactorization`, of which `LanczosFactorization`, `BlockLanczosFactorization` and `ArnoldiFactorization` are three concrete implementations: ```@docs @@ -93,7 +99,7 @@ KrylovKit.PackedHessenberg Given a linear map ``A`` and a starting vector ``x₀``, a Krylov factorization is obtained by sequentially building a Krylov subspace ``{x₀, A x₀, A² x₀, ...}``. Rather then using this set of vectors as a basis, an orthonormal basis is generated by a process known as -Lanczos or Arnoldi iteration (for symmetric/hermitian and for general matrices, +Lanczos, BlockLanczos or Arnoldi iteration (for symmetric/hermitian and for general matrices, respectively). These processes are represented as iterators in Julia: ```@docs diff --git a/src/algorithms.jl b/src/algorithms.jl index a0221dd8..3d0f2a6b 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -127,6 +127,28 @@ end # qr_tol is the tolerance that we think a vector is non-zero in abstract_qr! # This qr_tol will also be used in other zero_chcking in block Lanczos. +""" + BlockLanczos(blocksize::Int; + krylovdim=KrylovDefaults.blockkrylovdim[], + maxiter=KrylovDefaults.blockmaxiter[], + tol=KrylovDefaults.tol[], + orth=KrylovDefaults.orth, + eager=false, + verbosity=KrylovDefaults.verbosity[], + qr_tol::Real=KrylovDefaults.tol[]) + +Represents the BlockLanczos algorithm for building the Krylov subspace; assumes the linear +operator is real symmetric or complex Hermitian. Can be used in `eigsolve`. +`krylovdim, maxiter, tol, orth, eager, verbosity` are the same as `Lanczos`. +`qr_tol` is the tolerance that we think a vector is non-zero, mainly used in abstract_qr!. +`blocksize` must be specified by the user. The size of block shrinks during iteration. The folds of eigenvalues +BlockLanczos can capture are not more than the initial block size `blocksize`. + + +Use `Arnoldi` for non-symmetric or non-Hermitian linear operators. + +See also: `factorize`, `eigsolve`, `Arnoldi`, `Orthogonalizer` +""" struct BlockLanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm orth::O krylovdim::Int diff --git a/src/factorizations/krylov.jl b/src/factorizations/krylov.jl index c110c076..18be6885 100644 --- a/src/factorizations/krylov.jl +++ b/src/factorizations/krylov.jl @@ -29,10 +29,6 @@ factorizations of a given linear map and a starting vector. """ abstract type KrylovFactorization{T,S} end -# T is the type of the elements in the inner produnct space. -# S is the type of number field of the space. and SR is the real part of S. -abstract type BlockKrylovFactorization{T,S,SR} end - """ abstract type KrylovIterator{F,T} diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index 4c37ff8a..2f59e6b2 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -370,9 +370,15 @@ end # The basic theory of the Block Lanczos algorithm can be referred to : Golub, G. H., & Van Loan, C. F. (2013). Matrix computations (4th ed., pp. 566–569). Johns Hopkins University Press. -# We use BlockVec to store vectors as a block. Although now its fields are same to OrthonormalBasis, -# I plan to develop BlockVec a abstract of vector of number and inner product space, -# and process the block in the latter as a matrix with higher efficiency. +""" + struct BlockVec{T,S<:Number} + +Structure for storing vectors in a block format. The type parameter `T` represents the type of vector elements, +while `S` represents the type of inner products between vectors. Although the current implementation shares +the same field structure as `OrthonormalBasis`, future development plans include abstracting `BlockVec` to handle +both numerical vector blocks and `InnerProductVec` blocks, with optimized matrix-based +processing for improved computational efficiency in the former case. +""" struct BlockVec{T,S<:Number} vec::Vector{T} function BlockVec{S}(vec::Vector{T}) where {T,S<:Number} @@ -419,21 +425,21 @@ map `A` of the form A * V = V * B + r * b' ``` -For a given BlockLanczos factorization `fact` of length `k = length(fact)`, the basis `V` is -obtained via [`basis(fact)`](@ref basis) and is an instance of [`OrthonormalBasis{T}`](@ref -Basis), with also `length(V) == k` and where `T` denotes the type of vector like objects -used in the problem. The block tridiagonal matrix `B` is preallocated in BlockLanczosFactorization -and is of type `Hermitian{S<:Number}` with `size(B) == (k,k)`. The residual `r` is of type `T`. -One can also query [`normres(fact)`](@ref) to obtain `norm(r)`, the norm of the residual. The vector -`b` takes the default value ``e_k``, i.e. the matrix of size `(k,bs)` and an unit matrix in the last -`bs` rows and all zeros in the other rows. `bs` is the size of the last block. +For a given BlockLanczos factorization `fact`, length `k = length(fact)` and basis `V = basis(fact)` are +like Lanczos factorization. The block tridiagonal matrix `TDB` is preallocated in BlockLanczosFactorization +and is of type `Hermitian{S<:Number}` with `size(TDB) == (k,k)`. The residuals `r` is of type `Vector{T}`. +One can also query [`normres(fact)`](@ref) to obtain `norm(r)`, the norm of the residual. The matrix +`b` takes the default value ``[0;I]``, i.e. the matrix of size `(k,bs)` and an unit matrix in the last +`bs` rows and all zeros in the other rows. `bs` is the size of the last block. One can query [`r_size(fact)`] to obtain +size of the last block and the residuals. -`BlockLanczosFactorization` is mutable because it can [`expand!`](@ref) or [`shrink!`](@ref). +`BlockLanczosFactorization` is mutable because it can [`expand!`](@ref). But it does not support `shrink!` +because it is implemented in its `eigsolve`. See also [`BlockLanczosIterator`](@ref) for an iterator that constructs a progressively expanding BlockLanczos factorizations of a given linear map and a starting vector. """ mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: - BlockKrylovFactorization{T,S,SR} + KrylovFactorization{T,S} total_size::Int const V::OrthonormalBasis{T} # Block Lanczos Basis const TDB::AbstractMatrix{S} # TDB matrix, S is the matrix type @@ -448,16 +454,15 @@ residual(fact::BlockLanczosFactorization) = fact.r[1:(fact.r_size)] """ struct BlockLanczosIterator{F,T,O<:Orthogonalizer} <: KrylovIterator{F,T} - BlockLanczosIterator(f, x₀, [orth::Orthogonalizer = KrylovDefaults.orth, keepvecs::Bool = true]) + BlockLanczosIterator(f, x₀, maxdim, qr_tol, [orth::Orthogonalizer = KrylovDefaults.orth]) Iterator that takes a linear map `f::F` (supposed to be real symmetric or complex hermitian) -and an initial block `x₀::Vector{T}` and generates an expanding `BlockLanczosFactorization` thereof. In +and an initial block `x₀::BlockVec{T,S}` and generates an expanding `BlockLanczosFactorization` thereof. In particular, `BlockLanczosIterator` uses the [Block Lanczos iteration](https://en.wikipedia.org/wiki/Block_Lanczos_algorithm) scheme to build a successively expanding BlockLanczos factorization. While `f` cannot be tested to be symmetric or -hermitian directly when the linear map is encoded as a general callable object or function, -it is tested whether `block_inner(X, f.(X))` is sufficiently small to be -neglected. +hermitian directly when the linear map is encoded as a general callable object or function, with `block_inner(X, f.(X))`, +it is tested whether `norm(M-M')` is sufficiently small to be neglected. The argument `f` can be a matrix, or a function accepting a single argument `x`, so that `f(x)` implements the action of the linear map on the block `x`. @@ -466,32 +471,18 @@ The optional argument `orth` specifies which [`Orthogonalizer`](@ref) to be used default value in [`KrylovDefaults`](@ref) is to use [`ModifiedGramSchmidt2`](@ref), which uses reorthogonalization steps in every iteration. Now our orthogonalizer is only ModifiedGramSchmidt2. So we don't need to provide "keepvecs" because we have to reverse all krylove vectors. -Dimension of Krylov subspace in BlockLanczosIterator is usually much bigger than lanczos.So ClassicalGramSchmidt -and ModifiedGramSchmidt1 is numerically unstable. I don't add IR orthogonalizer because I find it sometimes unstable. -Householder reorthogonalization is theoretically stable and saves memory, but the algorithm I implemented is not stable. -In the future, I will add IR and Householder orthogonalizer. +Dimension of Krylov subspace in BlockLanczosIterator is usually much bigger than lanczos and its Default value is 100. +`qr_tol` is the tolerance with which we judge whether a vector is a zero vector. It's mainly used in `abstract_qr!`. When iterating over an instance of `BlockLanczosIterator`, the values being generated are -instances of [`BlockLanczosFactorization`](@ref), which can be destructured. For example as - -```julia -for (V, B, r, nr, b) in BlockLanczosIterator(f, x₀) - # do something - nr < tol && break # a typical stopping criterion -end -``` - -Since the iterator does not know the dimension of the underlying vector space of -objects of type `T`, it keeps expanding the Krylov subspace until the residual norm `nr` -falls below machine precision `eps(typeof(nr))`. +instances of [`BlockLanczosFactorization`](@ref). The internal state of `BlockLanczosIterator` is the same as the return value, i.e. the corresponding `BlockLanczosFactorization`. Here, [`initialize(::KrylovIterator)`](@ref) produces the first Krylov factorization, and `expand!(::KrylovIterator, ::KrylovFactorization)`(@ref) expands the -factorization in place. See also [`shrink!(::KrylovFactorization, k)`](@ref) to shrink an -existing factorization down to length `k`. +factorization in place. """ struct BlockLanczosIterator{F,T,S,O<:Orthogonalizer} <: KrylovIterator{F,T} operator::F From 2a8936045357e73e770966435ce6b080a3051ae0 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Tue, 6 May 2025 17:03:19 +0800 Subject: [PATCH 49/82] add more in document and revise a error in origin document --- docs/src/man/implementation.md | 14 ++++++++ src/algorithms.jl | 12 +++++-- src/eigsolve/eigsolve.jl | 4 +++ src/factorizations/lanczos.jl | 59 +++++++++++++++++++++++++++++----- 4 files changed, 79 insertions(+), 10 deletions(-) diff --git a/docs/src/man/implementation.md b/docs/src/man/implementation.md index 1787bd13..a31060c4 100644 --- a/docs/src/man/implementation.md +++ b/docs/src/man/implementation.md @@ -52,6 +52,20 @@ KrylovKit.basistransform! KrylovKit.BlockVec ``` +A block of vectors can be orthonormalized using +```@docs +KrylovKit.abstract_qr! +``` +This apply QR decomposition to a block of vectors using modified Gram-Schmidt process. + +Additional procedures applied to the block are as follows: +```@docs +KrylovKit.ortho_basis! +KrylovKit.compute_residual! +``` + + + ## Dense linear algebra KrylovKit relies on Julia's `LinearAlgebra` module from the standard library for most of its diff --git a/src/algorithms.jl b/src/algorithms.jl index 3d0f2a6b..2afc4bc7 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -144,8 +144,12 @@ operator is real symmetric or complex Hermitian. Can be used in `eigsolve`. `blocksize` must be specified by the user. The size of block shrinks during iteration. The folds of eigenvalues BlockLanczos can capture are not more than the initial block size `blocksize`. +In addition to utilizing `tol` as a convergence criterion, the Block Lanczos algorithm employs a supplementary convergence metric: +monitoring the number of converged eigenvalues at regular iteration intervals. +This metric refers to [Block Lanczos Method](https://www.netlib.org/utk/people/JackDongarra/etemplates/node250.html). -Use `Arnoldi` for non-symmetric or non-Hermitian linear operators. +Use `Arnoldi` for non-symmetric or non-Hermitian linear operators. A generalized Block Lanczos method for non-Hermitian linear operators, +as described in [Block Lanczos Method](https://www.netlib.org/utk/people/JackDongarra/etemplates/node250.html), is planned for future implementation. See also: `factorize`, `eigsolve`, `Arnoldi`, `Orthogonalizer` """ @@ -484,6 +488,8 @@ struct JacobiDavidson <: EigenSolver end const orth = KrylovKit.ModifiedGramSchmidtIR() const krylovdim = Ref(30) const maxiter = Ref(100) + const blockkrylovdim = Ref(100) + const blockmaxiter = Ref(2) const tol = Ref(1e-12) const verbosity = Ref(KrylovKit.WARN_LEVEL) end @@ -495,8 +501,10 @@ A module listing the default values for the typical parameters in Krylov based a - `krylovdim = 30`: the maximal dimension of the Krylov subspace that will be constructed - `maxiter = 100`: the maximal number of outer iterations, i.e. the maximum number of times the Krylov subspace may be rebuilt + - `blockkrylovdim = 100`: the maximal dimension of the Krylov subspace that will be constructed for block lanczos + - `blockmaxiter = 100`: the maximal number of outer iterations of block lanczos - `tol = 1e-12`: the tolerance to which the problem must be solved, based on a suitable - error measure, e.g. the norm of some residual. + error measure, e.g. the norm of some residual. It's also used as the default value of `qr_tol` !!! warning diff --git a/src/eigsolve/eigsolve.jl b/src/eigsolve/eigsolve.jl index 5d63ab9d..eee9ee7d 100644 --- a/src/eigsolve/eigsolve.jl +++ b/src/eigsolve/eigsolve.jl @@ -52,6 +52,10 @@ targeted. Valid specifications of `which` are given by numerical noise resulting from the orthogonalisation steps in the Lanczos or Arnoldi iteration. Nonetheless, it is important to take this into account and to try not to depend on this potentially fragile behaviour, especially for smaller problems. + The [`BlockLanczos`](@ref) method has been implemented to compute degenerate + eigenvalues and their corresponding eigenvectors. Given a block size parameter `p`, the + method can simultaneously determine `p`-fold degenerate eigenvalues and their + associated eigenvectors. The argument `T` acts as a hint in which `Number` type the computation should be performed, but is not restrictive. If the linear map automatically produces complex values, complex diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index 2f59e6b2..30091c33 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -119,7 +119,7 @@ end ``` Here, [`initialize(::KrylovIterator)`](@ref) produces the first Krylov factorization of -length 1, and `expand!(::KrylovIterator, ::KrylovFactorization)`(@ref) expands the +length 1, and [`expand!(iter::KrylovIterator, fact::KrylovFactorization)`](@ref) expands the factorization in place. See also factorization in place. See also [`initialize!(::KrylovIterator, ::KrylovFactorization)`](@ref) to initialize in an already existing factorization (most information will be discarded) and [`shrink!(::KrylovFactorization, k)`](@ref) to shrink an @@ -367,9 +367,6 @@ function lanczosrecurrence(operator, V::OrthonormalBasis, β, orth::ModifiedGram end # block lanczos - -# The basic theory of the Block Lanczos algorithm can be referred to : Golub, G. H., & Van Loan, C. F. (2013). Matrix computations (4th ed., pp. 566–569). Johns Hopkins University Press. - """ struct BlockVec{T,S<:Number} @@ -459,7 +456,7 @@ residual(fact::BlockLanczosFactorization) = fact.r[1:(fact.r_size)] Iterator that takes a linear map `f::F` (supposed to be real symmetric or complex hermitian) and an initial block `x₀::BlockVec{T,S}` and generates an expanding `BlockLanczosFactorization` thereof. In particular, `BlockLanczosIterator` uses the -[Block Lanczos iteration](https://en.wikipedia.org/wiki/Block_Lanczos_algorithm) scheme to build a +[BlockLanczos iteration](https://en.wikipedia.org/wiki/Block_Lanczos_algorithm) scheme to build a successively expanding BlockLanczos factorization. While `f` cannot be tested to be symmetric or hermitian directly when the linear map is encoded as a general callable object or function, with `block_inner(X, f.(X))`, it is tested whether `norm(M-M')` is sufficiently small to be neglected. @@ -481,7 +478,7 @@ The internal state of `BlockLanczosIterator` is the same as the return value, i. corresponding `BlockLanczosFactorization`. Here, [`initialize(::KrylovIterator)`](@ref) produces the first Krylov factorization, -and `expand!(::KrylovIterator, ::KrylovFactorization)`(@ref) expands the +and [`expand!(iter::KrylovIterator, fact::KrylovFactorization)`](@ref) expands the factorization in place. """ struct BlockLanczosIterator{F,T,S,O<:Orthogonalizer} <: KrylovIterator{F,T} @@ -592,6 +589,22 @@ function blocklanczosrecurrence(operator, V::OrthonormalBasis, Bₖ::AbstractMat return rₖnext, M end +""" + compute_residual!(AX::BlockVec{T,S}, X::BlockVec{T,S}, + M::AbstractMatrix, + X_prev::BlockVec{T,S}, B_prev::AbstractMatrix) where {T,S} + +This function computes the residual vector `AX` by subtracting the operator applied to `X` from `AX`, +and then subtracting the projection of `AX` onto the previously orthonormalized basis vectors in `X_prev`. +The result is stored in place in `AX`. + +``` + AX <- AX - X * M - X_prev * B_prev +``` + +Future versions of this function will incorporate optimized implementations tailored to different block types +(e.g., blocks of numerical vectors versus InnerProductVec objects) in order to enhance computational efficiency. +""" function compute_residual!(AX::BlockVec{T,S}, X::BlockVec{T,S}, M::AbstractMatrix, X_prev::BlockVec{T,S}, B_prev::AbstractMatrix) where {T,S} @@ -606,7 +619,21 @@ function compute_residual!(AX::BlockVec{T,S}, X::BlockVec{T,S}, return AX end -# This function is reserved for further improvement on case of vector of number input. +""" + ortho_basis!(basis::BlockVec{T,S}, basis_sofar::OrthonormalBasis{T}) where {T,S} + +This function orthogonalizes the vectors in `basis` with respect to the previously orthonormalized set `basis_sofar`. +Specifically, it modifies each vector `basis[i]` by projecting out its components along the directions spanned by `basis_sofar`, i.e., + +``` + basis[i] = basis[i] - sum(j=1:length(basis_sofar)) basis_sofar[j] +``` + +Here,`⟨·,·⟩` denotes the inner product. The function assumes that `basis_sofar` is already orthonormal. + +Future versions of this function will incorporate optimized implementations tailored to different block types +(e.g., blocks of numerical vectors versus InnerProductVec objects) in order to enhance computational efficiency. +""" function ortho_basis!(basis::BlockVec{T,S}, basis_sofar::OrthonormalBasis{T}) where {T,S} for i in 1:length(basis) for q in basis_sofar @@ -622,7 +649,23 @@ function warn_nonhermitian(M::AbstractMatrix) end end -# This is for block of abstract vectors and resolving the rank in block lanczos. +""" + abstract_qr!(block::BlockVec{T,S}, tol::Real) where {T,S} + +This function performs a QR factorization of a block of abstract vectors using the modified Gram-Schmidt process. + +``` + [v₁,..,vₚ] -> [u₁,..,uᵣ] * R +``` + +It takes as input a block of abstract vectors and a tolerance parameter, which is used to determine whether a vector is considered numerically zero. +The operation is performed in-place, transforming the input block into a block of orthonormal vectors. + +The function returns a matrix of size `(r, p)` and a vector of indices goodidx. Here, `p` denotes the number of input vectors, +and `r` is the numerical rank of the input block. The matrix represents the upper-triangular factor of the QR decomposition, +restricted to the `r` linearly independent components. The vector `goodidx` contains the indices of the non-zero +(i.e., numerically independent) vectors in the orthonormalized block. +""" function abstract_qr!(block::BlockVec{T,S}, tol::Real) where {T,S} n = length(block) rank_shrink = false From 01fddd9110b6f065f5751636b378a1548ff56c1a Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Wed, 7 May 2025 12:55:41 +0800 Subject: [PATCH 50/82] revise some reference, shrink!() to add --- docs/src/man/implementation.md | 5 ++++- src/algorithms.jl | 29 +++++++++++------------------ src/eigsolve/lanczos.jl | 6 +++--- src/factorizations/lanczos.jl | 26 +++++++++----------------- test/eigsolve.jl | 16 ++++++++-------- test/factorize.jl | 8 ++++---- test/testsetup.jl | 2 +- 7 files changed, 40 insertions(+), 52 deletions(-) diff --git a/docs/src/man/implementation.md b/docs/src/man/implementation.md index a31060c4..d36ac6b8 100644 --- a/docs/src/man/implementation.md +++ b/docs/src/man/implementation.md @@ -46,7 +46,10 @@ the subspace spanned by it, can be computed using KrylovKit.basistransform! ``` -## BlockVec +## Block Krylov method +The block version of the Krylov subspace algorithm is an approach to extending Krylov subspace techniques from single starting vectors to multiple starting vectors. It is mainly used for solving linear systems with degenerate dominant eigenvalues. + +In our implementation, the multiple-vector data structure is BlockVec, which implements the [`KrylovKit.abstract_qr!`](@ref), [`KrylovKit.ortho_basis!`](@ref), and [`KrylovKit.compute_residual!`](@ref) interfaces. ```@docs KrylovKit.BlockVec diff --git a/src/algorithms.jl b/src/algorithms.jl index 2afc4bc7..f1f9083b 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -125,31 +125,27 @@ function Lanczos(; return Lanczos(orth, krylovdim, maxiter, tol, eager, verbosity) end -# qr_tol is the tolerance that we think a vector is non-zero in abstract_qr! -# This qr_tol will also be used in other zero_chcking in block Lanczos. """ BlockLanczos(blocksize::Int; krylovdim=KrylovDefaults.blockkrylovdim[], - maxiter=KrylovDefaults.blockmaxiter[], + maxiter=KrylovDefaults.maxiter[], tol=KrylovDefaults.tol[], orth=KrylovDefaults.orth, eager=false, verbosity=KrylovDefaults.verbosity[], qr_tol::Real=KrylovDefaults.tol[]) -Represents the BlockLanczos algorithm for building the Krylov subspace; assumes the linear -operator is real symmetric or complex Hermitian. Can be used in `eigsolve`. -`krylovdim, maxiter, tol, orth, eager, verbosity` are the same as `Lanczos`. -`qr_tol` is the tolerance that we think a vector is non-zero, mainly used in abstract_qr!. -`blocksize` must be specified by the user. The size of block shrinks during iteration. The folds of eigenvalues -BlockLanczos can capture are not more than the initial block size `blocksize`. +The block version of [`Lanczos`](@ref) is suited for solving linear systems with degenerate dominant eigenvalues. +Arguments `krylovdim`, `maxiter`, `tol`, `orth`, `eager` and `verbosity` are the same as `Lanczos`. +`qr_tol` is the error tolerance for `abstract_qr!` - a subroutine used to orthorgonalize the vectors in the same block. +`blocksize` is the size of block, which shrinks during iterations. +The initial block size determines the maximum degeneracy of the target eigenvalue can be resolved. -In addition to utilizing `tol` as a convergence criterion, the Block Lanczos algorithm employs a supplementary convergence metric: +In addition to utilizing `tol` as a convergence criterion, the BlockLanczos algorithm employs a supplementary convergence metric: monitoring the number of converged eigenvalues at regular iteration intervals. -This metric refers to [Block Lanczos Method](https://www.netlib.org/utk/people/JackDongarra/etemplates/node250.html). +This metric refers to [BlockLanczos Method](https://www.netlib.org/utk/people/JackDongarra/etemplates/node250.html). -Use `Arnoldi` for non-symmetric or non-Hermitian linear operators. A generalized Block Lanczos method for non-Hermitian linear operators, -as described in [Block Lanczos Method](https://www.netlib.org/utk/people/JackDongarra/etemplates/node250.html), is planned for future implementation. +Use `Arnoldi` for non-symmetric or non-Hermitian linear operators. See also: `factorize`, `eigsolve`, `Arnoldi`, `Orthogonalizer` """ @@ -165,7 +161,7 @@ struct BlockLanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm end function BlockLanczos(blocksize::Int; krylovdim::Int=KrylovDefaults.blockkrylovdim[], - maxiter::Int=KrylovDefaults.blockmaxiter[], + maxiter::Int=KrylovDefaults.maxiter[], tol::Real=KrylovDefaults.tol[], orth::Orthogonalizer=KrylovDefaults.orth, eager::Bool=false, @@ -489,7 +485,6 @@ struct JacobiDavidson <: EigenSolver end const krylovdim = Ref(30) const maxiter = Ref(100) const blockkrylovdim = Ref(100) - const blockmaxiter = Ref(2) const tol = Ref(1e-12) const verbosity = Ref(KrylovKit.WARN_LEVEL) end @@ -501,8 +496,7 @@ A module listing the default values for the typical parameters in Krylov based a - `krylovdim = 30`: the maximal dimension of the Krylov subspace that will be constructed - `maxiter = 100`: the maximal number of outer iterations, i.e. the maximum number of times the Krylov subspace may be rebuilt - - `blockkrylovdim = 100`: the maximal dimension of the Krylov subspace that will be constructed for block lanczos - - `blockmaxiter = 100`: the maximal number of outer iterations of block lanczos + - `blockkrylovdim = 100`: the maximal dimension of the Krylov subspace that will be constructed for BlockLanczos - `tol = 1e-12`: the tolerance to which the problem must be solved, based on a suitable error measure, e.g. the norm of some residual. It's also used as the default value of `qr_tol` @@ -518,7 +512,6 @@ const orth = KrylovKit.ModifiedGramSchmidt2() # conservative choice const krylovdim = Ref(30) const maxiter = Ref(100) const blockkrylovdim = Ref(100) -const blockmaxiter = Ref(2) const tol = Ref(1e-12) const verbosity = Ref(KrylovKit.WARN_LEVEL) end diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index 6fdb44e2..8653f2af 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -202,7 +202,7 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) if converged >= howmany || β <= tol # successfully find enough eigenvalues break elseif verbosity >= EACHITERATION_LEVEL - @info "Block Lanczos eigsolve in iteration $numiter: $converged values converged, normres = $(normres2string(normresiduals))" + @info "BlockLanczos eigsolve in iteration $numiter: $converged values converged, normres = $(normres2string(normresiduals))" end end @@ -271,12 +271,12 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) normresiduals = normresiduals[1:howmany_actual] if (converged < howmany) && verbosity >= WARN_LEVEL - @warn """Block Lanczos eigsolve stopped without full convergence after $(K) iterations: + @warn """BlockLanczos eigsolve stopped without full convergence after $(K) iterations: * $converged eigenvalues converged * norm of residuals = $(normres2string(normresiduals)) * number of operations = $numops""" elseif verbosity >= STARTSTOP_LEVEL - @info """Block Lanczos eigsolve finished after $(K) iterations: + @info """BlockLanczos eigsolve finished after $(K) iterations: * $converged eigenvalues converged * norm of residuals = $(normres2string(normresiduals)) * number of operations = $numops""" diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index 30091c33..412b88d5 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -366,15 +366,12 @@ function lanczosrecurrence(operator, V::OrthonormalBasis, β, orth::ModifiedGram return w, α, β end -# block lanczos +# BlockLanczos """ struct BlockVec{T,S<:Number} Structure for storing vectors in a block format. The type parameter `T` represents the type of vector elements, -while `S` represents the type of inner products between vectors. Although the current implementation shares -the same field structure as `OrthonormalBasis`, future development plans include abstracting `BlockVec` to handle -both numerical vector blocks and `InnerProductVec` blocks, with optimized matrix-based -processing for improved computational efficiency in the former case. +while `S` represents the type of inner products between vectors. """ struct BlockVec{T,S<:Number} vec::Vector{T} @@ -415,7 +412,7 @@ Base.iterate(b::BlockVec, state) = iterate(b.vec, state) """ mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: BlockKrylovFactorization{T,S,SR} -Structure to store a block lanczos factorization of a real symmetric or complex hermitian linear +Structure to store a BlockLanczos factorization of a real symmetric or complex hermitian linear map `A` of the form ```julia @@ -423,7 +420,7 @@ A * V = V * B + r * b' ``` For a given BlockLanczos factorization `fact`, length `k = length(fact)` and basis `V = basis(fact)` are -like Lanczos factorization. The block tridiagonal matrix `TDB` is preallocated in BlockLanczosFactorization +like [`LanczosFactorization`](@ref). The block tridiagonal matrix `TDB` is preallocated in `BlockLanczosFactorization` and is of type `Hermitian{S<:Number}` with `size(TDB) == (k,k)`. The residuals `r` is of type `Vector{T}`. One can also query [`normres(fact)`](@ref) to obtain `norm(r)`, the norm of the residual. The matrix `b` takes the default value ``[0;I]``, i.e. the matrix of size `(k,bs)` and an unit matrix in the last @@ -438,7 +435,7 @@ BlockLanczos factorizations of a given linear map and a starting vector. mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: KrylovFactorization{T,S} total_size::Int - const V::OrthonormalBasis{T} # Block Lanczos Basis + const V::OrthonormalBasis{T} # BlockLanczos Basis const TDB::AbstractMatrix{S} # TDB matrix, S is the matrix type const r::BlockVec{T,S} # residual block r_size::Int # size of the residual block @@ -469,7 +466,7 @@ default value in [`KrylovDefaults`](@ref) is to use [`ModifiedGramSchmidt2`](@re uses reorthogonalization steps in every iteration. Now our orthogonalizer is only ModifiedGramSchmidt2. So we don't need to provide "keepvecs" because we have to reverse all krylove vectors. Dimension of Krylov subspace in BlockLanczosIterator is usually much bigger than lanczos and its Default value is 100. -`qr_tol` is the tolerance with which we judge whether a vector is a zero vector. It's mainly used in `abstract_qr!`. +`qr_tol` is the tolerance used in [`abstract_qr!`](@ref) to resolve the rank of a block of vectors. When iterating over an instance of `BlockLanczosIterator`, the values being generated are instances of [`BlockLanczosFactorization`](@ref). @@ -533,7 +530,7 @@ function initialize(iter::BlockLanczosIterator{F,T,S}; end norm_r = norm(AX₁) if verbosity > EACHITERATION_LEVEL - @info "Block Lanczos initiation at dimension $bs: subspace normres = $(normres2string(norm_r))" + @info "BlockLanczos initiation at dimension $bs: subspace normres = $(normres2string(norm_r))" end return BlockLanczosFactorization(bs, V, @@ -569,7 +566,7 @@ function expand!(iter::BlockLanczosIterator{F,T,S}, state.r_size = bs_next if verbosity > EACHITERATION_LEVEL - @info "Block Lanczos expansion to dimension $(state.total_size): subspace normres = $(normres2string(state.norm_r))" + @info "BlockLanczos expansion to dimension $(state.total_size): subspace normres = $(normres2string(state.norm_r))" end end @@ -602,8 +599,6 @@ The result is stored in place in `AX`. AX <- AX - X * M - X_prev * B_prev ``` -Future versions of this function will incorporate optimized implementations tailored to different block types -(e.g., blocks of numerical vectors versus InnerProductVec objects) in order to enhance computational efficiency. """ function compute_residual!(AX::BlockVec{T,S}, X::BlockVec{T,S}, M::AbstractMatrix, @@ -622,7 +617,7 @@ end """ ortho_basis!(basis::BlockVec{T,S}, basis_sofar::OrthonormalBasis{T}) where {T,S} -This function orthogonalizes the vectors in `basis` with respect to the previously orthonormalized set `basis_sofar`. +This function orthogonalizes the vectors in `basis` with respect to the previously orthonormalized set `basis_sofar` by using the modified Gram-Schmidt process. Specifically, it modifies each vector `basis[i]` by projecting out its components along the directions spanned by `basis_sofar`, i.e., ``` @@ -630,9 +625,6 @@ Specifically, it modifies each vector `basis[i]` by projecting out its component ``` Here,`⟨·,·⟩` denotes the inner product. The function assumes that `basis_sofar` is already orthonormal. - -Future versions of this function will incorporate optimized implementations tailored to different block types -(e.g., blocks of numerical vectors versus InnerProductVec objects) in order to enhance computational efficiency. """ function ortho_basis!(basis::BlockVec{T,S}, basis_sofar::OrthonormalBasis{T}) where {T,S} for i in 1:length(basis) diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 71deae71..485a894b 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -360,7 +360,7 @@ end @test_logs (:info,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=2)) end -@testset "Block Lanczos - eigsolve for large sparse matrix and map input" begin +@testset "BlockLanczos - eigsolve for large sparse matrix and map input" begin function toric_code_strings(m::Int, n::Int) li = LinearIndices((m, n)) bottom(i, j) = li[mod1(i, m), mod1(j, n)] + m * n @@ -441,7 +441,7 @@ end end # For user interface, input is single vector. -@testset "Block Lanczos - eigsolve full $mode" for mode in (:vector, :inplace, :outplace) +@testset "BlockLanczos - eigsolve full $mode" for mode in (:vector, :inplace, :outplace) scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @testset for T in scalartypes @@ -500,7 +500,7 @@ end end end -@testset "Block Lanczos - eigsolve iteratively $mode" for mode in +@testset "BlockLanczos - eigsolve iteratively $mode" for mode in (:vector, :inplace, :outplace) scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @@ -539,7 +539,7 @@ end end end -@testset "Block Lanczos - eigsolve for abstract type" begin +@testset "BlockLanczos - eigsolve for abstract type" begin T = ComplexF64 H = rand(T, (n, n)) H = H' * H + I @@ -558,8 +558,8 @@ end @test findmax([norm(Aip(V[i]) - D[i] * V[i]) for i in 1:eig_num])[1] < tolerance(T) end -# with the same krylovdim, block lanczos has lower accuracy with blocksize >1. -@testset "Complete Lanczos and Block Lanczos $mode" for mode in +# with the same krylovdim, BlockLanczos has lower accuracy with blocksize >1. +@testset "Complete Lanczos and BlockLanczos $mode" for mode in (:vector, :inplace, :outplace) scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @@ -595,8 +595,8 @@ end end end -# Test effectiveness of shrink!() in block lanczos -@testset "Test effectiveness of shrink!() in block lanczos $mode" for mode in +# Test effectiveness of shrink!() in BlockLanczos +@testset "Test effectiveness of shrink!() in BlockLanczos $mode" for mode in (:vector, :inplace, :outplace) scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : diff --git a/test/factorize.jl b/test/factorize.jl index 595c2304..9de7749f 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -297,8 +297,8 @@ end end end -# Test complete Block Lanczos factorization -@testset "Complete Block Lanczos factorization " for mode in (:vector, :inplace, :outplace) +# Test complete BlockLanczos factorization +@testset "Complete BlockLanczos factorization " for mode in (:vector, :inplace, :outplace) scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) using KrylovKit: EACHITERATION_LEVEL @@ -350,8 +350,8 @@ end end end -# Test incomplete Block Lanczos factorization -@testset "Incomplete Block Lanczos factorization " for mode in +# Test incomplete BlockLanczos factorization +@testset "Incomplete BlockLanczos factorization " for mode in (:vector, :inplace, :outplace) scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) diff --git a/test/testsetup.jl b/test/testsetup.jl index ef66a1e3..a14b43b1 100644 --- a/test/testsetup.jl +++ b/test/testsetup.jl @@ -57,7 +57,7 @@ function wrapvec(v, ::Val{mode}) where {mode} throw(ArgumentError("invalid mode ($mode)")) end Base.similar(v::MinimalVec{M}) where {M} = MinimalVec{M}(similar(v.vec)) -Random.randn!(v::MinimalVec) = (randn!(v.vec); v) # For tests of block lanczos +Random.randn!(v::MinimalVec) = (randn!(v.vec); v) # For tests of BlockLanczos function wrapvec2(v, ::Val{mode}) where {mode} return mode === :mixed ? MinimalMVec(v) : wrapvec(v, mode) end From da6f153add5c8b56f60641278b4566008907672c Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Wed, 7 May 2025 17:33:20 +0800 Subject: [PATCH 51/82] revise docstring --- src/algorithms.jl | 8 +++----- src/factorizations/lanczos.jl | 19 +++++++++++++------ test/eigsolve.jl | 8 ++++---- test/factorize.jl | 2 +- 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/algorithms.jl b/src/algorithms.jl index f1f9083b..c1c8e520 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -105,7 +105,7 @@ lack of convergence. Use `Arnoldi` for non-symmetric or non-Hermitian linear operators. -See also: `factorize`, `eigsolve`, `exponentiate`, `Arnoldi`, `Orthogonalizer` +See also: [Factorization types](@ref), [`eigsolve`](@ref), [`exponentiate`](@ref), [`Arnoldi`](@ref), [`Orthogonalizer`](@ref) """ struct Lanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm orth::O @@ -141,13 +141,11 @@ Arguments `krylovdim`, `maxiter`, `tol`, `orth`, `eager` and `verbosity` are the `blocksize` is the size of block, which shrinks during iterations. The initial block size determines the maximum degeneracy of the target eigenvalue can be resolved. -In addition to utilizing `tol` as a convergence criterion, the BlockLanczos algorithm employs a supplementary convergence metric: -monitoring the number of converged eigenvalues at regular iteration intervals. -This metric refers to [BlockLanczos Method](https://www.netlib.org/utk/people/JackDongarra/etemplates/node250.html). +The iteration stops when either the norm of residual is below tol or the enough eigenvectors are converged.[Reference](https://www.netlib.org/utk/people/JackDongarra/etemplates/node250.html) Use `Arnoldi` for non-symmetric or non-Hermitian linear operators. -See also: `factorize`, `eigsolve`, `Arnoldi`, `Orthogonalizer` +See also: [Factorization types](@ref), [`eigsolve`](@ref), [`Arnoldi`](@ref), [`Orthogonalizer`](@ref) """ struct BlockLanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm orth::O diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index 412b88d5..fbd2c2f6 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -453,8 +453,8 @@ residual(fact::BlockLanczosFactorization) = fact.r[1:(fact.r_size)] Iterator that takes a linear map `f::F` (supposed to be real symmetric or complex hermitian) and an initial block `x₀::BlockVec{T,S}` and generates an expanding `BlockLanczosFactorization` thereof. In particular, `BlockLanczosIterator` uses the -[BlockLanczos iteration](https://en.wikipedia.org/wiki/Block_Lanczos_algorithm) scheme to build a -successively expanding BlockLanczos factorization. While `f` cannot be tested to be symmetric or +BlockLanczos iteration(@footnote "Golub, G. H., & Van Loan, C. F. (2013). Matrix computations (4th ed.). Johns Hopkins University Press.") +scheme to build a successively expanding BlockLanczos factorization. While `f` cannot be tested to be symmetric or hermitian directly when the linear map is encoded as a general callable object or function, with `block_inner(X, f.(X))`, it is tested whether `norm(M-M')` is sufficiently small to be neglected. @@ -591,14 +591,21 @@ end M::AbstractMatrix, X_prev::BlockVec{T,S}, B_prev::AbstractMatrix) where {T,S} -This function computes the residual vector `AX` by subtracting the operator applied to `X` from `AX`, -and then subtracting the projection of `AX` onto the previously orthonormalized basis vectors in `X_prev`. -The result is stored in place in `AX`. +Computes the residual block and stores the result in `AX`. + +This function orthogonalizes `AX` against the two most recent basis blocks, `X` and `X_prev`. +Here, `AX` represents the image of the current block `X` under the action of the linear operator `A`. +The matrix `M` contains the inner products between `X` and `AX`, i.e., the projection of `AX` onto `X`. +Similarly, `B_prev` represents the projection of `AX` onto `X_prev`. + +The residual is computed as: ``` - AX <- AX - X * M - X_prev * B_prev + AX ← AX - X * M - X_prev * B_prev ``` +After this operation, `AX` is orthogonal (in the block inner product sense) to both `X` and `X_prev`. + """ function compute_residual!(AX::BlockVec{T,S}, X::BlockVec{T,S}, M::AbstractMatrix, diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 485a894b..e46a61dd 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -501,7 +501,7 @@ end end @testset "BlockLanczos - eigsolve iteratively $mode" for mode in - (:vector, :inplace, :outplace) + (:vector, :inplace, :outplace) scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @testset for T in scalartypes @@ -560,7 +560,7 @@ end # with the same krylovdim, BlockLanczos has lower accuracy with blocksize >1. @testset "Complete Lanczos and BlockLanczos $mode" for mode in - (:vector, :inplace, :outplace) + (:vector, :inplace, :outplace) scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @testset for T in scalartypes @@ -597,8 +597,8 @@ end # Test effectiveness of shrink!() in BlockLanczos @testset "Test effectiveness of shrink!() in BlockLanczos $mode" for mode in - (:vector, :inplace, - :outplace) + (:vector, :inplace, + :outplace) scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @testset for T in scalartypes diff --git a/test/factorize.jl b/test/factorize.jl index 9de7749f..03634ce4 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -352,7 +352,7 @@ end # Test incomplete BlockLanczos factorization @testset "Incomplete BlockLanczos factorization " for mode in - (:vector, :inplace, :outplace) + (:vector, :inplace, :outplace) scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @testset for T in scalartypes From dd8f098485b72d8dece7301877f6a20f1f933b4d Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Wed, 7 May 2025 21:08:48 +0800 Subject: [PATCH 52/82] fix bug and all tests pass --- Project.toml | 1 + src/factorizations/lanczos.jl | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 6e207ef8..675a075e 100644 --- a/Project.toml +++ b/Project.toml @@ -31,6 +31,7 @@ TestExtras = "0.2,0.3" VectorInterface = "0.5" Zygote = "0.6" julia = "1.6" +SparseArrays = "1" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index fbd2c2f6..8fdce705 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -581,7 +581,7 @@ function blocklanczosrecurrence(operator, V::OrthonormalBasis, Bₖ::AbstractMat M = block_inner(X, AX) # Calculate the new residual. Get Rnext Xlast = BlockVec{S}(V[(k - bs_last - bs + 1):(k - bs)]) - rₖnext = compute_residual!(AX, X, M, Xlast, Bₖ) + rₖnext = compute_residual!(AX, X, M, Xlast, Bₖ') ortho_basis!(rₖnext, V) return rₖnext, M end From ddf8d45d3c1ba8fcc845e979d720a642b2e50cba Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Thu, 8 May 2025 05:29:07 +0800 Subject: [PATCH 53/82] some revise according to review suggestion --- src/KrylovKit.jl | 1 + src/algorithms.jl | 6 +- src/factorizations/blocklanczos.jl | 322 ++++++++++++++++++++++++++++ src/factorizations/lanczos.jl | 325 +---------------------------- 4 files changed, 327 insertions(+), 327 deletions(-) create mode 100644 src/factorizations/blocklanczos.jl diff --git a/src/KrylovKit.jl b/src/KrylovKit.jl index 309ce9f1..aa588dd2 100644 --- a/src/KrylovKit.jl +++ b/src/KrylovKit.jl @@ -175,6 +175,7 @@ include("dense/reflector.jl") # Krylov and related factorizations and their iterators include("factorizations/krylov.jl") include("factorizations/lanczos.jl") +include("factorizations/blocklanczos.jl") include("factorizations/arnoldi.jl") include("factorizations/gkl.jl") diff --git a/src/algorithms.jl b/src/algorithms.jl index c1c8e520..70c4d854 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -135,13 +135,13 @@ end verbosity=KrylovDefaults.verbosity[], qr_tol::Real=KrylovDefaults.tol[]) -The block version of [`Lanczos`](@ref) is suited for solving linear systems with degenerate dominant eigenvalues. +The block version of [`Lanczos`](@ref) is suited for solving eigenvalue problems with degenerate dominant eigenvalues. Arguments `krylovdim`, `maxiter`, `tol`, `orth`, `eager` and `verbosity` are the same as `Lanczos`. `qr_tol` is the error tolerance for `abstract_qr!` - a subroutine used to orthorgonalize the vectors in the same block. `blocksize` is the size of block, which shrinks during iterations. -The initial block size determines the maximum degeneracy of the target eigenvalue can be resolved. +The initial block size determines the maximum degeneracy of the target eigenvalue can that be resolved. -The iteration stops when either the norm of residual is below tol or the enough eigenvectors are converged.[Reference](https://www.netlib.org/utk/people/JackDongarra/etemplates/node250.html) +The iteration stops when either the norm of the residual is below `tol` or a sufficient number of eigenvectors have converged. [Reference](https://www.netlib.org/utk/people/JackDongarra/etemplates/node250.html) Use `Arnoldi` for non-symmetric or non-Hermitian linear operators. diff --git a/src/factorizations/blocklanczos.jl b/src/factorizations/blocklanczos.jl new file mode 100644 index 00000000..eb4ab2cf --- /dev/null +++ b/src/factorizations/blocklanczos.jl @@ -0,0 +1,322 @@ +# BlockLanczos +""" + struct BlockVec{T,S<:Number} + +Structure for storing vectors in a block format. The type parameter `T` represents the type of vector elements, +while `S` represents the type of inner products between vectors. +""" +struct BlockVec{T,S<:Number} + vec::Vector{T} + function BlockVec{S}(vec::Vector{T}) where {T,S<:Number} + return new{T,S}(vec) + end +end +Base.length(b::BlockVec) = length(b.vec) +Base.getindex(b::BlockVec, i::Int) = b.vec[i] +function Base.getindex(b::BlockVec{T,S}, idxs::AbstractVector{Int}) where {T,S} + return BlockVec{S}([b.vec[i] for i in idxs]) +end +Base.setindex!(b::BlockVec{T}, v::T, i::Int) where {T} = (b.vec[i] = v) +function Base.setindex!(b₁::BlockVec{T}, b₂::BlockVec{T}, + idxs::AbstractVector{Int}) where {T} + return (b₁.vec[idxs] = b₂.vec; + b₁) +end +LinearAlgebra.norm(b::BlockVec) = norm(b.vec) +function apply(f, block::BlockVec{T,S}) where {T,S} + return BlockVec{S}([apply(f, block[i]) for i in 1:length(block)]) +end +function initialize(x₀, size::Int) + S = typeof(inner(x₀, x₀)) + x₀_vec = [randn!(similar(x₀)) for _ in 1:(size - 1)] + pushfirst!(x₀_vec, x₀) + return BlockVec{S}(x₀_vec) +end +function Base.push!(V::OrthonormalBasis{T}, b::BlockVec{T}) where {T} + for i in 1:length(b) + push!(V, b[i]) + end + return V +end +Base.iterate(b::BlockVec) = iterate(b.vec) +Base.iterate(b::BlockVec, state) = iterate(b.vec, state) + +""" + mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: BlockKrylovFactorization{T,S,SR} + +Structure to store a BlockLanczos factorization of a real symmetric or complex hermitian linear +map `A` of the form + +```julia +A * V = V * B + r * b' +``` + +For a given BlockLanczos factorization `fact`, length `k = length(fact)` and basis `V = basis(fact)` are +like [`LanczosFactorization`](@ref). The block tridiagonal matrix `TDB` is preallocated in `BlockLanczosFactorization` +and is of type `Hermitian{S<:Number}` with `size(TDB) == (k,k)`. The residuals `r` is of type `Vector{T}`. +One can also query [`normres(fact)`](@ref) to obtain `norm(r)`, the norm of the residual. The matrix +`b` takes the default value ``[0;I]``, i.e. the matrix of size `(k,bs)` and an unit matrix in the last +`bs` rows and all zeros in the other rows. `bs` is the size of the last block. One can query [`r_size(fact)`] to obtain +size of the last block and the residuals. + +`BlockLanczosFactorization` is mutable because it can [`expand!`](@ref). But it does not support `shrink!` +because it is implemented in its `eigsolve`. +See also [`BlockLanczosIterator`](@ref) for an iterator that constructs a progressively expanding +BlockLanczos factorizations of a given linear map and a starting vector. +""" +mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: + KrylovFactorization{T,S} + total_size::Int + const V::OrthonormalBasis{T} # BlockLanczos Basis + const TDB::AbstractMatrix{S} # TDB matrix, S is the matrix type + const r::BlockVec{T,S} # residual block + r_size::Int # size of the residual block + norm_r::SR # norm of the residual block +end +Base.length(fact::BlockLanczosFactorization) = fact.total_size +normres(fact::BlockLanczosFactorization) = fact.norm_r +basis(fact::BlockLanczosFactorization) = fact.V +residual(fact::BlockLanczosFactorization) = fact.r[1:(fact.r_size)] + +""" + struct BlockLanczosIterator{F,T,O<:Orthogonalizer} <: KrylovIterator{F,T} + BlockLanczosIterator(f, x₀, maxdim, qr_tol, [orth::Orthogonalizer = KrylovDefaults.orth]) + +Iterator that takes a linear map `f::F` (supposed to be real symmetric or complex hermitian) +and an initial block `x₀::BlockVec{T,S}` and generates an expanding `BlockLanczosFactorization` thereof. In +particular, `BlockLanczosIterator` uses the +BlockLanczos iteration(@footnote "Golub, G. H., & Van Loan, C. F. (2013). Matrix computations (4th ed.). Johns Hopkins University Press.") +scheme to build a successively expanding BlockLanczos factorization. While `f` cannot be tested to be symmetric or +hermitian directly when the linear map is encoded as a general callable object or function, with `block_inner(X, f.(X))`, +it is tested whether `norm(M-M')` is sufficiently small to be neglected. + +The argument `f` can be a matrix, or a function accepting a single argument `x`, so that +`f(x)` implements the action of the linear map on the block `x`. + +The optional argument `orth` specifies which [`Orthogonalizer`](@ref) to be used. The +default value in [`KrylovDefaults`](@ref) is to use [`ModifiedGramSchmidt2`](@ref), which +uses reorthogonalization steps in every iteration. +Now our orthogonalizer is only ModifiedGramSchmidt2. So we don't need to provide "keepvecs" because we have to reverse all krylove vectors. +Dimension of Krylov subspace in BlockLanczosIterator is usually much bigger than lanczos and its Default value is 100. +`qr_tol` is the tolerance used in [`abstract_qr!`](@ref) to resolve the rank of a block of vectors. + +When iterating over an instance of `BlockLanczosIterator`, the values being generated are +instances of [`BlockLanczosFactorization`](@ref). + +The internal state of `BlockLanczosIterator` is the same as the return value, i.e. the +corresponding `BlockLanczosFactorization`. + +Here, [`initialize(::KrylovIterator)`](@ref) produces the first Krylov factorization, +and [`expand!(iter::KrylovIterator, fact::KrylovFactorization)`](@ref) expands the +factorization in place. +""" +struct BlockLanczosIterator{F,T,S,O<:Orthogonalizer} <: KrylovIterator{F,T} + operator::F + x₀::BlockVec{T,S} + maxdim::Int + orth::O + qr_tol::Real + function BlockLanczosIterator{F,T,S,O}(operator::F, + x₀::BlockVec{T,S}, + maxdim::Int, + orth::O, + qr_tol::Real) where {F,T,S,O<:Orthogonalizer} + return new{F,T,S,O}(operator, x₀, maxdim, orth, qr_tol) + end +end +function BlockLanczosIterator(operator::F, + x₀::BlockVec{T,S}, + maxdim::Int, + qr_tol::Real, + orth::O=ModifiedGramSchmidt2()) where {F,T,S, + O<:Orthogonalizer} + norm(x₀) < qr_tol && @error "initial vector should not have norm zero" + orth != ModifiedGramSchmidt2() && + @error "BlockLanczosIterator only supports ModifiedGramSchmidt2 orthogonalizer" + return BlockLanczosIterator{F,T,S,O}(operator, x₀, maxdim, orth, qr_tol) +end + +function initialize(iter::BlockLanczosIterator{F,T,S}; + verbosity::Int=KrylovDefaults.verbosity[]) where {F,T,S} + X₀ = iter.x₀ + maxdim = iter.maxdim + bs = length(X₀) # block size now + A = iter.operator + TDB = zeros(S, maxdim, maxdim) + + # Orthogonalization of the initial block + X₁ = deepcopy(X₀) + abstract_qr!(X₁, iter.qr_tol) + V = OrthonormalBasis(X₁.vec) + + AX₁ = apply(A, X₁) + M₁ = block_inner(X₁, AX₁) + TDB[1:bs, 1:bs] .= M₁ + verbosity >= WARN_LEVEL && warn_nonhermitian(M₁) + + # Get the first residual + for j in 1:length(X₁) + for i in 1:length(X₁) + AX₁[j] = add!!(AX₁[j], X₁[i], -M₁[i, j]) + end + end + norm_r = norm(AX₁) + if verbosity > EACHITERATION_LEVEL + @info "BlockLanczos initiation at dimension $bs: subspace normres = $(normres2string(norm_r))" + end + return BlockLanczosFactorization(bs, + V, + TDB, + AX₁, + bs, + norm_r) +end + +function expand!(iter::BlockLanczosIterator{F,T,S}, + state::BlockLanczosFactorization{T,S,SR}; + verbosity::Int=KrylovDefaults.verbosity[]) where {F,T,S,SR} + k = state.total_size + rₖ = state.r[1:(state.r_size)] + bs_now = length(rₖ) + V = state.V + + # Calculate the new basis and Bₖ + Bₖ, good_idx = abstract_qr!(rₖ, iter.qr_tol) + bs_next = length(good_idx) + push!(V, rₖ[good_idx]) + state.TDB[(k + 1):(k + bs_next), (k - bs_now + 1):k] .= Bₖ + state.TDB[(k - bs_now + 1):k, (k + 1):(k + bs_next)] .= Bₖ' + + # Calculate the new residual and orthogonalize the new basis + rₖnext, Mnext = blocklanczosrecurrence(iter.operator, V, Bₖ, iter.orth) + verbosity >= WARN_LEVEL && warn_nonhermitian(Mnext) + + state.TDB[(k + 1):(k + bs_next), (k + 1):(k + bs_next)] .= Mnext + state.r.vec[1:bs_next] .= rₖnext.vec + state.norm_r = norm(rₖnext) + state.total_size += bs_next + state.r_size = bs_next + + if verbosity > EACHITERATION_LEVEL + @info "BlockLanczos expansion to dimension $(state.total_size): subspace normres = $(normres2string(state.norm_r))" + end +end + +function blocklanczosrecurrence(operator, V::OrthonormalBasis, Bₖ::AbstractMatrix, + orth::ModifiedGramSchmidt2) + # Apply the operator and calculate the M. Get Xnext and Mnext. + bs, bs_last = size(Bₖ) + S = eltype(Bₖ) + k = length(V) + X = BlockVec{S}(V[(k - bs + 1):k]) + AX = apply(operator, X) + M = block_inner(X, AX) + # Calculate the new residual. Get Rnext + Xlast = BlockVec{S}(V[(k - bs_last - bs + 1):(k - bs)]) + rₖnext = compute_residual!(AX, X, M, Xlast, Bₖ') + ortho_basis!(rₖnext, V) + return rₖnext, M +end + +""" + compute_residual!(AX::BlockVec{T,S}, X::BlockVec{T,S}, + M::AbstractMatrix, + X_prev::BlockVec{T,S}, B_prev::AbstractMatrix) where {T,S} + +Computes the residual block and stores the result in `AX`. + +This function orthogonalizes `AX` against the two most recent basis blocks, `X` and `X_prev`. +Here, `AX` represents the image of the current block `X` under the action of the linear operator `A`. +The matrix `M` contains the inner products between `X` and `AX`, i.e., the projection of `AX` onto `X`. +Similarly, `B_prev` represents the projection of `AX` onto `X_prev`. + +The residual is computed as: + +``` + AX ← AX - X * M - X_prev * B_prev +``` + +After this operation, `AX` is orthogonal (in the block inner product sense) to both `X` and `X_prev`. + +""" +function compute_residual!(AX::BlockVec{T,S}, X::BlockVec{T,S}, + M::AbstractMatrix, + X_prev::BlockVec{T,S}, B_prev::AbstractMatrix) where {T,S} + @inbounds for j in 1:length(X) + for i in 1:length(X) + AX[j] = add!!(AX[j], X[i], -M[i, j]) + end + for i in 1:length(X_prev) + AX[j] = add!!(AX[j], X_prev[i], -B_prev[i, j]) + end + end + return AX +end + +""" + ortho_basis!(basis::BlockVec{T,S}, basis_sofar::OrthonormalBasis{T}) where {T,S} + +This function orthogonalizes the vectors in `basis` with respect to the previously orthonormalized set `basis_sofar` by using the modified Gram-Schmidt process. +Specifically, it modifies each vector `basis[i]` by projecting out its components along the directions spanned by `basis_sofar`, i.e., + +``` + basis[i] = basis[i] - sum(j=1:length(basis_sofar)) basis_sofar[j] +``` + +Here,`⟨·,·⟩` denotes the inner product. The function assumes that `basis_sofar` is already orthonormal. +""" +function ortho_basis!(basis::BlockVec{T,S}, basis_sofar::OrthonormalBasis{T}) where {T,S} + for i in 1:length(basis) + for q in basis_sofar + basis[i], _ = orthogonalize!!(basis[i], q, ModifiedGramSchmidt()) + end + end + return basis +end + +function warn_nonhermitian(M::AbstractMatrix) + if norm(M - M') > eps(real(eltype(M)))^(2 / 5) + @warn "ignoring the antihermitian part of the block triangular matrix: operator might not be hermitian?" M + end +end + +""" + abstract_qr!(block::BlockVec{T,S}, tol::Real) where {T,S} + +This function performs a QR factorization of a block of abstract vectors using the modified Gram-Schmidt process. + +``` + [v₁,..,vₚ] -> [u₁,..,uᵣ] * R +``` + +It takes as input a block of abstract vectors and a tolerance parameter, which is used to determine whether a vector is considered numerically zero. +The operation is performed in-place, transforming the input block into a block of orthonormal vectors. + +The function returns a matrix of size `(r, p)` and a vector of indices goodidx. Here, `p` denotes the number of input vectors, +and `r` is the numerical rank of the input block. The matrix represents the upper-triangular factor of the QR decomposition, +restricted to the `r` linearly independent components. The vector `goodidx` contains the indices of the non-zero +(i.e., numerically independent) vectors in the orthonormalized block. +""" +function abstract_qr!(block::BlockVec{T,S}, tol::Real) where {T,S} + n = length(block) + rank_shrink = false + idx = ones(Int64, n) + R = zeros(S, n, n) + @inbounds for j in 1:n + for i in 1:(j - 1) + R[i, j] = inner(block[i], block[j]) + block[j] = add!!(block[j], block[i], -R[i, j]) + end + β = norm(block[j]) + if !(β ≤ tol) + R[j, j] = β + block[j] = scale(block[j], 1 / β) + else + block[j] = scale!!(block[j], 0) + rank_shrink = true + idx[j] = 0 + end + end + good_idx = findall(idx .> 0) + return R[good_idx, :], good_idx +end diff --git a/src/factorizations/lanczos.jl b/src/factorizations/lanczos.jl index 8fdce705..68728610 100644 --- a/src/factorizations/lanczos.jl +++ b/src/factorizations/lanczos.jl @@ -119,7 +119,7 @@ end ``` Here, [`initialize(::KrylovIterator)`](@ref) produces the first Krylov factorization of -length 1, and [`expand!(iter::KrylovIterator, fact::KrylovFactorization)`](@ref) expands the factorization in place. See also +length 1, and [`expand!(iter::KrylovIterator, fact::KrylovFactorization)`](@ref) expands the factorization in place. See also [`initialize!(::KrylovIterator, ::KrylovFactorization)`](@ref) to initialize in an already existing factorization (most information will be discarded) and [`shrink!(::KrylovFactorization, k)`](@ref) to shrink an @@ -365,326 +365,3 @@ function lanczosrecurrence(operator, V::OrthonormalBasis, β, orth::ModifiedGram end return w, α, β end - -# BlockLanczos -""" - struct BlockVec{T,S<:Number} - -Structure for storing vectors in a block format. The type parameter `T` represents the type of vector elements, -while `S` represents the type of inner products between vectors. -""" -struct BlockVec{T,S<:Number} - vec::Vector{T} - function BlockVec{S}(vec::Vector{T}) where {T,S<:Number} - return new{T,S}(vec) - end -end -Base.length(b::BlockVec) = length(b.vec) -Base.getindex(b::BlockVec, i::Int) = b.vec[i] -function Base.getindex(b::BlockVec{T,S}, idxs::AbstractVector{Int}) where {T,S} - return BlockVec{S}([b.vec[i] for i in idxs]) -end -Base.setindex!(b::BlockVec{T}, v::T, i::Int) where {T} = (b.vec[i] = v) -function Base.setindex!(b₁::BlockVec{T}, b₂::BlockVec{T}, - idxs::AbstractVector{Int}) where {T} - return (b₁.vec[idxs] = b₂.vec; - b₁) -end -LinearAlgebra.norm(b::BlockVec) = norm(b.vec) -function apply(f, block::BlockVec{T,S}) where {T,S} - return BlockVec{S}([apply(f, block[i]) for i in 1:length(block)]) -end -function initialize(x₀, size::Int) - S = typeof(inner(x₀, x₀)) - x₀_vec = [randn!(similar(x₀)) for _ in 1:(size - 1)] - pushfirst!(x₀_vec, x₀) - return BlockVec{S}(x₀_vec) -end -function Base.push!(V::OrthonormalBasis{T}, b::BlockVec{T}) where {T} - for i in 1:length(b) - push!(V, b[i]) - end - return V -end -Base.iterate(b::BlockVec) = iterate(b.vec) -Base.iterate(b::BlockVec, state) = iterate(b.vec, state) - -""" - mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: BlockKrylovFactorization{T,S,SR} - -Structure to store a BlockLanczos factorization of a real symmetric or complex hermitian linear -map `A` of the form - -```julia -A * V = V * B + r * b' -``` - -For a given BlockLanczos factorization `fact`, length `k = length(fact)` and basis `V = basis(fact)` are -like [`LanczosFactorization`](@ref). The block tridiagonal matrix `TDB` is preallocated in `BlockLanczosFactorization` -and is of type `Hermitian{S<:Number}` with `size(TDB) == (k,k)`. The residuals `r` is of type `Vector{T}`. -One can also query [`normres(fact)`](@ref) to obtain `norm(r)`, the norm of the residual. The matrix -`b` takes the default value ``[0;I]``, i.e. the matrix of size `(k,bs)` and an unit matrix in the last -`bs` rows and all zeros in the other rows. `bs` is the size of the last block. One can query [`r_size(fact)`] to obtain -size of the last block and the residuals. - -`BlockLanczosFactorization` is mutable because it can [`expand!`](@ref). But it does not support `shrink!` -because it is implemented in its `eigsolve`. -See also [`BlockLanczosIterator`](@ref) for an iterator that constructs a progressively expanding -BlockLanczos factorizations of a given linear map and a starting vector. -""" -mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: - KrylovFactorization{T,S} - total_size::Int - const V::OrthonormalBasis{T} # BlockLanczos Basis - const TDB::AbstractMatrix{S} # TDB matrix, S is the matrix type - const r::BlockVec{T,S} # residual block - r_size::Int # size of the residual block - norm_r::SR # norm of the residual block -end -Base.length(fact::BlockLanczosFactorization) = fact.total_size -normres(fact::BlockLanczosFactorization) = fact.norm_r -basis(fact::BlockLanczosFactorization) = fact.V -residual(fact::BlockLanczosFactorization) = fact.r[1:(fact.r_size)] - -""" - struct BlockLanczosIterator{F,T,O<:Orthogonalizer} <: KrylovIterator{F,T} - BlockLanczosIterator(f, x₀, maxdim, qr_tol, [orth::Orthogonalizer = KrylovDefaults.orth]) - -Iterator that takes a linear map `f::F` (supposed to be real symmetric or complex hermitian) -and an initial block `x₀::BlockVec{T,S}` and generates an expanding `BlockLanczosFactorization` thereof. In -particular, `BlockLanczosIterator` uses the -BlockLanczos iteration(@footnote "Golub, G. H., & Van Loan, C. F. (2013). Matrix computations (4th ed.). Johns Hopkins University Press.") -scheme to build a successively expanding BlockLanczos factorization. While `f` cannot be tested to be symmetric or -hermitian directly when the linear map is encoded as a general callable object or function, with `block_inner(X, f.(X))`, -it is tested whether `norm(M-M')` is sufficiently small to be neglected. - -The argument `f` can be a matrix, or a function accepting a single argument `x`, so that -`f(x)` implements the action of the linear map on the block `x`. - -The optional argument `orth` specifies which [`Orthogonalizer`](@ref) to be used. The -default value in [`KrylovDefaults`](@ref) is to use [`ModifiedGramSchmidt2`](@ref), which -uses reorthogonalization steps in every iteration. -Now our orthogonalizer is only ModifiedGramSchmidt2. So we don't need to provide "keepvecs" because we have to reverse all krylove vectors. -Dimension of Krylov subspace in BlockLanczosIterator is usually much bigger than lanczos and its Default value is 100. -`qr_tol` is the tolerance used in [`abstract_qr!`](@ref) to resolve the rank of a block of vectors. - -When iterating over an instance of `BlockLanczosIterator`, the values being generated are -instances of [`BlockLanczosFactorization`](@ref). - -The internal state of `BlockLanczosIterator` is the same as the return value, i.e. the -corresponding `BlockLanczosFactorization`. - -Here, [`initialize(::KrylovIterator)`](@ref) produces the first Krylov factorization, -and [`expand!(iter::KrylovIterator, fact::KrylovFactorization)`](@ref) expands the -factorization in place. -""" -struct BlockLanczosIterator{F,T,S,O<:Orthogonalizer} <: KrylovIterator{F,T} - operator::F - x₀::BlockVec{T,S} - maxdim::Int - orth::O - qr_tol::Real - function BlockLanczosIterator{F,T,S,O}(operator::F, - x₀::BlockVec{T,S}, - maxdim::Int, - orth::O, - qr_tol::Real) where {F,T,S,O<:Orthogonalizer} - return new{F,T,S,O}(operator, x₀, maxdim, orth, qr_tol) - end -end -function BlockLanczosIterator(operator::F, - x₀::BlockVec{T,S}, - maxdim::Int, - qr_tol::Real, - orth::O=ModifiedGramSchmidt2()) where {F,T,S, - O<:Orthogonalizer} - norm(x₀) < qr_tol && @error "initial vector should not have norm zero" - orth != ModifiedGramSchmidt2() && - @error "BlockLanczosIterator only supports ModifiedGramSchmidt2 orthogonalizer" - return BlockLanczosIterator{F,T,S,O}(operator, x₀, maxdim, orth, qr_tol) -end - -function initialize(iter::BlockLanczosIterator{F,T,S}; - verbosity::Int=KrylovDefaults.verbosity[]) where {F,T,S} - X₀ = iter.x₀ - maxdim = iter.maxdim - bs = length(X₀) # block size now - A = iter.operator - TDB = zeros(S, maxdim, maxdim) - - # Orthogonalization of the initial block - X₁ = deepcopy(X₀) - abstract_qr!(X₁, iter.qr_tol) - V = OrthonormalBasis(X₁.vec) - - AX₁ = apply(A, X₁) - M₁ = block_inner(X₁, AX₁) - TDB[1:bs, 1:bs] .= M₁ - verbosity >= WARN_LEVEL && warn_nonhermitian(M₁) - - # Get the first residual - for j in 1:length(X₁) - for i in 1:length(X₁) - AX₁[j] = add!!(AX₁[j], X₁[i], -M₁[i, j]) - end - end - norm_r = norm(AX₁) - if verbosity > EACHITERATION_LEVEL - @info "BlockLanczos initiation at dimension $bs: subspace normres = $(normres2string(norm_r))" - end - return BlockLanczosFactorization(bs, - V, - TDB, - AX₁, - bs, - norm_r) -end - -function expand!(iter::BlockLanczosIterator{F,T,S}, - state::BlockLanczosFactorization{T,S,SR}; - verbosity::Int=KrylovDefaults.verbosity[]) where {F,T,S,SR} - k = state.total_size - rₖ = state.r[1:(state.r_size)] - bs_now = length(rₖ) - V = state.V - - # Calculate the new basis and Bₖ - Bₖ, good_idx = abstract_qr!(rₖ, iter.qr_tol) - bs_next = length(good_idx) - push!(V, rₖ[good_idx]) - state.TDB[(k + 1):(k + bs_next), (k - bs_now + 1):k] .= Bₖ - state.TDB[(k - bs_now + 1):k, (k + 1):(k + bs_next)] .= Bₖ' - - # Calculate the new residual and orthogonalize the new basis - rₖnext, Mnext = blocklanczosrecurrence(iter.operator, V, Bₖ, iter.orth) - verbosity >= WARN_LEVEL && warn_nonhermitian(Mnext) - - state.TDB[(k + 1):(k + bs_next), (k + 1):(k + bs_next)] .= Mnext - state.r.vec[1:bs_next] .= rₖnext.vec - state.norm_r = norm(rₖnext) - state.total_size += bs_next - state.r_size = bs_next - - if verbosity > EACHITERATION_LEVEL - @info "BlockLanczos expansion to dimension $(state.total_size): subspace normres = $(normres2string(state.norm_r))" - end -end - -function blocklanczosrecurrence(operator, V::OrthonormalBasis, Bₖ::AbstractMatrix, - orth::ModifiedGramSchmidt2) - # Apply the operator and calculate the M. Get Xnext and Mnext. - bs, bs_last = size(Bₖ) - S = eltype(Bₖ) - k = length(V) - X = BlockVec{S}(V[(k - bs + 1):k]) - AX = apply(operator, X) - M = block_inner(X, AX) - # Calculate the new residual. Get Rnext - Xlast = BlockVec{S}(V[(k - bs_last - bs + 1):(k - bs)]) - rₖnext = compute_residual!(AX, X, M, Xlast, Bₖ') - ortho_basis!(rₖnext, V) - return rₖnext, M -end - -""" - compute_residual!(AX::BlockVec{T,S}, X::BlockVec{T,S}, - M::AbstractMatrix, - X_prev::BlockVec{T,S}, B_prev::AbstractMatrix) where {T,S} - -Computes the residual block and stores the result in `AX`. - -This function orthogonalizes `AX` against the two most recent basis blocks, `X` and `X_prev`. -Here, `AX` represents the image of the current block `X` under the action of the linear operator `A`. -The matrix `M` contains the inner products between `X` and `AX`, i.e., the projection of `AX` onto `X`. -Similarly, `B_prev` represents the projection of `AX` onto `X_prev`. - -The residual is computed as: - -``` - AX ← AX - X * M - X_prev * B_prev -``` - -After this operation, `AX` is orthogonal (in the block inner product sense) to both `X` and `X_prev`. - -""" -function compute_residual!(AX::BlockVec{T,S}, X::BlockVec{T,S}, - M::AbstractMatrix, - X_prev::BlockVec{T,S}, B_prev::AbstractMatrix) where {T,S} - @inbounds for j in 1:length(X) - for i in 1:length(X) - AX[j] = add!!(AX[j], X[i], -M[i, j]) - end - for i in 1:length(X_prev) - AX[j] = add!!(AX[j], X_prev[i], -B_prev[i, j]) - end - end - return AX -end - -""" - ortho_basis!(basis::BlockVec{T,S}, basis_sofar::OrthonormalBasis{T}) where {T,S} - -This function orthogonalizes the vectors in `basis` with respect to the previously orthonormalized set `basis_sofar` by using the modified Gram-Schmidt process. -Specifically, it modifies each vector `basis[i]` by projecting out its components along the directions spanned by `basis_sofar`, i.e., - -``` - basis[i] = basis[i] - sum(j=1:length(basis_sofar)) basis_sofar[j] -``` - -Here,`⟨·,·⟩` denotes the inner product. The function assumes that `basis_sofar` is already orthonormal. -""" -function ortho_basis!(basis::BlockVec{T,S}, basis_sofar::OrthonormalBasis{T}) where {T,S} - for i in 1:length(basis) - for q in basis_sofar - basis[i], _ = orthogonalize!!(basis[i], q, ModifiedGramSchmidt()) - end - end - return basis -end - -function warn_nonhermitian(M::AbstractMatrix) - if norm(M - M') > eps(real(eltype(M)))^(2 / 5) - @warn "Enforce Hermiticity on the triangular diagonal blocks matrix, even though the operator may not be Hermitian." - end -end - -""" - abstract_qr!(block::BlockVec{T,S}, tol::Real) where {T,S} - -This function performs a QR factorization of a block of abstract vectors using the modified Gram-Schmidt process. - -``` - [v₁,..,vₚ] -> [u₁,..,uᵣ] * R -``` - -It takes as input a block of abstract vectors and a tolerance parameter, which is used to determine whether a vector is considered numerically zero. -The operation is performed in-place, transforming the input block into a block of orthonormal vectors. - -The function returns a matrix of size `(r, p)` and a vector of indices goodidx. Here, `p` denotes the number of input vectors, -and `r` is the numerical rank of the input block. The matrix represents the upper-triangular factor of the QR decomposition, -restricted to the `r` linearly independent components. The vector `goodidx` contains the indices of the non-zero -(i.e., numerically independent) vectors in the orthonormalized block. -""" -function abstract_qr!(block::BlockVec{T,S}, tol::Real) where {T,S} - n = length(block) - rank_shrink = false - idx = ones(Int64, n) - R = zeros(S, n, n) - @inbounds for j in 1:n - for i in 1:(j - 1) - R[i, j] = inner(block[i], block[j]) - block[j] = add!!(block[j], block[i], -R[i, j]) - end - β = norm(block[j]) - if !(β ≤ tol) - R[j, j] = β - block[j] = scale(block[j], 1 / β) - else - block[j] = scale!!(block[j], 0) - rank_shrink = true - idx[j] = 0 - end - end - good_idx = findall(idx .> 0) - return R[good_idx, :], good_idx -end From ddc20cb91a5af7ac93fe7e6af48b3437f427f97c Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Thu, 8 May 2025 06:00:59 +0800 Subject: [PATCH 54/82] some revise according to review suggestions --- src/factorizations/blocklanczos.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/factorizations/blocklanczos.jl b/src/factorizations/blocklanczos.jl index eb4ab2cf..1a0c4bf7 100644 --- a/src/factorizations/blocklanczos.jl +++ b/src/factorizations/blocklanczos.jl @@ -310,9 +310,9 @@ function abstract_qr!(block::BlockVec{T,S}, tol::Real) where {T,S} β = norm(block[j]) if !(β ≤ tol) R[j, j] = β - block[j] = scale(block[j], 1 / β) + block[j] = scale!!(block[j], 1 / β) else - block[j] = scale!!(block[j], 0) + block[j] = zerovector!!(block[j]) rank_shrink = true idx[j] = 0 end From 58ca05332fe0825ff9602a13471dac61e4561640 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Thu, 8 May 2025 14:17:47 +0800 Subject: [PATCH 55/82] some revise --- docs/src/man/implementation.md | 4 ++-- src/factorizations/blocklanczos.jl | 12 ++++++++---- test/BlockVec.jl | 21 +++++++++++++++++++++ test/innerproductvec.jl | 4 ++-- test/orthonormal.jl | 11 ++++++----- 5 files changed, 39 insertions(+), 13 deletions(-) diff --git a/docs/src/man/implementation.md b/docs/src/man/implementation.md index d36ac6b8..a93e1370 100644 --- a/docs/src/man/implementation.md +++ b/docs/src/man/implementation.md @@ -49,7 +49,7 @@ KrylovKit.basistransform! ## Block Krylov method The block version of the Krylov subspace algorithm is an approach to extending Krylov subspace techniques from single starting vectors to multiple starting vectors. It is mainly used for solving linear systems with degenerate dominant eigenvalues. -In our implementation, the multiple-vector data structure is BlockVec, which implements the [`KrylovKit.abstract_qr!`](@ref), [`KrylovKit.ortho_basis!`](@ref), and [`KrylovKit.compute_residual!`](@ref) interfaces. +In our implementation, the multiple-vector data structure is BlockVec, which implements the [`KrylovKit.abstract_qr!`](@ref), [`KrylovKit.block_reorthogonalize!`](@ref), and [`KrylovKit.compute_residual!`](@ref) interfaces. ```@docs KrylovKit.BlockVec @@ -63,7 +63,7 @@ This apply QR decomposition to a block of vectors using modified Gram-Schmidt pr Additional procedures applied to the block are as follows: ```@docs -KrylovKit.ortho_basis! +KrylovKit.block_reorthogonalize! KrylovKit.compute_residual! ``` diff --git a/src/factorizations/blocklanczos.jl b/src/factorizations/blocklanczos.jl index 1a0c4bf7..c94624ab 100644 --- a/src/factorizations/blocklanczos.jl +++ b/src/factorizations/blocklanczos.jl @@ -38,6 +38,9 @@ function Base.push!(V::OrthonormalBasis{T}, b::BlockVec{T}) where {T} end return V end +function Base.copy(b::BlockVec) + return BlockVec{typeof(b).parameters[2]}(scale.(b.vec, 1)) +end Base.iterate(b::BlockVec) = iterate(b.vec) Base.iterate(b::BlockVec, state) = iterate(b.vec, state) @@ -145,7 +148,7 @@ function initialize(iter::BlockLanczosIterator{F,T,S}; TDB = zeros(S, maxdim, maxdim) # Orthogonalization of the initial block - X₁ = deepcopy(X₀) + X₁ = copy(X₀) abstract_qr!(X₁, iter.qr_tol) V = OrthonormalBasis(X₁.vec) @@ -214,7 +217,7 @@ function blocklanczosrecurrence(operator, V::OrthonormalBasis, Bₖ::AbstractMat # Calculate the new residual. Get Rnext Xlast = BlockVec{S}(V[(k - bs_last - bs + 1):(k - bs)]) rₖnext = compute_residual!(AX, X, M, Xlast, Bₖ') - ortho_basis!(rₖnext, V) + block_reorthogonalize!(rₖnext, V) return rₖnext, M end @@ -254,7 +257,7 @@ function compute_residual!(AX::BlockVec{T,S}, X::BlockVec{T,S}, end """ - ortho_basis!(basis::BlockVec{T,S}, basis_sofar::OrthonormalBasis{T}) where {T,S} + block_reorthogonalize!(basis::BlockVec{T,S}, basis_sofar::OrthonormalBasis{T}) where {T,S} This function orthogonalizes the vectors in `basis` with respect to the previously orthonormalized set `basis_sofar` by using the modified Gram-Schmidt process. Specifically, it modifies each vector `basis[i]` by projecting out its components along the directions spanned by `basis_sofar`, i.e., @@ -265,7 +268,8 @@ Specifically, it modifies each vector `basis[i]` by projecting out its component Here,`⟨·,·⟩` denotes the inner product. The function assumes that `basis_sofar` is already orthonormal. """ -function ortho_basis!(basis::BlockVec{T,S}, basis_sofar::OrthonormalBasis{T}) where {T,S} +function block_reorthogonalize!(basis::BlockVec{T,S}, + basis_sofar::OrthonormalBasis{T}) where {T,S} for i in 1:length(basis) for q in basis_sofar basis[i], _ = orthogonalize!!(basis[i], q, ModifiedGramSchmidt()) diff --git a/test/BlockVec.jl b/test/BlockVec.jl index 0d6334e0..037c7b7d 100644 --- a/test/BlockVec.jl +++ b/test/BlockVec.jl @@ -50,3 +50,24 @@ end @test length(block0) == n @test Tuple(typeof(block0).parameters) == (typeof(x0), T) end + +@testset "copy for BlockVec" begin + for mode in (:vector, :inplace, :outplace) + scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : + (ComplexF64,) + @testset for T in scalartypes + block0 = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) + block1 = copy(block0) + @test typeof(block0) == typeof(block1) + @test unwrapvec.(block0.vec) == unwrapvec.(block1.vec) + end + end + + # test for abtract type + T = ComplexF64 + f(x, y) = x' * y + block0 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), f) for _ in 1:n]) + block1 = copy(block0) + @test typeof(block0) == typeof(block1) + @test [block0.vec[i].vec for i in 1:n] == [block1.vec[i].vec for i in 1:n] +end diff --git a/test/innerproductvec.jl b/test/innerproductvec.jl index 8afd4973..03ca5eaa 100644 --- a/test/innerproductvec.jl +++ b/test/innerproductvec.jl @@ -47,7 +47,7 @@ end X0 = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) X1 = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) AX1 = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) - AX1copy = deepcopy(AX1) + AX1copy = copy(AX1) KrylovKit.compute_residual!(AX1, X1, M, X0, B) _bw2m(X) = hcat(unwrapvec.(X)...) @test isapprox(_bw2m(AX1), _bw2m(AX1copy) - _bw2m(X1) * M - _bw2m(X0) * B; @@ -66,7 +66,7 @@ end X0 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) X1 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) AX1 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) - AX1copy = deepcopy(AX1) + AX1copy = copy(AX1) KrylovKit.compute_residual!(AX1, X1, M, X0, B) @test isapprox(hcat([AX1.vec[i].vec for i in 1:n]...), diff --git a/test/orthonormal.jl b/test/orthonormal.jl index a538fc76..ac7adce0 100644 --- a/test/orthonormal.jl +++ b/test/orthonormal.jl @@ -47,8 +47,9 @@ end @test isapprox(norm(ΔX), T(0); atol=tolerance(T)) end -@testset "ortho_basis! for non-full vectors $mode" for mode in - (:vector, :inplace, :outplace) +@testset "block_reorthogonalize! for non-full vectors $mode" for mode in + (:vector, :inplace, + :outplace) scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @testset for T in scalartypes @@ -60,12 +61,12 @@ end b₁ = KrylovKit.BlockVec{T}(x₁) KrylovKit.abstract_qr!(b₁, qr_tol(T)) orthobasis_x₁ = KrylovKit.OrthonormalBasis(b₁.vec) - KrylovKit.ortho_basis!(b₀, orthobasis_x₁) + KrylovKit.block_reorthogonalize!(b₀, orthobasis_x₁) @test norm(KrylovKit.block_inner(b₀, b₁)) < tolerance(T) end end -@testset "ortho_basis! for abstract inner product" begin +@testset "block_reorthogonalize! for abstract inner product" begin T = ComplexF64 H = rand(T, N, N) H = H' * H + I @@ -78,6 +79,6 @@ end b₁ = KrylovKit.BlockVec{T}(x₁) KrylovKit.abstract_qr!(b₁, qr_tol(T)) orthobasis_x₁ = KrylovKit.OrthonormalBasis(b₁.vec) - KrylovKit.ortho_basis!(b₀, orthobasis_x₁) + KrylovKit.block_reorthogonalize!(b₀, orthobasis_x₁) @test norm(KrylovKit.block_inner(b₀, b₁)) < tolerance(T) end From 69a6630376cefd695d8e2e9a71c60b1f3d04c99f Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Fri, 9 May 2025 16:44:49 +0800 Subject: [PATCH 56/82] revise of review --- src/algorithms.jl | 16 +++--- src/eigsolve/eigsolve.jl | 6 +- src/eigsolve/lanczos.jl | 30 +++++----- src/factorizations/blocklanczos.jl | 36 +++++------- src/innerproductvec.jl | 2 - test/BlockVec.jl | 36 ++++-------- test/eigsolve.jl | 89 ++++++++++++++---------------- test/factorize.jl | 11 ++-- test/testsetup.jl | 4 -- 9 files changed, 98 insertions(+), 132 deletions(-) diff --git a/src/algorithms.jl b/src/algorithms.jl index 70c4d854..4fdbc945 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -126,8 +126,7 @@ function Lanczos(; end """ - BlockLanczos(blocksize::Int; - krylovdim=KrylovDefaults.blockkrylovdim[], + BlockLanczos(; krylovdim=KrylovDefaults.blockkrylovdim[], maxiter=KrylovDefaults.maxiter[], tol=KrylovDefaults.tol[], orth=KrylovDefaults.orth, @@ -136,9 +135,10 @@ end qr_tol::Real=KrylovDefaults.tol[]) The block version of [`Lanczos`](@ref) is suited for solving eigenvalue problems with degenerate dominant eigenvalues. +Its implementation is mainly based on *Golub, G. H., & Van Loan, C. F. (2013). Matrix Computations* (4th ed., pp. 566–569). Arguments `krylovdim`, `maxiter`, `tol`, `orth`, `eager` and `verbosity` are the same as `Lanczos`. `qr_tol` is the error tolerance for `abstract_qr!` - a subroutine used to orthorgonalize the vectors in the same block. -`blocksize` is the size of block, which shrinks during iterations. +The initial size of the block is determined by the number of start vectors that a user provides. And the size of the block shrinks during iterations. The initial block size determines the maximum degeneracy of the target eigenvalue can that be resolved. The iteration stops when either the norm of the residual is below `tol` or a sufficient number of eigenvectors have converged. [Reference](https://www.netlib.org/utk/people/JackDongarra/etemplates/node250.html) @@ -152,12 +152,11 @@ struct BlockLanczos{O<:Orthogonalizer,S<:Real} <: KrylovAlgorithm krylovdim::Int maxiter::Int tol::S - blocksize::Int qr_tol::Real eager::Bool verbosity::Int end -function BlockLanczos(blocksize::Int; +function BlockLanczos(; krylovdim::Int=KrylovDefaults.blockkrylovdim[], maxiter::Int=KrylovDefaults.maxiter[], tol::Real=KrylovDefaults.tol[], @@ -165,8 +164,7 @@ function BlockLanczos(blocksize::Int; eager::Bool=false, verbosity::Int=KrylovDefaults.verbosity[], qr_tol::Real=KrylovDefaults.tol[]) - blocksize < 1 && error("blocksize must be greater than 0") - return BlockLanczos(orth, krylovdim, maxiter, tol, blocksize, qr_tol, eager, verbosity) + return BlockLanczos(orth, krylovdim, maxiter, tol, qr_tol, eager, verbosity) end """ @@ -494,9 +492,9 @@ A module listing the default values for the typical parameters in Krylov based a - `krylovdim = 30`: the maximal dimension of the Krylov subspace that will be constructed - `maxiter = 100`: the maximal number of outer iterations, i.e. the maximum number of times the Krylov subspace may be rebuilt - - `blockkrylovdim = 100`: the maximal dimension of the Krylov subspace that will be constructed for BlockLanczos + - `blockkrylovdim = 100`: the maximal dimension of the Krylov subspace that will be constructed for `BlockLanczos` - `tol = 1e-12`: the tolerance to which the problem must be solved, based on a suitable - error measure, e.g. the norm of some residual. It's also used as the default value of `qr_tol` + error measure, e.g. the norm of some residual !!! warning diff --git a/src/eigsolve/eigsolve.jl b/src/eigsolve/eigsolve.jl index eee9ee7d..539251d4 100644 --- a/src/eigsolve/eigsolve.jl +++ b/src/eigsolve/eigsolve.jl @@ -53,9 +53,9 @@ targeted. Valid specifications of `which` are given by iteration. Nonetheless, it is important to take this into account and to try not to depend on this potentially fragile behaviour, especially for smaller problems. The [`BlockLanczos`](@ref) method has been implemented to compute degenerate - eigenvalues and their corresponding eigenvectors. Given a block size parameter `p`, the - method can simultaneously determine `p`-fold degenerate eigenvalues and their - associated eigenvectors. + eigenvalues and their corresponding eigenvectors. Given a block size `p`, + which is the number of starting vectors, the method can at most simultaneously determine + `p`-fold degenerate eigenvalues and their associated eigenvectors. The argument `T` acts as a hint in which `Number` type the computation should be performed, but is not restrictive. If the linear map automatically produces complex values, complex diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index 8653f2af..831acfd7 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -151,7 +151,7 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::Lanczos; ConvergenceInfo(converged, residuals, normresiduals, numiter, numops) end -function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) where {T} +function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) maxiter = alg.maxiter krylovdim = alg.krylovdim if howmany > krylovdim @@ -160,13 +160,14 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) tol = alg.tol verbosity = alg.verbosity - # Initialize a block of vectors from the initial vector, randomly generated - block0 = initialize(x₀, alg.blocksize) + # The initial block size is determined by the number of the starting vectors provided by the user + S = typeof(inner(x₀[1], x₀[1])) # Scalar type of the inner product between vectors in x₀ + block0 = BlockVec{S}(x₀) bs = length(block0) + bs < 1 && error("The length of the starting vector x₀ must be greater than 0") iter = BlockLanczosIterator(A, block0, krylovdim + bs, alg.qr_tol, alg.orth) fact = initialize(iter; verbosity=verbosity) # Returns a BlockLanczosFactorization - S = eltype(fact.TDB) # The element type (Note: can be Complex) of the block tridiagonal matrix numops = 1 # Number of matrix-vector multiplications (for logging) numiter = 1 @@ -186,8 +187,8 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) if K >= krylovdim || β <= tol || (alg.eager && K >= howmany) # compute eigenvalues # Note: Fast eigen solver for block tridiagonal matrices is not implemented yet. - TDB = view(fact.TDB, 1:K, 1:K) - D, U = eigen(Hermitian(TDB)) + BTD = view(fact.T, 1:K, 1:K) + D, U = eigen(Hermitian(BTD)) by, rev = eigsort(which) p = sortperm(D; by=by, rev=rev) D, U = permuteeig!(D, U, p) @@ -196,9 +197,10 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) bs_r = fact.r_size # the block size of the residual (decreases as the iteration goes) r = residual(fact) UU = U[(end - bs_r + 1):end, :] # the last bs_r rows of U, used to compute the residuals - normresiduals = diag(UU' * block_inner(r, r) * UU) - normresiduals = sqrt.(real.(normresiduals)) - converged = count(nr -> nr <= tol, normresiduals) + normresiduals = let R = block_inner(r, r) + map(u->sqrt(real(dot(u, R, u))), cols(UU)) + end + converged = count(<=(tol), normresiduals) if converged >= howmany || β <= tol # successfully find enough eigenvalues break elseif verbosity >= EACHITERATION_LEVEL @@ -229,9 +231,9 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) rmul!(view(H, 1:(j + bs - 1), :), h') rmul!(U, h') end - # transform the basis and update the residual and update the TDB. - TDB .= S(0) - TDB[1:keep, 1:keep] .= H[1:keep, 1:keep] + # transform the basis and update the residual and update the BTD. + BTD .= S(0) + BTD[1:keep, 1:keep] .= H[1:keep, 1:keep] B = basis(fact) basistransform!(B, view(U, :, 1:keep)) @@ -242,7 +244,7 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) while length(fact) > keep pop!(fact.V) - fact.total_size -= 1 + fact.k -= 1 end numiter += 1 end @@ -262,7 +264,7 @@ function eigsolve(A, x₀::T, howmany::Int, which::Selector, alg::BlockLanczos) K = length(fact) U2 = view(U, (K - bs_r + 1):K, 1:howmany_actual) R = fact.r - residuals = [zerovector(x₀) for _ in 1:howmany_actual] + residuals = [zerovector(x₀[1]) for _ in 1:howmany_actual] @inbounds for i in 1:howmany_actual for j in 1:bs_r residuals[i] = add!!(residuals[i], R[j], U2[j, i]) diff --git a/src/factorizations/blocklanczos.jl b/src/factorizations/blocklanczos.jl index c94624ab..a6ae9ad0 100644 --- a/src/factorizations/blocklanczos.jl +++ b/src/factorizations/blocklanczos.jl @@ -26,12 +26,6 @@ LinearAlgebra.norm(b::BlockVec) = norm(b.vec) function apply(f, block::BlockVec{T,S}) where {T,S} return BlockVec{S}([apply(f, block[i]) for i in 1:length(block)]) end -function initialize(x₀, size::Int) - S = typeof(inner(x₀, x₀)) - x₀_vec = [randn!(similar(x₀)) for _ in 1:(size - 1)] - pushfirst!(x₀_vec, x₀) - return BlockVec{S}(x₀_vec) -end function Base.push!(V::OrthonormalBasis{T}, b::BlockVec{T}) where {T} for i in 1:length(b) push!(V, b[i]) @@ -55,8 +49,8 @@ A * V = V * B + r * b' ``` For a given BlockLanczos factorization `fact`, length `k = length(fact)` and basis `V = basis(fact)` are -like [`LanczosFactorization`](@ref). The block tridiagonal matrix `TDB` is preallocated in `BlockLanczosFactorization` -and is of type `Hermitian{S<:Number}` with `size(TDB) == (k,k)`. The residuals `r` is of type `Vector{T}`. +like [`LanczosFactorization`](@ref). The block tridiagonal matrix `T` is preallocated in `BlockLanczosFactorization` +and is of type `Hermitian{S<:Number}` with `size(T) == (k,k)`. The residuals `r` is of type `Vector{T}`. One can also query [`normres(fact)`](@ref) to obtain `norm(r)`, the norm of the residual. The matrix `b` takes the default value ``[0;I]``, i.e. the matrix of size `(k,bs)` and an unit matrix in the last `bs` rows and all zeros in the other rows. `bs` is the size of the last block. One can query [`r_size(fact)`] to obtain @@ -69,14 +63,14 @@ BlockLanczos factorizations of a given linear map and a starting vector. """ mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: KrylovFactorization{T,S} - total_size::Int + k::Int const V::OrthonormalBasis{T} # BlockLanczos Basis - const TDB::AbstractMatrix{S} # TDB matrix, S is the matrix type + const T::AbstractMatrix{S} # block tridiagonal matrix, and S is the matrix element type const r::BlockVec{T,S} # residual block r_size::Int # size of the residual block norm_r::SR # norm of the residual block end -Base.length(fact::BlockLanczosFactorization) = fact.total_size +Base.length(fact::BlockLanczosFactorization) = fact.k normres(fact::BlockLanczosFactorization) = fact.norm_r basis(fact::BlockLanczosFactorization) = fact.V residual(fact::BlockLanczosFactorization) = fact.r[1:(fact.r_size)] @@ -88,7 +82,7 @@ residual(fact::BlockLanczosFactorization) = fact.r[1:(fact.r_size)] Iterator that takes a linear map `f::F` (supposed to be real symmetric or complex hermitian) and an initial block `x₀::BlockVec{T,S}` and generates an expanding `BlockLanczosFactorization` thereof. In particular, `BlockLanczosIterator` uses the -BlockLanczos iteration(@footnote "Golub, G. H., & Van Loan, C. F. (2013). Matrix computations (4th ed.). Johns Hopkins University Press.") +BlockLanczos iteration(see: *Golub, G. H., & Van Loan, C. F. (2013). Matrix Computations* (4th ed., pp. 566–569)) scheme to build a successively expanding BlockLanczos factorization. While `f` cannot be tested to be symmetric or hermitian directly when the linear map is encoded as a general callable object or function, with `block_inner(X, f.(X))`, it is tested whether `norm(M-M')` is sufficiently small to be neglected. @@ -145,7 +139,7 @@ function initialize(iter::BlockLanczosIterator{F,T,S}; maxdim = iter.maxdim bs = length(X₀) # block size now A = iter.operator - TDB = zeros(S, maxdim, maxdim) + BTD = zeros(S, maxdim, maxdim) # Orthogonalization of the initial block X₁ = copy(X₀) @@ -154,7 +148,7 @@ function initialize(iter::BlockLanczosIterator{F,T,S}; AX₁ = apply(A, X₁) M₁ = block_inner(X₁, AX₁) - TDB[1:bs, 1:bs] .= M₁ + BTD[1:bs, 1:bs] .= M₁ verbosity >= WARN_LEVEL && warn_nonhermitian(M₁) # Get the first residual @@ -169,7 +163,7 @@ function initialize(iter::BlockLanczosIterator{F,T,S}; end return BlockLanczosFactorization(bs, V, - TDB, + BTD, AX₁, bs, norm_r) @@ -178,7 +172,7 @@ end function expand!(iter::BlockLanczosIterator{F,T,S}, state::BlockLanczosFactorization{T,S,SR}; verbosity::Int=KrylovDefaults.verbosity[]) where {F,T,S,SR} - k = state.total_size + k = state.k rₖ = state.r[1:(state.r_size)] bs_now = length(rₖ) V = state.V @@ -187,21 +181,21 @@ function expand!(iter::BlockLanczosIterator{F,T,S}, Bₖ, good_idx = abstract_qr!(rₖ, iter.qr_tol) bs_next = length(good_idx) push!(V, rₖ[good_idx]) - state.TDB[(k + 1):(k + bs_next), (k - bs_now + 1):k] .= Bₖ - state.TDB[(k - bs_now + 1):k, (k + 1):(k + bs_next)] .= Bₖ' + state.T[(k + 1):(k + bs_next), (k - bs_now + 1):k] .= Bₖ + state.T[(k - bs_now + 1):k, (k + 1):(k + bs_next)] .= Bₖ' # Calculate the new residual and orthogonalize the new basis rₖnext, Mnext = blocklanczosrecurrence(iter.operator, V, Bₖ, iter.orth) verbosity >= WARN_LEVEL && warn_nonhermitian(Mnext) - state.TDB[(k + 1):(k + bs_next), (k + 1):(k + bs_next)] .= Mnext + state.T[(k + 1):(k + bs_next), (k + 1):(k + bs_next)] .= Mnext state.r.vec[1:bs_next] .= rₖnext.vec state.norm_r = norm(rₖnext) - state.total_size += bs_next + state.k += bs_next state.r_size = bs_next if verbosity > EACHITERATION_LEVEL - @info "BlockLanczos expansion to dimension $(state.total_size): subspace normres = $(normres2string(state.norm_r))" + @info "BlockLanczos expansion to dimension $(state.k): subspace normres = $(normres2string(state.norm_r))" end end diff --git a/src/innerproductvec.jl b/src/innerproductvec.jl index a4674052..a45f999f 100644 --- a/src/innerproductvec.jl +++ b/src/innerproductvec.jl @@ -36,8 +36,6 @@ function Base.similar(v::InnerProductVec, ::Type{T}=scalartype(v)) where {T} return InnerProductVec(similar(v.vec), v.dotf) end -Random.randn!(v::InnerProductVec) = (randn!(v.vec); v) - Base.getindex(v::InnerProductVec) = v.vec function Base.copy!(w::InnerProductVec{F}, v::InnerProductVec{F}) where {F} diff --git a/test/BlockVec.jl b/test/BlockVec.jl index 037c7b7d..780dcbe6 100644 --- a/test/BlockVec.jl +++ b/test/BlockVec.jl @@ -25,32 +25,6 @@ atol=tolerance(T)) end -@testset "initialize for BlockVec" begin - for mode in (:vector, :inplace, :outplace) - scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : - (ComplexF64,) - @testset for T in scalartypes - block0 = KrylovKit.initialize(wrapvec(rand(T, N), Val(mode)), n) - @test block0 isa KrylovKit.BlockVec - @test length(block0) == n - Tv = mode === :vector ? Vector{T} : - mode === :inplace ? MinimalVec{true,Vector{T}} : - MinimalVec{false,Vector{T}} - - @test Tuple(typeof(block0).parameters) == (Tv, T) - end - end - - # test for abtract type - T = ComplexF64 - f(x, y) = x' * y - x0 = InnerProductVec(rand(T, N), f) - block0 = KrylovKit.initialize(x0, n) - @test block0 isa KrylovKit.BlockVec - @test length(block0) == n - @test Tuple(typeof(block0).parameters) == (typeof(x0), T) -end - @testset "copy for BlockVec" begin for mode in (:vector, :inplace, :outplace) scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : @@ -71,3 +45,13 @@ end @test typeof(block0) == typeof(block1) @test [block0.vec[i].vec for i in 1:n] == [block1.vec[i].vec for i in 1:n] end + + +struct ms{T,S} + x::Vector{T} + y::S + T::Matrix{S} +end + +a = ms(rand(10), 1.0, rand(10, 10)) +a.T \ No newline at end of file diff --git a/test/eigsolve.jl b/test/eigsolve.jl index e46a61dd..32c1b955 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -423,13 +423,14 @@ end Random.seed!(4) sites_num = 3 p = 5 # block size - x₀ = rand(2^(2 * sites_num^2)) + M = 2^(2 * sites_num^2) + x₀ = [rand(M) for _ in 1:p] get_value_num = 10 tol = 1e-6 h_mat = toric_code_hamiltonian_matrix(sites_num, sites_num) # matrix input - alg = BlockLanczos(p; tol=tol, maxiter=1) + alg = BlockLanczos(; tol=tol, maxiter=1) D, U, info = eigsolve(-h_mat, x₀, get_value_num, :SR, alg) @test count(x -> abs(x + 16.0) < 2.0 - tol, D[1:get_value_num]) == 4 @test count(x -> abs(x + 16.0) < tol, D[1:get_value_num]) == 4 @@ -449,38 +450,36 @@ end A = rand(T, (n, n)) .- one(T) / 2 A = (A + A') / 2 block_size = 2 - x₀ = rand(T, n) + x₀ = [wrapvec(rand(T, n), Val(mode)) for _ in 1:block_size] n1 = div(n, 2) # eigenvalues to solve eigvalsA = eigvals(A) - alg = BlockLanczos(block_size; krylovdim=n, maxiter=1, tol=tolerance(T), + alg = BlockLanczos(; krylovdim=n, maxiter=1, tol=tolerance(T), verbosity=2) D1, V1, info = @test_logs (:info,) eigsolve(wrapop(A, Val(mode)), - wrapvec(x₀, Val(mode)), n1, :SR, alg) - alg = BlockLanczos(block_size; krylovdim=n, maxiter=1, tol=tolerance(T), + x₀, n1, :SR, alg) + alg = BlockLanczos(; krylovdim=n, maxiter=1, tol=tolerance(T), verbosity=1) - @test_logs eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n1, :SR, alg) - alg = BlockLanczos(block_size; krylovdim=n1 + 1, maxiter=1, tol=tolerance(T), + @test_logs eigsolve(wrapop(A, Val(mode)), x₀, n1, :SR, alg) + alg = BlockLanczos(; krylovdim=n1 + 1, maxiter=1, tol=tolerance(T), verbosity=1) - @test_logs (:warn,) eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n1, :SR, - alg) - alg = BlockLanczos(block_size; krylovdim=n, maxiter=1, tol=tolerance(T), + @test_logs (:warn,) eigsolve(wrapop(A, Val(mode)), x₀, n1, :SR, alg) + alg = BlockLanczos(; krylovdim=n, maxiter=1, tol=tolerance(T), verbosity=2) - @test_logs (:info,) eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n1, :SR, - alg) - alg = BlockLanczos(block_size; krylovdim=3, maxiter=3, tol=tolerance(T), + @test_logs (:info,) eigsolve(wrapop(A, Val(mode)), x₀, n1, :SR, alg) + alg = BlockLanczos(; krylovdim=3, maxiter=3, tol=tolerance(T), verbosity=3) @test_logs((:info,), (:info,), (:info,), (:warn,), - eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), 1, :SR, alg)) - alg = BlockLanczos(block_size; krylovdim=4, maxiter=1, tol=tolerance(T), + eigsolve(wrapop(A, Val(mode)), x₀, 1, :SR, alg)) + alg = BlockLanczos(; krylovdim=4, maxiter=1, tol=tolerance(T), verbosity=4) @test_logs((:info,), (:info,), (:info,), (:warn,), - eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), 1, :SR, alg)) + eigsolve(wrapop(A, Val(mode)), x₀, 1, :SR, alg)) # To use blockmode, users have to explicitly set blockmode = true, we don't allow them to use eigselector. # Because of the _residual! function, I can't make sure the stability of types temporarily. # So I ignore the test of @constinferred n2 = n - n1 - alg = BlockLanczos(block_size; krylovdim=2 * n, maxiter=4, tol=tolerance(T)) - D2, V2, info = eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n2, :LR, alg) + alg = BlockLanczos(; krylovdim=2 * n, maxiter=4, tol=tolerance(T)) + D2, V2, info = eigsolve(wrapop(A, Val(mode)), x₀, n2, :LR, alg) D2[1:n2] @test vcat(D1[1:n1], reverse(D2[1:n2])) ≊ eigvalsA @@ -493,10 +492,9 @@ end @test (x -> KrylovKit.apply(A, x)).(unwrapvec.(V1)) ≈ D1 .* unwrapvec.(V1) @test (x -> KrylovKit.apply(A, x)).(unwrapvec.(V2)) ≈ D2 .* unwrapvec.(V2) - alg = BlockLanczos(block_size; krylovdim=2n, maxiter=1, tol=tolerance(T), + alg = BlockLanczos(; krylovdim=2n, maxiter=1, tol=tolerance(T), verbosity=1) - @test_logs (:warn,) (:warn,) eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), - n + 1, :LM, alg) + @test_logs (:warn,) (:warn,) eigsolve(wrapop(A, Val(mode)), x₀, n + 1, :LM, alg) end end @@ -509,13 +507,13 @@ end A = rand(T, (N, N)) .- one(T) / 2 A = (A + A') / 2 block_size = 2 - x₀ = normalize(rand(T, N)) + x₀ = [wrapvec(rand(T, N), Val(mode)) for _ in 1:block_size] eigvalsA = eigvals(A) - alg = BlockLanczos(block_size; krylovdim=N, maxiter=10, tol=tolerance(T), + alg = BlockLanczos(; krylovdim=N, maxiter=10, tol=tolerance(T), eager=true, verbosity=0) - D1, V1, info1 = eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n, :SR, alg) - D2, V2, info2 = eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n, :LR, alg) + D1, V1, info1 = eigsolve(wrapop(A, Val(mode)), x₀, n, :SR, alg) + D2, V2, info2 = eigsolve(wrapop(A, Val(mode)), x₀, n, :LR, alg) l1 = info1.converged l2 = info2.converged @@ -546,10 +544,10 @@ end block_size = 2 eig_num = 2 Hip(x::Vector, y::Vector) = x' * H * y - x₀ = InnerProductVec(rand(T, n), Hip) + x₀ = [InnerProductVec(rand(T, n), Hip) for _ in 1:block_size] Aip(x::InnerProductVec) = InnerProductVec(H * x.vec, Hip) D, V, info = eigsolve(Aip, x₀, eig_num, :SR, - BlockLanczos(block_size; krylovdim=n, maxiter=1, tol=tolerance(T), + BlockLanczos(; krylovdim=n, maxiter=1, tol=tolerance(T), verbosity=0)) D_true = eigvals(H) BlockV = KrylovKit.BlockVec{T}(V) @@ -558,7 +556,7 @@ end @test findmax([norm(Aip(V[i]) - D[i] * V[i]) for i in 1:eig_num])[1] < tolerance(T) end -# with the same krylovdim, BlockLanczos has lower accuracy with blocksize >1. +# with the same krylovdim, BlockLanczos has lower accuracy with the size of block larger than 1. @testset "Complete Lanczos and BlockLanczos $mode" for mode in (:vector, :inplace, :outplace) scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : @@ -568,14 +566,13 @@ end A = rand(T, (2N, 2N)) A = (A + A') / 2 block_size = 1 - x₀ = rand(T, 2N) + x₀_block = [wrapvec(rand(T, 2N), Val(mode)) for _ in 1:block_size] + x₀_lanczos = x₀_block[1] alg1 = Lanczos(; krylovdim=2n, maxiter=10, tol=tolerance(T), verbosity=1) - alg2 = BlockLanczos(block_size; krylovdim=2n, maxiter=10, tol=tolerance(T), + alg2 = BlockLanczos(; krylovdim=2n, maxiter=10, tol=tolerance(T), verbosity=1) - evals1, _, info1 = eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n, :SR, - alg1) - evals2, _, info2 = eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n, :SR, - alg2) + evals1, _, info1 = eigsolve(wrapop(A, Val(mode)), x₀_lanczos, n, :SR, alg1) + evals2, _, info2 = eigsolve(wrapop(A, Val(mode)), x₀_block, n, :SR, alg2) @test info1.converged == info2.converged end @testset for T in scalartypes @@ -583,14 +580,13 @@ end A = rand(T, (2N, 2N)) A = (A + A') / 2 block_size = 4 - x₀ = rand(T, 2N) + x₀_block = [wrapvec(rand(T, 2N), Val(mode)) for _ in 1:block_size] + x₀_lanczos = x₀_block[1] alg1 = Lanczos(; krylovdim=2n, maxiter=10, tol=tolerance(T), verbosity=1) - alg2 = BlockLanczos(block_size; krylovdim=2n, maxiter=10, tol=tolerance(T), + alg2 = BlockLanczos(; krylovdim=2n, maxiter=10, tol=tolerance(T), verbosity=1) - evals1, _, info1 = eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n, :SR, - alg1) - evals2, _, info2 = eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n, :SR, - alg2) + evals1, _, info1 = eigsolve(wrapop(A, Val(mode)), x₀_lanczos, n, :SR, alg1) + evals2, _, info2 = eigsolve(wrapop(A, Val(mode)), x₀_block, n, :SR, alg2) @test info1.converged >= info2.converged + 1 end end @@ -606,15 +602,14 @@ end A = rand(T, (N, N)) .- one(T) / 2 A = (A + A') / 2 block_size = 5 - x₀ = rand(T, N) + x₀ = [wrapvec(rand(T, N), Val(mode)) for _ in 1:block_size] values0 = eigvals(A)[1:n] n1 = n ÷ 2 - alg = BlockLanczos(block_size; krylovdim=3 * n ÷ 2, maxiter=1, tol=1e-12) - values, _, _ = eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n, :SR, alg) + alg = BlockLanczos(; krylovdim=3 * n ÷ 2, maxiter=1, tol=1e-12) + values, _, _ = eigsolve(wrapop(A, Val(mode)), x₀, n, :SR, alg) error1 = norm(values[1:n1] - values0[1:n1]) - alg_shrink = BlockLanczos(block_size; krylovdim=3 * n ÷ 2, maxiter=2, tol=1e-12) - values_shrink, _, _ = eigsolve(wrapop(A, Val(mode)), wrapvec(x₀, Val(mode)), n, :SR, - alg_shrink) + alg_shrink = BlockLanczos(; krylovdim=3 * n ÷ 2, maxiter=2, tol=1e-12) + values_shrink, _, _ = eigsolve(wrapop(A, Val(mode)), x₀, n, :SR, alg_shrink) error2 = norm(values_shrink[1:n1] - values0[1:n1]) @test error2 < error1 end diff --git a/test/factorize.jl b/test/factorize.jl index 03634ce4..e88076ce 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -310,13 +310,12 @@ end x₀ = KrylovKit.BlockVec{T}([wrapvec(x₀m[:, i], Val(mode)) for i in 1:block_size]) eigvalsA = eigvals(A) iter = BlockLanczosIterator(wrapop(A, Val(mode)), x₀, N, qr_tol(T)) - # TODO: Why type unstable? fact = @constinferred initialize(iter) @constinferred expand!(iter, fact) @test_logs initialize(iter; verbosity=EACHITERATION_LEVEL) @test_logs (:info,) initialize(iter; verbosity=EACHITERATION_LEVEL + 1) verbosity = EACHITERATION_LEVEL + 1 - while fact.total_size < n + while fact.k < n if verbosity == EACHITERATION_LEVEL + 1 @test_logs (:info,) expand!(iter, fact; verbosity=verbosity) verbosity = EACHITERATION_LEVEL @@ -337,7 +336,7 @@ end @test_logs initialize(iter; verbosity=0) @test_logs (:warn,) initialize(iter) verbosity = 1 - while fact.total_size < n + while fact.k < n if verbosity == 1 @test_logs (:warn,) expand!(iter, fact; verbosity=verbosity) verbosity = 0 @@ -364,13 +363,13 @@ end iter = @constinferred BlockLanczosIterator(wrapop(A, Val(mode)), x₀, N, qr_tol(T)) krylovdim = n fact = initialize(iter) - while fact.norm_r > eps(float(real(T))) && fact.total_size < krylovdim + while fact.norm_r > eps(float(real(T))) && fact.k < krylovdim @constinferred expand!(iter, fact) - k = fact.total_size + k = fact.k rs = fact.r_size V0 = fact.V[1:k] r0 = fact.r[1:rs] - H = fact.TDB[1:k, 1:k] + H = fact.T[1:k, 1:k] norm_r = fact.norm_r V = hcat([unwrapvec(v) for v in V0]...) r = hcat([unwrapvec(r0[i]) for i in 1:rs]...) diff --git a/test/testsetup.jl b/test/testsetup.jl index a14b43b1..45621c5b 100644 --- a/test/testsetup.jl +++ b/test/testsetup.jl @@ -56,8 +56,6 @@ function wrapvec(v, ::Val{mode}) where {mode} mode === :mixed ? MinimalSVec(v) : throw(ArgumentError("invalid mode ($mode)")) end -Base.similar(v::MinimalVec{M}) where {M} = MinimalVec{M}(similar(v.vec)) -Random.randn!(v::MinimalVec) = (randn!(v.vec); v) # For tests of BlockLanczos function wrapvec2(v, ::Val{mode}) where {mode} return mode === :mixed ? MinimalMVec(v) : wrapvec(v, mode) end @@ -84,8 +82,6 @@ function wrapop(A, ::Val{mode}) where {mode} end end -# block operations -# ---------------- if VERSION < v"1.9" stack(f, itr) = mapreduce(f, hcat, itr) stack(itr) = reduce(hcat, itr) From 86abfeb91acd9b71ba3ab28ac5aa36186516a92c Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Fri, 9 May 2025 21:16:44 +0800 Subject: [PATCH 57/82] format and some revise --- docs/src/man/implementation.md | 2 +- src/algorithms.jl | 4 ++-- src/eigsolve/lanczos.jl | 2 +- src/factorizations/blocklanczos.jl | 7 ++++--- test/BlockVec.jl | 10 ---------- test/eigsolve.jl | 2 +- 6 files changed, 9 insertions(+), 18 deletions(-) diff --git a/docs/src/man/implementation.md b/docs/src/man/implementation.md index a93e1370..5acac6b4 100644 --- a/docs/src/man/implementation.md +++ b/docs/src/man/implementation.md @@ -47,7 +47,7 @@ KrylovKit.basistransform! ``` ## Block Krylov method -The block version of the Krylov subspace algorithm is an approach to extending Krylov subspace techniques from single starting vectors to multiple starting vectors. It is mainly used for solving linear systems with degenerate dominant eigenvalues. +The block version of the Krylov subspace algorithm is an approach to extending Krylov subspace techniques from a block of starting vectors. It is mainly used for solving linear systems with degenerate dominant eigenvalues. In our implementation, the multiple-vector data structure is BlockVec, which implements the [`KrylovKit.abstract_qr!`](@ref), [`KrylovKit.block_reorthogonalize!`](@ref), and [`KrylovKit.compute_residual!`](@ref) interfaces. diff --git a/src/algorithms.jl b/src/algorithms.jl index 4fdbc945..0baa4f49 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -138,7 +138,7 @@ The block version of [`Lanczos`](@ref) is suited for solving eigenvalue problems Its implementation is mainly based on *Golub, G. H., & Van Loan, C. F. (2013). Matrix Computations* (4th ed., pp. 566–569). Arguments `krylovdim`, `maxiter`, `tol`, `orth`, `eager` and `verbosity` are the same as `Lanczos`. `qr_tol` is the error tolerance for `abstract_qr!` - a subroutine used to orthorgonalize the vectors in the same block. -The initial size of the block is determined by the number of start vectors that a user provides. And the size of the block shrinks during iterations. +The initial size of the block is determined by the number of starting vectors that a user provides. And the size of the block shrinks during iterations. The initial block size determines the maximum degeneracy of the target eigenvalue can that be resolved. The iteration stops when either the norm of the residual is below `tol` or a sufficient number of eigenvectors have converged. [Reference](https://www.netlib.org/utk/people/JackDongarra/etemplates/node250.html) @@ -494,7 +494,7 @@ A module listing the default values for the typical parameters in Krylov based a times the Krylov subspace may be rebuilt - `blockkrylovdim = 100`: the maximal dimension of the Krylov subspace that will be constructed for `BlockLanczos` - `tol = 1e-12`: the tolerance to which the problem must be solved, based on a suitable - error measure, e.g. the norm of some residual + error measure, e.g. the norm of some residual. !!! warning diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index 831acfd7..02137212 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -198,7 +198,7 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) r = residual(fact) UU = U[(end - bs_r + 1):end, :] # the last bs_r rows of U, used to compute the residuals normresiduals = let R = block_inner(r, r) - map(u->sqrt(real(dot(u, R, u))), cols(UU)) + map(u -> sqrt(real(dot(u, R, u))), cols(UU)) end converged = count(<=(tol), normresiduals) if converged >= howmany || β <= tol # successfully find enough eigenvalues diff --git a/src/factorizations/blocklanczos.jl b/src/factorizations/blocklanczos.jl index a6ae9ad0..e4c693a8 100644 --- a/src/factorizations/blocklanczos.jl +++ b/src/factorizations/blocklanczos.jl @@ -50,7 +50,8 @@ A * V = V * B + r * b' For a given BlockLanczos factorization `fact`, length `k = length(fact)` and basis `V = basis(fact)` are like [`LanczosFactorization`](@ref). The block tridiagonal matrix `T` is preallocated in `BlockLanczosFactorization` -and is of type `Hermitian{S<:Number}` with `size(T) == (k,k)`. The residuals `r` is of type `Vector{T}`. +and is of type `Hermitian{S<:Number}` with `size(T) == (krylovdim + bs₀, krylovdim + bs₀)` where `bs₀` is the size of the initial block +and `krylovdim` is the maximum dimension of the Krylov subspace. The residuals `r` is of type `Vector{T}`. One can also query [`normres(fact)`](@ref) to obtain `norm(r)`, the norm of the residual. The matrix `b` takes the default value ``[0;I]``, i.e. the matrix of size `(k,bs)` and an unit matrix in the last `bs` rows and all zeros in the other rows. `bs` is the size of the last block. One can query [`r_size(fact)`] to obtain @@ -87,8 +88,8 @@ scheme to build a successively expanding BlockLanczos factorization. While `f` c hermitian directly when the linear map is encoded as a general callable object or function, with `block_inner(X, f.(X))`, it is tested whether `norm(M-M')` is sufficiently small to be neglected. -The argument `f` can be a matrix, or a function accepting a single argument `x`, so that -`f(x)` implements the action of the linear map on the block `x`. +The argument `f` can be a matrix, or a function accepting a single argument `v`, so that +`f(v)` implements the action of the linear map on the vector `v`. The optional argument `orth` specifies which [`Orthogonalizer`](@ref) to be used. The default value in [`KrylovDefaults`](@ref) is to use [`ModifiedGramSchmidt2`](@ref), which diff --git a/test/BlockVec.jl b/test/BlockVec.jl index 780dcbe6..0df0f24e 100644 --- a/test/BlockVec.jl +++ b/test/BlockVec.jl @@ -45,13 +45,3 @@ end @test typeof(block0) == typeof(block1) @test [block0.vec[i].vec for i in 1:n] == [block1.vec[i].vec for i in 1:n] end - - -struct ms{T,S} - x::Vector{T} - y::S - T::Matrix{S} -end - -a = ms(rand(10), 1.0, rand(10, 10)) -a.T \ No newline at end of file diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 32c1b955..55abc5ed 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -441,7 +441,7 @@ end @test count(x -> abs(x + 16.0) < 1e-8, D[1:get_value_num]) == 4 end -# For user interface, input is single vector. +# For user interface, input is a vector of starting vectors. @testset "BlockLanczos - eigsolve full $mode" for mode in (:vector, :inplace, :outplace) scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) From 8ad491f7ad94340cc90fabe0cd5224ce59959a3c Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Sat, 10 May 2025 18:53:38 +0800 Subject: [PATCH 58/82] revise for review --- docs/src/man/implementation.md | 4 +- src/eigsolve/lanczos.jl | 16 ++++---- src/factorizations/blocklanczos.jl | 66 +++++++++++++++--------------- test/BlockVec.jl | 8 ++-- test/eigsolve.jl | 8 +--- test/factorize.jl | 6 +-- test/innerproductvec.jl | 15 ++++--- test/orthonormal.jl | 13 +++--- test/testsetup.jl | 3 +- 9 files changed, 65 insertions(+), 74 deletions(-) diff --git a/docs/src/man/implementation.md b/docs/src/man/implementation.md index 5acac6b4..c3512948 100644 --- a/docs/src/man/implementation.md +++ b/docs/src/man/implementation.md @@ -49,7 +49,7 @@ KrylovKit.basistransform! ## Block Krylov method The block version of the Krylov subspace algorithm is an approach to extending Krylov subspace techniques from a block of starting vectors. It is mainly used for solving linear systems with degenerate dominant eigenvalues. -In our implementation, the multiple-vector data structure is BlockVec, which implements the [`KrylovKit.abstract_qr!`](@ref), [`KrylovKit.block_reorthogonalize!`](@ref), and [`KrylovKit.compute_residual!`](@ref) interfaces. +In our implementation, the multiple-vector data structure is BlockVec, which implements the [`KrylovKit.abstract_qr!`](@ref), [`KrylovKit.block_reorthogonalize!`](@ref), and [`KrylovKit.block_orthogonalize!`](@ref) interfaces. ```@docs KrylovKit.BlockVec @@ -64,7 +64,7 @@ This apply QR decomposition to a block of vectors using modified Gram-Schmidt pr Additional procedures applied to the block are as follows: ```@docs KrylovKit.block_reorthogonalize! -KrylovKit.compute_residual! +KrylovKit.block_orthogonalize! ``` diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index 02137212..080aa9c9 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -185,7 +185,7 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) end # BlockLanczos can access the case of K = 1 and doesn't need extra processing if K >= krylovdim || β <= tol || (alg.eager && K >= howmany) - # compute eigenvalues + # Compute eigenvalues # Note: Fast eigen solver for block tridiagonal matrices is not implemented yet. BTD = view(fact.T, 1:K, 1:K) D, U = eigen(Hermitian(BTD)) @@ -193,15 +193,15 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) p = sortperm(D; by=by, rev=rev) D, U = permuteeig!(D, U, p) - # detect convergence by computing the residuals - bs_r = fact.r_size # the block size of the residual (decreases as the iteration goes) + # Detect convergence by computing the residuals + bs_r = fact.r_size # The block size of the residual (decreases as the iteration goes) r = residual(fact) - UU = U[(end - bs_r + 1):end, :] # the last bs_r rows of U, used to compute the residuals + UU = U[(end - bs_r + 1):end, :] # The last bs_r rows of U, used to compute the residuals normresiduals = let R = block_inner(r, r) map(u -> sqrt(real(dot(u, R, u))), cols(UU)) end converged = count(<=(tol), normresiduals) - if converged >= howmany || β <= tol # successfully find enough eigenvalues + if converged >= howmany || β <= tol # Successfully find enough eigenvalues. break elseif verbosity >= EACHITERATION_LEVEL @info "BlockLanczos eigsolve in iteration $numiter: $converged values converged, normres = $(normres2string(normresiduals))" @@ -211,7 +211,7 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) if K < krylovdim expand!(iter, fact; verbosity=verbosity) numops += 1 - else # shrink and restart + else # Shrink and restart. Following the shrinking method of Lanczos in the above `eigsolve`. numiter >= maxiter && break bsn = max(div(3 * krylovdim + 2 * converged, 5) ÷ bs, 1) # Divide basis into blocks with the same size keep = bs * bsn @@ -222,7 +222,7 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) H[(bsn * bs + 1):end, j] = U[(K - bs + 1):K, j] end # Turn diagonal matrix D into a block tridiagonal matrix, and make sure - # the residual of krylov subspace keeps the form of [0,..,0,R] + # The residual of krylov subspace keeps the form of [0,..,0,R] @inbounds for j in keep:-1:1 h, ν = householder(H, j + bs, 1:j, j) H[j + bs, j] = ν @@ -231,7 +231,7 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) rmul!(view(H, 1:(j + bs - 1), :), h') rmul!(U, h') end - # transform the basis and update the residual and update the BTD. + # Transform the basis and update the residual and update the BTD. BTD .= S(0) BTD[1:keep, 1:keep] .= H[1:keep, 1:keep] B = basis(fact) diff --git a/src/factorizations/blocklanczos.jl b/src/factorizations/blocklanczos.jl index e4c693a8..a0821db6 100644 --- a/src/factorizations/blocklanczos.jl +++ b/src/factorizations/blocklanczos.jl @@ -174,24 +174,24 @@ function expand!(iter::BlockLanczosIterator{F,T,S}, state::BlockLanczosFactorization{T,S,SR}; verbosity::Int=KrylovDefaults.verbosity[]) where {F,T,S,SR} k = state.k - rₖ = state.r[1:(state.r_size)] - bs_now = length(rₖ) + R = state.r[1:(state.r_size)] + bs = length(R) V = state.V - # Calculate the new basis and Bₖ - Bₖ, good_idx = abstract_qr!(rₖ, iter.qr_tol) + # Calculate the new basis and B + B, good_idx = abstract_qr!(R, iter.qr_tol) bs_next = length(good_idx) - push!(V, rₖ[good_idx]) - state.T[(k + 1):(k + bs_next), (k - bs_now + 1):k] .= Bₖ - state.T[(k - bs_now + 1):k, (k + 1):(k + bs_next)] .= Bₖ' + push!(V, R[good_idx]) + state.T[(k + 1):(k + bs_next), (k - bs + 1):k] .= B + state.T[(k - bs + 1):k, (k + 1):(k + bs_next)] .= B' # Calculate the new residual and orthogonalize the new basis - rₖnext, Mnext = blocklanczosrecurrence(iter.operator, V, Bₖ, iter.orth) + Rnext, Mnext = blocklanczosrecurrence(iter.operator, V, B, iter.orth) verbosity >= WARN_LEVEL && warn_nonhermitian(Mnext) state.T[(k + 1):(k + bs_next), (k + 1):(k + bs_next)] .= Mnext - state.r.vec[1:bs_next] .= rₖnext.vec - state.norm_r = norm(rₖnext) + state.r.vec[1:bs_next] .= Rnext.vec + state.norm_r = norm(Rnext) state.k += bs_next state.r_size = bs_next @@ -200,52 +200,52 @@ function expand!(iter::BlockLanczosIterator{F,T,S}, end end -function blocklanczosrecurrence(operator, V::OrthonormalBasis, Bₖ::AbstractMatrix, +function blocklanczosrecurrence(operator, V::OrthonormalBasis, B::AbstractMatrix, orth::ModifiedGramSchmidt2) # Apply the operator and calculate the M. Get Xnext and Mnext. - bs, bs_last = size(Bₖ) - S = eltype(Bₖ) + bs, bs_prev = size(B) + S = eltype(B) k = length(V) X = BlockVec{S}(V[(k - bs + 1):k]) AX = apply(operator, X) M = block_inner(X, AX) # Calculate the new residual. Get Rnext - Xlast = BlockVec{S}(V[(k - bs_last - bs + 1):(k - bs)]) - rₖnext = compute_residual!(AX, X, M, Xlast, Bₖ') - block_reorthogonalize!(rₖnext, V) - return rₖnext, M + Xprev = BlockVec{S}(V[(k - bs_prev - bs + 1):(k - bs)]) + Rnext = block_orthogonalize!(AX, X, M, Xprev, B') + block_reorthogonalize!(Rnext, V) + return Rnext, M end """ - compute_residual!(AX::BlockVec{T,S}, X::BlockVec{T,S}, + block_orthogonalize!(AX::BlockVec{T,S}, X::BlockVec{T,S}, M::AbstractMatrix, - X_prev::BlockVec{T,S}, B_prev::AbstractMatrix) where {T,S} + Xprev::BlockVec{T,S}, Bprev::AbstractMatrix) where {T,S} Computes the residual block and stores the result in `AX`. -This function orthogonalizes `AX` against the two most recent basis blocks, `X` and `X_prev`. +This function orthogonalizes `AX` against the two most recent basis blocks, `X` and `Xprev`. Here, `AX` represents the image of the current block `X` under the action of the linear operator `A`. The matrix `M` contains the inner products between `X` and `AX`, i.e., the projection of `AX` onto `X`. -Similarly, `B_prev` represents the projection of `AX` onto `X_prev`. +Similarly, `Bprev` represents the projection of `AX` onto `Xprev`. The residual is computed as: ``` - AX ← AX - X * M - X_prev * B_prev + AX ← AX - X * M - Xprev * Bprev ``` -After this operation, `AX` is orthogonal (in the block inner product sense) to both `X` and `X_prev`. +After this operation, `AX` is orthogonal (in the block inner product sense) to both `X` and `Xprev`. """ -function compute_residual!(AX::BlockVec{T,S}, X::BlockVec{T,S}, +function block_orthogonalize!(AX::BlockVec{T,S}, X::BlockVec{T,S}, M::AbstractMatrix, - X_prev::BlockVec{T,S}, B_prev::AbstractMatrix) where {T,S} + Xprev::BlockVec{T,S}, Bprev::AbstractMatrix) where {T,S} @inbounds for j in 1:length(X) for i in 1:length(X) AX[j] = add!!(AX[j], X[i], -M[i, j]) end - for i in 1:length(X_prev) - AX[j] = add!!(AX[j], X_prev[i], -B_prev[i, j]) + for i in 1:length(Xprev) + AX[j] = add!!(AX[j], Xprev[i], -Bprev[i, j]) end end return AX @@ -263,14 +263,14 @@ Specifically, it modifies each vector `basis[i]` by projecting out its component Here,`⟨·,·⟩` denotes the inner product. The function assumes that `basis_sofar` is already orthonormal. """ -function block_reorthogonalize!(basis::BlockVec{T,S}, - basis_sofar::OrthonormalBasis{T}) where {T,S} - for i in 1:length(basis) - for q in basis_sofar - basis[i], _ = orthogonalize!!(basis[i], q, ModifiedGramSchmidt()) +function block_reorthogonalize!(R::BlockVec{T,S}, + V::OrthonormalBasis{T}) where {T,S} + for i in 1:length(R) + for q in V + R[i], _ = orthogonalize!!(R[i], q, ModifiedGramSchmidt()) end end - return basis + return R end function warn_nonhermitian(M::AbstractMatrix) diff --git a/test/BlockVec.jl b/test/BlockVec.jl index 0df0f24e..c379b54a 100644 --- a/test/BlockVec.jl +++ b/test/BlockVec.jl @@ -3,8 +3,6 @@ scalartypes = mode === :vector ? (Float32, Float64, ComplexF64) : (ComplexF64,) @testset for T in scalartypes - mode = :inplace - T = ComplexF64 A = rand(T, N, N) .- one(T) / 2 A = (A + A') / 2 wx₀ = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) @@ -16,7 +14,7 @@ end T = ComplexF64 A = rand(T, N, N) .- one(T) / 2 - A = (A + A') / 2 + A = A' * A + I f(x, y) = x' * A * y Af(x::InnerProductVec) = KrylovKit.InnerProductVec(A * x[], x.dotf) x₀ = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), f) for _ in 1:n]) @@ -39,7 +37,9 @@ end # test for abtract type T = ComplexF64 - f(x, y) = x' * y + A = rand(T, N, N) .- one(T) / 2 + A = A' * A + I + f(x, y) = x' * A * y block0 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), f) for _ in 1:n]) block1 = copy(block0) @test typeof(block0) == typeof(block1) diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 55abc5ed..4a94509c 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -420,7 +420,6 @@ end return H end - Random.seed!(4) sites_num = 3 p = 5 # block size M = 2^(2 * sites_num^2) @@ -446,7 +445,6 @@ end scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @testset for T in scalartypes - Random.seed!(6) A = rand(T, (n, n)) .- one(T) / 2 A = (A + A') / 2 block_size = 2 @@ -503,7 +501,6 @@ end scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @testset for T in scalartypes - Random.seed!(6) A = rand(T, (N, N)) .- one(T) / 2 A = (A + A') / 2 block_size = 2 @@ -539,7 +536,7 @@ end @testset "BlockLanczos - eigsolve for abstract type" begin T = ComplexF64 - H = rand(T, (n, n)) + H = rand(T, (n, n)) .- one(T) / 2 H = H' * H + I block_size = 2 eig_num = 2 @@ -562,7 +559,6 @@ end scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @testset for T in scalartypes - Random.seed!(6) A = rand(T, (2N, 2N)) A = (A + A') / 2 block_size = 1 @@ -576,7 +572,6 @@ end @test info1.converged == info2.converged end @testset for T in scalartypes - Random.seed!(6) A = rand(T, (2N, 2N)) A = (A + A') / 2 block_size = 4 @@ -598,7 +593,6 @@ end scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @testset for T in scalartypes - Random.seed!(6) A = rand(T, (N, N)) .- one(T) / 2 A = (A + A') / 2 block_size = 5 diff --git a/test/factorize.jl b/test/factorize.jl index e88076ce..1210ed77 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -309,7 +309,7 @@ end x₀m = Matrix(qr(rand(T, N, block_size)).Q) x₀ = KrylovKit.BlockVec{T}([wrapvec(x₀m[:, i], Val(mode)) for i in 1:block_size]) eigvalsA = eigvals(A) - iter = BlockLanczosIterator(wrapop(A, Val(mode)), x₀, N, qr_tol(T)) + iter = BlockLanczosIterator(wrapop(A, Val(mode)), x₀, N, tolerance(T)) fact = @constinferred initialize(iter) @constinferred expand!(iter, fact) @test_logs initialize(iter; verbosity=EACHITERATION_LEVEL) @@ -330,7 +330,7 @@ end bs = 2 v₀m = Matrix(qr(rand(T, n, bs)).Q) v₀ = KrylovKit.BlockVec{T}([wrapvec(v₀m[:, i], Val(mode)) for i in 1:bs]) - iter = BlockLanczosIterator(wrapop(B, Val(mode)), v₀, N, qr_tol(T)) + iter = BlockLanczosIterator(wrapop(B, Val(mode)), v₀, N, tolerance(T)) fact = initialize(iter) @constinferred expand!(iter, fact; verbosity=0) @test_logs initialize(iter; verbosity=0) @@ -360,7 +360,7 @@ end block_size = 5 x₀m = Matrix(qr(rand(T, N, block_size)).Q) x₀ = KrylovKit.BlockVec{T}([wrapvec(x₀m[:, i], Val(mode)) for i in 1:block_size]) - iter = @constinferred BlockLanczosIterator(wrapop(A, Val(mode)), x₀, N, qr_tol(T)) + iter = @constinferred BlockLanczosIterator(wrapop(A, Val(mode)), x₀, N, tolerance(T)) krylovdim = n fact = initialize(iter) while fact.norm_r > eps(float(real(T))) && fact.k < krylovdim diff --git a/test/innerproductvec.jl b/test/innerproductvec.jl index 03ca5eaa..84b206d9 100644 --- a/test/innerproductvec.jl +++ b/test/innerproductvec.jl @@ -19,9 +19,8 @@ end @testset "block_inner for abstract inner product" begin T = ComplexF64 - H = rand(T, N, N) + H = rand(T, N, N) .- one(T) / 2 H = H' * H + I - H = (H + H') / 2 ip(x, y) = x' * H * y X = [InnerProductVec(rand(T, N), ip) for _ in 1:n] Y = [InnerProductVec(rand(T, N), ip) for _ in 1:n] @@ -35,12 +34,12 @@ end @test isapprox(M, M0; atol=relax_tol(T)) end -@testset "compute_residual! $mode" for mode in (:vector, :inplace, :outplace) +@testset "block_orthogonalize! $mode" for mode in (:vector, :inplace, :outplace) scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @testset for T in scalartypes A = rand(T, N, N) .- one(T) / 2 - A = A + A' + A = (A + A') / 2 M = rand(T, n, n) M = M' + M B = qr(rand(T, n, n)).R @@ -48,16 +47,16 @@ end X1 = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) AX1 = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) AX1copy = copy(AX1) - KrylovKit.compute_residual!(AX1, X1, M, X0, B) + KrylovKit.block_orthogonalize!(AX1, X1, M, X0, B) _bw2m(X) = hcat(unwrapvec.(X)...) @test isapprox(_bw2m(AX1), _bw2m(AX1copy) - _bw2m(X1) * M - _bw2m(X0) * B; atol=tolerance(T)) end end -@testset "compute_residual! for abstract inner product" begin +@testset "block_orthogonalize! for abstract inner product" begin T = ComplexF64 - A = rand(T, N, N) + A = rand(T, N, N) .- one(T) / 2 A = A * A' + I ip(x, y) = x' * A * y M = rand(T, n, n) @@ -67,7 +66,7 @@ end X1 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) AX1 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) AX1copy = copy(AX1) - KrylovKit.compute_residual!(AX1, X1, M, X0, B) + KrylovKit.block_orthogonalize!(AX1, X1, M, X0, B) @test isapprox(hcat([AX1.vec[i].vec for i in 1:n]...), hcat([AX1copy.vec[i].vec for i in 1:n]...) - diff --git a/test/orthonormal.jl b/test/orthonormal.jl index ac7adce0..679dff3c 100644 --- a/test/orthonormal.jl +++ b/test/orthonormal.jl @@ -10,7 +10,7 @@ Av[n ÷ 2] = sum(Av[(n ÷ 2 + 1):end] .* rand(T, n - n ÷ 2)) Bv = deepcopy(Av) wAv = wrapvec.(Av, Val(mode)) - R, gi = KrylovKit.abstract_qr!(KrylovKit.BlockVec{T}(wAv), qr_tol(T)) + R, gi = KrylovKit.abstract_qr!(KrylovKit.BlockVec{T}(wAv), tolerance(T)) Av1 = [unwrapvec(wAv[i]) for i in gi] @test hcat(Av1...)' * hcat(Av1...) ≈ I @test length(gi) < n @@ -24,7 +24,7 @@ end @testset "abstract_qr! for abstract inner product" begin T = ComplexF64 - H = rand(T, N, N) + H = rand(T, N, N) .- one(T) / 2 H = H' * H + I ip(x, y) = x' * H * y X₁ = InnerProductVec(rand(T, N), ip) @@ -37,7 +37,7 @@ end # Make sure X is not full rank X[end] = sum(X[1:(end - 1)] .* rand(T, n - 1)) Xcopy = deepcopy(X) - R, gi = KrylovKit.abstract_qr!(KrylovKit.BlockVec{T}(X), qr_tol(T)) + R, gi = KrylovKit.abstract_qr!(KrylovKit.BlockVec{T}(X), tolerance(T)) @test length(gi) < n @test eltype(R) == T @@ -59,7 +59,7 @@ end x₁ = [wrapvec(rand(T, N), Val(mode)) for i in 1:(2 * n)] b₀ = KrylovKit.BlockVec{T}(x₀) b₁ = KrylovKit.BlockVec{T}(x₁) - KrylovKit.abstract_qr!(b₁, qr_tol(T)) + KrylovKit.abstract_qr!(b₁, tolerance(T)) orthobasis_x₁ = KrylovKit.OrthonormalBasis(b₁.vec) KrylovKit.block_reorthogonalize!(b₀, orthobasis_x₁) @test norm(KrylovKit.block_inner(b₀, b₁)) < tolerance(T) @@ -68,16 +68,15 @@ end @testset "block_reorthogonalize! for abstract inner product" begin T = ComplexF64 - H = rand(T, N, N) + H = rand(T, N, N) .- one(T) / 2 H = H' * H + I - H = (H + H') / 2 ip(x, y) = x' * H * y x₀ = [InnerProductVec(rand(T, N), ip) for i in 1:n] x₁ = [InnerProductVec(rand(T, N), ip) for i in 1:(2 * n)] b₀ = KrylovKit.BlockVec{T}(x₀) b₁ = KrylovKit.BlockVec{T}(x₁) - KrylovKit.abstract_qr!(b₁, qr_tol(T)) + KrylovKit.abstract_qr!(b₁, tolerance(T)) orthobasis_x₁ = KrylovKit.OrthonormalBasis(b₁.vec) KrylovKit.block_reorthogonalize!(b₀, orthobasis_x₁) @test norm(KrylovKit.block_inner(b₀, b₁)) < tolerance(T) diff --git a/test/testsetup.jl b/test/testsetup.jl index 45621c5b..93c6e0b3 100644 --- a/test/testsetup.jl +++ b/test/testsetup.jl @@ -13,8 +13,7 @@ using Random # ----------------- "function for determining the precision of a type" tolerance(T::Type{<:Number}) = eps(real(T))^(2 // 3) -qr_tol(T::Type{<:Number}) = 1e4 * eps(real(T)) -relax_tol(T::Type{<:Number}) = eps(real(T))^(0.5) +relax_tol(T::Type{<:Number}) = eps(real(T))^(1//2) "function for comparing sets of eigenvalues" function ≊(list1::AbstractVector, list2::AbstractVector) From 9557400c59c7c8f04d2007ef14977886f0e0feed Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Sat, 10 May 2025 23:45:21 +0800 Subject: [PATCH 59/82] format --- src/factorizations/blocklanczos.jl | 4 ++-- test/factorize.jl | 3 ++- test/testsetup.jl | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/factorizations/blocklanczos.jl b/src/factorizations/blocklanczos.jl index a0821db6..f2eacf9c 100644 --- a/src/factorizations/blocklanczos.jl +++ b/src/factorizations/blocklanczos.jl @@ -238,8 +238,8 @@ After this operation, `AX` is orthogonal (in the block inner product sense) to b """ function block_orthogonalize!(AX::BlockVec{T,S}, X::BlockVec{T,S}, - M::AbstractMatrix, - Xprev::BlockVec{T,S}, Bprev::AbstractMatrix) where {T,S} + M::AbstractMatrix, + Xprev::BlockVec{T,S}, Bprev::AbstractMatrix) where {T,S} @inbounds for j in 1:length(X) for i in 1:length(X) AX[j] = add!!(AX[j], X[i], -M[i, j]) diff --git a/test/factorize.jl b/test/factorize.jl index 1210ed77..4373ee68 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -360,7 +360,8 @@ end block_size = 5 x₀m = Matrix(qr(rand(T, N, block_size)).Q) x₀ = KrylovKit.BlockVec{T}([wrapvec(x₀m[:, i], Val(mode)) for i in 1:block_size]) - iter = @constinferred BlockLanczosIterator(wrapop(A, Val(mode)), x₀, N, tolerance(T)) + iter = @constinferred BlockLanczosIterator(wrapop(A, Val(mode)), x₀, N, + tolerance(T)) krylovdim = n fact = initialize(iter) while fact.norm_r > eps(float(real(T))) && fact.k < krylovdim diff --git a/test/testsetup.jl b/test/testsetup.jl index 93c6e0b3..20a11cbc 100644 --- a/test/testsetup.jl +++ b/test/testsetup.jl @@ -13,7 +13,7 @@ using Random # ----------------- "function for determining the precision of a type" tolerance(T::Type{<:Number}) = eps(real(T))^(2 // 3) -relax_tol(T::Type{<:Number}) = eps(real(T))^(1//2) +relax_tol(T::Type{<:Number}) = eps(real(T))^(1 // 2) "function for comparing sets of eigenvalues" function ≊(list1::AbstractVector, list2::AbstractVector) From 7f7bdf1807cc22fb61b5a3bec00ed89c8a58432a Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Sun, 11 May 2025 18:54:07 +0800 Subject: [PATCH 60/82] try to pass all tests in github --- src/factorizations/blocklanczos.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/factorizations/blocklanczos.jl b/src/factorizations/blocklanczos.jl index f2eacf9c..9c6fb056 100644 --- a/src/factorizations/blocklanczos.jl +++ b/src/factorizations/blocklanczos.jl @@ -65,9 +65,9 @@ BlockLanczos factorizations of a given linear map and a starting vector. mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: KrylovFactorization{T,S} k::Int - const V::OrthonormalBasis{T} # BlockLanczos Basis - const T::AbstractMatrix{S} # block tridiagonal matrix, and S is the matrix element type - const r::BlockVec{T,S} # residual block + V::OrthonormalBasis{T} # BlockLanczos Basis + T::AbstractMatrix{S} # block tridiagonal matrix, and S is the matrix element type + r::BlockVec{T,S} # residual block r_size::Int # size of the residual block norm_r::SR # norm of the residual block end From 4653e99e9bda4f84c49dee59bc93868b388efa5f Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Wed, 14 May 2025 01:08:51 +0800 Subject: [PATCH 61/82] try to pass CI --- test/innerproductvec.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/innerproductvec.jl b/test/innerproductvec.jl index 84b206d9..2401f09e 100644 --- a/test/innerproductvec.jl +++ b/test/innerproductvec.jl @@ -49,8 +49,9 @@ end AX1copy = copy(AX1) KrylovKit.block_orthogonalize!(AX1, X1, M, X0, B) _bw2m(X) = hcat(unwrapvec.(X)...) + tol = T == ComplexF32 ? relax_tol(T) : tolerance(T) @test isapprox(_bw2m(AX1), _bw2m(AX1copy) - _bw2m(X1) * M - _bw2m(X0) * B; - atol=tolerance(T)) + atol=tol) end end From ed89305be185f322df889ab5e1d8447e4a6edf57 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Wed, 14 May 2025 01:19:30 +0800 Subject: [PATCH 62/82] format with the lastest JuliaFormatter --- src/dense/linalg.jl | 4 ++-- src/eigsolve/eigsolve.jl | 2 +- src/eigsolve/geneigsolve.jl | 2 +- src/factorizations/blocklanczos.jl | 2 +- src/innerproductvec.jl | 2 +- src/linsolve/linsolve.jl | 4 ++-- test/ad/degenerateeigsolve.jl | 24 ++++++++++++------------ test/ad/eigsolve.jl | 8 ++++---- test/issues.jl | 4 ++-- 9 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/dense/linalg.jl b/src/dense/linalg.jl index e42c5b9c..4d4b52ba 100644 --- a/src/dense/linalg.jl +++ b/src/dense/linalg.jl @@ -129,7 +129,7 @@ end function bidiagsvd!(B::Bidiagonal{T}, U::AbstractMatrix{T}=one(B), VT::AbstractMatrix{T}=one(B)) where {T<:BlasReal} - s, Vt, U, = LAPACK.bdsqr!(B.uplo, B.dv, B.ev, VT, U, similar(U, (size(B, 1), 0))) + s, Vt, U = LAPACK.bdsqr!(B.uplo, B.dv, B.ev, VT, U, similar(U, (size(B, 1), 0))) return U, s, Vt end @@ -394,7 +394,7 @@ end function partitionschur!(T::AbstractMatrix{S}, Q::AbstractMatrix{S}, select::AbstractVector{Bool}) where {S<:BlasFloat} - T, Q, vals, = trsen!('N', 'V', convert(Vector{BlasInt}, select), T, Q) + T, Q, vals = trsen!('N', 'V', convert(Vector{BlasInt}, select), T, Q) return T, Q, vals end diff --git a/src/eigsolve/eigsolve.jl b/src/eigsolve/eigsolve.jl index 539251d4..00572f96 100644 --- a/src/eigsolve/eigsolve.jl +++ b/src/eigsolve/eigsolve.jl @@ -256,7 +256,7 @@ function eigselector(f, end function eigselector(A::AbstractMatrix, T::Type; - issymmetric::Bool=T <: Real && LinearAlgebra.issymmetric(A), + issymmetric::Bool=(T <: Real && LinearAlgebra.issymmetric(A)), ishermitian::Bool=issymmetric || LinearAlgebra.ishermitian(A), krylovdim::Int=KrylovDefaults.krylovdim[], maxiter::Int=KrylovDefaults.maxiter[], diff --git a/src/eigsolve/geneigsolve.jl b/src/eigsolve/geneigsolve.jl index f27cb643..52e81f1f 100644 --- a/src/eigsolve/geneigsolve.jl +++ b/src/eigsolve/geneigsolve.jl @@ -199,7 +199,7 @@ end function geneigselector(AB::Tuple{AbstractMatrix,AbstractMatrix}, T::Type; - issymmetric=T <: Real && all(LinearAlgebra.issymmetric, AB), + issymmetric=(T <: Real && all(LinearAlgebra.issymmetric, AB)), ishermitian=issymmetric || all(LinearAlgebra.ishermitian, AB), isposdef=ishermitian && LinearAlgebra.isposdef(AB[2]), kwargs...) diff --git a/src/factorizations/blocklanczos.jl b/src/factorizations/blocklanczos.jl index 9c6fb056..30e47c90 100644 --- a/src/factorizations/blocklanczos.jl +++ b/src/factorizations/blocklanczos.jl @@ -19,7 +19,7 @@ end Base.setindex!(b::BlockVec{T}, v::T, i::Int) where {T} = (b.vec[i] = v) function Base.setindex!(b₁::BlockVec{T}, b₂::BlockVec{T}, idxs::AbstractVector{Int}) where {T} - return (b₁.vec[idxs] = b₂.vec; + return (b₁.vec[idxs]=b₂.vec; b₁) end LinearAlgebra.norm(b::BlockVec) = norm(b.vec) diff --git a/src/innerproductvec.jl b/src/innerproductvec.jl index a45f999f..1c3db91e 100644 --- a/src/innerproductvec.jl +++ b/src/innerproductvec.jl @@ -32,7 +32,7 @@ Base.:*(a::Number, v::InnerProductVec) = InnerProductVec(a * v.vec, v.dotf) Base.:/(v::InnerProductVec, a::Number) = InnerProductVec(v.vec / a, v.dotf) Base.:\(a::Number, v::InnerProductVec) = InnerProductVec(a \ v.vec, v.dotf) -function Base.similar(v::InnerProductVec, ::Type{T}=scalartype(v)) where {T} +function Base.similar(v::InnerProductVec, (::Type{T})=scalartype(v)) where {T} return InnerProductVec(similar(v.vec), v.dotf) end diff --git a/src/linsolve/linsolve.jl b/src/linsolve/linsolve.jl index 41500de0..0576ee67 100644 --- a/src/linsolve/linsolve.jl +++ b/src/linsolve/linsolve.jl @@ -126,7 +126,7 @@ function linselector(f, b, T::Type; issymmetric::Bool=false, - ishermitian::Bool=T <: Real && issymmetric, + ishermitian::Bool=(T <: Real && issymmetric), isposdef::Bool=false, krylovdim::Int=KrylovDefaults.krylovdim[], maxiter::Int=KrylovDefaults.maxiter[], @@ -152,7 +152,7 @@ end function linselector(A::AbstractMatrix, b, T::Type; - issymmetric::Bool=T <: Real && LinearAlgebra.issymmetric(A), + issymmetric::Bool=(T <: Real && LinearAlgebra.issymmetric(A)), ishermitian::Bool=issymmetric || LinearAlgebra.ishermitian(A), isposdef::Bool=ishermitian ? LinearAlgebra.isposdef(A) : false, krylovdim::Int=KrylovDefaults.krylovdim[], diff --git a/test/ad/degenerateeigsolve.jl b/test/ad/degenerateeigsolve.jl index f3b98865..619a4ee5 100644 --- a/test/ad/degenerateeigsolve.jl +++ b/test/ad/degenerateeigsolve.jl @@ -141,19 +141,19 @@ end ∂vecsC = complex.(JC1[1 .+ (1:N), :], JC1[N + 2 .+ (1:N), :]) if T <: Complex # test holomorphicity / Cauchy-Riemann equations # for eigenvalues - @test real(∂valsA[1:2:(2n^2)]) ≈ +imag(∂valsA[2:2:(2n^2)]) - @test imag(∂valsA[1:2:(2n^2)]) ≈ -real(∂valsA[2:2:(2n^2)]) - @test real(∂valsB[1:2:(2n^2)]) ≈ +imag(∂valsB[2:2:(2n^2)]) - @test imag(∂valsB[1:2:(2n^2)]) ≈ -real(∂valsB[2:2:(2n^2)]) - @test real(∂valsC[1:2:(2n^2)]) ≈ +imag(∂valsC[2:2:(2n^2)]) - @test imag(∂valsC[1:2:(2n^2)]) ≈ -real(∂valsC[2:2:(2n^2)]) + @test real(∂valsA[1:2:(2n ^ 2)]) ≈ +imag(∂valsA[2:2:(2n ^ 2)]) + @test imag(∂valsA[1:2:(2n ^ 2)]) ≈ -real(∂valsA[2:2:(2n ^ 2)]) + @test real(∂valsB[1:2:(2n ^ 2)]) ≈ +imag(∂valsB[2:2:(2n ^ 2)]) + @test imag(∂valsB[1:2:(2n ^ 2)]) ≈ -real(∂valsB[2:2:(2n ^ 2)]) + @test real(∂valsC[1:2:(2n ^ 2)]) ≈ +imag(∂valsC[2:2:(2n ^ 2)]) + @test imag(∂valsC[1:2:(2n ^ 2)]) ≈ -real(∂valsC[2:2:(2n ^ 2)]) # and for eigenvectors - @test real(∂vecsA[:, 1:2:(2n^2)]) ≈ +imag(∂vecsA[:, 2:2:(2n^2)]) - @test imag(∂vecsA[:, 1:2:(2n^2)]) ≈ -real(∂vecsA[:, 2:2:(2n^2)]) - @test real(∂vecsB[:, 1:2:(2n^2)]) ≈ +imag(∂vecsB[:, 2:2:(2n^2)]) - @test imag(∂vecsB[:, 1:2:(2n^2)]) ≈ -real(∂vecsB[:, 2:2:(2n^2)]) - @test real(∂vecsC[:, 1:2:(2n^2)]) ≈ +imag(∂vecsC[:, 2:2:(2n^2)]) - @test imag(∂vecsC[:, 1:2:(2n^2)]) ≈ -real(∂vecsC[:, 2:2:(2n^2)]) + @test real(∂vecsA[:, 1:2:(2n ^ 2)]) ≈ +imag(∂vecsA[:, 2:2:(2n ^ 2)]) + @test imag(∂vecsA[:, 1:2:(2n ^ 2)]) ≈ -real(∂vecsA[:, 2:2:(2n ^ 2)]) + @test real(∂vecsB[:, 1:2:(2n ^ 2)]) ≈ +imag(∂vecsB[:, 2:2:(2n ^ 2)]) + @test imag(∂vecsB[:, 1:2:(2n ^ 2)]) ≈ -real(∂vecsB[:, 2:2:(2n ^ 2)]) + @test real(∂vecsC[:, 1:2:(2n ^ 2)]) ≈ +imag(∂vecsC[:, 2:2:(2n ^ 2)]) + @test imag(∂vecsC[:, 1:2:(2n ^ 2)]) ≈ -real(∂vecsC[:, 2:2:(2n ^ 2)]) end # test orthogonality of vecs and ∂vecs @test all(isapprox.(abs.(vecs[1]' * ∂vecsA), 0; atol=sqrt(eps(real(T))))) diff --git a/test/ad/eigsolve.jl b/test/ad/eigsolve.jl index 656226b7..a9a2b812 100644 --- a/test/ad/eigsolve.jl +++ b/test/ad/eigsolve.jl @@ -264,12 +264,12 @@ end end if eltype(A) <: Complex # test holomorphicity / Cauchy-Riemann equations # for eigenvalues - @test real(∂vals[:, 1:2:(2n^2)]) ≈ +imag(∂vals[:, 2:2:(2n^2)]) - @test imag(∂vals[:, 1:2:(2n^2)]) ≈ -real(∂vals[:, 2:2:(2n^2)]) + @test real(∂vals[:, 1:2:(2n ^ 2)]) ≈ +imag(∂vals[:, 2:2:(2n ^ 2)]) + @test imag(∂vals[:, 1:2:(2n ^ 2)]) ≈ -real(∂vals[:, 2:2:(2n ^ 2)]) # and for eigenvectors for i in 1:howmany - @test real(∂vecs[i][:, 1:2:(2n^2)]) ≈ +imag(∂vecs[i][:, 2:2:(2n^2)]) - @test imag(∂vecs[i][:, 1:2:(2n^2)]) ≈ -real(∂vecs[i][:, 2:2:(2n^2)]) + @test real(∂vecs[i][:, 1:2:(2n ^ 2)]) ≈ +imag(∂vecs[i][:, 2:2:(2n ^ 2)]) + @test imag(∂vecs[i][:, 1:2:(2n ^ 2)]) ≈ -real(∂vecs[i][:, 2:2:(2n ^ 2)]) end end # test orthogonality of vecs and ∂vecs diff --git a/test/issues.jl b/test/issues.jl index 7efc7876..fdcb05fe 100644 --- a/test/issues.jl +++ b/test/issues.jl @@ -5,13 +5,13 @@ A += A' v₀ = [rand(N ÷ 2), rand(N ÷ 2)] - vals, vecs, = eigsolve(v₀, 4, :LM; ishermitian=true) do v + vals, vecs = eigsolve(v₀, 4, :LM; ishermitian=true) do v v′ = vcat(v...) y = A * v′ return [y[1:(N ÷ 2)], y[(N ÷ 2 + 1):end]] end - vals2, vecs2, = eigsolve(A, 4, :LM; ishermitian=true) + vals2, vecs2 = eigsolve(A, 4, :LM; ishermitian=true) @test vals ≈ vals2 for (v, v′) in zip(vecs, vecs2) @test abs(inner(vcat(v...), v′)) ≈ 1 From 79a7cd88d5a7b0c9b8e087b6a9805fdb7ea1b6cb Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Wed, 14 May 2025 05:09:53 +0800 Subject: [PATCH 63/82] When the Julia version is lower and multi-threading is used, we will skip the test 'Compare Lanczos and BlockLanczos' with T = ComplexF32 and blocksize = 1 in order to pass the CI. --- test/eigsolve.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 4a94509c..65410ca2 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -554,11 +554,13 @@ end end # with the same krylovdim, BlockLanczos has lower accuracy with the size of block larger than 1. -@testset "Complete Lanczos and BlockLanczos $mode" for mode in - (:vector, :inplace, :outplace) +@testset "Compare Lanczos and BlockLanczos $mode" for mode in + (:vector, :inplace, :outplace) scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @testset for T in scalartypes + # When VERSION == v"1.6", Threads.nthreads() == 4 and T == ComplexF32, the test may fail. + VERSION < v"1.11" && Threads.nthreads() > 1 && T == ComplexF32 && continue A = rand(T, (2N, 2N)) A = (A + A') / 2 block_size = 1 From a08b2b5dc31da7712ed3a34953236991d90b1264 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Wed, 14 May 2025 14:57:32 +0800 Subject: [PATCH 64/82] Different from the last commit, we don't ignore any test. We change some 'A = rand(T,N,N)' into 'A = rand(T,N,N) .- one(T)' to improve numerical stability to pass 'Compare Lanczos and BlockLanczos' with ComplexF32 and multi-thread --- test/eigsolve.jl | 6 ++---- test/innerproductvec.jl | 8 ++++---- test/orthonormal.jl | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 65410ca2..10e69000 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -559,9 +559,7 @@ end scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @testset for T in scalartypes - # When VERSION == v"1.6", Threads.nthreads() == 4 and T == ComplexF32, the test may fail. - VERSION < v"1.11" && Threads.nthreads() > 1 && T == ComplexF32 && continue - A = rand(T, (2N, 2N)) + A = rand(T, (2N, 2N)) .- one(T) / 2 A = (A + A') / 2 block_size = 1 x₀_block = [wrapvec(rand(T, 2N), Val(mode)) for _ in 1:block_size] @@ -574,7 +572,7 @@ end @test info1.converged == info2.converged end @testset for T in scalartypes - A = rand(T, (2N, 2N)) + A = rand(T, (2N, 2N)) .- one(T) / 2 A = (A + A') / 2 block_size = 4 x₀_block = [wrapvec(rand(T, 2N), Val(mode)) for _ in 1:block_size] diff --git a/test/innerproductvec.jl b/test/innerproductvec.jl index 2401f09e..5ac99450 100644 --- a/test/innerproductvec.jl +++ b/test/innerproductvec.jl @@ -40,9 +40,9 @@ end @testset for T in scalartypes A = rand(T, N, N) .- one(T) / 2 A = (A + A') / 2 - M = rand(T, n, n) + M = rand(T, n, n) .- one(T) / 2 M = M' + M - B = qr(rand(T, n, n)).R + B = qr(rand(T, n, n) .- one(T) / 2).R X0 = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) X1 = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) AX1 = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) @@ -60,9 +60,9 @@ end A = rand(T, N, N) .- one(T) / 2 A = A * A' + I ip(x, y) = x' * A * y - M = rand(T, n, n) + M = rand(T, n, n) .- one(T) / 2 M = M' + M - B = qr(rand(T, n, n)).R + B = qr(rand(T, n, n) .- one(T) / 2).R X0 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) X1 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) AX1 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) diff --git a/test/orthonormal.jl b/test/orthonormal.jl index 679dff3c..916935d0 100644 --- a/test/orthonormal.jl +++ b/test/orthonormal.jl @@ -3,7 +3,7 @@ scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @testset for T in scalartypes - A = rand(T, n, n) + A = rand(T, n, n) .- one(T) / 2 B = copy(A) Av = [A[:, i] for i in 1:size(A, 2)] # A is a non-full rank matrix From 9b3ce65acc0afc6ee552cc5a85d9c5eeafd80502 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Wed, 14 May 2025 19:09:48 +0800 Subject: [PATCH 65/82] All checks have passed --- src/factorizations/blocklanczos.jl | 10 +++++----- test/testsetup.jl | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/factorizations/blocklanczos.jl b/src/factorizations/blocklanczos.jl index 30e47c90..180af123 100644 --- a/src/factorizations/blocklanczos.jl +++ b/src/factorizations/blocklanczos.jl @@ -252,16 +252,16 @@ function block_orthogonalize!(AX::BlockVec{T,S}, X::BlockVec{T,S}, end """ - block_reorthogonalize!(basis::BlockVec{T,S}, basis_sofar::OrthonormalBasis{T}) where {T,S} + block_reorthogonalize!(R::BlockVec{T,S}, V::OrthonormalBasis{T}) where {T,S} -This function orthogonalizes the vectors in `basis` with respect to the previously orthonormalized set `basis_sofar` by using the modified Gram-Schmidt process. -Specifically, it modifies each vector `basis[i]` by projecting out its components along the directions spanned by `basis_sofar`, i.e., +This function orthogonalizes the vectors in `R` with respect to the previously orthonormalized set `V` by using the modified Gram-Schmidt process. +Specifically, it modifies each vector `R[i]` by projecting out its components along the directions spanned by `V`, i.e., ``` - basis[i] = basis[i] - sum(j=1:length(basis_sofar)) basis_sofar[j] + R[i] = R[i] - sum(j=1:length(V)) V[j] ``` -Here,`⟨·,·⟩` denotes the inner product. The function assumes that `basis_sofar` is already orthonormal. +Here,`⟨·,·⟩` denotes the inner product. The function assumes that `V` is already orthonormal. """ function block_reorthogonalize!(R::BlockVec{T,S}, V::OrthonormalBasis{T}) where {T,S} diff --git a/test/testsetup.jl b/test/testsetup.jl index 20a11cbc..f1402656 100644 --- a/test/testsetup.jl +++ b/test/testsetup.jl @@ -2,12 +2,11 @@ module TestSetup export tolerance, ≊, MinimalVec, isinplace, stack export wrapop, wrapvec, unwrapvec, buildrealmap -export qr_tol, relax_tol +export relax_tol import VectorInterface as VI using VectorInterface using LinearAlgebra: LinearAlgebra -using Random # Utility functions # ----------------- From 1ee3b71371cee10d9dcc68960066c262c03a2c20 Mon Sep 17 00:00:00 2001 From: KaiwenJin <92569362+yuiyuiui@users.noreply.github.com> Date: Wed, 14 May 2025 22:39:39 +0800 Subject: [PATCH 66/82] Update docs/src/man/implementation.md Co-authored-by: Jutho --- docs/src/man/implementation.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/src/man/implementation.md b/docs/src/man/implementation.md index c3512948..c5c4fff3 100644 --- a/docs/src/man/implementation.md +++ b/docs/src/man/implementation.md @@ -49,7 +49,8 @@ KrylovKit.basistransform! ## Block Krylov method The block version of the Krylov subspace algorithm is an approach to extending Krylov subspace techniques from a block of starting vectors. It is mainly used for solving linear systems with degenerate dominant eigenvalues. -In our implementation, the multiple-vector data structure is BlockVec, which implements the [`KrylovKit.abstract_qr!`](@ref), [`KrylovKit.block_reorthogonalize!`](@ref), and [`KrylovKit.block_orthogonalize!`](@ref) interfaces. +In our implementation, a block of vectors is stored in a new data structure `BlockVec`, +which implements the [`KrylovKit.abstract_qr!`](@ref), [`KrylovKit.block_reorthogonalize!`](@ref), and [`KrylovKit.block_orthogonalize!`](@ref) interfaces. ```@docs KrylovKit.BlockVec From afb1eb7336275a0833ed02b852eadb0567c6e955 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Thu, 15 May 2025 04:14:47 +0800 Subject: [PATCH 67/82] revise for review --- src/eigsolve/lanczos.jl | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index 080aa9c9..2d8a93ce 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -200,26 +200,28 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) normresiduals = let R = block_inner(r, r) map(u -> sqrt(real(dot(u, R, u))), cols(UU)) end - converged = count(<=(tol), normresiduals) + converged = 0 + while converged < K && normresiduals[converged + 1] <= tol + converged += 1 + end if converged >= howmany || β <= tol # Successfully find enough eigenvalues. break elseif verbosity >= EACHITERATION_LEVEL - @info "BlockLanczos eigsolve in iteration $numiter: $converged values converged, normres = $(normres2string(normresiduals))" + @info "BlockLanczos eigsolve in iteration $numiter, Krylov dimension = $K: $converged values converged, normres = $(normres2string(normresiduals[1:howmany]))" end end if K < krylovdim expand!(iter, fact; verbosity=verbosity) numops += 1 - else # Shrink and restart. Following the shrinking method of Lanczos in the above `eigsolve`. + else # Shrink and restart following the shrinking method of `Lanczos`. numiter >= maxiter && break - bsn = max(div(3 * krylovdim + 2 * converged, 5) ÷ bs, 1) # Divide basis into blocks with the same size - keep = bs * bsn - H = zeros(S, (bsn + 1) * bs, bsn * bs) + keep = max(floor(Int, (3 * krylovdim + 2 * converged) / (5 * bs)), 1) * bs + H = zeros(S, keep + bs, keep) # The last bs rows of U contribute to calculate errors of Ritz values. @inbounds for j in 1:keep H[j, j] = D[j] - H[(bsn * bs + 1):end, j] = U[(K - bs + 1):K, j] + H[(keep + 1):end, j] = U[(K - bs + 1):K, j] end # Turn diagonal matrix D into a block tridiagonal matrix, and make sure # The residual of krylov subspace keeps the form of [0,..,0,R] From 883ffced2eb8c800865f4eec11b71278bf553702 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Fri, 16 May 2025 08:26:25 +0800 Subject: [PATCH 68/82] revise for review --- docs/src/man/implementation.md | 6 +- src/KrylovKit.jl | 1 + src/algorithms.jl | 4 +- src/eigsolve/arnoldi.jl | 12 +- src/eigsolve/blocklanczos.jl | 141 +++++++++++++++ src/eigsolve/eigsolve.jl | 10 +- src/eigsolve/lanczos.jl | 140 --------------- src/factorizations/blocklanczos.jl | 20 ++- src/innerproductvec.jl | 11 -- test/BlockVec.jl | 161 ++++++++++++++++++ ...enerateeigsolve.jl => repeatedeigsolve.jl} | 4 +- test/expintegrator.jl | 2 +- test/innerproductvec.jl | 76 --------- test/orthonormal.jl | 83 --------- test/runtests.jl | 8 +- 15 files changed, 338 insertions(+), 341 deletions(-) create mode 100644 src/eigsolve/blocklanczos.jl rename test/ad/{degenerateeigsolve.jl => repeatedeigsolve.jl} (98%) delete mode 100644 test/innerproductvec.jl delete mode 100644 test/orthonormal.jl diff --git a/docs/src/man/implementation.md b/docs/src/man/implementation.md index c5c4fff3..f5dc6d5c 100644 --- a/docs/src/man/implementation.md +++ b/docs/src/man/implementation.md @@ -47,10 +47,10 @@ KrylovKit.basistransform! ``` ## Block Krylov method -The block version of the Krylov subspace algorithm is an approach to extending Krylov subspace techniques from a block of starting vectors. It is mainly used for solving linear systems with degenerate dominant eigenvalues. +The block version of the Krylov subspace algorithm is an approach to extending Krylov subspace techniques from a block of starting vectors. It is mainly used for solving linear systems with repeated dominant eigenvalues. In our implementation, a block of vectors is stored in a new data structure `BlockVec`, -which implements the [`KrylovKit.abstract_qr!`](@ref), [`KrylovKit.block_reorthogonalize!`](@ref), and [`KrylovKit.block_orthogonalize!`](@ref) interfaces. +which implements the [`KrylovKit.block_qr!`](@ref), [`KrylovKit.block_reorthogonalize!`](@ref), and [`KrylovKit.block_orthogonalize!`](@ref) interfaces. ```@docs KrylovKit.BlockVec @@ -58,7 +58,7 @@ KrylovKit.BlockVec A block of vectors can be orthonormalized using ```@docs -KrylovKit.abstract_qr! +KrylovKit.block_qr! ``` This apply QR decomposition to a block of vectors using modified Gram-Schmidt process. diff --git a/src/KrylovKit.jl b/src/KrylovKit.jl index aa588dd2..009fd9b4 100644 --- a/src/KrylovKit.jl +++ b/src/KrylovKit.jl @@ -269,6 +269,7 @@ include("lssolve/lsmr.jl") # eigsolve and svdsolve include("eigsolve/eigsolve.jl") include("eigsolve/lanczos.jl") +include("eigsolve/blocklanczos.jl") include("eigsolve/arnoldi.jl") include("eigsolve/geneigsolve.jl") include("eigsolve/golubye.jl") diff --git a/src/algorithms.jl b/src/algorithms.jl index 0baa4f49..238aaf01 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -134,10 +134,10 @@ end verbosity=KrylovDefaults.verbosity[], qr_tol::Real=KrylovDefaults.tol[]) -The block version of [`Lanczos`](@ref) is suited for solving eigenvalue problems with degenerate dominant eigenvalues. +The block version of [`Lanczos`](@ref) is suited for solving eigenvalue problems with repeated dominant eigenvalues. Its implementation is mainly based on *Golub, G. H., & Van Loan, C. F. (2013). Matrix Computations* (4th ed., pp. 566–569). Arguments `krylovdim`, `maxiter`, `tol`, `orth`, `eager` and `verbosity` are the same as `Lanczos`. -`qr_tol` is the error tolerance for `abstract_qr!` - a subroutine used to orthorgonalize the vectors in the same block. +`qr_tol` is the error tolerance for `block_qr!` - a subroutine used to orthorgonalize the vectors in the same block. The initial size of the block is determined by the number of starting vectors that a user provides. And the size of the block shrinks during iterations. The initial block size determines the maximum degeneracy of the target eigenvalue can that be resolved. diff --git a/src/eigsolve/arnoldi.jl b/src/eigsolve/arnoldi.jl index 52f1824e..0e3d8978 100644 --- a/src/eigsolve/arnoldi.jl +++ b/src/eigsolve/arnoldi.jl @@ -36,11 +36,11 @@ should be targeted. Valid specifications of `which` are only be successful if you somehow know that eigenvalues close to zero are also close to the periphery of the spectrum. -!!! warning "Degenerate eigenvalues" +!!! warning "Repeated eigenvalues" From a theoretical point of view, Krylov methods can at most find a single eigenvector - associated with a targetted eigenvalue, even if the latter is degenerate. In the case of - a degenerate eigenvalue, the specific eigenvector that is returned is determined by the + associated with a targetted eigenvalue, even if the latter is repeated. In the case of + a repeated eigenvalue, the specific eigenvector that is returned is determined by the starting vector `x₀`. For large problems, this turns out to be less of an issue in practice, as often a second linearly independent eigenvector is generated out of the numerical noise resulting from the orthogonalisation steps in the Lanczos or Arnoldi @@ -238,11 +238,11 @@ problems are given by only be successful if you somehow know that eigenvalues close to zero are also close to the periphery of the spectrum. -!!! warning "Degenerate eigenvalues" +!!! warning "Repeated eigenvalues" From a theoretical point of view, Krylov methods can at most find a single eigenvector - associated with a targetted eigenvalue, even if the latter is degenerate. In the case of - a degenerate eigenvalue, the specific eigenvector that is returned is determined by the + associated with a targetted eigenvalue, even if the latter is repeated. In the case of + a repeated eigenvalue, the specific eigenvector that is returned is determined by the starting vector `x₀`. For large problems, this turns out to be less of an issue in practice, as often a second linearly independent eigenvector is generated out of the numerical noise resulting from the orthogonalisation steps in the Lanczos or Arnoldi diff --git a/src/eigsolve/blocklanczos.jl b/src/eigsolve/blocklanczos.jl new file mode 100644 index 00000000..c7d0e334 --- /dev/null +++ b/src/eigsolve/blocklanczos.jl @@ -0,0 +1,141 @@ +function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) + maxiter = alg.maxiter + krylovdim = alg.krylovdim + if howmany > krylovdim + error("krylov dimension $(krylovdim) too small to compute $howmany eigenvalues") + end + tol = alg.tol + verbosity = alg.verbosity + + # The initial block size is determined by the number of the starting vectors provided by the user + S = typeof(inner(x₀[1], x₀[1])) # Scalar type of the inner product between vectors in x₀ + block0 = BlockVec{S}(x₀) + bs = length(block0) + bs < 1 && error("The length of the starting vector x₀ must be greater than 0") + + iter = BlockLanczosIterator(A, block0, krylovdim + bs, alg.qr_tol, alg.orth) + fact = initialize(iter; verbosity=verbosity) # Returns a BlockLanczosFactorization + numops = 1 # Number of matrix-vector multiplications (for logging) + numiter = 1 + + converged = 0 + local normresiduals, D, U + + while true + K = length(fact) + β = normres(fact) + + if β < tol && K < howmany && verbosity >= WARN_LEVEL + msg = "Invariant subspace of dimension $(K) (up to requested tolerance `tol = $tol`), " + msg *= "which is smaller than the number of requested eigenvalues (i.e. `howmany == $howmany`)." + @warn msg + end + # BlockLanczos can access the case of K = 1 and doesn't need extra processing + if K >= krylovdim || β <= tol || (alg.eager && K >= howmany) + # Compute eigenvalues + # Note: Fast eigen solver for block tridiagonal matrices is not implemented yet. + BTD = view(fact.T, 1:K, 1:K) + D, U = eigen(Hermitian(BTD)) + by, rev = eigsort(which) + p = sortperm(D; by=by, rev=rev) + D, U = permuteeig!(D, U, p) + + # Detect convergence by computing the residuals + bs_r = fact.r_size # The block size of the residual (decreases as the iteration goes) + r = residual(fact) + UU = U[(end - bs_r + 1):end, :] # The last bs_r rows of U, used to compute the residuals + normresiduals = let R = block_inner(r, r) + map(u -> sqrt(real(dot(u, R, u))), cols(UU)) + end + converged = 0 + while converged < K && normresiduals[converged + 1] <= tol + converged += 1 + end + if converged >= howmany || β <= tol # Successfully find enough eigenvalues. + break + elseif verbosity >= EACHITERATION_LEVEL + @info "BlockLanczos eigsolve in iteration $numiter, Krylov dimension = $K: $converged values converged, normres = $(normres2string(normresiduals[1:howmany]))" + end + end + + if K < krylovdim + expand!(iter, fact; verbosity=verbosity) + numops += 1 + else # Shrink and restart following the shrinking method of `Lanczos`. + numiter >= maxiter && break + keep = max(div(3 * krylovdim + 2 * converged, 5 * bs), 1) * bs + H = zeros(S, keep + bs, keep) + # The last bs rows of U contribute to calculate errors of Ritz values. + @inbounds for j in 1:keep + H[j, j] = D[j] + H[(keep + 1):end, j] = U[(K - bs + 1):K, j] + end + # Turn diagonal matrix D into a block tridiagonal matrix, and make sure + # The residual of krylov subspace keeps the form of [0,..,0,R] + @inbounds for j in keep:-1:1 + h, ν = householder(H, j + bs, 1:j, j) + H[j + bs, j] = ν + H[j + bs, 1:(j - 1)] .= zero(eltype(H)) + lmul!(h, H) + rmul!(view(H, 1:(j + bs - 1), :), h') + rmul!(U, h') + end + # Transform the basis and update the residual and update the BTD. + BTD .= S(0) + Hkeep = view(H, 1:keep, 1:keep) + BTD[1:keep, 1:keep] .= (Hkeep .+ Hkeep') ./ 2 # make exactly Hermitian + B = basis(fact) + basistransform!(B, view(U, :, 1:keep)) + + r_new = OrthonormalBasis(fact.r.vec[1:bs_r]) + view_H = view(H, (keep + bs - bs_r + 1):(keep + bs), (keep - bs_r + 1):keep) + basistransform!(r_new, view_H) + fact.r.vec[1:bs_r] = r_new[1:bs_r] + + while length(fact) > keep + pop!(fact.V) + fact.k -= 1 + end + numiter += 1 + end + end + + howmany_actual = howmany + if converged > howmany + howmany_actual = converged + elseif length(D) < howmany + howmany_actual = length(D) + end + values = D[1:howmany_actual] + U1 = view(U, :, 1:howmany_actual) + vectors = let V = basis(fact) + [V * u for u in cols(U1)] + end + bs_r = fact.r_size + K = length(fact) + U2 = view(U, (K - bs_r + 1):K, 1:howmany_actual) + R = fact.r + residuals = [zerovector(x₀[1]) for _ in 1:howmany_actual] + @inbounds for i in 1:howmany_actual + for j in 1:bs_r + residuals[i] = add!!(residuals[i], R[j], U2[j, i]) + end + end + normresiduals = normresiduals[1:howmany_actual] + + if (converged < howmany) && verbosity >= WARN_LEVEL + @warn """BlockLanczos eigsolve stopped without full convergence after $(K) iterations: + * $converged eigenvalues converged + * norm of residuals = $(normres2string(normresiduals)) + * number of operations = $numops""" + elseif verbosity >= STARTSTOP_LEVEL + @info """BlockLanczos eigsolve finished after $(K) iterations: + * $converged eigenvalues converged + * norm of residuals = $(normres2string(normresiduals)) + * number of operations = $numops""" + end + + return values, + vectors, + ConvergenceInfo(converged, residuals, normresiduals, numiter, numops) +end diff --git a/src/eigsolve/eigsolve.jl b/src/eigsolve/eigsolve.jl index 00572f96..e4f29f1e 100644 --- a/src/eigsolve/eigsolve.jl +++ b/src/eigsolve/eigsolve.jl @@ -42,20 +42,20 @@ targeted. Valid specifications of `which` are given by only be successful if you somehow know that eigenvalues close to zero are also close to the periphery of the spectrum. -!!! warning "Degenerate eigenvalues" +!!! warning "Repeated eigenvalues" From a theoretical point of view, Krylov methods can at most find a single eigenvector - associated with a targetted eigenvalue, even if the latter is degenerate. In the case of - a degenerate eigenvalue, the specific eigenvector that is returned is determined by the + associated with a targetted eigenvalue, even if the latter is repeated. In the case of + a repeated eigenvalue, the specific eigenvector that is returned is determined by the starting vector `x₀`. For large problems, this turns out to be less of an issue in practice, as often a second linearly independent eigenvector is generated out of the numerical noise resulting from the orthogonalisation steps in the Lanczos or Arnoldi iteration. Nonetheless, it is important to take this into account and to try not to depend on this potentially fragile behaviour, especially for smaller problems. - The [`BlockLanczos`](@ref) method has been implemented to compute degenerate + The [`BlockLanczos`](@ref) method has been implemented to compute repeated eigenvalues and their corresponding eigenvectors. Given a block size `p`, which is the number of starting vectors, the method can at most simultaneously determine - `p`-fold degenerate eigenvalues and their associated eigenvectors. + `p`-fold repeated eigenvalues and their associated eigenvectors. The argument `T` acts as a hint in which `Number` type the computation should be performed, but is not restrictive. If the linear map automatically produces complex values, complex diff --git a/src/eigsolve/lanczos.jl b/src/eigsolve/lanczos.jl index 2d8a93ce..b23f98fd 100644 --- a/src/eigsolve/lanczos.jl +++ b/src/eigsolve/lanczos.jl @@ -150,143 +150,3 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::Lanczos; vectors, ConvergenceInfo(converged, residuals, normresiduals, numiter, numops) end - -function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) - maxiter = alg.maxiter - krylovdim = alg.krylovdim - if howmany > krylovdim - error("krylov dimension $(krylovdim) too small to compute $howmany eigenvalues") - end - tol = alg.tol - verbosity = alg.verbosity - - # The initial block size is determined by the number of the starting vectors provided by the user - S = typeof(inner(x₀[1], x₀[1])) # Scalar type of the inner product between vectors in x₀ - block0 = BlockVec{S}(x₀) - bs = length(block0) - bs < 1 && error("The length of the starting vector x₀ must be greater than 0") - - iter = BlockLanczosIterator(A, block0, krylovdim + bs, alg.qr_tol, alg.orth) - fact = initialize(iter; verbosity=verbosity) # Returns a BlockLanczosFactorization - numops = 1 # Number of matrix-vector multiplications (for logging) - numiter = 1 - - converged = 0 - local normresiduals, D, U - - while true - K = length(fact) - β = normres(fact) - - if β < tol && K < howmany && verbosity >= WARN_LEVEL - msg = "Invariant subspace of dimension $(K) (up to requested tolerance `tol = $tol`), " - msg *= "which is smaller than the number of requested eigenvalues (i.e. `howmany == $howmany`)." - @warn msg - end - # BlockLanczos can access the case of K = 1 and doesn't need extra processing - if K >= krylovdim || β <= tol || (alg.eager && K >= howmany) - # Compute eigenvalues - # Note: Fast eigen solver for block tridiagonal matrices is not implemented yet. - BTD = view(fact.T, 1:K, 1:K) - D, U = eigen(Hermitian(BTD)) - by, rev = eigsort(which) - p = sortperm(D; by=by, rev=rev) - D, U = permuteeig!(D, U, p) - - # Detect convergence by computing the residuals - bs_r = fact.r_size # The block size of the residual (decreases as the iteration goes) - r = residual(fact) - UU = U[(end - bs_r + 1):end, :] # The last bs_r rows of U, used to compute the residuals - normresiduals = let R = block_inner(r, r) - map(u -> sqrt(real(dot(u, R, u))), cols(UU)) - end - converged = 0 - while converged < K && normresiduals[converged + 1] <= tol - converged += 1 - end - if converged >= howmany || β <= tol # Successfully find enough eigenvalues. - break - elseif verbosity >= EACHITERATION_LEVEL - @info "BlockLanczos eigsolve in iteration $numiter, Krylov dimension = $K: $converged values converged, normres = $(normres2string(normresiduals[1:howmany]))" - end - end - - if K < krylovdim - expand!(iter, fact; verbosity=verbosity) - numops += 1 - else # Shrink and restart following the shrinking method of `Lanczos`. - numiter >= maxiter && break - keep = max(floor(Int, (3 * krylovdim + 2 * converged) / (5 * bs)), 1) * bs - H = zeros(S, keep + bs, keep) - # The last bs rows of U contribute to calculate errors of Ritz values. - @inbounds for j in 1:keep - H[j, j] = D[j] - H[(keep + 1):end, j] = U[(K - bs + 1):K, j] - end - # Turn diagonal matrix D into a block tridiagonal matrix, and make sure - # The residual of krylov subspace keeps the form of [0,..,0,R] - @inbounds for j in keep:-1:1 - h, ν = householder(H, j + bs, 1:j, j) - H[j + bs, j] = ν - H[j + bs, 1:(j - 1)] .= zero(eltype(H)) - lmul!(h, H) - rmul!(view(H, 1:(j + bs - 1), :), h') - rmul!(U, h') - end - # Transform the basis and update the residual and update the BTD. - BTD .= S(0) - BTD[1:keep, 1:keep] .= H[1:keep, 1:keep] - B = basis(fact) - basistransform!(B, view(U, :, 1:keep)) - - r_new = OrthonormalBasis(fact.r.vec[1:bs_r]) - view_U = view(U, (K - bs_r + 1):K, (keep - bs_r + 1):keep) - basistransform!(r_new, view_U) - fact.r.vec[1:bs_r] = r_new[1:bs_r] - - while length(fact) > keep - pop!(fact.V) - fact.k -= 1 - end - numiter += 1 - end - end - - howmany_actual = howmany - if converged > howmany - howmany_actual = converged - elseif length(D) < howmany - howmany_actual = length(D) - end - U1 = view(U, :, 1:howmany_actual) - vectors = let V = basis(fact) - [V * u for u in cols(U1)] - end - bs_r = fact.r_size - K = length(fact) - U2 = view(U, (K - bs_r + 1):K, 1:howmany_actual) - R = fact.r - residuals = [zerovector(x₀[1]) for _ in 1:howmany_actual] - @inbounds for i in 1:howmany_actual - for j in 1:bs_r - residuals[i] = add!!(residuals[i], R[j], U2[j, i]) - end - end - normresiduals = normresiduals[1:howmany_actual] - - if (converged < howmany) && verbosity >= WARN_LEVEL - @warn """BlockLanczos eigsolve stopped without full convergence after $(K) iterations: - * $converged eigenvalues converged - * norm of residuals = $(normres2string(normresiduals)) - * number of operations = $numops""" - elseif verbosity >= STARTSTOP_LEVEL - @info """BlockLanczos eigsolve finished after $(K) iterations: - * $converged eigenvalues converged - * norm of residuals = $(normres2string(normresiduals)) - * number of operations = $numops""" - end - - return D[1:howmany_actual], - vectors[1:howmany_actual], - ConvergenceInfo(converged, residuals, normresiduals, numiter, numops) -end diff --git a/src/factorizations/blocklanczos.jl b/src/factorizations/blocklanczos.jl index 180af123..a4f3bb81 100644 --- a/src/factorizations/blocklanczos.jl +++ b/src/factorizations/blocklanczos.jl @@ -26,6 +26,16 @@ LinearAlgebra.norm(b::BlockVec) = norm(b.vec) function apply(f, block::BlockVec{T,S}) where {T,S} return BlockVec{S}([apply(f, block[i]) for i in 1:length(block)]) end +function block_inner(B₁::BlockVec{T,S}, B₂::BlockVec{T,S}) where {T,S} + M = Matrix{S}(undef, length(B₁.vec), length(B₂.vec)) + @inbounds for j in 1:length(B₂) + yj = B₂[j] + for i in 1:length(B₁) + M[i, j] = inner(B₁[i], yj) + end + end + return M +end function Base.push!(V::OrthonormalBasis{T}, b::BlockVec{T}) where {T} for i in 1:length(b) push!(V, b[i]) @@ -96,7 +106,7 @@ default value in [`KrylovDefaults`](@ref) is to use [`ModifiedGramSchmidt2`](@re uses reorthogonalization steps in every iteration. Now our orthogonalizer is only ModifiedGramSchmidt2. So we don't need to provide "keepvecs" because we have to reverse all krylove vectors. Dimension of Krylov subspace in BlockLanczosIterator is usually much bigger than lanczos and its Default value is 100. -`qr_tol` is the tolerance used in [`abstract_qr!`](@ref) to resolve the rank of a block of vectors. +`qr_tol` is the tolerance used in [`block_qr!`](@ref) to resolve the rank of a block of vectors. When iterating over an instance of `BlockLanczosIterator`, the values being generated are instances of [`BlockLanczosFactorization`](@ref). @@ -144,7 +154,7 @@ function initialize(iter::BlockLanczosIterator{F,T,S}; # Orthogonalization of the initial block X₁ = copy(X₀) - abstract_qr!(X₁, iter.qr_tol) + block_qr!(X₁, iter.qr_tol) V = OrthonormalBasis(X₁.vec) AX₁ = apply(A, X₁) @@ -179,7 +189,7 @@ function expand!(iter::BlockLanczosIterator{F,T,S}, V = state.V # Calculate the new basis and B - B, good_idx = abstract_qr!(R, iter.qr_tol) + B, good_idx = block_qr!(R, iter.qr_tol) bs_next = length(good_idx) push!(V, R[good_idx]) state.T[(k + 1):(k + bs_next), (k - bs + 1):k] .= B @@ -280,7 +290,7 @@ function warn_nonhermitian(M::AbstractMatrix) end """ - abstract_qr!(block::BlockVec{T,S}, tol::Real) where {T,S} + block_qr!(block::BlockVec{T,S}, tol::Real) where {T,S} This function performs a QR factorization of a block of abstract vectors using the modified Gram-Schmidt process. @@ -296,7 +306,7 @@ and `r` is the numerical rank of the input block. The matrix represents the uppe restricted to the `r` linearly independent components. The vector `goodidx` contains the indices of the non-zero (i.e., numerically independent) vectors in the orthonormalized block. """ -function abstract_qr!(block::BlockVec{T,S}, tol::Real) where {T,S} +function block_qr!(block::BlockVec{T,S}, tol::Real) where {T,S} n = length(block) rank_shrink = false idx = ones(Int64, n) diff --git a/src/innerproductvec.jl b/src/innerproductvec.jl index 1c3db91e..7e74c769 100644 --- a/src/innerproductvec.jl +++ b/src/innerproductvec.jl @@ -125,14 +125,3 @@ function VectorInterface.inner(v::InnerProductVec{F}, w::InnerProductVec{F}) whe end VectorInterface.norm(v::InnerProductVec) = sqrt(real(inner(v, v))) - -function block_inner(B₁::BlockVec{T,S}, B₂::BlockVec{T,S}) where {T,S} - M = Matrix{S}(undef, length(B₁.vec), length(B₂.vec)) - @inbounds for j in 1:length(B₂) - yj = B₂[j] - for i in 1:length(B₁) - M[i, j] = inner(B₁[i], yj) - end - end - return M -end diff --git a/test/BlockVec.jl b/test/BlockVec.jl index c379b54a..77801149 100644 --- a/test/BlockVec.jl +++ b/test/BlockVec.jl @@ -45,3 +45,164 @@ end @test typeof(block0) == typeof(block1) @test [block0.vec[i].vec for i in 1:n] == [block1.vec[i].vec for i in 1:n] end + +#= +block_inner(x,y): +M[i,j] = inner(x[i],y[j]) +=# +@testset "block_inner for non-full vectors $mode" for mode in (:vector, :inplace, :outplace) + scalartypes = mode === :vector ? (Float32, Float64, ComplexF64) : + (ComplexF64,) + @testset for T in scalartypes + A = [rand(T, N) for _ in 1:n] + B = [rand(T, N) for _ in 1:n] + M0 = hcat(A...)' * hcat(B...) + BlockA = KrylovKit.BlockVec{T}(wrapvec.(A, Val(mode))) + BlockB = KrylovKit.BlockVec{T}(wrapvec.(B, Val(mode))) + M = KrylovKit.block_inner(BlockA, BlockB) + @test eltype(M) == T + @test isapprox(M, M0; atol=relax_tol(T)) + end +end + +@testset "block_inner for abstract inner product" begin + T = ComplexF64 + H = rand(T, N, N) .- one(T) / 2 + H = H' * H + I + ip(x, y) = x' * H * y + X = [InnerProductVec(rand(T, N), ip) for _ in 1:n] + Y = [InnerProductVec(rand(T, N), ip) for _ in 1:n] + BlockX = KrylovKit.BlockVec{T}(X) + BlockY = KrylovKit.BlockVec{T}(Y) + M = KrylovKit.block_inner(BlockX, BlockY) + Xm = hcat([X[i].vec for i in 1:n]...) + Ym = hcat([Y[i].vec for i in 1:n]...) + M0 = Xm' * H * Ym + @test eltype(M) == T + @test isapprox(M, M0; atol=relax_tol(T)) +end + +@testset "block_orthogonalize! $mode" for mode in (:vector, :inplace, :outplace) + scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : + (ComplexF64,) + @testset for T in scalartypes + A = rand(T, N, N) .- one(T) / 2 + A = (A + A') / 2 + M = rand(T, n, n) .- one(T) / 2 + M = M' + M + B = qr(rand(T, n, n) .- one(T) / 2).R + X0 = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) + X1 = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) + AX1 = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) + AX1copy = copy(AX1) + KrylovKit.block_orthogonalize!(AX1, X1, M, X0, B) + _bw2m(X) = hcat(unwrapvec.(X)...) + tol = T == ComplexF32 ? relax_tol(T) : tolerance(T) + @test isapprox(_bw2m(AX1), _bw2m(AX1copy) - _bw2m(X1) * M - _bw2m(X0) * B; + atol=tol) + end +end + +@testset "block_orthogonalize! for abstract inner product" begin + T = ComplexF64 + A = rand(T, N, N) .- one(T) / 2 + A = A * A' + I + ip(x, y) = x' * A * y + M = rand(T, n, n) .- one(T) / 2 + M = M' + M + B = qr(rand(T, n, n) .- one(T) / 2).R + X0 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) + X1 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) + AX1 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) + AX1copy = copy(AX1) + KrylovKit.block_orthogonalize!(AX1, X1, M, X0, B) + + @test isapprox(hcat([AX1.vec[i].vec for i in 1:n]...), + hcat([AX1copy.vec[i].vec for i in 1:n]...) - + hcat([X1.vec[i].vec for i in 1:n]...) * M - + hcat([X0.vec[i].vec for i in 1:n]...) * B; atol=tolerance(T)) +end + +@testset "block_reorthogonalize! for non-full vectors $mode" for mode in + (:vector, :inplace, + :outplace) + scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : + (ComplexF64,) + @testset for T in scalartypes + A = rand(T, n, n) .- one(T) / 2 + A = (A + A') / 2 + x₀ = [wrapvec(rand(T, N), Val(mode)) for i in 1:n] + x₁ = [wrapvec(rand(T, N), Val(mode)) for i in 1:(2 * n)] + b₀ = KrylovKit.BlockVec{T}(x₀) + b₁ = KrylovKit.BlockVec{T}(x₁) + KrylovKit.block_qr!(b₁, tolerance(T)) + orthobasis_x₁ = KrylovKit.OrthonormalBasis(b₁.vec) + KrylovKit.block_reorthogonalize!(b₀, orthobasis_x₁) + @test norm(KrylovKit.block_inner(b₀, b₁)) < tolerance(T) + end +end + +@testset "block_reorthogonalize! for abstract inner product" begin + T = ComplexF64 + H = rand(T, N, N) .- one(T) / 2 + H = H' * H + I + ip(x, y) = x' * H * y + + x₀ = [InnerProductVec(rand(T, N), ip) for i in 1:n] + x₁ = [InnerProductVec(rand(T, N), ip) for i in 1:(2 * n)] + b₀ = KrylovKit.BlockVec{T}(x₀) + b₁ = KrylovKit.BlockVec{T}(x₁) + KrylovKit.block_qr!(b₁, tolerance(T)) + orthobasis_x₁ = KrylovKit.OrthonormalBasis(b₁.vec) + KrylovKit.block_reorthogonalize!(b₀, orthobasis_x₁) + @test norm(KrylovKit.block_inner(b₀, b₁)) < tolerance(T) +end + +@testset "block_qr! for non-full vectors $mode" for mode in + (:vector, :inplace, :outplace) + scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : + (ComplexF64,) + @testset for T in scalartypes + A = rand(T, n, n) .- one(T) / 2 + B = copy(A) + Av = [A[:, i] for i in 1:size(A, 2)] + # A is a non-full rank matrix + Av[n ÷ 2] = sum(Av[(n ÷ 2 + 1):end] .* rand(T, n - n ÷ 2)) + Bv = deepcopy(Av) + wAv = wrapvec.(Av, Val(mode)) + R, gi = KrylovKit.block_qr!(KrylovKit.BlockVec{T}(wAv), tolerance(T)) + Av1 = [unwrapvec(wAv[i]) for i in gi] + @test hcat(Av1...)' * hcat(Av1...) ≈ I + @test length(gi) < n + @test eltype(R) == eltype(eltype(A)) == T + + norm(hcat(Av1...) * R - hcat(Bv...)) + @test isapprox(hcat(Av1...) * R, hcat(Bv...); atol=tolerance(T)) + @test isapprox(hcat(Av1...)' * hcat(Av1...), I; atol=tolerance(T)) + end +end + +@testset "block_qr! for abstract inner product" begin + T = ComplexF64 + H = rand(T, N, N) .- one(T) / 2 + H = H' * H + I + ip(x, y) = x' * H * y + X₁ = InnerProductVec(rand(T, N), ip) + X = [similar(X₁) for _ in 1:n] + X[1] = X₁ + for i in 2:(n - 1) + X[i] = InnerProductVec(rand(T, N), ip) + end + + # Make sure X is not full rank + X[end] = sum(X[1:(end - 1)] .* rand(T, n - 1)) + Xcopy = deepcopy(X) + R, gi = KrylovKit.block_qr!(KrylovKit.BlockVec{T}(X), tolerance(T)) + + @test length(gi) < n + @test eltype(R) == T + BlockX = KrylovKit.BlockVec{T}(X[gi]) + @test isapprox(KrylovKit.block_inner(BlockX, BlockX), I; atol=tolerance(T)) + ΔX = norm.([sum(X[gi] .* R[:, i]) for i in 1:size(R, 2)] - Xcopy) + @test isapprox(norm(ΔX), T(0); atol=tolerance(T)) +end diff --git a/test/ad/degenerateeigsolve.jl b/test/ad/repeatedeigsolve.jl similarity index 98% rename from test/ad/degenerateeigsolve.jl rename to test/ad/repeatedeigsolve.jl index 619a4ee5..78f0920b 100644 --- a/test/ad/degenerateeigsolve.jl +++ b/test/ad/repeatedeigsolve.jl @@ -1,4 +1,4 @@ -module DegenerateEigsolveAD +module RepeatedEigsolveAD using KrylovKit, LinearAlgebra using Random, Test, TestExtras @@ -82,7 +82,7 @@ function build_mat_example(A, B, C, x, alg, alg_rrule) vecs end -@timedtestset "Degenerate eigsolve AD test with eltype=$T" for T in (Float64, ComplexF64) +@timedtestset "Repeated eigsolve AD test with eltype=$T" for T in (Float64, ComplexF64) n = 10 N = 3n diff --git a/test/expintegrator.jl b/test/expintegrator.jl index f59b72f3..2aa92c4e 100644 --- a/test/expintegrator.jl +++ b/test/expintegrator.jl @@ -157,7 +157,7 @@ end end @testset "Arnoldi - expintegrator fixed point branch" begin - @testset for T in (ComplexF32, ComplexF64) # less probable that :LR eig is degenerate + @testset for T in (ComplexF32, ComplexF64) # less probable that :LR eig is repeated A = rand(T, (N, N)) / 10 v₀ = rand(T, N) λs, vs, infoR = eigsolve(A, v₀, 1, :LR) diff --git a/test/innerproductvec.jl b/test/innerproductvec.jl deleted file mode 100644 index 5ac99450..00000000 --- a/test/innerproductvec.jl +++ /dev/null @@ -1,76 +0,0 @@ -#= -block_inner(x,y): -M[i,j] = inner(x[i],y[j]) -=# -@testset "block_inner for non-full vectors $mode" for mode in (:vector, :inplace, :outplace) - scalartypes = mode === :vector ? (Float32, Float64, ComplexF64) : - (ComplexF64,) - @testset for T in scalartypes - A = [rand(T, N) for _ in 1:n] - B = [rand(T, N) for _ in 1:n] - M0 = hcat(A...)' * hcat(B...) - BlockA = KrylovKit.BlockVec{T}(wrapvec.(A, Val(mode))) - BlockB = KrylovKit.BlockVec{T}(wrapvec.(B, Val(mode))) - M = KrylovKit.block_inner(BlockA, BlockB) - @test eltype(M) == T - @test isapprox(M, M0; atol=relax_tol(T)) - end -end - -@testset "block_inner for abstract inner product" begin - T = ComplexF64 - H = rand(T, N, N) .- one(T) / 2 - H = H' * H + I - ip(x, y) = x' * H * y - X = [InnerProductVec(rand(T, N), ip) for _ in 1:n] - Y = [InnerProductVec(rand(T, N), ip) for _ in 1:n] - BlockX = KrylovKit.BlockVec{T}(X) - BlockY = KrylovKit.BlockVec{T}(Y) - M = KrylovKit.block_inner(BlockX, BlockY) - Xm = hcat([X[i].vec for i in 1:n]...) - Ym = hcat([Y[i].vec for i in 1:n]...) - M0 = Xm' * H * Ym - @test eltype(M) == T - @test isapprox(M, M0; atol=relax_tol(T)) -end - -@testset "block_orthogonalize! $mode" for mode in (:vector, :inplace, :outplace) - scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : - (ComplexF64,) - @testset for T in scalartypes - A = rand(T, N, N) .- one(T) / 2 - A = (A + A') / 2 - M = rand(T, n, n) .- one(T) / 2 - M = M' + M - B = qr(rand(T, n, n) .- one(T) / 2).R - X0 = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) - X1 = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) - AX1 = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) - AX1copy = copy(AX1) - KrylovKit.block_orthogonalize!(AX1, X1, M, X0, B) - _bw2m(X) = hcat(unwrapvec.(X)...) - tol = T == ComplexF32 ? relax_tol(T) : tolerance(T) - @test isapprox(_bw2m(AX1), _bw2m(AX1copy) - _bw2m(X1) * M - _bw2m(X0) * B; - atol=tol) - end -end - -@testset "block_orthogonalize! for abstract inner product" begin - T = ComplexF64 - A = rand(T, N, N) .- one(T) / 2 - A = A * A' + I - ip(x, y) = x' * A * y - M = rand(T, n, n) .- one(T) / 2 - M = M' + M - B = qr(rand(T, n, n) .- one(T) / 2).R - X0 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) - X1 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) - AX1 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) - AX1copy = copy(AX1) - KrylovKit.block_orthogonalize!(AX1, X1, M, X0, B) - - @test isapprox(hcat([AX1.vec[i].vec for i in 1:n]...), - hcat([AX1copy.vec[i].vec for i in 1:n]...) - - hcat([X1.vec[i].vec for i in 1:n]...) * M - - hcat([X0.vec[i].vec for i in 1:n]...) * B; atol=tolerance(T)) -end diff --git a/test/orthonormal.jl b/test/orthonormal.jl deleted file mode 100644 index 916935d0..00000000 --- a/test/orthonormal.jl +++ /dev/null @@ -1,83 +0,0 @@ -@testset "abstract_qr! for non-full vectors $mode" for mode in - (:vector, :inplace, :outplace) - scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : - (ComplexF64,) - @testset for T in scalartypes - A = rand(T, n, n) .- one(T) / 2 - B = copy(A) - Av = [A[:, i] for i in 1:size(A, 2)] - # A is a non-full rank matrix - Av[n ÷ 2] = sum(Av[(n ÷ 2 + 1):end] .* rand(T, n - n ÷ 2)) - Bv = deepcopy(Av) - wAv = wrapvec.(Av, Val(mode)) - R, gi = KrylovKit.abstract_qr!(KrylovKit.BlockVec{T}(wAv), tolerance(T)) - Av1 = [unwrapvec(wAv[i]) for i in gi] - @test hcat(Av1...)' * hcat(Av1...) ≈ I - @test length(gi) < n - @test eltype(R) == eltype(eltype(A)) == T - - norm(hcat(Av1...) * R - hcat(Bv...)) - @test isapprox(hcat(Av1...) * R, hcat(Bv...); atol=tolerance(T)) - @test isapprox(hcat(Av1...)' * hcat(Av1...), I; atol=tolerance(T)) - end -end - -@testset "abstract_qr! for abstract inner product" begin - T = ComplexF64 - H = rand(T, N, N) .- one(T) / 2 - H = H' * H + I - ip(x, y) = x' * H * y - X₁ = InnerProductVec(rand(T, N), ip) - X = [similar(X₁) for _ in 1:n] - X[1] = X₁ - for i in 2:(n - 1) - X[i] = InnerProductVec(rand(T, N), ip) - end - - # Make sure X is not full rank - X[end] = sum(X[1:(end - 1)] .* rand(T, n - 1)) - Xcopy = deepcopy(X) - R, gi = KrylovKit.abstract_qr!(KrylovKit.BlockVec{T}(X), tolerance(T)) - - @test length(gi) < n - @test eltype(R) == T - BlockX = KrylovKit.BlockVec{T}(X[gi]) - @test isapprox(KrylovKit.block_inner(BlockX, BlockX), I; atol=tolerance(T)) - ΔX = norm.([sum(X[gi] .* R[:, i]) for i in 1:size(R, 2)] - Xcopy) - @test isapprox(norm(ΔX), T(0); atol=tolerance(T)) -end - -@testset "block_reorthogonalize! for non-full vectors $mode" for mode in - (:vector, :inplace, - :outplace) - scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : - (ComplexF64,) - @testset for T in scalartypes - A = rand(T, n, n) .- one(T) / 2 - A = (A + A') / 2 - x₀ = [wrapvec(rand(T, N), Val(mode)) for i in 1:n] - x₁ = [wrapvec(rand(T, N), Val(mode)) for i in 1:(2 * n)] - b₀ = KrylovKit.BlockVec{T}(x₀) - b₁ = KrylovKit.BlockVec{T}(x₁) - KrylovKit.abstract_qr!(b₁, tolerance(T)) - orthobasis_x₁ = KrylovKit.OrthonormalBasis(b₁.vec) - KrylovKit.block_reorthogonalize!(b₀, orthobasis_x₁) - @test norm(KrylovKit.block_inner(b₀, b₁)) < tolerance(T) - end -end - -@testset "block_reorthogonalize! for abstract inner product" begin - T = ComplexF64 - H = rand(T, N, N) .- one(T) / 2 - H = H' * H + I - ip(x, y) = x' * H * y - - x₀ = [InnerProductVec(rand(T, N), ip) for i in 1:n] - x₁ = [InnerProductVec(rand(T, N), ip) for i in 1:(2 * n)] - b₀ = KrylovKit.BlockVec{T}(x₀) - b₁ = KrylovKit.BlockVec{T}(x₁) - KrylovKit.abstract_qr!(b₁, tolerance(T)) - orthobasis_x₁ = KrylovKit.OrthonormalBasis(b₁.vec) - KrylovKit.block_reorthogonalize!(b₀, orthobasis_x₁) - @test norm(KrylovKit.block_inner(b₀, b₁)) < tolerance(T) -end diff --git a/test/runtests.jl b/test/runtests.jl index 9a68a7c7..8d2156d3 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -57,17 +57,11 @@ end end @testset "Eigsolve differentiation rules" verbose = true begin include("ad/eigsolve.jl") - include("ad/degenerateeigsolve.jl") + include("ad/repeatedeigsolve.jl") end @testset "Svdsolve differentiation rules" verbose = true begin include("ad/svdsolve.jl") end -@testset "Orthonormal" verbose = true begin - include("orthonormal.jl") -end -@testset "Inner product vector" verbose = true begin - include("innerproductvec.jl") -end @testset "BlockVec" verbose = true begin include("BlockVec.jl") end From 52db1aa6d622801ec24bdad5fb61b988c2179080 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Sat, 17 May 2025 13:49:49 +0800 Subject: [PATCH 69/82] revise for review --- docs/src/man/implementation.md | 3 ++- src/algorithms.jl | 3 ++- src/eigsolve/arnoldi.jl | 8 ++++++-- src/eigsolve/blocklanczos.jl | 4 ++-- src/eigsolve/eigsolve.jl | 6 ++++-- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/docs/src/man/implementation.md b/docs/src/man/implementation.md index f5dc6d5c..f93eb34e 100644 --- a/docs/src/man/implementation.md +++ b/docs/src/man/implementation.md @@ -47,7 +47,8 @@ KrylovKit.basistransform! ``` ## Block Krylov method -The block version of the Krylov subspace algorithm is an approach to extending Krylov subspace techniques from a block of starting vectors. It is mainly used for solving linear systems with repeated dominant eigenvalues. +The block version of the Krylov subspace algorithm is an approach to extending Krylov subspace techniques from +a block of starting vectors. It is mainly used for solving eigenvalue problems with repeated eigenvalues. In our implementation, a block of vectors is stored in a new data structure `BlockVec`, which implements the [`KrylovKit.block_qr!`](@ref), [`KrylovKit.block_reorthogonalize!`](@ref), and [`KrylovKit.block_orthogonalize!`](@ref) interfaces. diff --git a/src/algorithms.jl b/src/algorithms.jl index 238aaf01..6a07c931 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -138,7 +138,8 @@ The block version of [`Lanczos`](@ref) is suited for solving eigenvalue problems Its implementation is mainly based on *Golub, G. H., & Van Loan, C. F. (2013). Matrix Computations* (4th ed., pp. 566–569). Arguments `krylovdim`, `maxiter`, `tol`, `orth`, `eager` and `verbosity` are the same as `Lanczos`. `qr_tol` is the error tolerance for `block_qr!` - a subroutine used to orthorgonalize the vectors in the same block. -The initial size of the block is determined by the number of starting vectors that a user provides. And the size of the block shrinks during iterations. +The initial size of the block is determined by the number of starting vectors that a user provides; +the size of the block can shrink during iterations. The initial block size determines the maximum degeneracy of the target eigenvalue can that be resolved. The iteration stops when either the norm of the residual is below `tol` or a sufficient number of eigenvectors have converged. [Reference](https://www.netlib.org/utk/people/JackDongarra/etemplates/node250.html) diff --git a/src/eigsolve/arnoldi.jl b/src/eigsolve/arnoldi.jl index 0e3d8978..22f36fcf 100644 --- a/src/eigsolve/arnoldi.jl +++ b/src/eigsolve/arnoldi.jl @@ -39,7 +39,9 @@ should be targeted. Valid specifications of `which` are !!! warning "Repeated eigenvalues" From a theoretical point of view, Krylov methods can at most find a single eigenvector - associated with a targetted eigenvalue, even if the latter is repeated. In the case of + associated with a targetted eigenvalue, even if the latter is repeated, i.e. the + eigenvalue has multiple linearly independent eigenvectors or thus an eigenspace + with dimension (geometric multiplicity) larger than one. In the case of such a repeated eigenvalue, the specific eigenvector that is returned is determined by the starting vector `x₀`. For large problems, this turns out to be less of an issue in practice, as often a second linearly independent eigenvector is generated out of the @@ -241,7 +243,9 @@ problems are given by !!! warning "Repeated eigenvalues" From a theoretical point of view, Krylov methods can at most find a single eigenvector - associated with a targetted eigenvalue, even if the latter is repeated. In the case of + associated with a targetted eigenvalue, even if the latter is repeated, i.e. the + eigenvalue has multiple linearly independent eigenvectors or thus an eigenspace + with dimension (geometric multiplicity) larger than one. In the case of such a repeated eigenvalue, the specific eigenvector that is returned is determined by the starting vector `x₀`. For large problems, this turns out to be less of an issue in practice, as often a second linearly independent eigenvector is generated out of the diff --git a/src/eigsolve/blocklanczos.jl b/src/eigsolve/blocklanczos.jl index c7d0e334..6355ebac 100644 --- a/src/eigsolve/blocklanczos.jl +++ b/src/eigsolve/blocklanczos.jl @@ -15,7 +15,7 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) iter = BlockLanczosIterator(A, block0, krylovdim + bs, alg.qr_tol, alg.orth) fact = initialize(iter; verbosity=verbosity) # Returns a BlockLanczosFactorization - numops = 1 # Number of matrix-vector multiplications (for logging) + numops = bs # Number of matrix-vector multiplications (for logging) numiter = 1 converged = 0 @@ -60,7 +60,7 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) if K < krylovdim expand!(iter, fact; verbosity=verbosity) - numops += 1 + numops += fact.r_size else # Shrink and restart following the shrinking method of `Lanczos`. numiter >= maxiter && break keep = max(div(3 * krylovdim + 2 * converged, 5 * bs), 1) * bs diff --git a/src/eigsolve/eigsolve.jl b/src/eigsolve/eigsolve.jl index e4f29f1e..cc7fe312 100644 --- a/src/eigsolve/eigsolve.jl +++ b/src/eigsolve/eigsolve.jl @@ -45,7 +45,9 @@ targeted. Valid specifications of `which` are given by !!! warning "Repeated eigenvalues" From a theoretical point of view, Krylov methods can at most find a single eigenvector - associated with a targetted eigenvalue, even if the latter is repeated. In the case of + associated with a targetted eigenvalue, even if the latter is repeated, i.e. the + eigenvalue has multiple linearly independent eigenvectors or thus an eigenspace + with dimension (geometric multiplicity) larger than one. In the case of such a repeated eigenvalue, the specific eigenvector that is returned is determined by the starting vector `x₀`. For large problems, this turns out to be less of an issue in practice, as often a second linearly independent eigenvector is generated out of the @@ -53,7 +55,7 @@ targeted. Valid specifications of `which` are given by iteration. Nonetheless, it is important to take this into account and to try not to depend on this potentially fragile behaviour, especially for smaller problems. The [`BlockLanczos`](@ref) method has been implemented to compute repeated - eigenvalues and their corresponding eigenvectors. Given a block size `p`, + eigenvalues and their corresponding eigenvectors more reliably. Given a block size `p`, which is the number of starting vectors, the method can at most simultaneously determine `p`-fold repeated eigenvalues and their associated eigenvectors. From fb5a49d6cd3e644ebaabf25d33c8941f7c909e77 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Thu, 22 May 2025 00:27:47 +0800 Subject: [PATCH 70/82] revise for review --- docs/src/man/implementation.md | 1 - src/algorithms.jl | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/src/man/implementation.md b/docs/src/man/implementation.md index f93eb34e..e374a71c 100644 --- a/docs/src/man/implementation.md +++ b/docs/src/man/implementation.md @@ -61,7 +61,6 @@ A block of vectors can be orthonormalized using ```@docs KrylovKit.block_qr! ``` -This apply QR decomposition to a block of vectors using modified Gram-Schmidt process. Additional procedures applied to the block are as follows: ```@docs diff --git a/src/algorithms.jl b/src/algorithms.jl index 6a07c931..d8c838dc 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -134,7 +134,7 @@ end verbosity=KrylovDefaults.verbosity[], qr_tol::Real=KrylovDefaults.tol[]) -The block version of [`Lanczos`](@ref) is suited for solving eigenvalue problems with repeated dominant eigenvalues. +The block version of [`Lanczos`](@ref) is suited for solving eigenvalue problems with repeated extremal eigenvalues. Its implementation is mainly based on *Golub, G. H., & Van Loan, C. F. (2013). Matrix Computations* (4th ed., pp. 566–569). Arguments `krylovdim`, `maxiter`, `tol`, `orth`, `eager` and `verbosity` are the same as `Lanczos`. `qr_tol` is the error tolerance for `block_qr!` - a subroutine used to orthorgonalize the vectors in the same block. From 5daae2e7e35751c337c1883a5bf250539dffaad6 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Thu, 22 May 2025 11:20:30 +0800 Subject: [PATCH 71/82] revise for review --- src/eigsolve/blocklanczos.jl | 26 ++++++++-------- src/factorizations/blocklanczos.jl | 49 +++++++++++++++--------------- test/factorize.jl | 12 ++++---- 3 files changed, 43 insertions(+), 44 deletions(-) diff --git a/src/eigsolve/blocklanczos.jl b/src/eigsolve/blocklanczos.jl index 6355ebac..133bf9e4 100644 --- a/src/eigsolve/blocklanczos.jl +++ b/src/eigsolve/blocklanczos.jl @@ -34,16 +34,16 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) if K >= krylovdim || β <= tol || (alg.eager && K >= howmany) # Compute eigenvalues # Note: Fast eigen solver for block tridiagonal matrices is not implemented yet. - BTD = view(fact.T, 1:K, 1:K) + BTD = view(fact.H, 1:K, 1:K) D, U = eigen(Hermitian(BTD)) by, rev = eigsort(which) p = sortperm(D; by=by, rev=rev) D, U = permuteeig!(D, U, p) # Detect convergence by computing the residuals - bs_r = fact.r_size # The block size of the residual (decreases as the iteration goes) + bs_R = fact.R_size # The block size of the residual (decreases as the iteration goes) r = residual(fact) - UU = U[(end - bs_r + 1):end, :] # The last bs_r rows of U, used to compute the residuals + UU = view(U, (K - bs_R + 1):K, :) # The last bs_R rows of U, used to compute the residuals normresiduals = let R = block_inner(r, r) map(u -> sqrt(real(dot(u, R, u))), cols(UU)) end @@ -60,7 +60,7 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) if K < krylovdim expand!(iter, fact; verbosity=verbosity) - numops += fact.r_size + numops += fact.R_size else # Shrink and restart following the shrinking method of `Lanczos`. numiter >= maxiter && break keep = max(div(3 * krylovdim + 2 * converged, 5 * bs), 1) * bs @@ -68,7 +68,7 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) # The last bs rows of U contribute to calculate errors of Ritz values. @inbounds for j in 1:keep H[j, j] = D[j] - H[(keep + 1):end, j] = U[(K - bs + 1):K, j] + H[(keep + 1):end, j] = view(U, (K - bs + 1):K, j) end # Turn diagonal matrix D into a block tridiagonal matrix, and make sure # The residual of krylov subspace keeps the form of [0,..,0,R] @@ -87,10 +87,10 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) B = basis(fact) basistransform!(B, view(U, :, 1:keep)) - r_new = OrthonormalBasis(fact.r.vec[1:bs_r]) - view_H = view(H, (keep + bs - bs_r + 1):(keep + bs), (keep - bs_r + 1):keep) - basistransform!(r_new, view_H) - fact.r.vec[1:bs_r] = r_new[1:bs_r] + R_new = OrthonormalBasis(fact.R.vec[1:bs_R]) + view_H = view(H, (keep + bs - bs_R + 1):(keep + bs), (keep - bs_R + 1):keep) + basistransform!(R_new, view_H) + fact.R.vec[1:bs_R] = R_new[1:bs_R] while length(fact) > keep pop!(fact.V) @@ -111,13 +111,13 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) vectors = let V = basis(fact) [V * u for u in cols(U1)] end - bs_r = fact.r_size + bs_R = fact.R_size K = length(fact) - U2 = view(U, (K - bs_r + 1):K, 1:howmany_actual) - R = fact.r + U2 = view(U, (K - bs_R + 1):K, 1:howmany_actual) + R = fact.R residuals = [zerovector(x₀[1]) for _ in 1:howmany_actual] @inbounds for i in 1:howmany_actual - for j in 1:bs_r + for j in 1:bs_R residuals[i] = add!!(residuals[i], R[j], U2[j, i]) end end diff --git a/src/factorizations/blocklanczos.jl b/src/factorizations/blocklanczos.jl index a4f3bb81..f4581980 100644 --- a/src/factorizations/blocklanczos.jl +++ b/src/factorizations/blocklanczos.jl @@ -55,17 +55,16 @@ Structure to store a BlockLanczos factorization of a real symmetric or complex h map `A` of the form ```julia -A * V = V * B + r * b' +A * V = V * H + R * B' ``` For a given BlockLanczos factorization `fact`, length `k = length(fact)` and basis `V = basis(fact)` are -like [`LanczosFactorization`](@ref). The block tridiagonal matrix `T` is preallocated in `BlockLanczosFactorization` -and is of type `Hermitian{S<:Number}` with `size(T) == (krylovdim + bs₀, krylovdim + bs₀)` where `bs₀` is the size of the initial block -and `krylovdim` is the maximum dimension of the Krylov subspace. The residuals `r` is of type `Vector{T}`. -One can also query [`normres(fact)`](@ref) to obtain `norm(r)`, the norm of the residual. The matrix -`b` takes the default value ``[0;I]``, i.e. the matrix of size `(k,bs)` and an unit matrix in the last -`bs` rows and all zeros in the other rows. `bs` is the size of the last block. One can query [`r_size(fact)`] to obtain -size of the last block and the residuals. +like [`LanczosFactorization`](@ref). The hermitian block tridiagonal matrix `H` is preallocated +in `BlockLanczosFactorization` and can reach a maximal size of `(krylovdim + bs₀, krylovdim + bs₀)`, where `bs₀` is the size of the initial block +and `krylovdim` is the maximum dimension of the Krylov subspace. A list of residual vectors is contained in `R` is of type `Vector{T}`. +One can also query [`normres(fact)`](@ref) to obtain `norm(R)`, which computes a total norm of all residual vectors combined. The matrix +`B` takes the default value ``[0; I]``, i.e. the matrix of size `(k,bs)` containing a unit matrix in the last +`bs` rows and all zeros in the other rows. `bs` is the size of the last block. `BlockLanczosFactorization` is mutable because it can [`expand!`](@ref). But it does not support `shrink!` because it is implemented in its `eigsolve`. @@ -76,15 +75,15 @@ mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: KrylovFactorization{T,S} k::Int V::OrthonormalBasis{T} # BlockLanczos Basis - T::AbstractMatrix{S} # block tridiagonal matrix, and S is the matrix element type - r::BlockVec{T,S} # residual block - r_size::Int # size of the residual block - norm_r::SR # norm of the residual block + H::AbstractMatrix{S} # block tridiagonal matrix, and S is the matrix element type + R::BlockVec{T,S} # residual block + R_size::Int # size of the residual block + norm_R::SR # norm of the residual block end Base.length(fact::BlockLanczosFactorization) = fact.k -normres(fact::BlockLanczosFactorization) = fact.norm_r +normres(fact::BlockLanczosFactorization) = fact.norm_R basis(fact::BlockLanczosFactorization) = fact.V -residual(fact::BlockLanczosFactorization) = fact.r[1:(fact.r_size)] +residual(fact::BlockLanczosFactorization) = fact.R[1:(fact.R_size)] """ struct BlockLanczosIterator{F,T,O<:Orthogonalizer} <: KrylovIterator{F,T} @@ -168,23 +167,23 @@ function initialize(iter::BlockLanczosIterator{F,T,S}; AX₁[j] = add!!(AX₁[j], X₁[i], -M₁[i, j]) end end - norm_r = norm(AX₁) + norm_R = norm(AX₁) if verbosity > EACHITERATION_LEVEL - @info "BlockLanczos initiation at dimension $bs: subspace normres = $(normres2string(norm_r))" + @info "BlockLanczos initiation at dimension $bs: subspace normres = $(normres2string(norm_R))" end return BlockLanczosFactorization(bs, V, BTD, AX₁, bs, - norm_r) + norm_R) end function expand!(iter::BlockLanczosIterator{F,T,S}, state::BlockLanczosFactorization{T,S,SR}; verbosity::Int=KrylovDefaults.verbosity[]) where {F,T,S,SR} k = state.k - R = state.r[1:(state.r_size)] + R = state.R[1:(state.R_size)] bs = length(R) V = state.V @@ -192,21 +191,21 @@ function expand!(iter::BlockLanczosIterator{F,T,S}, B, good_idx = block_qr!(R, iter.qr_tol) bs_next = length(good_idx) push!(V, R[good_idx]) - state.T[(k + 1):(k + bs_next), (k - bs + 1):k] .= B - state.T[(k - bs + 1):k, (k + 1):(k + bs_next)] .= B' + state.H[(k + 1):(k + bs_next), (k - bs + 1):k] .= B + state.H[(k - bs + 1):k, (k + 1):(k + bs_next)] .= B' # Calculate the new residual and orthogonalize the new basis Rnext, Mnext = blocklanczosrecurrence(iter.operator, V, B, iter.orth) verbosity >= WARN_LEVEL && warn_nonhermitian(Mnext) - state.T[(k + 1):(k + bs_next), (k + 1):(k + bs_next)] .= Mnext - state.r.vec[1:bs_next] .= Rnext.vec - state.norm_r = norm(Rnext) + state.H[(k + 1):(k + bs_next), (k + 1):(k + bs_next)] .= Mnext + state.R.vec[1:bs_next] .= Rnext.vec + state.norm_R = norm(Rnext) state.k += bs_next - state.r_size = bs_next + state.R_size = bs_next if verbosity > EACHITERATION_LEVEL - @info "BlockLanczos expansion to dimension $(state.k): subspace normres = $(normres2string(state.norm_r))" + @info "BlockLanczos expansion to dimension $(state.k): subspace normres = $(normres2string(state.norm_R))" end end diff --git a/test/factorize.jl b/test/factorize.jl index 4373ee68..5ff501da 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -364,20 +364,20 @@ end tolerance(T)) krylovdim = n fact = initialize(iter) - while fact.norm_r > eps(float(real(T))) && fact.k < krylovdim + while fact.norm_R > eps(float(real(T))) && fact.k < krylovdim @constinferred expand!(iter, fact) k = fact.k - rs = fact.r_size + rs = fact.R_size V0 = fact.V[1:k] - r0 = fact.r[1:rs] - H = fact.T[1:k, 1:k] - norm_r = fact.norm_r + r0 = fact.R[1:rs] + H = fact.H[1:k, 1:k] + norm_R = fact.norm_R V = hcat([unwrapvec(v) for v in V0]...) r = hcat([unwrapvec(r0[i]) for i in 1:rs]...) e = hcat(zeros(T, rs, k - rs), I) norm(V' * V - I) @test V' * V ≈ I - @test norm(r) ≈ norm_r + @test norm(r) ≈ norm_R @test A * V ≈ V * H + r * e end end From e28f4bce644d5dc4e3b1e1dfc57468cd0887d90b Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Fri, 23 May 2025 01:22:33 +0800 Subject: [PATCH 72/82] revise for review --- src/factorizations/blocklanczos.jl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/factorizations/blocklanczos.jl b/src/factorizations/blocklanczos.jl index f4581980..262f4cb7 100644 --- a/src/factorizations/blocklanczos.jl +++ b/src/factorizations/blocklanczos.jl @@ -147,18 +147,19 @@ function initialize(iter::BlockLanczosIterator{F,T,S}; verbosity::Int=KrylovDefaults.verbosity[]) where {F,T,S} X₀ = iter.x₀ maxdim = iter.maxdim - bs = length(X₀) # block size now A = iter.operator BTD = zeros(S, maxdim, maxdim) # Orthogonalization of the initial block X₁ = copy(X₀) - block_qr!(X₁, iter.qr_tol) + _, good_idx = block_qr!(X₁, iter.qr_tol) + X₁ = X₁[good_idx] V = OrthonormalBasis(X₁.vec) + bs = length(X₁) # block size of the first block AX₁ = apply(A, X₁) M₁ = block_inner(X₁, AX₁) - BTD[1:bs, 1:bs] .= M₁ + BTD[1:bs, 1:bs] = view(M₁, 1:bs, 1:bs) verbosity >= WARN_LEVEL && warn_nonhermitian(M₁) # Get the first residual @@ -191,14 +192,14 @@ function expand!(iter::BlockLanczosIterator{F,T,S}, B, good_idx = block_qr!(R, iter.qr_tol) bs_next = length(good_idx) push!(V, R[good_idx]) - state.H[(k + 1):(k + bs_next), (k - bs + 1):k] .= B - state.H[(k - bs + 1):k, (k + 1):(k + bs_next)] .= B' + state.H[(k + 1):(k + bs_next), (k - bs + 1):k] = view(B, 1:bs_next, 1:bs) + state.H[(k - bs + 1):k, (k + 1):(k + bs_next)] = view(B, 1:bs_next, 1:bs)' # Calculate the new residual and orthogonalize the new basis Rnext, Mnext = blocklanczosrecurrence(iter.operator, V, B, iter.orth) verbosity >= WARN_LEVEL && warn_nonhermitian(Mnext) - state.H[(k + 1):(k + bs_next), (k + 1):(k + bs_next)] .= Mnext + state.H[(k + 1):(k + bs_next), (k + 1):(k + bs_next)] = view(Mnext, 1:bs_next, 1:bs_next) state.R.vec[1:bs_next] .= Rnext.vec state.norm_R = norm(Rnext) state.k += bs_next From 4624426df783983920572eb3f8520fea8bb8a4aa Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Fri, 23 May 2025 01:26:08 +0800 Subject: [PATCH 73/82] format --- src/factorizations/blocklanczos.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/factorizations/blocklanczos.jl b/src/factorizations/blocklanczos.jl index 262f4cb7..62cfa0a0 100644 --- a/src/factorizations/blocklanczos.jl +++ b/src/factorizations/blocklanczos.jl @@ -199,7 +199,8 @@ function expand!(iter::BlockLanczosIterator{F,T,S}, Rnext, Mnext = blocklanczosrecurrence(iter.operator, V, B, iter.orth) verbosity >= WARN_LEVEL && warn_nonhermitian(Mnext) - state.H[(k + 1):(k + bs_next), (k + 1):(k + bs_next)] = view(Mnext, 1:bs_next, 1:bs_next) + state.H[(k + 1):(k + bs_next), (k + 1):(k + bs_next)] = view(Mnext, 1:bs_next, + 1:bs_next) state.R.vec[1:bs_next] .= Rnext.vec state.norm_R = norm(Rnext) state.k += bs_next From b93ad1acb999247bdd1c92c30660d265fa305774 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Fri, 23 May 2025 20:42:37 +0800 Subject: [PATCH 74/82] revise for review --- docs/src/man/implementation.md | 6 +-- src/KrylovKit.jl | 2 +- src/algorithms.jl | 6 +-- src/eigsolve/blocklanczos.jl | 16 +++--- src/eigsolve/eigsolve.jl | 51 ++++++++++++++----- src/factorizations/blocklanczos.jl | 79 +++++++++++++++++------------- test/{BlockVec.jl => Block.jl} | 71 +++++++++++++++++---------- test/eigsolve.jl | 43 ++++++++++++---- test/factorize.jl | 6 +-- test/runtests.jl | 4 +- 10 files changed, 184 insertions(+), 100 deletions(-) rename test/{BlockVec.jl => Block.jl} (77%) diff --git a/docs/src/man/implementation.md b/docs/src/man/implementation.md index e374a71c..c1edde84 100644 --- a/docs/src/man/implementation.md +++ b/docs/src/man/implementation.md @@ -48,13 +48,13 @@ KrylovKit.basistransform! ## Block Krylov method The block version of the Krylov subspace algorithm is an approach to extending Krylov subspace techniques from -a block of starting vectors. It is mainly used for solving eigenvalue problems with repeated eigenvalues. +a starting block. It is mainly used for solving eigenvalue problems with repeated eigenvalues. -In our implementation, a block of vectors is stored in a new data structure `BlockVec`, +In our implementation, a block of vectors is stored in a new data structure `Block`, which implements the [`KrylovKit.block_qr!`](@ref), [`KrylovKit.block_reorthogonalize!`](@ref), and [`KrylovKit.block_orthogonalize!`](@ref) interfaces. ```@docs -KrylovKit.BlockVec +KrylovKit.Block ``` A block of vectors can be orthonormalized using diff --git a/src/KrylovKit.jl b/src/KrylovKit.jl index 009fd9b4..13d5ede5 100644 --- a/src/KrylovKit.jl +++ b/src/KrylovKit.jl @@ -39,7 +39,7 @@ export ModifiedGramSchmidt, ModifiedGramSchmidt2, ModifiedGramSchmidtIR export LanczosIterator, BlockLanczosIterator, ArnoldiIterator, GKLIterator export CG, GMRES, BiCGStab, Lanczos, BlockLanczos, Arnoldi, GKL, GolubYe, LSMR export KrylovDefaults, EigSorter -export RecursiveVec, InnerProductVec +export RecursiveVec, InnerProductVec, Block # Multithreading const _NTHREADS = Ref(1) diff --git a/src/algorithms.jl b/src/algorithms.jl index d8c838dc..3fb7846d 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -138,7 +138,7 @@ The block version of [`Lanczos`](@ref) is suited for solving eigenvalue problems Its implementation is mainly based on *Golub, G. H., & Van Loan, C. F. (2013). Matrix Computations* (4th ed., pp. 566–569). Arguments `krylovdim`, `maxiter`, `tol`, `orth`, `eager` and `verbosity` are the same as `Lanczos`. `qr_tol` is the error tolerance for `block_qr!` - a subroutine used to orthorgonalize the vectors in the same block. -The initial size of the block is determined by the number of starting vectors that a user provides; +The initial size of the block is determined by the number of vectors that a user provides in the starting block; the size of the block can shrink during iterations. The initial block size determines the maximum degeneracy of the target eigenvalue can that be resolved. @@ -161,10 +161,10 @@ function BlockLanczos(; krylovdim::Int=KrylovDefaults.blockkrylovdim[], maxiter::Int=KrylovDefaults.maxiter[], tol::Real=KrylovDefaults.tol[], + qr_tol::Real=KrylovDefaults.tol[], orth::Orthogonalizer=KrylovDefaults.orth, eager::Bool=false, - verbosity::Int=KrylovDefaults.verbosity[], - qr_tol::Real=KrylovDefaults.tol[]) + verbosity::Int=KrylovDefaults.verbosity[]) return BlockLanczos(orth, krylovdim, maxiter, tol, qr_tol, eager, verbosity) end diff --git a/src/eigsolve/blocklanczos.jl b/src/eigsolve/blocklanczos.jl index 133bf9e4..9d876cb8 100644 --- a/src/eigsolve/blocklanczos.jl +++ b/src/eigsolve/blocklanczos.jl @@ -1,4 +1,9 @@ -function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) +function eigsolve(A, x₀::Block{T,S}, howmany::Int, which::Selector, alg::BlockLanczos; + alg_rrule=Arnoldi(; tol=alg.tol, + krylovdim=alg.krylovdim, + maxiter=alg.maxiter, + eager=alg.eager, + orth=alg.orth)) where {T,S} maxiter = alg.maxiter krylovdim = alg.krylovdim if howmany > krylovdim @@ -7,13 +12,10 @@ function eigsolve(A, x₀, howmany::Int, which::Selector, alg::BlockLanczos) tol = alg.tol verbosity = alg.verbosity - # The initial block size is determined by the number of the starting vectors provided by the user - S = typeof(inner(x₀[1], x₀[1])) # Scalar type of the inner product between vectors in x₀ - block0 = BlockVec{S}(x₀) - bs = length(block0) - bs < 1 && error("The length of the starting vector x₀ must be greater than 0") + bs = length(x₀) + bs < 1 && error("The length of the starting block x₀ must be greater than 0") - iter = BlockLanczosIterator(A, block0, krylovdim + bs, alg.qr_tol, alg.orth) + iter = BlockLanczosIterator(A, x₀, krylovdim + bs, alg.qr_tol, alg.orth) fact = initialize(iter; verbosity=verbosity) # Returns a BlockLanczosFactorization numops = bs # Number of matrix-vector multiplications (for logging) numiter = 1 diff --git a/src/eigsolve/eigsolve.jl b/src/eigsolve/eigsolve.jl index cc7fe312..64150b12 100644 --- a/src/eigsolve/eigsolve.jl +++ b/src/eigsolve/eigsolve.jl @@ -5,6 +5,11 @@ # expert version: eigsolve(f, x₀, howmany, which, algorithm; alg_rrule=...) + # block version: + eigsolve(f, x₀::Block, [howmany = 1, which = :LM]; kwargs...) + # expert block version: + eigsolve(f, x₀::Block, howmany, which, algorithm; alg_rrule=...) + Compute at least `howmany` eigenvalues from the linear map encoded in the matrix `A` or by the function `f`. Return eigenvalues, eigenvectors and a `ConvergenceInfo` structure. @@ -55,9 +60,9 @@ targeted. Valid specifications of `which` are given by iteration. Nonetheless, it is important to take this into account and to try not to depend on this potentially fragile behaviour, especially for smaller problems. The [`BlockLanczos`](@ref) method has been implemented to compute repeated - eigenvalues and their corresponding eigenvectors more reliably. Given a block size `p`, - which is the number of starting vectors, the method can at most simultaneously determine - `p`-fold repeated eigenvalues and their associated eigenvectors. + eigenvalues and their corresponding eigenvectors more reliably. Given a starting block `x₀` + with length `p`, which is the number of vectors in `x₀`, the method can at most simultaneously + determine `p`-fold repeated eigenvalues and their associated eigenvectors. The argument `T` acts as a hint in which `Number` type the computation should be performed, but is not restrictive. If the linear map automatically produces complex values, complex @@ -74,7 +79,7 @@ The return value is always of the form `vals, vecs, info = eigsolve(...)` with - `vals`: a `Vector` containing the eigenvalues, of length at least `howmany`, but could be longer if more eigenvalues were converged at the same cost. Eigenvalues will be real - if [`Lanczos`](@ref) was used and complex if [`Arnoldi`](@ref) was used (see below). + if [`Lanczos`](@ref) or [`BlockLanczos`](@ref) was used and complex if [`Arnoldi`](@ref) was used (see below). - `vecs`: a `Vector` of corresponding eigenvectors, of the same length as `vals`. Note that eigenvectors are not returned as a matrix, as the linear map could act on any custom Julia type with vector like behavior, i.e. the elements of the list `vecs` are @@ -109,7 +114,7 @@ Keyword arguments and their default values are given by: - 1 (only warnings) - 2 (one message with convergence info at the end) - 3 (progress info after every iteration) - - 4+ (all of the above and additional information about the Lanczos or Arnoldi iteration) + - 4+ (all of the above and additional information about the Lanczos, BlockLanczos, or Arnoldi iteration) - `tol::Real`: the requested accuracy (corresponding to the 2-norm of the residual for Schur vectors, not the eigenvectors). If you work in e.g. single precision (`Float32`), you should definitely change the default value. @@ -151,7 +156,7 @@ being used. The final (expert) method, without default values and keyword arguments, is the one that is finally called, and can also be used directly. Here, one specifies the algorithm explicitly -as either [`Lanczos`](@ref), for real symmetric or complex hermitian problems, or +as either [`Lanczos`](@ref) or [`BlockLanczos`](@ref), for real symmetric or complex hermitian problems, or [`Arnoldi`](@ref), for general problems. Note that these names refer to the process for building the Krylov subspace, but the actual algorithm is an implementation of the Krylov-Schur algorithm, which can dynamically shrink and grow the Krylov subspace, i.e. the @@ -204,11 +209,11 @@ function eigsolve(f, x₀, howmany::Int=1, which::Selector=:LM; kwargs...) Tx = typeof(x₀) Tfx = Core.Compiler.return_type(apply, Tuple{typeof(f),Tx}) T = Core.Compiler.return_type(dot, Tuple{Tx,Tfx}) - alg = eigselector(f, T; kwargs...) + alg = eigselector(f, T; Tx=Tx, kwargs...) checkwhich(which) || error("Unknown eigenvalue selector: which = $which") - if alg isa Lanczos + if alg isa Lanczos || alg isa BlockLanczos if which == :LI || which == :SI - error("Eigenvalue selector which = $which invalid: real eigenvalues expected with Lanczos algorithm") + error("Eigenvalue selector which = $which invalid: real eigenvalues expected with Lanczos and BlockLanczos algorithms") end elseif T <: Real by, rev = eigsort(which) @@ -231,16 +236,28 @@ end function eigselector(f, T::Type; + Tx::Type=Nothing, issymmetric::Bool=false, ishermitian::Bool=issymmetric && (T <: Real), krylovdim::Int=KrylovDefaults.krylovdim[], maxiter::Int=KrylovDefaults.maxiter[], tol::Real=KrylovDefaults.tol[], + qr_tol::Real=KrylovDefaults.tol[], orth::Orthogonalizer=KrylovDefaults.orth, eager::Bool=false, verbosity::Int=KrylovDefaults.verbosity[], alg_rrule=nothing) - if (T <: Real && issymmetric) || ishermitian + if Tx <: Block + !(issymmetric || ishermitian) && + error("BlockLanczos requires a symmetric or hermitian linear map. A BlockArnoldi method has not yet been implemented but will be in the future") + return BlockLanczos(; krylovdim=krylovdim, + maxiter=maxiter, + tol=tol, + qr_tol=qr_tol, + orth=orth, + eager=eager, + verbosity=verbosity) + elseif (T <: Real && issymmetric) || ishermitian return Lanczos(; krylovdim=krylovdim, maxiter=maxiter, tol=tol, @@ -258,16 +275,28 @@ function eigselector(f, end function eigselector(A::AbstractMatrix, T::Type; + Tx::Type=Nothing, issymmetric::Bool=(T <: Real && LinearAlgebra.issymmetric(A)), ishermitian::Bool=issymmetric || LinearAlgebra.ishermitian(A), krylovdim::Int=KrylovDefaults.krylovdim[], maxiter::Int=KrylovDefaults.maxiter[], tol::Real=KrylovDefaults.tol[], + qr_tol::Real=KrylovDefaults.tol[], orth::Orthogonalizer=KrylovDefaults.orth, eager::Bool=false, verbosity::Int=KrylovDefaults.verbosity[], alg_rrule=nothing) - if (T <: Real && issymmetric) || ishermitian + if Tx <: Block + !(issymmetric || ishermitian) && + error("BlockLanczos requires a symmetric or hermitian linear map. A BlockArnoldi method has not yet been implemented but will be in the future") + return BlockLanczos(; krylovdim=krylovdim, + maxiter=maxiter, + tol=tol, + qr_tol=qr_tol, + orth=orth, + eager=eager, + verbosity=verbosity) + elseif (T <: Real && issymmetric) || ishermitian return Lanczos(; krylovdim=krylovdim, maxiter=maxiter, tol=tol, diff --git a/src/factorizations/blocklanczos.jl b/src/factorizations/blocklanczos.jl index 62cfa0a0..82d3af39 100644 --- a/src/factorizations/blocklanczos.jl +++ b/src/factorizations/blocklanczos.jl @@ -1,32 +1,41 @@ # BlockLanczos """ - struct BlockVec{T,S<:Number} + x₀ = Block{S}(vec) + x₀ = Block(vec) Structure for storing vectors in a block format. The type parameter `T` represents the type of vector elements, -while `S` represents the type of inner products between vectors. +while `S` represents the type of inner products between vectors. To create an instance of the `Block` type, +one can specify `S` explicitly and use `Block{S}(vec)`, or use `Block(vec)` directly, in which case an +inner product is performed to infer `S`. """ -struct BlockVec{T,S<:Number} +struct Block{T,S<:Number} vec::Vector{T} - function BlockVec{S}(vec::Vector{T}) where {T,S<:Number} + function Block{S}(vec::Vector{T}) where {T,S<:Number} + @assert length(vec) > 1 return new{T,S}(vec) end end -Base.length(b::BlockVec) = length(b.vec) -Base.getindex(b::BlockVec, i::Int) = b.vec[i] -function Base.getindex(b::BlockVec{T,S}, idxs::AbstractVector{Int}) where {T,S} - return BlockVec{S}([b.vec[i] for i in idxs]) +function Block(vec::Vector{T}) where {T} + @assert length(vec) > 1 + S = typeof(inner(vec[1], vec[1])) + return Block{S}(vec) +end # A convenient constructor for users +Base.length(b::Block) = length(b.vec) +Base.getindex(b::Block, i::Int) = b.vec[i] +function Base.getindex(b::Block{T,S}, idxs::AbstractVector{Int}) where {T,S} + return Block{S}([b.vec[i] for i in idxs]) end -Base.setindex!(b::BlockVec{T}, v::T, i::Int) where {T} = (b.vec[i] = v) -function Base.setindex!(b₁::BlockVec{T}, b₂::BlockVec{T}, +Base.setindex!(b::Block{T}, v::T, i::Int) where {T} = (b.vec[i] = v) +function Base.setindex!(b₁::Block{T}, b₂::Block{T}, idxs::AbstractVector{Int}) where {T} return (b₁.vec[idxs]=b₂.vec; b₁) end -LinearAlgebra.norm(b::BlockVec) = norm(b.vec) -function apply(f, block::BlockVec{T,S}) where {T,S} - return BlockVec{S}([apply(f, block[i]) for i in 1:length(block)]) +LinearAlgebra.norm(b::Block) = norm(b.vec) +function apply(f, block::Block{T,S}) where {T,S} + return Block{S}([apply(f, block[i]) for i in 1:length(block)]) end -function block_inner(B₁::BlockVec{T,S}, B₂::BlockVec{T,S}) where {T,S} +function block_inner(B₁::Block{T,S}, B₂::Block{T,S}) where {T,S} M = Matrix{S}(undef, length(B₁.vec), length(B₂.vec)) @inbounds for j in 1:length(B₂) yj = B₂[j] @@ -36,17 +45,17 @@ function block_inner(B₁::BlockVec{T,S}, B₂::BlockVec{T,S}) where {T,S} end return M end -function Base.push!(V::OrthonormalBasis{T}, b::BlockVec{T}) where {T} +function Base.push!(V::OrthonormalBasis{T}, b::Block{T}) where {T} for i in 1:length(b) push!(V, b[i]) end return V end -function Base.copy(b::BlockVec) - return BlockVec{typeof(b).parameters[2]}(scale.(b.vec, 1)) +function Base.copy(b::Block) + return Block{typeof(b).parameters[2]}(scale.(b.vec, 1)) end -Base.iterate(b::BlockVec) = iterate(b.vec) -Base.iterate(b::BlockVec, state) = iterate(b.vec, state) +Base.iterate(b::Block) = iterate(b.vec) +Base.iterate(b::Block, state) = iterate(b.vec, state) """ mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: BlockKrylovFactorization{T,S,SR} @@ -69,14 +78,14 @@ One can also query [`normres(fact)`](@ref) to obtain `norm(R)`, which computes a `BlockLanczosFactorization` is mutable because it can [`expand!`](@ref). But it does not support `shrink!` because it is implemented in its `eigsolve`. See also [`BlockLanczosIterator`](@ref) for an iterator that constructs a progressively expanding -BlockLanczos factorizations of a given linear map and a starting vector. +BlockLanczos factorizations of a given linear map and a starting block. """ mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: KrylovFactorization{T,S} k::Int V::OrthonormalBasis{T} # BlockLanczos Basis H::AbstractMatrix{S} # block tridiagonal matrix, and S is the matrix element type - R::BlockVec{T,S} # residual block + R::Block{T,S} # residual block R_size::Int # size of the residual block norm_R::SR # norm of the residual block end @@ -90,7 +99,7 @@ residual(fact::BlockLanczosFactorization) = fact.R[1:(fact.R_size)] BlockLanczosIterator(f, x₀, maxdim, qr_tol, [orth::Orthogonalizer = KrylovDefaults.orth]) Iterator that takes a linear map `f::F` (supposed to be real symmetric or complex hermitian) -and an initial block `x₀::BlockVec{T,S}` and generates an expanding `BlockLanczosFactorization` thereof. In +and an initial block `x₀::Block{T,S}` and generates an expanding `BlockLanczosFactorization` thereof. In particular, `BlockLanczosIterator` uses the BlockLanczos iteration(see: *Golub, G. H., & Van Loan, C. F. (2013). Matrix Computations* (4th ed., pp. 566–569)) scheme to build a successively expanding BlockLanczos factorization. While `f` cannot be tested to be symmetric or @@ -119,12 +128,12 @@ factorization in place. """ struct BlockLanczosIterator{F,T,S,O<:Orthogonalizer} <: KrylovIterator{F,T} operator::F - x₀::BlockVec{T,S} + x₀::Block{T,S} maxdim::Int orth::O qr_tol::Real function BlockLanczosIterator{F,T,S,O}(operator::F, - x₀::BlockVec{T,S}, + x₀::Block{T,S}, maxdim::Int, orth::O, qr_tol::Real) where {F,T,S,O<:Orthogonalizer} @@ -132,7 +141,7 @@ struct BlockLanczosIterator{F,T,S,O<:Orthogonalizer} <: KrylovIterator{F,T} end end function BlockLanczosIterator(operator::F, - x₀::BlockVec{T,S}, + x₀::Block{T,S}, maxdim::Int, qr_tol::Real, orth::O=ModifiedGramSchmidt2()) where {F,T,S, @@ -217,20 +226,20 @@ function blocklanczosrecurrence(operator, V::OrthonormalBasis, B::AbstractMatrix bs, bs_prev = size(B) S = eltype(B) k = length(V) - X = BlockVec{S}(V[(k - bs + 1):k]) + X = Block{S}(V[(k - bs + 1):k]) AX = apply(operator, X) M = block_inner(X, AX) # Calculate the new residual. Get Rnext - Xprev = BlockVec{S}(V[(k - bs_prev - bs + 1):(k - bs)]) + Xprev = Block{S}(V[(k - bs_prev - bs + 1):(k - bs)]) Rnext = block_orthogonalize!(AX, X, M, Xprev, B') block_reorthogonalize!(Rnext, V) return Rnext, M end """ - block_orthogonalize!(AX::BlockVec{T,S}, X::BlockVec{T,S}, + block_orthogonalize!(AX::Block{T,S}, X::Block{T,S}, M::AbstractMatrix, - Xprev::BlockVec{T,S}, Bprev::AbstractMatrix) where {T,S} + Xprev::Block{T,S}, Bprev::AbstractMatrix) where {T,S} Computes the residual block and stores the result in `AX`. @@ -248,9 +257,9 @@ The residual is computed as: After this operation, `AX` is orthogonal (in the block inner product sense) to both `X` and `Xprev`. """ -function block_orthogonalize!(AX::BlockVec{T,S}, X::BlockVec{T,S}, +function block_orthogonalize!(AX::Block{T,S}, X::Block{T,S}, M::AbstractMatrix, - Xprev::BlockVec{T,S}, Bprev::AbstractMatrix) where {T,S} + Xprev::Block{T,S}, Bprev::AbstractMatrix) where {T,S} @inbounds for j in 1:length(X) for i in 1:length(X) AX[j] = add!!(AX[j], X[i], -M[i, j]) @@ -263,7 +272,7 @@ function block_orthogonalize!(AX::BlockVec{T,S}, X::BlockVec{T,S}, end """ - block_reorthogonalize!(R::BlockVec{T,S}, V::OrthonormalBasis{T}) where {T,S} + block_reorthogonalize!(R::Block{T,S}, V::OrthonormalBasis{T}) where {T,S} This function orthogonalizes the vectors in `R` with respect to the previously orthonormalized set `V` by using the modified Gram-Schmidt process. Specifically, it modifies each vector `R[i]` by projecting out its components along the directions spanned by `V`, i.e., @@ -274,7 +283,7 @@ Specifically, it modifies each vector `R[i]` by projecting out its components al Here,`⟨·,·⟩` denotes the inner product. The function assumes that `V` is already orthonormal. """ -function block_reorthogonalize!(R::BlockVec{T,S}, +function block_reorthogonalize!(R::Block{T,S}, V::OrthonormalBasis{T}) where {T,S} for i in 1:length(R) for q in V @@ -291,7 +300,7 @@ function warn_nonhermitian(M::AbstractMatrix) end """ - block_qr!(block::BlockVec{T,S}, tol::Real) where {T,S} + block_qr!(block::Block{T,S}, tol::Real) where {T,S} This function performs a QR factorization of a block of abstract vectors using the modified Gram-Schmidt process. @@ -307,7 +316,7 @@ and `r` is the numerical rank of the input block. The matrix represents the uppe restricted to the `r` linearly independent components. The vector `goodidx` contains the indices of the non-zero (i.e., numerically independent) vectors in the orthonormalized block. """ -function block_qr!(block::BlockVec{T,S}, tol::Real) where {T,S} +function block_qr!(block::Block{T,S}, tol::Real) where {T,S} n = length(block) rank_shrink = false idx = ones(Int64, n) diff --git a/test/BlockVec.jl b/test/Block.jl similarity index 77% rename from test/BlockVec.jl rename to test/Block.jl index 77801149..b0fc3fae 100644 --- a/test/BlockVec.jl +++ b/test/Block.jl @@ -1,11 +1,30 @@ -@testset "apply on BlockVec" begin +@testset "Block constructor" begin for mode in (:vector, :inplace, :outplace) - scalartypes = mode === :vector ? (Float32, Float64, ComplexF64) : + scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @testset for T in scalartypes - A = rand(T, N, N) .- one(T) / 2 + x₀ = Block{T}([wrapvec(rand(T, n), Val(mode)) for _ in 1:n]) + x₁ = Block([wrapvec(rand(T, n), Val(mode)) for _ in 1:n]) + @test typeof(x₀) == typeof(x₁) + end + end + T = ComplexF64 + A = rand(T, n, n) .- one(T) / 2 + A = A' * A + I + f(x, y) = x' * A * y + x₀ = Block{T}([InnerProductVec(rand(T, n), f) for _ in 1:n]) + x₁ = Block([InnerProductVec(rand(T, n), f) for _ in 1:n]) + @test typeof(x₀) == typeof(x₁) +end + +@testset "apply on Block" begin + for mode in (:vector, :inplace, :outplace) + scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : + (ComplexF64,) + @testset for T in scalartypes + A = rand(T, n, n) .- one(T) / 2 A = (A + A') / 2 - wx₀ = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) + wx₀ = Block{T}([wrapvec(rand(T, n), Val(mode)) for _ in 1:n]) wy = KrylovKit.apply(wrapop(A, Val(mode)), wx₀) y = unwrapvec.(wy) x₀ = unwrapvec.(wx₀) @@ -13,22 +32,22 @@ end end T = ComplexF64 - A = rand(T, N, N) .- one(T) / 2 + A = rand(T, n, n) .- one(T) / 2 A = A' * A + I f(x, y) = x' * A * y Af(x::InnerProductVec) = KrylovKit.InnerProductVec(A * x[], x.dotf) - x₀ = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), f) for _ in 1:n]) + x₀ = Block{T}([InnerProductVec(rand(T, n), f) for _ in 1:n]) y = KrylovKit.apply(Af, x₀) @test isapprox(hcat([y[i].vec for i in 1:n]...), A * hcat([x₀[i].vec for i in 1:n]...); atol=tolerance(T)) end -@testset "copy for BlockVec" begin +@testset "copy for Block" begin for mode in (:vector, :inplace, :outplace) scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @testset for T in scalartypes - block0 = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) + block0 = Block{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) block1 = copy(block0) @test typeof(block0) == typeof(block1) @test unwrapvec.(block0.vec) == unwrapvec.(block1.vec) @@ -40,7 +59,7 @@ end A = rand(T, N, N) .- one(T) / 2 A = A' * A + I f(x, y) = x' * A * y - block0 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), f) for _ in 1:n]) + block0 = Block{T}([InnerProductVec(rand(T, N), f) for _ in 1:n]) block1 = copy(block0) @test typeof(block0) == typeof(block1) @test [block0.vec[i].vec for i in 1:n] == [block1.vec[i].vec for i in 1:n] @@ -57,8 +76,8 @@ M[i,j] = inner(x[i],y[j]) A = [rand(T, N) for _ in 1:n] B = [rand(T, N) for _ in 1:n] M0 = hcat(A...)' * hcat(B...) - BlockA = KrylovKit.BlockVec{T}(wrapvec.(A, Val(mode))) - BlockB = KrylovKit.BlockVec{T}(wrapvec.(B, Val(mode))) + BlockA = Block{T}(wrapvec.(A, Val(mode))) + BlockB = Block{T}(wrapvec.(B, Val(mode))) M = KrylovKit.block_inner(BlockA, BlockB) @test eltype(M) == T @test isapprox(M, M0; atol=relax_tol(T)) @@ -72,8 +91,8 @@ end ip(x, y) = x' * H * y X = [InnerProductVec(rand(T, N), ip) for _ in 1:n] Y = [InnerProductVec(rand(T, N), ip) for _ in 1:n] - BlockX = KrylovKit.BlockVec{T}(X) - BlockY = KrylovKit.BlockVec{T}(Y) + BlockX = Block{T}(X) + BlockY = Block{T}(Y) M = KrylovKit.block_inner(BlockX, BlockY) Xm = hcat([X[i].vec for i in 1:n]...) Ym = hcat([Y[i].vec for i in 1:n]...) @@ -91,9 +110,9 @@ end M = rand(T, n, n) .- one(T) / 2 M = M' + M B = qr(rand(T, n, n) .- one(T) / 2).R - X0 = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) - X1 = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) - AX1 = KrylovKit.BlockVec{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) + X0 = Block{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) + X1 = Block{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) + AX1 = Block{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) AX1copy = copy(AX1) KrylovKit.block_orthogonalize!(AX1, X1, M, X0, B) _bw2m(X) = hcat(unwrapvec.(X)...) @@ -111,9 +130,9 @@ end M = rand(T, n, n) .- one(T) / 2 M = M' + M B = qr(rand(T, n, n) .- one(T) / 2).R - X0 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) - X1 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) - AX1 = KrylovKit.BlockVec{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) + X0 = Block{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) + X1 = Block{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) + AX1 = Block{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) AX1copy = copy(AX1) KrylovKit.block_orthogonalize!(AX1, X1, M, X0, B) @@ -133,8 +152,8 @@ end A = (A + A') / 2 x₀ = [wrapvec(rand(T, N), Val(mode)) for i in 1:n] x₁ = [wrapvec(rand(T, N), Val(mode)) for i in 1:(2 * n)] - b₀ = KrylovKit.BlockVec{T}(x₀) - b₁ = KrylovKit.BlockVec{T}(x₁) + b₀ = Block{T}(x₀) + b₁ = Block{T}(x₁) KrylovKit.block_qr!(b₁, tolerance(T)) orthobasis_x₁ = KrylovKit.OrthonormalBasis(b₁.vec) KrylovKit.block_reorthogonalize!(b₀, orthobasis_x₁) @@ -150,8 +169,8 @@ end x₀ = [InnerProductVec(rand(T, N), ip) for i in 1:n] x₁ = [InnerProductVec(rand(T, N), ip) for i in 1:(2 * n)] - b₀ = KrylovKit.BlockVec{T}(x₀) - b₁ = KrylovKit.BlockVec{T}(x₁) + b₀ = Block{T}(x₀) + b₁ = Block{T}(x₁) KrylovKit.block_qr!(b₁, tolerance(T)) orthobasis_x₁ = KrylovKit.OrthonormalBasis(b₁.vec) KrylovKit.block_reorthogonalize!(b₀, orthobasis_x₁) @@ -170,7 +189,7 @@ end Av[n ÷ 2] = sum(Av[(n ÷ 2 + 1):end] .* rand(T, n - n ÷ 2)) Bv = deepcopy(Av) wAv = wrapvec.(Av, Val(mode)) - R, gi = KrylovKit.block_qr!(KrylovKit.BlockVec{T}(wAv), tolerance(T)) + R, gi = KrylovKit.block_qr!(Block{T}(wAv), tolerance(T)) Av1 = [unwrapvec(wAv[i]) for i in gi] @test hcat(Av1...)' * hcat(Av1...) ≈ I @test length(gi) < n @@ -197,11 +216,11 @@ end # Make sure X is not full rank X[end] = sum(X[1:(end - 1)] .* rand(T, n - 1)) Xcopy = deepcopy(X) - R, gi = KrylovKit.block_qr!(KrylovKit.BlockVec{T}(X), tolerance(T)) + R, gi = KrylovKit.block_qr!(Block{T}(X), tolerance(T)) @test length(gi) < n @test eltype(R) == T - BlockX = KrylovKit.BlockVec{T}(X[gi]) + BlockX = Block{T}(X[gi]) @test isapprox(KrylovKit.block_inner(BlockX, BlockX), I; atol=tolerance(T)) ΔX = norm.([sum(X[gi] .* R[:, i]) for i in 1:size(R, 2)] - Xcopy) @test isapprox(norm(ΔX), T(0); atol=tolerance(T)) diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 10e69000..8e6f9412 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -423,7 +423,7 @@ end sites_num = 3 p = 5 # block size M = 2^(2 * sites_num^2) - x₀ = [rand(M) for _ in 1:p] + x₀ = Block{Float64}([rand(M) for _ in 1:p]) get_value_num = 10 tol = 1e-6 h_mat = toric_code_hamiltonian_matrix(sites_num, sites_num) @@ -440,7 +440,7 @@ end @test count(x -> abs(x + 16.0) < 1e-8, D[1:get_value_num]) == 4 end -# For user interface, input is a vector of starting vectors. +# For user interface, input is a block. @testset "BlockLanczos - eigsolve full $mode" for mode in (:vector, :inplace, :outplace) scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @@ -448,7 +448,7 @@ end A = rand(T, (n, n)) .- one(T) / 2 A = (A + A') / 2 block_size = 2 - x₀ = [wrapvec(rand(T, n), Val(mode)) for _ in 1:block_size] + x₀ = Block{T}([wrapvec(rand(T, n), Val(mode)) for _ in 1:block_size]) n1 = div(n, 2) # eigenvalues to solve eigvalsA = eigvals(A) alg = BlockLanczos(; krylovdim=n, maxiter=1, tol=tolerance(T), @@ -504,7 +504,7 @@ end A = rand(T, (N, N)) .- one(T) / 2 A = (A + A') / 2 block_size = 2 - x₀ = [wrapvec(rand(T, N), Val(mode)) for _ in 1:block_size] + x₀ = Block{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:block_size]) eigvalsA = eigvals(A) alg = BlockLanczos(; krylovdim=N, maxiter=10, tol=tolerance(T), @@ -541,13 +541,13 @@ end block_size = 2 eig_num = 2 Hip(x::Vector, y::Vector) = x' * H * y - x₀ = [InnerProductVec(rand(T, n), Hip) for _ in 1:block_size] + x₀ = Block{T}([InnerProductVec(rand(T, n), Hip) for _ in 1:block_size]) Aip(x::InnerProductVec) = InnerProductVec(H * x.vec, Hip) D, V, info = eigsolve(Aip, x₀, eig_num, :SR, BlockLanczos(; krylovdim=n, maxiter=1, tol=tolerance(T), verbosity=0)) D_true = eigvals(H) - BlockV = KrylovKit.BlockVec{T}(V) + BlockV = KrylovKit.Block{T}(V) @test D[1:eig_num] ≈ D_true[1:eig_num] @test KrylovKit.block_inner(BlockV, BlockV) ≈ I @test findmax([norm(Aip(V[i]) - D[i] * V[i]) for i in 1:eig_num])[1] < tolerance(T) @@ -562,7 +562,7 @@ end A = rand(T, (2N, 2N)) .- one(T) / 2 A = (A + A') / 2 block_size = 1 - x₀_block = [wrapvec(rand(T, 2N), Val(mode)) for _ in 1:block_size] + x₀_block = Block{T}([wrapvec(rand(T, 2N), Val(mode)) for _ in 1:block_size]) x₀_lanczos = x₀_block[1] alg1 = Lanczos(; krylovdim=2n, maxiter=10, tol=tolerance(T), verbosity=1) alg2 = BlockLanczos(; krylovdim=2n, maxiter=10, tol=tolerance(T), @@ -575,7 +575,7 @@ end A = rand(T, (2N, 2N)) .- one(T) / 2 A = (A + A') / 2 block_size = 4 - x₀_block = [wrapvec(rand(T, 2N), Val(mode)) for _ in 1:block_size] + x₀_block = Block{T}([wrapvec(rand(T, 2N), Val(mode)) for _ in 1:block_size]) x₀_lanczos = x₀_block[1] alg1 = Lanczos(; krylovdim=2n, maxiter=10, tol=tolerance(T), verbosity=1) alg2 = BlockLanczos(; krylovdim=2n, maxiter=10, tol=tolerance(T), @@ -596,7 +596,7 @@ end A = rand(T, (N, N)) .- one(T) / 2 A = (A + A') / 2 block_size = 5 - x₀ = [wrapvec(rand(T, N), Val(mode)) for _ in 1:block_size] + x₀ = Block{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:block_size]) values0 = eigvals(A)[1:n] n1 = n ÷ 2 alg = BlockLanczos(; krylovdim=3 * n ÷ 2, maxiter=1, tol=1e-12) @@ -608,3 +608,28 @@ end @test error2 < error1 end end + +@testset "BlockLanczos - eigsolve without alg $mode" for mode in + (:vector, :inplace, :outplace) + scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : + (ComplexF64,) + @testset for T in scalartypes + A = rand(T, (n, n)) .- one(T) / 2 + A = (A + A') / 2 + block_size = 2 + x₀ = Block{T}([wrapvec(rand(T, n), Val(mode)) for _ in 1:block_size]) + if mode === :vector + D1, V1, info1 = eigsolve(wrapop(A, Val(mode)), x₀, 1, :SR) + eigA = eigvals(A) + @test D1[1] ≈ eigA[1] + D2, V2, info2 = eigsolve(wrapop(A, Val(mode)), x₀) + D2[1] = max(abs(eigA[1]), abs(eigA[end])) + @test_throws ErrorException eigsolve(wrapop(A, Val(mode)), x₀, 1, :LI) + B = copy(A) + B[1, 2] += T(1) # error for non-symmetric/hermitian operator + @test_throws ErrorException eigsolve(wrapop(B, Val(mode)), x₀, 1, :SR) + else + @test_throws ErrorException eigsolve(wrapop(A, Val(mode)), x₀, 1, :SR) + end + end +end diff --git a/test/factorize.jl b/test/factorize.jl index 5ff501da..8e96d1ba 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -307,7 +307,7 @@ end A = (A + A') / 2 block_size = 5 x₀m = Matrix(qr(rand(T, N, block_size)).Q) - x₀ = KrylovKit.BlockVec{T}([wrapvec(x₀m[:, i], Val(mode)) for i in 1:block_size]) + x₀ = KrylovKit.Block{T}([wrapvec(x₀m[:, i], Val(mode)) for i in 1:block_size]) eigvalsA = eigvals(A) iter = BlockLanczosIterator(wrapop(A, Val(mode)), x₀, N, tolerance(T)) fact = @constinferred initialize(iter) @@ -329,7 +329,7 @@ end B = rand(T, (n, n)) # test warnings for non-hermitian matrices bs = 2 v₀m = Matrix(qr(rand(T, n, bs)).Q) - v₀ = KrylovKit.BlockVec{T}([wrapvec(v₀m[:, i], Val(mode)) for i in 1:bs]) + v₀ = KrylovKit.Block{T}([wrapvec(v₀m[:, i], Val(mode)) for i in 1:bs]) iter = BlockLanczosIterator(wrapop(B, Val(mode)), v₀, N, tolerance(T)) fact = initialize(iter) @constinferred expand!(iter, fact; verbosity=0) @@ -359,7 +359,7 @@ end A = (A + A') / 2 block_size = 5 x₀m = Matrix(qr(rand(T, N, block_size)).Q) - x₀ = KrylovKit.BlockVec{T}([wrapvec(x₀m[:, i], Val(mode)) for i in 1:block_size]) + x₀ = KrylovKit.Block{T}([wrapvec(x₀m[:, i], Val(mode)) for i in 1:block_size]) iter = @constinferred BlockLanczosIterator(wrapop(A, Val(mode)), x₀, N, tolerance(T)) krylovdim = n diff --git a/test/runtests.jl b/test/runtests.jl index 8d2156d3..42bc26d9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -62,8 +62,8 @@ end @testset "Svdsolve differentiation rules" verbose = true begin include("ad/svdsolve.jl") end -@testset "BlockVec" verbose = true begin - include("BlockVec.jl") +@testset "Block" verbose = true begin + include("Block.jl") end t = time() - t From 7f44f38ce0bbaff1c58ce44f2cba911baef3bb01 Mon Sep 17 00:00:00 2001 From: Yui <2946723935@qq.com> Date: Fri, 23 May 2025 20:51:16 +0800 Subject: [PATCH 75/82] revise for review --- src/factorizations/blocklanczos.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/factorizations/blocklanczos.jl b/src/factorizations/blocklanczos.jl index 82d3af39..6e40cae6 100644 --- a/src/factorizations/blocklanczos.jl +++ b/src/factorizations/blocklanczos.jl @@ -11,12 +11,12 @@ inner product is performed to infer `S`. struct Block{T,S<:Number} vec::Vector{T} function Block{S}(vec::Vector{T}) where {T,S<:Number} - @assert length(vec) > 1 + @assert length(vec) > 0 return new{T,S}(vec) end end function Block(vec::Vector{T}) where {T} - @assert length(vec) > 1 + @assert length(vec) > 0 S = typeof(inner(vec[1], vec[1])) return Block{S}(vec) end # A convenient constructor for users From 69ea50af40482c59609aa87cded0c7f0686bf7cb Mon Sep 17 00:00:00 2001 From: syyui Date: Tue, 27 May 2025 19:01:30 +0800 Subject: [PATCH 76/82] revise for review --- src/algorithms.jl | 2 +- src/factorizations/blocklanczos.jl | 6 +++--- test/Block.jl | 16 ++++------------ test/factorize.jl | 4 ++-- 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/algorithms.jl b/src/algorithms.jl index 3fb7846d..bb878c59 100644 --- a/src/algorithms.jl +++ b/src/algorithms.jl @@ -140,7 +140,7 @@ Arguments `krylovdim`, `maxiter`, `tol`, `orth`, `eager` and `verbosity` are the `qr_tol` is the error tolerance for `block_qr!` - a subroutine used to orthorgonalize the vectors in the same block. The initial size of the block is determined by the number of vectors that a user provides in the starting block; the size of the block can shrink during iterations. -The initial block size determines the maximum degeneracy of the target eigenvalue can that be resolved. +The initial block size determines the maximum multiplicity of the target eigenvalue can that be resolved. The iteration stops when either the norm of the residual is below `tol` or a sufficient number of eigenvectors have converged. [Reference](https://www.netlib.org/utk/people/JackDongarra/etemplates/node250.html) diff --git a/src/factorizations/blocklanczos.jl b/src/factorizations/blocklanczos.jl index 6e40cae6..69f9c169 100644 --- a/src/factorizations/blocklanczos.jl +++ b/src/factorizations/blocklanczos.jl @@ -319,7 +319,7 @@ restricted to the `r` linearly independent components. The vector `goodidx` cont function block_qr!(block::Block{T,S}, tol::Real) where {T,S} n = length(block) rank_shrink = false - idx = ones(Int64, n) + idx = trues(n) R = zeros(S, n, n) @inbounds for j in 1:n for i in 1:(j - 1) @@ -333,9 +333,9 @@ function block_qr!(block::Block{T,S}, tol::Real) where {T,S} else block[j] = zerovector!!(block[j]) rank_shrink = true - idx[j] = 0 + idx[j] = false end end - good_idx = findall(idx .> 0) + good_idx = findall(idx) return R[good_idx, :], good_idx end diff --git a/test/Block.jl b/test/Block.jl index b0fc3fae..19e01ed0 100644 --- a/test/Block.jl +++ b/test/Block.jl @@ -191,11 +191,8 @@ end wAv = wrapvec.(Av, Val(mode)) R, gi = KrylovKit.block_qr!(Block{T}(wAv), tolerance(T)) Av1 = [unwrapvec(wAv[i]) for i in gi] - @test hcat(Av1...)' * hcat(Av1...) ≈ I @test length(gi) < n - @test eltype(R) == eltype(eltype(A)) == T - - norm(hcat(Av1...) * R - hcat(Bv...)) + @test eltype(R) == eltype(A) == T @test isapprox(hcat(Av1...) * R, hcat(Bv...); atol=tolerance(T)) @test isapprox(hcat(Av1...)' * hcat(Av1...), I; atol=tolerance(T)) end @@ -206,12 +203,7 @@ end H = rand(T, N, N) .- one(T) / 2 H = H' * H + I ip(x, y) = x' * H * y - X₁ = InnerProductVec(rand(T, N), ip) - X = [similar(X₁) for _ in 1:n] - X[1] = X₁ - for i in 2:(n - 1) - X[i] = InnerProductVec(rand(T, N), ip) - end + X = [InnerProductVec(rand(T, N), ip) for _ in 1:n] # Make sure X is not full rank X[end] = sum(X[1:(end - 1)] .* rand(T, n - 1)) @@ -222,6 +214,6 @@ end @test eltype(R) == T BlockX = Block{T}(X[gi]) @test isapprox(KrylovKit.block_inner(BlockX, BlockX), I; atol=tolerance(T)) - ΔX = norm.([sum(X[gi] .* R[:, i]) for i in 1:size(R, 2)] - Xcopy) - @test isapprox(norm(ΔX), T(0); atol=tolerance(T)) + @test isapprox(hcat([X[i].vec for i in gi]...) * R, + hcat([Xcopy[i].vec for i in 1:n]...); atol=tolerance(T)) end diff --git a/test/factorize.jl b/test/factorize.jl index 8e96d1ba..776043a3 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -331,7 +331,7 @@ end v₀m = Matrix(qr(rand(T, n, bs)).Q) v₀ = KrylovKit.Block{T}([wrapvec(v₀m[:, i], Val(mode)) for i in 1:bs]) iter = BlockLanczosIterator(wrapop(B, Val(mode)), v₀, N, tolerance(T)) - fact = initialize(iter) + fact = @constinferred initialize(iter) @constinferred expand!(iter, fact; verbosity=0) @test_logs initialize(iter; verbosity=0) @test_logs (:warn,) initialize(iter) @@ -363,7 +363,7 @@ end iter = @constinferred BlockLanczosIterator(wrapop(A, Val(mode)), x₀, N, tolerance(T)) krylovdim = n - fact = initialize(iter) + fact = @constinferred initialize(iter) while fact.norm_R > eps(float(real(T))) && fact.k < krylovdim @constinferred expand!(iter, fact) k = fact.k From 421e188dc9eab411cb479614d7705cdcdb69117a Mon Sep 17 00:00:00 2001 From: syyui Date: Tue, 27 May 2025 22:36:02 +0800 Subject: [PATCH 77/82] revise for review --- docs/src/man/implementation.md | 5 ++-- src/factorizations/blocklanczos.jl | 36 +++----------------------- test/Block.jl | 41 ------------------------------ test/factorize.jl | 1 - 4 files changed, 6 insertions(+), 77 deletions(-) diff --git a/docs/src/man/implementation.md b/docs/src/man/implementation.md index c1edde84..c631bf22 100644 --- a/docs/src/man/implementation.md +++ b/docs/src/man/implementation.md @@ -51,7 +51,7 @@ The block version of the Krylov subspace algorithm is an approach to extending K a starting block. It is mainly used for solving eigenvalue problems with repeated eigenvalues. In our implementation, a block of vectors is stored in a new data structure `Block`, -which implements the [`KrylovKit.block_qr!`](@ref), [`KrylovKit.block_reorthogonalize!`](@ref), and [`KrylovKit.block_orthogonalize!`](@ref) interfaces. +which implements the [`KrylovKit.block_qr!`](@ref) and [`KrylovKit.block_reorthogonalize!`](@ref) interfaces. ```@docs KrylovKit.Block @@ -62,10 +62,9 @@ A block of vectors can be orthonormalized using KrylovKit.block_qr! ``` -Additional procedures applied to the block are as follows: +Additional procedure applied to the block is as follows: ```@docs KrylovKit.block_reorthogonalize! -KrylovKit.block_orthogonalize! ``` diff --git a/src/factorizations/blocklanczos.jl b/src/factorizations/blocklanczos.jl index 69f9c169..9a1c3a96 100644 --- a/src/factorizations/blocklanczos.jl +++ b/src/factorizations/blocklanczos.jl @@ -229,46 +229,18 @@ function blocklanczosrecurrence(operator, V::OrthonormalBasis, B::AbstractMatrix X = Block{S}(V[(k - bs + 1):k]) AX = apply(operator, X) M = block_inner(X, AX) - # Calculate the new residual. Get Rnext + # Calculate the new residual in AX. Xprev = Block{S}(V[(k - bs_prev - bs + 1):(k - bs)]) - Rnext = block_orthogonalize!(AX, X, M, Xprev, B') - block_reorthogonalize!(Rnext, V) - return Rnext, M -end - -""" - block_orthogonalize!(AX::Block{T,S}, X::Block{T,S}, - M::AbstractMatrix, - Xprev::Block{T,S}, Bprev::AbstractMatrix) where {T,S} - -Computes the residual block and stores the result in `AX`. - -This function orthogonalizes `AX` against the two most recent basis blocks, `X` and `Xprev`. -Here, `AX` represents the image of the current block `X` under the action of the linear operator `A`. -The matrix `M` contains the inner products between `X` and `AX`, i.e., the projection of `AX` onto `X`. -Similarly, `Bprev` represents the projection of `AX` onto `Xprev`. - -The residual is computed as: - -``` - AX ← AX - X * M - Xprev * Bprev -``` - -After this operation, `AX` is orthogonal (in the block inner product sense) to both `X` and `Xprev`. - -""" -function block_orthogonalize!(AX::Block{T,S}, X::Block{T,S}, - M::AbstractMatrix, - Xprev::Block{T,S}, Bprev::AbstractMatrix) where {T,S} @inbounds for j in 1:length(X) for i in 1:length(X) AX[j] = add!!(AX[j], X[i], -M[i, j]) end for i in 1:length(Xprev) - AX[j] = add!!(AX[j], Xprev[i], -Bprev[i, j]) + AX[j] = add!!(AX[j], Xprev[i], -conj(B[j, i])) end end - return AX + block_reorthogonalize!(AX, V) + return AX, M end """ diff --git a/test/Block.jl b/test/Block.jl index 19e01ed0..6c86f02d 100644 --- a/test/Block.jl +++ b/test/Block.jl @@ -101,47 +101,6 @@ end @test isapprox(M, M0; atol=relax_tol(T)) end -@testset "block_orthogonalize! $mode" for mode in (:vector, :inplace, :outplace) - scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : - (ComplexF64,) - @testset for T in scalartypes - A = rand(T, N, N) .- one(T) / 2 - A = (A + A') / 2 - M = rand(T, n, n) .- one(T) / 2 - M = M' + M - B = qr(rand(T, n, n) .- one(T) / 2).R - X0 = Block{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) - X1 = Block{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) - AX1 = Block{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:n]) - AX1copy = copy(AX1) - KrylovKit.block_orthogonalize!(AX1, X1, M, X0, B) - _bw2m(X) = hcat(unwrapvec.(X)...) - tol = T == ComplexF32 ? relax_tol(T) : tolerance(T) - @test isapprox(_bw2m(AX1), _bw2m(AX1copy) - _bw2m(X1) * M - _bw2m(X0) * B; - atol=tol) - end -end - -@testset "block_orthogonalize! for abstract inner product" begin - T = ComplexF64 - A = rand(T, N, N) .- one(T) / 2 - A = A * A' + I - ip(x, y) = x' * A * y - M = rand(T, n, n) .- one(T) / 2 - M = M' + M - B = qr(rand(T, n, n) .- one(T) / 2).R - X0 = Block{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) - X1 = Block{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) - AX1 = Block{T}([InnerProductVec(rand(T, N), ip) for _ in 1:n]) - AX1copy = copy(AX1) - KrylovKit.block_orthogonalize!(AX1, X1, M, X0, B) - - @test isapprox(hcat([AX1.vec[i].vec for i in 1:n]...), - hcat([AX1copy.vec[i].vec for i in 1:n]...) - - hcat([X1.vec[i].vec for i in 1:n]...) * M - - hcat([X0.vec[i].vec for i in 1:n]...) * B; atol=tolerance(T)) -end - @testset "block_reorthogonalize! for non-full vectors $mode" for mode in (:vector, :inplace, :outplace) diff --git a/test/factorize.jl b/test/factorize.jl index 776043a3..2ea8db4c 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -375,7 +375,6 @@ end V = hcat([unwrapvec(v) for v in V0]...) r = hcat([unwrapvec(r0[i]) for i in 1:rs]...) e = hcat(zeros(T, rs, k - rs), I) - norm(V' * V - I) @test V' * V ≈ I @test norm(r) ≈ norm_R @test A * V ≈ V * H + r * e From ff5f5cbbfba19c6b740c63a09931d832c1980c5f Mon Sep 17 00:00:00 2001 From: syyui Date: Tue, 27 May 2025 22:41:38 +0800 Subject: [PATCH 78/82] Rename Block.jl to block.jl --- test/{Block.jl => block.jl} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{Block.jl => block.jl} (100%) diff --git a/test/Block.jl b/test/block.jl similarity index 100% rename from test/Block.jl rename to test/block.jl From e7dfd881820d3e953e083b98bac0386ea3174378 Mon Sep 17 00:00:00 2001 From: syyui Date: Wed, 28 May 2025 19:18:08 +0800 Subject: [PATCH 79/82] revise for review --- README.md | 2 +- docs/src/man/eig.md | 2 +- docs/src/man/intro.md | 8 +- docs/src/man/svd.md | 2 +- ext/KrylovKitChainRulesCoreExt/linsolve.jl | 4 +- src/KrylovKit.jl | 1 + src/eigsolve/eigsolve.jl | 12 +-- src/eigsolve/geneigsolve.jl | 10 +- src/eigsolve/svdsolve.jl | 12 +-- src/linsolve/gmres.jl | 6 +- src/linsolve/linsolve.jl | 10 +- src/lssolve/lssolve.jl | 10 +- src/matrixfun/expintegrator.jl | 12 +-- src/matrixfun/exponentiate.jl | 14 +-- test/ad/eigsolve.jl | 37 +++---- test/ad/repeatedeigsolve.jl | 5 +- test/ad/svdsolve.jl | 33 +++--- test/eigsolve.jl | 118 ++++++++++----------- test/expintegrator.jl | 8 +- test/factorize.jl | 35 +++--- test/geneigsolve.jl | 18 ++-- test/linsolve.jl | 94 +++++++++------- test/lssolve.jl | 25 ++--- test/runtests.jl | 5 +- test/schursolve.jl | 8 +- test/svdsolve.jl | 10 +- test/testsetup.jl | 16 ++- 27 files changed, 268 insertions(+), 249 deletions(-) diff --git a/README.md b/README.md index 286628bc..161f05bd 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ KrylovKit v0.9 adds two new sets of functionality: In addition, the following is technically a breaking change: * The verbosity system, the different verbosity levels and the output formatting have been redesigned (both in the primal methods - and the rrules). The default verbosity level is now 1, which means that warnings will be printed by default, but all other output + and the rrules). The default verbosity level is now `WARN_LEVEL`, which means that warnings will be printed by default, but all other output (info messages) are suppressed. Before, the default verbosity was such that all output (including warnings) were suppressed. ## Overview diff --git a/docs/src/man/eig.md b/docs/src/man/eig.md index 323a0a5d..b63fa17a 100644 --- a/docs/src/man/eig.md +++ b/docs/src/man/eig.md @@ -74,7 +74,7 @@ by changing the phase of those eigenvectors, i.e. the cost function should be 'g If this is not the case, the cost function is said to be 'gauge dependent', and this can be detected in the resulting adjoint variables for those eigenvectors. The KrylovKit `rrule` for `eigsolve` will print a warning if it detects from the incoming adjoint variables that the cost function is gauge -dependent. This warning can be suppressed by passing `alg_rrule` an algorithm with `verbosity=-1`. +dependent. This warning can be suppressed by passing `alg_rrule` an algorithm with `verbosity=SILENT_LEVEL-1`. ## Generalized eigenvalue problems diff --git a/docs/src/man/intro.md b/docs/src/man/intro.md index b60bb8c1..0aa6ec17 100644 --- a/docs/src/man/intro.md +++ b/docs/src/man/intro.md @@ -54,11 +54,11 @@ Furthermore, `args` is a set of additional arguments to specify the problem. The arguments `kwargs` contain information about the linear map (`issymmetric`, `ishermitian`, `isposdef`) and about the solution strategy (`tol`, `krylovdim`, `maxiter`). Finally, there is a keyword argument `verbosity` that determines how much information is printed to -`STDOUT`. The default value `verbosity = 0` means that no information will be printed. With -`verbosity = 1`, a single message at the end of the algorithm will be displayed, which is a +`STDOUT`. The default value `verbosity = SILENT_LEVEL` means that no information will be printed. With +`verbosity = WARN_LEVEL`, a single message at the end of the algorithm will be displayed, which is a warning if the algorithm did not succeed in finding the solution, or some information if it -did. For `verbosity = 2`, information about the current state is displayed after every -iteration of the algorithm. Finally, for `verbosity > 2`, information about the individual Krylov expansion steps is displayed. +did. For `verbosity = STARTSTOP_LEVEL`, information about the current state is displayed after every +iteration of the algorithm. Finally, for `verbosity > STARTSTOP_LEVEL`, information about the individual Krylov expansion steps is displayed. The return value contains one or more entries that define the solution, and a final entry `info` of type `ConvergeInfo` that encodes information about the solution, i.e. diff --git a/docs/src/man/svd.md b/docs/src/man/svd.md index f8872352..afd44bf1 100644 --- a/docs/src/man/svd.md +++ b/docs/src/man/svd.md @@ -37,7 +37,7 @@ a common phase factor, i.e. the cost function should be 'gauge invariant'. If th the cost function is said to be 'gauge dependent', and this can be detected in the resulting adjoint variables for those singular vectors. The KrylovKit `rrule` for `svdsolve` will print a warning if it detects from the incoming adjoint variables that the cost function is gauge dependent. This -warning can be suppressed by passing `alg_rrule` an algorithm with `verbosity=-1`. +warning can be suppressed by passing `alg_rrule` an algorithm with `verbosity=SILENT_LEVEL-1`. [^1]: For a linear map, the adjoint or pullback required in the reverse-order chain rule coincides with its (conjugate) transpose, at least with respect to the standard Euclidean inner product. diff --git a/ext/KrylovKitChainRulesCoreExt/linsolve.jl b/ext/KrylovKitChainRulesCoreExt/linsolve.jl index e083bf25..05b555c7 100644 --- a/ext/KrylovKitChainRulesCoreExt/linsolve.jl +++ b/ext/KrylovKitChainRulesCoreExt/linsolve.jl @@ -99,7 +99,7 @@ end # rhs = mul!(rhs, ΔA, x, -a₁, true) # end # (Δx, forward_info) = linsolve(A, rhs, zerovector(rhs), algorithm, a₀, a₁) -# if info.converged > 0 && forward_info.converged == 0 && alg_rrule.verbosity >= 0 +# if info.converged > 0 && forward_info.converged == 0 && alg_rrule.verbosity >= SILENT_LEVEL # @warn "The tangent linear problem did not converge, whereas the primal linear problem did." # end # return (x, info), (Δx, NoTangent()) @@ -129,7 +129,7 @@ end # rhs = add!!(rhs, frule_via_ad(config, (Δf, ZeroTangent()), f, x), -a₀) # end # (Δx, forward_info) = linsolve(f, rhs, zerovector(rhs), algorithm, a₀, a₁) -# if info.converged > 0 && forward_info.converged == 0 && alg_rrule.verbosity >= 0 +# if info.converged > 0 && forward_info.converged == 0 && alg_rrule.verbosity >= SILENT_LEVEL # @warn "The tangent linear problem did not converge, whereas the primal linear problem did." # end # return (x, info), (Δx, NoTangent()) diff --git a/src/KrylovKit.jl b/src/KrylovKit.jl index 13d5ede5..6361dd37 100644 --- a/src/KrylovKit.jl +++ b/src/KrylovKit.jl @@ -156,6 +156,7 @@ end include("apply.jl") # Verbosity levels +const SILENT_LEVEL = 0 const WARN_LEVEL = 1 const STARTSTOP_LEVEL = 2 const EACHITERATION_LEVEL = 3 diff --git a/src/eigsolve/eigsolve.jl b/src/eigsolve/eigsolve.jl index 64150b12..f6985934 100644 --- a/src/eigsolve/eigsolve.jl +++ b/src/eigsolve/eigsolve.jl @@ -109,12 +109,12 @@ The return value is always of the form `vals, vecs, info = eigsolve(...)` with Keyword arguments and their default values are given by: - - `verbosity::Int = 0`: verbosity level, i.e. - - 0 (suppress all messages) - - 1 (only warnings) - - 2 (one message with convergence info at the end) - - 3 (progress info after every iteration) - - 4+ (all of the above and additional information about the Lanczos, BlockLanczos, or Arnoldi iteration) + - `verbosity::Int = SILENT_LEVEL`: verbosity level, i.e. + - SILENT_LEVEL (suppress all messages) + - WARN_LEVEL (only warnings) + - STARTSTOP_LEVEL (one message with convergence info at the end) + - EACHITERATION_LEVEL (progress info after every iteration) + - EACHITERATION_LEVEL+ (all of the above and additional information about the Lanczos, BlockLanczos, or Arnoldi iteration) - `tol::Real`: the requested accuracy (corresponding to the 2-norm of the residual for Schur vectors, not the eigenvectors). If you work in e.g. single precision (`Float32`), you should definitely change the default value. diff --git a/src/eigsolve/geneigsolve.jl b/src/eigsolve/geneigsolve.jl index 52e81f1f..77cf7245 100644 --- a/src/eigsolve/geneigsolve.jl +++ b/src/eigsolve/geneigsolve.jl @@ -88,11 +88,11 @@ The return value is always of the form `vals, vecs, info = geneigsolve(...)` wit Keyword arguments and their default values are given by: - - `verbosity::Int = 0`: verbosity level, i.e. - - 0 (suppress all messages) - - 1 (only warnings) - - 2 (one message with convergence info at the end) - - 3 (progress info after every iteration) + - `verbosity::Int = SILENT_LEVEL`: verbosity level, i.e. + - SILENT_LEVEL (suppress all messages) + - WARN_LEVEL (only warnings) + - STARTSTOP_LEVEL (one message with convergence info at the end) + - EACHITERATION_LEVEL (progress info after every iteration) - `tol::Real`: the requested accuracy, relative to the 2-norm of the corresponding eigenvectors, i.e. convergence is achieved if `norm((A - λB)x) < tol * norm(x)`. Because eigenvectors are now normalised such that `dot(x, B*x) = 1`, `norm(x)` is not diff --git a/src/eigsolve/svdsolve.jl b/src/eigsolve/svdsolve.jl index 2ee60f0b..eb27a07f 100644 --- a/src/eigsolve/svdsolve.jl +++ b/src/eigsolve/svdsolve.jl @@ -81,12 +81,12 @@ The return value is always of the form `vals, lvecs, rvecs, info = svdsolve(...) Keyword arguments and their default values are given by: - - `verbosity::Int = 0`: verbosity level - - 0 (suppress all messages) - - 1 (only warnings) - - 2 (one message with convergence info at the end) - - 3 (progress info after every iteration) - - 4+ (all of the above and additional information about the GKL iteration) + - `verbosity::Int = SILENT_LEVEL`: verbosity level + - SILENT_LEVEL (suppress all messages) + - WARN_LEVEL (only warnings) + - STARTSTOP_LEVEL (one message with convergence info at the end) + - EACHITERATION_LEVEL (progress info after every iteration) + - EACHITERATION_LEVEL+ (all of the above and additional information about the GKL iteration) - `krylovdim`: the maximum dimension of the Krylov subspace that will be constructed. Note that the dimension of the vector space is not known or checked, e.g. `x₀` should not necessarily support the `Base.length` function. If you know the actual problem diff --git a/src/linsolve/gmres.jl b/src/linsolve/gmres.jl index 45619bc1..3c08f66b 100644 --- a/src/linsolve/gmres.jl +++ b/src/linsolve/gmres.jl @@ -37,7 +37,7 @@ function linsolve(operator, b, x₀, alg::GMRES, a₀::Number=0, a₁::Number=1; numops = 1 # operator has been applied once to determine T and r iter = ArnoldiIterator(operator, r, alg.orth) - fact = initialize(iter; verbosity=0) + fact = initialize(iter; verbosity=SILENT_LEVEL) sizehint!(fact, alg.krylovdim) numops += 1 # start applies operator once @@ -56,7 +56,7 @@ function linsolve(operator, b, x₀, alg::GMRES, a₀::Number=0, a₁::Number=1; if alg.verbosity >= EACHITERATION_LEVEL @info "GMRES linsolve in iteration $numiter; step $k: normres = $(normres2string(β))" end - fact = expand!(iter, fact; verbosity=0) + fact = expand!(iter, fact; verbosity=SILENT_LEVEL) numops += 1 # expand! applies the operator once k = length(fact) H = rayleighquotient(fact) @@ -132,6 +132,6 @@ function linsolve(operator, b, x₀, alg::GMRES, a₀::Number=0, a₁::Number=1; # Restart Arnoldi factorization with new r iter = ArnoldiIterator(operator, r, alg.orth) - fact = initialize!(iter, fact; verbosity=0) + fact = initialize!(iter, fact; verbosity=SILENT_LEVEL) end end diff --git a/src/linsolve/linsolve.jl b/src/linsolve/linsolve.jl index 0576ee67..5db10a0e 100644 --- a/src/linsolve/linsolve.jl +++ b/src/linsolve/linsolve.jl @@ -43,11 +43,11 @@ The return value is always of the form `x, info = linsolve(...)` with Keyword arguments are given by: - - `verbosity::Int = 0`: verbosity level, i.e. - - 0 (suppress all messages) - - 1 (only warnings) - - 2 (information at the beginning and end) - - 3 (progress info after every iteration) + - `verbosity::Int = SILENT_LEVEL`: verbosity level, i.e. + - SILENT_LEVEL (suppress all messages) + - WARN_LEVEL (only warnings) + - STARTSTOP_LEVEL (information at the beginning and end) + - EACHITERATION_LEVEL (progress info after every iteration) - `atol::Real`: the requested accuracy, i.e. absolute tolerance, on the norm of the residual. - `rtol::Real`: the requested accuracy on the norm of the residual, relative to the norm diff --git a/src/lssolve/lssolve.jl b/src/lssolve/lssolve.jl index 00148617..ca28994b 100644 --- a/src/lssolve/lssolve.jl +++ b/src/lssolve/lssolve.jl @@ -71,11 +71,11 @@ The return value is always of the form `x, info = lssolve(...)` with Keyword arguments are given by: - - `verbosity::Int = 0`: verbosity level, i.e. - - 0 (suppress all messages) - - 1 (only warnings) - - 2 (information at the beginning and end) - - 3 (progress info after every iteration) + - `verbosity::Int = SILENT_LEVEL`: verbosity level, i.e. + - SILENT_LEVEL (suppress all messages) + - WARN_LEVEL (only warnings) + - STARTSTOP_LEVEL (information at the beginning and end) + - EACHITERATION_LEVEL (progress info after every iteration) - `atol::Real`: the requested accuracy, i.e. absolute tolerance, on the norm of the residual. - `rtol::Real`: the requested accuracy on the norm of the residual, relative to the norm diff --git a/src/matrixfun/expintegrator.jl b/src/matrixfun/expintegrator.jl index 618f9a39..58c4da40 100644 --- a/src/matrixfun/expintegrator.jl +++ b/src/matrixfun/expintegrator.jl @@ -53,12 +53,12 @@ The return value is always of the form `y, info = expintegrator(...)` with Keyword arguments and their default values are given by: - - `verbosity::Int = 0`: verbosity level, i.e. - - 0 (suppress all messages) - - 1 (only warnings) - - 2 (one message with convergence info at the end) - - 3 (progress info after every iteration) - - 4+ (all of the above and additional information about the Lanczos or Arnoldi iteration) + - `verbosity::Int = SILENT_LEVEL`: verbosity level, i.e. + - SILENT_LEVEL (suppress all messages) + - WARN_LEVEL (only warnings) + - STARTSTOP_LEVEL (one message with convergence info at the end) + - EACHITERATION_LEVEL (progress info after every iteration) + - EACHITERATION_LEVEL+ (all of the above and additional information about the Lanczos or Arnoldi iteration) - `krylovdim = 30`: the maximum dimension of the Krylov subspace that will be constructed. Note that the dimension of the vector space is not known or checked, e.g. `x₀` should not necessarily support the `Base.length` function. If you know the actual problem diff --git a/src/matrixfun/exponentiate.jl b/src/matrixfun/exponentiate.jl index 9de0d2b9..9fc9628c 100644 --- a/src/matrixfun/exponentiate.jl +++ b/src/matrixfun/exponentiate.jl @@ -39,19 +39,19 @@ The return value is always of the form `y, info = exponentiate(...)` with !!! warning "Check for convergence" - By default (i.e. if `verbosity = 0`, see below), no warning is printed if the solution + By default (i.e. if `verbosity = SILENT_LEVEL`, see below), no warning is printed if the solution was not found with the requested precision, so be sure to check `info.converged == 1`. ### Keyword arguments: Keyword arguments and their default values are given by: - - `verbosity::Int = 0`: verbosity level, i.e. - - 0 (suppress all messages) - - 1 (only warnings) - - 2 (one message with convergence info at the end) - - 3 (progress info after every iteration) - - 4+ (all of the above and additional information about the Lanczos or Arnoldi iteration) + - `verbosity::Int = SILENT_LEVEL`: verbosity level, i.e. + - SILENT_LEVEL (suppress all messages) + - WARN_LEVEL (only warnings) + - STARTSTOP_LEVEL (one message with convergence info at the end) + - EACHITERATION_LEVEL (progress info after every iteration) + - EACHITERATION_LEVEL+ (all of the above and additional information about the Lanczos or Arnoldi iteration) - `krylovdim = 30`: the maximum dimension of the Krylov subspace that will be constructed. Note that the dimension of the vector space is not known or checked, e.g. `x₀` should not necessarily support the `Base.length` function. If you know the actual problem diff --git a/test/ad/eigsolve.jl b/test/ad/eigsolve.jl index a9a2b812..ca7d7481 100644 --- a/test/ad/eigsolve.jl +++ b/test/ad/eigsolve.jl @@ -1,5 +1,6 @@ module EigsolveAD using KrylovKit, LinearAlgebra +using KrylovKit: SILENT_LEVEL, WARN_LEVEL, STARTSTOP_LEVEL, EACHITERATION_LEVEL using Random, Test, TestExtras using ChainRulesCore, ChainRulesTestUtils, Zygote, FiniteDifferences using ..TestSetup @@ -220,8 +221,8 @@ end condA = cond(A) tol = tolerance(T) # n * condA * (T <: Real ? eps(T) : 4 * eps(real(T))) alg = Arnoldi(; tol=tol, krylovdim=n) - alg_rrule1 = Arnoldi(; tol=tol, krylovdim=2n, verbosity=0) - alg_rrule2 = GMRES(; tol=tol, krylovdim=n + 1, verbosity=0) + alg_rrule1 = Arnoldi(; tol=tol, krylovdim=2n, verbosity=SILENT_LEVEL) + alg_rrule2 = GMRES(; tol=tol, krylovdim=n + 1, verbosity=SILENT_LEVEL) config = Zygote.ZygoteRuleConfig() @testset for which in whichlist for alg_rrule in (alg_rrule1, alg_rrule2) @@ -281,14 +282,14 @@ end if T <: Complex @testset "test warnings and info" begin - alg = Arnoldi(; tol=tol, krylovdim=n, verbosity=0) - alg_rrule = Arnoldi(; tol=tol, krylovdim=n, verbosity=0) + alg = Arnoldi(; tol=tol, krylovdim=n, verbosity=SILENT_LEVEL) + alg_rrule = Arnoldi(; tol=tol, krylovdim=n, verbosity=SILENT_LEVEL) (vals, vecs, info), pb = ChainRulesCore.rrule(config, eigsolve, A, x, howmany, :LR, alg; alg_rrule=alg_rrule) @test_logs pb((ZeroTangent(), im .* vecs[1:2] .+ vecs[2:-1:1], NoTangent())) - alg = Arnoldi(; tol=tol, krylovdim=n, verbosity=0) - alg_rrule = Arnoldi(; tol=tol, krylovdim=n, verbosity=1) + alg = Arnoldi(; tol=tol, krylovdim=n, verbosity=SILENT_LEVEL) + alg_rrule = Arnoldi(; tol=tol, krylovdim=n, verbosity=WARN_LEVEL) (vals, vecs, info), pb = ChainRulesCore.rrule(config, eigsolve, A, x, howmany, :LR, alg; alg_rrule=alg_rrule) @test_logs (:warn,) pb((ZeroTangent(), im .* vecs[1:2] .+ vecs[2:-1:1], @@ -296,8 +297,8 @@ end pbs = @test_logs pb((ZeroTangent(), vecs[1:2], NoTangent())) @test norm(unthunk(pbs[1]), Inf) < condA * sqrt(eps(real(T))) - alg = Arnoldi(; tol=tol, krylovdim=n, verbosity=1) - alg_rrule = Arnoldi(; tol=tol, krylovdim=n, verbosity=2) + alg = Arnoldi(; tol=tol, krylovdim=n, verbosity=WARN_LEVEL) + alg_rrule = Arnoldi(; tol=tol, krylovdim=n, verbosity=STARTSTOP_LEVEL) (vals, vecs, info), pb = ChainRulesCore.rrule(config, eigsolve, A, x, howmany, :LR, alg; alg_rrule=alg_rrule) @test_logs (:warn,) (:info,) pb((ZeroTangent(), im .* vecs[1:2] .+ vecs[2:-1:1], @@ -305,14 +306,14 @@ end pbs = @test_logs (:info,) pb((ZeroTangent(), vecs[1:2], NoTangent())) @test norm(unthunk(pbs[1]), Inf) < condA * sqrt(eps(real(T))) - alg = Arnoldi(; tol=tol, krylovdim=n, verbosity=0) - alg_rrule = GMRES(; tol=tol, krylovdim=n, verbosity=0) + alg = Arnoldi(; tol=tol, krylovdim=n, verbosity=SILENT_LEVEL) + alg_rrule = GMRES(; tol=tol, krylovdim=n, verbosity=SILENT_LEVEL) (vals, vecs, info), pb = ChainRulesCore.rrule(config, eigsolve, A, x, howmany, :LR, alg; alg_rrule=alg_rrule) @test_logs pb((ZeroTangent(), im .* vecs[1:2] .+ vecs[2:-1:1], NoTangent())) - alg = Arnoldi(; tol=tol, krylovdim=n, verbosity=0) - alg_rrule = GMRES(; tol=tol, krylovdim=n, verbosity=1) + alg = Arnoldi(; tol=tol, krylovdim=n, verbosity=SILENT_LEVEL) + alg_rrule = GMRES(; tol=tol, krylovdim=n, verbosity=WARN_LEVEL) (vals, vecs, info), pb = ChainRulesCore.rrule(config, eigsolve, A, x, howmany, :LR, alg; alg_rrule=alg_rrule) @test_logs (:warn,) (:warn,) pb((ZeroTangent(), @@ -322,8 +323,8 @@ end pbs = @test_logs pb((ZeroTangent(), vecs[1:2], NoTangent())) @test norm(unthunk(pbs[1]), Inf) < condA * sqrt(eps(real(T))) - alg = Arnoldi(; tol=tol, krylovdim=n, verbosity=1) - alg_rrule = GMRES(; tol=tol, krylovdim=n, verbosity=2) + alg = Arnoldi(; tol=tol, krylovdim=n, verbosity=WARN_LEVEL) + alg_rrule = GMRES(; tol=tol, krylovdim=n, verbosity=STARTSTOP_LEVEL) (vals, vecs, info), pb = ChainRulesCore.rrule(config, eigsolve, A, x, howmany, :LR, alg; alg_rrule=alg_rrule) @test_logs (:warn,) (:info,) (:info,) (:warn,) (:info,) (:info,) pb((ZeroTangent(), @@ -353,8 +354,8 @@ end howmany = 2 tol = tolerance(T) # 2 * N^2 * eps(real(T)) alg = Arnoldi(; tol=tol, krylovdim=2n) - alg_rrule1 = Arnoldi(; tol=tol, krylovdim=2n, verbosity=0) - alg_rrule2 = GMRES(; tol=tol, krylovdim=2n, verbosity=0) + alg_rrule1 = Arnoldi(; tol=tol, krylovdim=2n, verbosity=SILENT_LEVEL) + alg_rrule2 = GMRES(; tol=tol, krylovdim=2n, verbosity=SILENT_LEVEL) @testset for alg_rrule in (alg_rrule1, alg_rrule2) #! format: off fun_example, fun_example_fd, Avec, xvec, cvec, dvec, vals, vecs, howmany = @@ -385,8 +386,8 @@ end howmany = 2 tol = tolerance(T) alg = Lanczos(; tol=tol, krylovdim=2n) - alg_rrule1 = Arnoldi(; tol=tol, krylovdim=2n, verbosity=0) - alg_rrule2 = GMRES(; tol=tol, krylovdim=2n, verbosity=0) + alg_rrule1 = Arnoldi(; tol=tol, krylovdim=2n, verbosity=SILENT_LEVEL) + alg_rrule2 = GMRES(; tol=tol, krylovdim=2n, verbosity=SILENT_LEVEL) @testset for alg_rrule in (alg_rrule1, alg_rrule2) #! format: off fun_example, fun_example_fd, Avec, xvec, cvec, vals, vecs, howmany = diff --git a/test/ad/repeatedeigsolve.jl b/test/ad/repeatedeigsolve.jl index 78f0920b..86eef3ff 100644 --- a/test/ad/repeatedeigsolve.jl +++ b/test/ad/repeatedeigsolve.jl @@ -1,6 +1,7 @@ module RepeatedEigsolveAD using KrylovKit, LinearAlgebra +using KrylovKit: SILENT_LEVEL, WARN_LEVEL, STARTSTOP_LEVEL, EACHITERATION_LEVEL using Random, Test, TestExtras using ChainRulesCore, ChainRulesTestUtils, Zygote, FiniteDifferences using ..TestSetup @@ -95,8 +96,8 @@ end tol = tolerance(T) #2 * N^2 * eps(real(T)) alg = Arnoldi(; tol=tol, krylovdim=2n) - alg_rrule1 = Arnoldi(; tol=tol, krylovdim=2n, verbosity=0) - alg_rrule2 = GMRES(; tol=tol, krylovdim=2n, verbosity=0) + alg_rrule1 = Arnoldi(; tol=tol, krylovdim=2n, verbosity=SILENT_LEVEL) + alg_rrule2 = GMRES(; tol=tol, krylovdim=2n, verbosity=SILENT_LEVEL) #! format: off mat_example1, mat_example_fun1, mat_example_fd, Avec, Bvec, Cvec, xvec, vals, vecs = build_mat_example(A, B, C, x, alg, alg_rrule1) diff --git a/test/ad/svdsolve.jl b/test/ad/svdsolve.jl index bf6ec38f..c2736ed9 100644 --- a/test/ad/svdsolve.jl +++ b/test/ad/svdsolve.jl @@ -1,5 +1,6 @@ module SvdsolveAD using KrylovKit, LinearAlgebra +using KrylovKit: SILENT_LEVEL, WARN_LEVEL, STARTSTOP_LEVEL, EACHITERATION_LEVEL using Random, Test, TestExtras using ChainRulesCore, ChainRulesTestUtils, Zygote, FiniteDifferences Random.seed!(123456789) @@ -154,8 +155,8 @@ end howmany = 3 tol = 3 * n * condA * (T <: Real ? eps(T) : 4 * eps(real(T))) alg = GKL(; krylovdim=2n, tol=tol) - alg_rrule1 = Arnoldi(; tol=tol, krylovdim=4n, verbosity=0) - alg_rrule2 = GMRES(; tol=tol, krylovdim=3n, verbosity=0) + alg_rrule1 = Arnoldi(; tol=tol, krylovdim=4n, verbosity=SILENT_LEVEL) + alg_rrule2 = GMRES(; tol=tol, krylovdim=3n, verbosity=SILENT_LEVEL) config = Zygote.ZygoteRuleConfig() for alg_rrule in (alg_rrule1, alg_rrule2) # unfortunately, rrule does not seem type stable for function arguments, because the @@ -219,16 +220,16 @@ end end if T <: Complex @testset "test warnings and info" begin - alg = GKL(; krylovdim=2n, tol=tol, verbosity=0) - alg_rrule = Arnoldi(; tol=tol, krylovdim=4n, verbosity=0) + alg = GKL(; krylovdim=2n, tol=tol, verbosity=SILENT_LEVEL) + alg_rrule = Arnoldi(; tol=tol, krylovdim=4n, verbosity=SILENT_LEVEL) (vals, lvecs, rvecs, info), pb = ChainRulesCore.rrule(config, svdsolve, A, x, howmany, :LR, alg; alg_rrule=alg_rrule) @test_logs pb((ZeroTangent(), im .* lvecs[1:2] .+ lvecs[2:-1:1], ZeroTangent(), NoTangent())) - alg = GKL(; krylovdim=2n, tol=tol, verbosity=1) - alg_rrule = Arnoldi(; tol=tol, krylovdim=4n, verbosity=1) + alg = GKL(; krylovdim=2n, tol=tol, verbosity=WARN_LEVEL) + alg_rrule = Arnoldi(; tol=tol, krylovdim=4n, verbosity=WARN_LEVEL) (vals, lvecs, rvecs, info), pb = ChainRulesCore.rrule(config, svdsolve, A, x, howmany, :LR, alg; alg_rrule=alg_rrule) @@ -251,8 +252,8 @@ end (1 - im) .* rvecs[1:2] + rvecs[2:-1:1], NoTangent())) - alg = GKL(; krylovdim=2n, tol=tol, verbosity=1) - alg_rrule = Arnoldi(; tol=tol, krylovdim=4n, verbosity=2) + alg = GKL(; krylovdim=2n, tol=tol, verbosity=WARN_LEVEL) + alg_rrule = Arnoldi(; tol=tol, krylovdim=4n, verbosity=STARTSTOP_LEVEL) (vals, lvecs, rvecs, info), pb = ChainRulesCore.rrule(config, svdsolve, A, x, howmany, :LR, alg; alg_rrule=alg_rrule) @@ -275,16 +276,16 @@ end (1 - im) .* rvecs[1:2] + rvecs[2:-1:1], NoTangent())) - alg = GKL(; krylovdim=2n, tol=tol, verbosity=0) - alg_rrule = GMRES(; tol=tol, krylovdim=3n, verbosity=0) + alg = GKL(; krylovdim=2n, tol=tol, verbosity=SILENT_LEVEL) + alg_rrule = GMRES(; tol=tol, krylovdim=3n, verbosity=SILENT_LEVEL) (vals, lvecs, rvecs, info), pb = ChainRulesCore.rrule(config, svdsolve, A, x, howmany, :LR, alg; alg_rrule=alg_rrule) @test_logs pb((ZeroTangent(), im .* lvecs[1:2] .+ lvecs[2:-1:1], ZeroTangent(), NoTangent())) - alg = GKL(; krylovdim=2n, tol=tol, verbosity=1) - alg_rrule = GMRES(; tol=tol, krylovdim=3n, verbosity=1) + alg = GKL(; krylovdim=2n, tol=tol, verbosity=WARN_LEVEL) + alg_rrule = GMRES(; tol=tol, krylovdim=3n, verbosity=WARN_LEVEL) (vals, lvecs, rvecs, info), pb = ChainRulesCore.rrule(config, svdsolve, A, x, howmany, :LR, alg; alg_rrule=alg_rrule) @@ -310,8 +311,8 @@ end (1 - im) .* rvecs[1:2] + rvecs[2:-1:1], NoTangent())) - alg = GKL(; krylovdim=2n, tol=tol, verbosity=1) - alg_rrule = GMRES(; tol=tol, krylovdim=3n, verbosity=2) + alg = GKL(; krylovdim=2n, tol=tol, verbosity=WARN_LEVEL) + alg_rrule = GMRES(; tol=tol, krylovdim=3n, verbosity=STARTSTOP_LEVEL) (vals, lvecs, rvecs, info), pb = ChainRulesCore.rrule(config, svdsolve, A, x, howmany, :LR, alg; alg_rrule=alg_rrule) @@ -361,8 +362,8 @@ end howmany = 2 tol = 2 * N^2 * eps(real(T)) alg = GKL(; tol=tol, krylovdim=2n) - alg_rrule1 = Arnoldi(; tol=tol, krylovdim=2n, verbosity=-1) - alg_rrule2 = GMRES(; tol=tol, krylovdim=2n, verbosity=-1) + alg_rrule1 = Arnoldi(; tol=tol, krylovdim=2n, verbosity=SILENT_LEVEL-1) + alg_rrule2 = GMRES(; tol=tol, krylovdim=2n, verbosity=SILENT_LEVEL-1) for alg_rrule in (alg_rrule1, alg_rrule2) #! format: off fun_example_ad, fun_example_fd, Avec, xvec, cvec, dvec, vals, lvecs, rvecs = diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 8e6f9412..47213feb 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -9,27 +9,27 @@ v = rand(T, (n,)) n1 = div(n, 2) alg = Lanczos(; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), - verbosity=2) + verbosity=STARTSTOP_LEVEL) D1, V1, info = @test_logs (:info,) eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n1, :SR, alg) alg = Lanczos(; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), - verbosity=1) + verbosity=WARN_LEVEL) @test_logs eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n1, :SR, alg) alg = Lanczos(; orth=orth, krylovdim=n1 + 1, maxiter=1, tol=tolerance(T), - verbosity=1) + verbosity=WARN_LEVEL) @test_logs (:warn,) eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n1, :SR, alg) alg = Lanczos(; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), - verbosity=2) + verbosity=STARTSTOP_LEVEL) @test_logs (:info,) eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n1, :SR, alg) alg = Lanczos(; orth=orth, krylovdim=n1, maxiter=3, tol=tolerance(T), - verbosity=3) + verbosity=EACHITERATION_LEVEL) @test_logs((:info,), (:info,), (:info,), (:warn,), eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), 1, :SR, alg)) alg = Lanczos(; orth=orth, krylovdim=4, maxiter=1, tol=tolerance(T), - verbosity=4) + verbosity=EACHITERATION_LEVEL+1) # since it is impossible to know exactly the size of the Krylov subspace after shrinking, # we only know the output for a sigle iteration @test_logs((:info,), (:info,), (:info,), (:info,), (:info,), (:warn,), @@ -54,7 +54,7 @@ @test A * U2 ≈ U2 * Diagonal(D2) alg = Lanczos(; orth=orth, krylovdim=2n, maxiter=1, tol=tolerance(T), - verbosity=1) + verbosity=WARN_LEVEL) @test_logs (:warn,) (:warn,) eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n + 1, :LM, alg) end @@ -71,7 +71,7 @@ end A = (A + A') / 2 v = rand(T, (N,)) alg = Lanczos(; krylovdim=2 * n, maxiter=10, - tol=tolerance(T), eager=true, verbosity=0) + tol=tolerance(T), eager=true, verbosity=SILENT_LEVEL) D1, V1, info1 = @constinferred eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n, :SR, alg) D2, V2, info2 = eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n, :LR, @@ -111,25 +111,25 @@ end wrapvec(v, Val(mode)), n1, :SR, alg) alg = Arnoldi(; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), - verbosity=0) + verbosity=SILENT_LEVEL) @test_logs eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n1, :SR, alg) alg = Arnoldi(; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), - verbosity=1) + verbosity=WARN_LEVEL) @test_logs eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n1, :SR, alg) alg = Arnoldi(; orth=orth, krylovdim=n1 + 2, maxiter=1, tol=tolerance(T), - verbosity=1) + verbosity=WARN_LEVEL) @test_logs (:warn,) eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n1, :SR, alg) alg = Arnoldi(; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), - verbosity=2) + verbosity=STARTSTOP_LEVEL) @test_logs (:info,) eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n1, :SR, alg) alg = Arnoldi(; orth=orth, krylovdim=n1, maxiter=3, tol=tolerance(T), - verbosity=3) + verbosity=EACHITERATION_LEVEL) @test_logs((:info,), (:info,), (:info,), (:warn,), eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), 1, :SR, alg)) alg = Arnoldi(; orth=orth, krylovdim=4, maxiter=1, tol=tolerance(T), - verbosity=4) + verbosity=EACHITERATION_LEVEL+1) # since it is impossible to know exactly the size of the Krylov subspace after shrinking, # we only know the output for a sigle iteration @test_logs((:info,), (:info,), (:info,), (:info,), (:info,), (:warn,), @@ -171,7 +171,7 @@ end end alg = Arnoldi(; orth=orth, krylovdim=2n, maxiter=1, tol=tolerance(T), - verbosity=1) + verbosity=WARN_LEVEL) @test_logs (:warn,) (:warn,) eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n + 1, :LM, alg) end @@ -187,7 +187,7 @@ end A = rand(T, (N, N)) .- one(T) / 2 v = rand(T, (N,)) alg = Arnoldi(; krylovdim=3 * n, maxiter=20, - tol=tolerance(T), eager=true, verbosity=0) + tol=tolerance(T), eager=true, verbosity=SILENT_LEVEL) D1, V1, info1 = @constinferred eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n, :SR, alg) D2, V2, info2 = eigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n, :LR, @@ -254,7 +254,7 @@ end A = V * Diagonal(D) / V v = rand(T, (N,)) alg = Arnoldi(; krylovdim=3 * n, maxiter=20, - tol=tolerance(T), eager=true, verbosity=0) + tol=tolerance(T), eager=true, verbosity=SILENT_LEVEL) D1, V1, info1 = @constinferred realeigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n, :SR, alg) D2, V2, info2 = realeigsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n, @@ -302,7 +302,7 @@ end f = buildrealmap(A, B) v = rand(complex(T), (N,)) alg = Arnoldi(; krylovdim=3 * n, maxiter=20, - tol=tolerance(T), eager=true, verbosity=0) + tol=tolerance(T), eager=true, verbosity=SILENT_LEVEL) D1, V1, info1 = @constinferred realeigsolve(f, v, n, :SR, alg) D2, V2, info2 = realeigsolve(f, v, n, :LR, alg) D3, V3, info3 = realeigsolve(f, v, n, :LM, alg) @@ -342,12 +342,12 @@ end A[2, 1] = 1e-9 A[1, 2] = -1e-9 v = ones(Float64, size(A, 1)) - @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-8, verbosity=0)) - @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-8, verbosity=1)) - @test_logs (:info,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-8, verbosity=2)) - @test_logs (:warn,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-10, verbosity=1)) + @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-8, verbosity=SILENT_LEVEL)) + @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-8, verbosity=WARN_LEVEL)) + @test_logs (:info,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-8, verbosity=STARTSTOP_LEVEL)) + @test_logs (:warn,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-10, verbosity=WARN_LEVEL)) @test_logs (:warn,) (:info,) realeigsolve(A, v, 1, :LM, - Arnoldi(; tol=1e-10, verbosity=2)) + Arnoldi(; tol=1e-10, verbosity=STARTSTOP_LEVEL)) # this should not trigger a warning A[1, 2] = A[2, 1] = 0 @@ -355,23 +355,25 @@ end A[2, 2] = A[3, 3] = 0.99 A[3, 2] = 1e-6 A[2, 3] = -1e-6 - @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=0)) - @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=1)) - @test_logs (:info,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=2)) + @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=SILENT_LEVEL)) + @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=WARN_LEVEL)) + @test_logs (:info,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=STARTSTOP_LEVEL)) end @testset "BlockLanczos - eigsolve for large sparse matrix and map input" begin + # There are 2 * m * n qubits and I choose to enumerate all the horizontal ones from 1:m*n + # and all the vertical ones as (m*n+1):(2*m*n) function toric_code_strings(m::Int, n::Int) li = LinearIndices((m, n)) bottom(i, j) = li[mod1(i, m), mod1(j, n)] + m * n right(i, j) = li[mod1(i, m), mod1(j, n)] - xstrings = Vector{Int}[] - zstrings = Vector{Int}[] + xstrings = NTuple{4,Int}[] + zstrings = NTuple{4,Int}[] for i in 1:m, j in 1:n - # face center - push!(xstrings, [bottom(i, j - 1), right(i, j), bottom(i, j), right(i - 1, j)]) - # cross - push!(zstrings, [right(i, j), bottom(i, j), right(i, j + 1), bottom(i + 1, j)]) + # plaquette + push!(xstrings, (bottom(i, j + 1), right(i, j), bottom(i, j), right(i - 1, j))) + # vertex + push!(zstrings, (right(i, j), bottom(i, j), right(i, j - 1), bottom(i + 1, j))) end return xstrings, zstrings end @@ -408,13 +410,11 @@ end # add the X-type operator terms for xs in xstrings[1:(end - 1)] - ops = [i => 'X' for i in xs] - H += pauli_kron(N, ops...) + H += pauli_kron(N, (i => 'X' for i in xs)...) end for zs in zstrings[1:(end - 1)] - ops = [i => 'Z' for i in zs] - H += pauli_kron(N, ops...) + H += pauli_kron(N, (i => 'Z' for i in zs)...) end return H @@ -445,40 +445,35 @@ end scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @testset for T in scalartypes - A = rand(T, (n, n)) .- one(T) / 2 - A = (A + A') / 2 block_size = 2 + A = mat_with_eigrepition(T, n, block_size) x₀ = Block{T}([wrapvec(rand(T, n), Val(mode)) for _ in 1:block_size]) n1 = div(n, 2) # eigenvalues to solve eigvalsA = eigvals(A) alg = BlockLanczos(; krylovdim=n, maxiter=1, tol=tolerance(T), - verbosity=2) + verbosity=STARTSTOP_LEVEL) D1, V1, info = @test_logs (:info,) eigsolve(wrapop(A, Val(mode)), x₀, n1, :SR, alg) alg = BlockLanczos(; krylovdim=n, maxiter=1, tol=tolerance(T), - verbosity=1) + verbosity=WARN_LEVEL) @test_logs eigsolve(wrapop(A, Val(mode)), x₀, n1, :SR, alg) alg = BlockLanczos(; krylovdim=n1 + 1, maxiter=1, tol=tolerance(T), - verbosity=1) + verbosity=WARN_LEVEL) @test_logs (:warn,) eigsolve(wrapop(A, Val(mode)), x₀, n1, :SR, alg) alg = BlockLanczos(; krylovdim=n, maxiter=1, tol=tolerance(T), - verbosity=2) + verbosity=STARTSTOP_LEVEL) @test_logs (:info,) eigsolve(wrapop(A, Val(mode)), x₀, n1, :SR, alg) alg = BlockLanczos(; krylovdim=3, maxiter=3, tol=tolerance(T), - verbosity=3) + verbosity=EACHITERATION_LEVEL) @test_logs((:info,), (:info,), (:info,), (:warn,), eigsolve(wrapop(A, Val(mode)), x₀, 1, :SR, alg)) alg = BlockLanczos(; krylovdim=4, maxiter=1, tol=tolerance(T), - verbosity=4) + verbosity=EACHITERATION_LEVEL+1) @test_logs((:info,), (:info,), (:info,), (:warn,), eigsolve(wrapop(A, Val(mode)), x₀, 1, :SR, alg)) - # To use blockmode, users have to explicitly set blockmode = true, we don't allow them to use eigselector. - # Because of the _residual! function, I can't make sure the stability of types temporarily. - # So I ignore the test of @constinferred n2 = n - n1 alg = BlockLanczos(; krylovdim=2 * n, maxiter=4, tol=tolerance(T)) D2, V2, info = eigsolve(wrapop(A, Val(mode)), x₀, n2, :LR, alg) - D2[1:n2] @test vcat(D1[1:n1], reverse(D2[1:n2])) ≊ eigvalsA U1 = hcat(unwrapvec.(V1)...) @@ -491,7 +486,7 @@ end @test (x -> KrylovKit.apply(A, x)).(unwrapvec.(V2)) ≈ D2 .* unwrapvec.(V2) alg = BlockLanczos(; krylovdim=2n, maxiter=1, tol=tolerance(T), - verbosity=1) + verbosity=WARN_LEVEL) @test_logs (:warn,) (:warn,) eigsolve(wrapop(A, Val(mode)), x₀, n + 1, :LM, alg) end end @@ -501,14 +496,13 @@ end scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @testset for T in scalartypes - A = rand(T, (N, N)) .- one(T) / 2 - A = (A + A') / 2 block_size = 2 + A = mat_with_eigrepition(T, N, block_size) x₀ = Block{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:block_size]) eigvalsA = eigvals(A) alg = BlockLanczos(; krylovdim=N, maxiter=10, tol=tolerance(T), - eager=true, verbosity=0) + eager=true, verbosity=SILENT_LEVEL) D1, V1, info1 = eigsolve(wrapop(A, Val(mode)), x₀, n, :SR, alg) D2, V2, info2 = eigsolve(wrapop(A, Val(mode)), x₀, n, :LR, alg) @@ -536,16 +530,16 @@ end @testset "BlockLanczos - eigsolve for abstract type" begin T = ComplexF64 - H = rand(T, (n, n)) .- one(T) / 2 - H = H' * H + I block_size = 2 + H = mat_with_eigrepition(T, n, block_size) + H = H' * H + I eig_num = 2 Hip(x::Vector, y::Vector) = x' * H * y x₀ = Block{T}([InnerProductVec(rand(T, n), Hip) for _ in 1:block_size]) Aip(x::InnerProductVec) = InnerProductVec(H * x.vec, Hip) D, V, info = eigsolve(Aip, x₀, eig_num, :SR, BlockLanczos(; krylovdim=n, maxiter=1, tol=tolerance(T), - verbosity=0)) + verbosity=SILENT_LEVEL)) D_true = eigvals(H) BlockV = KrylovKit.Block{T}(V) @test D[1:eig_num] ≈ D_true[1:eig_num] @@ -564,9 +558,9 @@ end block_size = 1 x₀_block = Block{T}([wrapvec(rand(T, 2N), Val(mode)) for _ in 1:block_size]) x₀_lanczos = x₀_block[1] - alg1 = Lanczos(; krylovdim=2n, maxiter=10, tol=tolerance(T), verbosity=1) + alg1 = Lanczos(; krylovdim=2n, maxiter=10, tol=tolerance(T), verbosity=WARN_LEVEL) alg2 = BlockLanczos(; krylovdim=2n, maxiter=10, tol=tolerance(T), - verbosity=1) + verbosity=WARN_LEVEL) evals1, _, info1 = eigsolve(wrapop(A, Val(mode)), x₀_lanczos, n, :SR, alg1) evals2, _, info2 = eigsolve(wrapop(A, Val(mode)), x₀_block, n, :SR, alg2) @test info1.converged == info2.converged @@ -577,9 +571,9 @@ end block_size = 4 x₀_block = Block{T}([wrapvec(rand(T, 2N), Val(mode)) for _ in 1:block_size]) x₀_lanczos = x₀_block[1] - alg1 = Lanczos(; krylovdim=2n, maxiter=10, tol=tolerance(T), verbosity=1) + alg1 = Lanczos(; krylovdim=2n, maxiter=10, tol=tolerance(T), verbosity=WARN_LEVEL) alg2 = BlockLanczos(; krylovdim=2n, maxiter=10, tol=tolerance(T), - verbosity=1) + verbosity=WARN_LEVEL) evals1, _, info1 = eigsolve(wrapop(A, Val(mode)), x₀_lanczos, n, :SR, alg1) evals2, _, info2 = eigsolve(wrapop(A, Val(mode)), x₀_block, n, :SR, alg2) @test info1.converged >= info2.converged + 1 @@ -593,9 +587,8 @@ end scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @testset for T in scalartypes - A = rand(T, (N, N)) .- one(T) / 2 - A = (A + A') / 2 block_size = 5 + A = mat_with_eigrepition(T, N, block_size) x₀ = Block{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:block_size]) values0 = eigvals(A)[1:n] n1 = n ÷ 2 @@ -614,9 +607,8 @@ end scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @testset for T in scalartypes - A = rand(T, (n, n)) .- one(T) / 2 - A = (A + A') / 2 block_size = 2 + A = mat_with_eigrepition(T, n, block_size) x₀ = Block{T}([wrapvec(rand(T, n), Val(mode)) for _ in 1:block_size]) if mode === :vector D1, V1, info1 = eigsolve(wrapop(A, Val(mode)), x₀, 1, :SR) diff --git a/test/expintegrator.jl b/test/expintegrator.jl index 2aa92c4e..d6511fba 100644 --- a/test/expintegrator.jl +++ b/test/expintegrator.jl @@ -23,7 +23,7 @@ end V = one(A) W = zero(A) alg = Lanczos(; orth=orth, krylovdim=n, maxiter=2, tol=tolerance(T), - verbosity=2) + verbosity=STARTSTOP_LEVEL) for k in 1:n w, = @test_logs (:info,) exponentiate(wrapop(A, Val(mode)), 1, wrapvec(view(V, :, k), @@ -34,7 +34,7 @@ end pmax = 5 alg = Lanczos(; orth=orth, krylovdim=n, maxiter=2, tol=tolerance(T), - verbosity=0) + verbosity=SILENT_LEVEL) for t in (rand(real(T)), -rand(real(T)), im * randn(real(T)), randn(real(T)) + im * randn(real(T))) for p in 1:pmax @@ -63,7 +63,7 @@ end V = one(A) W = zero(A) alg = Arnoldi(; orth=orth, krylovdim=n, maxiter=2, tol=tolerance(T), - verbosity=2) + verbosity=STARTSTOP_LEVEL) for k in 1:n w, = @test_logs (:info,) exponentiate(wrapop(A, Val(mode)), 1, wrapvec(view(V, :, k), @@ -74,7 +74,7 @@ end pmax = 5 alg = Arnoldi(; orth=orth, krylovdim=n, maxiter=2, tol=tolerance(T), - verbosity=0) + verbosity=SILENT_LEVEL) for t in (rand(real(T)), -rand(real(T)), im * randn(real(T)), randn(real(T)) + im * randn(real(T))) for p in 1:pmax diff --git a/test/factorize.jl b/test/factorize.jl index 2ea8db4c..b040104b 100644 --- a/test/factorize.jl +++ b/test/factorize.jl @@ -3,8 +3,6 @@ scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) orths = mode === :vector ? (cgs2, mgs2, cgsr, mgsr) : (cgs2,) - using KrylovKit: EACHITERATION_LEVEL - @testset for T in scalartypes @testset for orth in orths # tests fail miserably for cgs and mgs A = rand(T, (n, n)) @@ -46,18 +44,18 @@ A = rand(T, (n, n)) # test warnings for non-hermitian matrices v = rand(T, (n,)) iter = LanczosIterator(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), orth) - fact = @constinferred initialize(iter; verbosity=0) - @constinferred expand!(iter, fact; verbosity=0) - @test_logs initialize(iter; verbosity=0) + fact = @constinferred initialize(iter; verbosity=SILENT_LEVEL) + @constinferred expand!(iter, fact; verbosity=SILENT_LEVEL) + @test_logs initialize(iter; verbosity=SILENT_LEVEL) @test_logs (:warn,) initialize(iter) - verbosity = 1 + verbosity = WARN_LEVEL while length(fact) < n - if verbosity == 1 + if verbosity == WARN_LEVEL @test_logs (:warn,) expand!(iter, fact; verbosity=verbosity) - verbosity = 0 + verbosity = SILENT_LEVEL else @test_logs expand!(iter, fact; verbosity=verbosity) - verbosity = 1 + verbosity = WARN_LEVEL end end end @@ -301,11 +299,9 @@ end @testset "Complete BlockLanczos factorization " for mode in (:vector, :inplace, :outplace) scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) - using KrylovKit: EACHITERATION_LEVEL @testset for T in scalartypes - A = rand(T, (N, N)) - A = (A + A') / 2 block_size = 5 + A = mat_with_eigrepition(T, N, block_size) x₀m = Matrix(qr(rand(T, N, block_size)).Q) x₀ = KrylovKit.Block{T}([wrapvec(x₀m[:, i], Val(mode)) for i in 1:block_size]) eigvalsA = eigvals(A) @@ -332,17 +328,17 @@ end v₀ = KrylovKit.Block{T}([wrapvec(v₀m[:, i], Val(mode)) for i in 1:bs]) iter = BlockLanczosIterator(wrapop(B, Val(mode)), v₀, N, tolerance(T)) fact = @constinferred initialize(iter) - @constinferred expand!(iter, fact; verbosity=0) - @test_logs initialize(iter; verbosity=0) + @constinferred expand!(iter, fact; verbosity=SILENT_LEVEL) + @test_logs initialize(iter; verbosity=SILENT_LEVEL) @test_logs (:warn,) initialize(iter) - verbosity = 1 + verbosity = WARN_LEVEL while fact.k < n - if verbosity == 1 + if verbosity == WARN_LEVEL @test_logs (:warn,) expand!(iter, fact; verbosity=verbosity) - verbosity = 0 + verbosity = SILENT_LEVEL else @test_logs expand!(iter, fact; verbosity=verbosity) - verbosity = 1 + verbosity = WARN_LEVEL end end end @@ -355,9 +351,8 @@ end scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @testset for T in scalartypes - A = rand(T, (N, N)) .- one(T) / 2 - A = (A + A') / 2 block_size = 5 + A = mat_with_eigrepition(T, N, block_size) x₀m = Matrix(qr(rand(T, N, block_size)).Q) x₀ = KrylovKit.Block{T}([wrapvec(x₀m[:, i], Val(mode)) for i in 1:block_size]) iter = @constinferred BlockLanczosIterator(wrapop(A, Val(mode)), x₀, N, diff --git a/test/geneigsolve.jl b/test/geneigsolve.jl index 17e2d439..34c1e26d 100644 --- a/test/geneigsolve.jl +++ b/test/geneigsolve.jl @@ -11,7 +11,7 @@ B = sqrt(B * B') v = rand(T, (n,)) alg = GolubYe(; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), - verbosity=1) + verbosity=WARN_LEVEL) n1 = div(n, 2) D1, V1, info = @constinferred geneigsolve((wrapop(A, Val(mode)), wrapop(B, Val(mode))), @@ -19,7 +19,7 @@ n1, :SR; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), ishermitian=true, isposdef=true, - verbosity=0) + verbosity=SILENT_LEVEL) if info.converged < n1 @test_logs geneigsolve((wrapop(A, Val(mode)), @@ -28,35 +28,35 @@ n1, :SR; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), ishermitian=true, isposdef=true, - verbosity=0) + verbosity=SILENT_LEVEL) @test_logs geneigsolve((wrapop(A, Val(mode)), wrapop(B, Val(mode))), wrapvec(v, Val(mode)), n1, :SR; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), ishermitian=true, isposdef=true, - verbosity=1) + verbosity=WARN_LEVEL) @test_logs (:warn,) geneigsolve((wrapop(A, Val(mode)), wrapop(B, Val(mode))), wrapvec(v, Val(mode)), n1, :SR; orth=orth, krylovdim=n1 + 1, maxiter=1, tol=tolerance(T), ishermitian=true, isposdef=true, - verbosity=1) + verbosity=WARN_LEVEL) @test_logs (:info,) geneigsolve((wrapop(A, Val(mode)), wrapop(B, Val(mode))), wrapvec(v, Val(mode)), n1, :SR; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), ishermitian=true, isposdef=true, - verbosity=2) + verbosity=STARTSTOP_LEVEL) alg = GolubYe(; orth=orth, krylovdim=n1, maxiter=3, tol=tolerance(T), - verbosity=3) + verbosity=EACHITERATION_LEVEL) @test_logs((:info,), (:info,), (:info,), (:warn,), geneigsolve((wrapop(A, Val(mode)), wrapop(B, Val(mode))), wrapvec(v, Val(mode)), 1, :SR, alg)) alg = GolubYe(; orth=orth, krylovdim=3, maxiter=2, tol=tolerance(T), - verbosity=4) + verbosity=EACHITERATION_LEVEL+1) @test_logs((:info,), (:info,), (:info,), (:info,), (:info,), (:info,), (:info,), (:info,), (:warn,), geneigsolve((wrapop(A, Val(mode)), wrapop(B, Val(mode))), @@ -97,7 +97,7 @@ end B = sqrt(B * B') v = rand(T, (N,)) alg = GolubYe(; orth=orth, krylovdim=3 * n, maxiter=100, - tol=cond(B) * tolerance(T), verbosity=0) + tol=cond(B) * tolerance(T), verbosity=SILENT_LEVEL) D1, V1, info1 = @constinferred geneigsolve((wrapop(A, Val(mode)), wrapop(B, Val(mode))), wrapvec(v, Val(mode)), diff --git a/test/linsolve.jl b/test/linsolve.jl index d2f4e96e..9abced5b 100644 --- a/test/linsolve.jl +++ b/test/linsolve.jl @@ -6,40 +6,40 @@ A = rand(T, (n, n)) A = sqrt(A * A') b = rand(T, n) - alg = CG(; maxiter=2n, tol=tolerance(T) * norm(b), verbosity=0) # because of loss of orthogonality, we choose maxiter = 2n + alg = CG(; maxiter=2n, tol=tolerance(T) * norm(b), verbosity=SILENT_LEVEL) # because of loss of orthogonality, we choose maxiter = 2n x, info = @constinferred linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)); ishermitian=true, isposdef=true, maxiter=2n, krylovdim=1, rtol=tolerance(T), - verbosity=0) + verbosity=SILENT_LEVEL) @test info.converged > 0 @test unwrapvec(b) ≈ A * unwrapvec(x) @test_logs linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)); ishermitian=true, isposdef=true, maxiter=2n, krylovdim=1, rtol=tolerance(T), - verbosity=0) + verbosity=SILENT_LEVEL) @test_logs linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)); ishermitian=true, isposdef=true, maxiter=2n, krylovdim=1, rtol=tolerance(T), - verbosity=1) + verbosity=WARN_LEVEL) @test_logs (:info,) (:info,) linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)); ishermitian=true, isposdef=true, maxiter=2n, krylovdim=1, rtol=tolerance(T), - verbosity=2) + verbosity=STARTSTOP_LEVEL) @test_logs min_level = Logging.Warn linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)); ishermitian=true, isposdef=true, maxiter=2n, krylovdim=1, rtol=tolerance(T), - verbosity=3) + verbosity=EACHITERATION_LEVEL) x, info = linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), x, alg) @test info.numops == 1 @test_logs linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), x, alg) - alg = CG(; maxiter=2n, tol=tolerance(T) * norm(b), verbosity=1) + alg = CG(; maxiter=2n, tol=tolerance(T) * norm(b), verbosity=WARN_LEVEL) @test_logs linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), x, alg) - alg = CG(; maxiter=2n, tol=tolerance(T) * norm(b), verbosity=2) + alg = CG(; maxiter=2n, tol=tolerance(T) * norm(b), verbosity=STARTSTOP_LEVEL) @test_logs (:info,) linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), x, alg) - alg = CG(; maxiter=2n, tol=tolerance(T) * norm(b), verbosity=0) + alg = CG(; maxiter=2n, tol=tolerance(T) * norm(b), verbosity=SILENT_LEVEL) A = rand(T, (n, n)) A = sqrt(A * A') @@ -70,16 +70,17 @@ end @test_logs linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), wrapvec(x₀, Val(mode)); isposdef=true, maxiter=1, krylovdim=N, - rtol=tolerance(T), verbosity=0) + rtol=tolerance(T), verbosity=SILENT_LEVEL) @test_logs (:warn,) linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), wrapvec(x₀, Val(mode)); isposdef=true, maxiter=1, krylovdim=N, - rtol=tolerance(T), verbosity=1) + rtol=tolerance(T), verbosity=WARN_LEVEL) @test_logs (:info,) (:warn,) linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), wrapvec(x₀, Val(mode)); isposdef=true, maxiter=1, krylovdim=N, - rtol=tolerance(T), verbosity=2) + rtol=tolerance(T), + verbosity=STARTSTOP_LEVEL) end α₀ = rand(real(T)) + 1 @@ -99,39 +100,46 @@ end @testset for T in scalartypes A = rand(T, (n, n)) .- one(T) / 2 b = rand(T, n) - alg = GMRES(; krylovdim=n, maxiter=2, tol=tolerance(T) * norm(b), verbosity=0) + alg = GMRES(; krylovdim=n, maxiter=2, tol=tolerance(T) * norm(b), + verbosity=SILENT_LEVEL) x, info = @constinferred linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)); krylovdim=n, maxiter=2, - rtol=tolerance(T), verbosity=0) + rtol=tolerance(T), verbosity=SILENT_LEVEL) @test info.converged == 1 @test unwrapvec(b) ≈ A * unwrapvec(x) @test_logs linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)); krylovdim=n, maxiter=2, - rtol=tolerance(T), verbosity=0) + rtol=tolerance(T), verbosity=SILENT_LEVEL) @test_logs linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)); krylovdim=n, maxiter=2, - rtol=tolerance(T), verbosity=1) + rtol=tolerance(T), verbosity=WARN_LEVEL) @test_logs (:info,) (:info,) linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)); krylovdim=n, maxiter=2, - rtol=tolerance(T), verbosity=2) + rtol=tolerance(T), verbosity=STARTSTOP_LEVEL) @test_logs min_level = Logging.Warn linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)); krylovdim=n, maxiter=2, - rtol=tolerance(T), verbosity=3) + rtol=tolerance(T), + verbosity=EACHITERATION_LEVEL) - alg = GMRES(; krylovdim=n, maxiter=2, tol=tolerance(T) * norm(b), verbosity=0) + alg = GMRES(; krylovdim=n, maxiter=2, tol=tolerance(T) * norm(b), + verbosity=SILENT_LEVEL) x, info = @constinferred linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), x, alg) @test info.numops == 1 @test_logs linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), x, alg) - alg = GMRES(; krylovdim=n, maxiter=2, tol=tolerance(T) * norm(b), verbosity=1) + alg = GMRES(; krylovdim=n, maxiter=2, tol=tolerance(T) * norm(b), + verbosity=WARN_LEVEL) @test_logs linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), x, alg) - alg = GMRES(; krylovdim=n, maxiter=2, tol=tolerance(T) * norm(b), verbosity=2) + alg = GMRES(; krylovdim=n, maxiter=2, tol=tolerance(T) * norm(b), + verbosity=STARTSTOP_LEVEL) @test_logs (:info,) linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), x, alg) - alg = GMRES(; krylovdim=n, maxiter=2, tol=tolerance(T) * norm(b), verbosity=0) + alg = GMRES(; krylovdim=n, maxiter=2, tol=tolerance(T) * norm(b), + verbosity=SILENT_LEVEL) nreal = (T <: Real) ? n : 2n - algr = GMRES(; krylovdim=nreal, maxiter=2, tol=tolerance(T) * norm(b), verbosity=0) + algr = GMRES(; krylovdim=nreal, maxiter=2, tol=tolerance(T) * norm(b), + verbosity=SILENT_LEVEL) xr, infor = @constinferred reallinsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), zerovector(x), algr) @test infor.converged == 1 @@ -174,20 +182,22 @@ end @test_logs linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), wrapvec(x₀, Val(mode)); krylovdim=3 * n, - maxiter=50, rtol=tolerance(T), verbosity=0) + maxiter=50, rtol=tolerance(T), verbosity=SILENT_LEVEL) @test_logs (:warn,) linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), wrapvec(x₀, Val(mode)); krylovdim=3 * n, - maxiter=50, rtol=tolerance(T), verbosity=1) + maxiter=50, rtol=tolerance(T), + verbosity=WARN_LEVEL) @test_logs (:info,) (:warn,) linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), wrapvec(x₀, Val(mode)); krylovdim=3 * n, maxiter=50, rtol=tolerance(T), - verbosity=2) + verbosity=STARTSTOP_LEVEL) end - alg = GMRES(; krylovdim=3 * n, maxiter=50, tol=tolerance(T) * norm(b), verbosity=0) + alg = GMRES(; krylovdim=3 * n, maxiter=50, tol=tolerance(T) * norm(b), + verbosity=SILENT_LEVEL) xr, infor = @constinferred reallinsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), zerovector(x), alg) @test unwrapvec(b) ≈ A * unwrapvec(xr) + unwrapvec(infor.residual) @@ -220,36 +230,37 @@ end A = rand(T, (n, n)) .- one(T) / 2 A = I - T(9 / 10) * A / maximum(abs, eigvals(A)) b = rand(T, n) - alg = BiCGStab(; maxiter=4n, tol=tolerance(T) * norm(b), verbosity=0) + alg = BiCGStab(; maxiter=4n, tol=tolerance(T) * norm(b), verbosity=SILENT_LEVEL) x, info = @constinferred linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), wrapvec(zerovector(b), Val(mode)), alg) @test info.converged > 0 @test unwrapvec(b) ≈ A * unwrapvec(x) - alg = BiCGStab(; maxiter=4n, tol=tolerance(T) * norm(b), verbosity=0) + alg = BiCGStab(; maxiter=4n, tol=tolerance(T) * norm(b), verbosity=SILENT_LEVEL) @test_logs linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), wrapvec(zerovector(b), Val(mode)), alg) - alg = BiCGStab(; maxiter=4n, tol=tolerance(T) * norm(b), verbosity=1) + alg = BiCGStab(; maxiter=4n, tol=tolerance(T) * norm(b), verbosity=WARN_LEVEL) @test_logs linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), wrapvec(zerovector(b), Val(mode)), alg) - alg = BiCGStab(; maxiter=4n, tol=tolerance(T) * norm(b), verbosity=2) + alg = BiCGStab(; maxiter=4n, tol=tolerance(T) * norm(b), verbosity=STARTSTOP_LEVEL) @test_logs (:info,) (:info,) linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), wrapvec(zerovector(b), Val(mode)), alg) - alg = BiCGStab(; maxiter=4n, tol=tolerance(T) * norm(b), verbosity=3) + alg = BiCGStab(; maxiter=4n, tol=tolerance(T) * norm(b), + verbosity=EACHITERATION_LEVEL) @test_logs min_level = Logging.Warn linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), wrapvec(zerovector(b), Val(mode)), alg) - alg = BiCGStab(; maxiter=4n, tol=tolerance(T) * norm(b), verbosity=0) + alg = BiCGStab(; maxiter=4n, tol=tolerance(T) * norm(b), verbosity=SILENT_LEVEL) x, info = @constinferred linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), x, alg) @test info.numops == 1 - alg = BiCGStab(; maxiter=4n, tol=tolerance(T) * norm(b), verbosity=0) + alg = BiCGStab(; maxiter=4n, tol=tolerance(T) * norm(b), verbosity=SILENT_LEVEL) @test_logs linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), x, alg) - alg = BiCGStab(; maxiter=4n, tol=tolerance(T) * norm(b), verbosity=1) + alg = BiCGStab(; maxiter=4n, tol=tolerance(T) * norm(b), verbosity=WARN_LEVEL) @test_logs linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), x, alg) - alg = BiCGStab(; maxiter=4n, tol=tolerance(T) * norm(b), verbosity=2) + alg = BiCGStab(; maxiter=4n, tol=tolerance(T) * norm(b), verbosity=STARTSTOP_LEVEL) @test_logs (:info,) linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), x, alg) - alg = BiCGStab(; maxiter=4n, tol=tolerance(T) * norm(b), verbosity=0) + alg = BiCGStab(; maxiter=4n, tol=tolerance(T) * norm(b), verbosity=SILENT_LEVEL) α₀ = rand(real(T)) + 1 α₁ = rand(real(T)) @@ -268,7 +279,7 @@ end b = rand(T, N) α₀ = maximum(abs, eigvals(A)) α₁ = -9 * rand(real(T)) / 10 - alg = BiCGStab(; maxiter=2, tol=tolerance(T) * norm(b), verbosity=0) + alg = BiCGStab(; maxiter=2, tol=tolerance(T) * norm(b), verbosity=SILENT_LEVEL) x, info = @constinferred linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), wrapvec(zerovector(b), Val(mode)), alg, α₀, α₁) @@ -276,17 +287,18 @@ end if info.converged == 0 @test_logs linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), wrapvec(zerovector(b), Val(mode)), alg, α₀, α₁) - alg = BiCGStab(; maxiter=2, tol=tolerance(T) * norm(b), verbosity=1) + alg = BiCGStab(; maxiter=2, tol=tolerance(T) * norm(b), verbosity=WARN_LEVEL) @test_logs (:warn,) linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), wrapvec(zerovector(b), Val(mode)), alg, α₀, α₁) - alg = BiCGStab(; maxiter=2, tol=tolerance(T) * norm(b), verbosity=2) + alg = BiCGStab(; maxiter=2, tol=tolerance(T) * norm(b), + verbosity=STARTSTOP_LEVEL) @test_logs (:info,) (:warn,) linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), wrapvec(zerovector(b), Val(mode)), alg, α₀, α₁) end - alg = BiCGStab(; maxiter=10 * N, tol=tolerance(T) * norm(b), verbosity=0) + alg = BiCGStab(; maxiter=10 * N, tol=tolerance(T) * norm(b), verbosity=SILENT_LEVEL) x, info = @constinferred linsolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), x, alg, α₀, α₁) @test info.converged > 0 diff --git a/test/lssolve.jl b/test/lssolve.jl index c91f3371..c5573b6c 100644 --- a/test/lssolve.jl +++ b/test/lssolve.jl @@ -13,19 +13,19 @@ b = rand(T, 2 * n) tol = tol = 10 * n * eps(real(T)) x, info = @constinferred lssolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)); - maxiter=3, krylovdim=1, verbosity=0) # no reorthogonalization + maxiter=3, krylovdim=1, verbosity=SILENT_LEVEL) # no reorthogonalization r = b - A * unwrapvec(x) @test unwrapvec(info.residual) ≈ r @test info.normres ≈ norm(A' * r) @test info.converged == 0 @test_logs lssolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)); maxiter=3, - verbosity=0) + verbosity=SILENT_LEVEL) @test_logs (:warn,) lssolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)); maxiter=3, - verbosity=1) + verbosity=WARN_LEVEL) @test_logs (:info,) (:warn,) lssolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)); - maxiter=3, verbosity=2) + maxiter=3, verbosity=STARTSTOP_LEVEL) - alg = LSMR(; maxiter=n, tol=tol, verbosity=0, krylovdim=n) + alg = LSMR(; maxiter=n, tol=tol, verbosity=SILENT_LEVEL, krylovdim=n) # reorthogonalisation is essential here to converge in exactly n iterations x, info = @constinferred lssolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), alg) @@ -33,18 +33,18 @@ @test abs(inner(V[:, end], unwrapvec(x))) < alg.tol @test unwrapvec(x) ≈ V * Diagonal(invS) * U' * b @test_logs lssolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), alg) - alg = LSMR(; maxiter=2 * n, tol=tol, verbosity=1) + alg = LSMR(; maxiter=2 * n, tol=tol, verbosity=WARN_LEVEL) @test_logs lssolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), alg) - alg = LSMR(; maxiter=2 * n, tol=tol, verbosity=2) + alg = LSMR(; maxiter=2 * n, tol=tol, verbosity=STARTSTOP_LEVEL) @test_logs (:info,) (:info,) lssolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), alg) - alg = LSMR(; maxiter=2 * n, tol=tol, verbosity=3) + alg = LSMR(; maxiter=2 * n, tol=tol, verbosity=EACHITERATION_LEVEL) @test_logs min_level = Logging.Warn lssolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), alg) λ = rand(real(T)) - alg = LSMR(; maxiter=n, tol=tol, verbosity=0, krylovdim=n) + alg = LSMR(; maxiter=n, tol=tol, verbosity=SILENT_LEVEL, krylovdim=n) # reorthogonalisation is essential here to converge in exactly n iterations x, info = @constinferred lssolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)), alg, λ) @@ -58,7 +58,7 @@ B = rand(T, (2 * n, n)) .- one(T) / 2 f = buildrealmap(A, B) # the effective linear problem has twice the size, so 4n x 2n - alg = LSMR(; maxiter=2 * n, tol=tol, verbosity=0, krylovdim=2 * n) + alg = LSMR(; maxiter=2 * n, tol=tol, verbosity=SILENT_LEVEL, krylovdim=2 * n) xr, infor = @constinferred reallssolve(f, b, alg) @test infor.converged > 0 y = (A * xr + B * conj(xr)) @@ -75,7 +75,8 @@ end tol = 10 * N * eps(real(T)) x, info = @constinferred lssolve(wrapop(A, Val(mode)), wrapvec(b, Val(mode)); - maxiter=N, tol=tol, verbosity=0, krylovdim=5) + maxiter=N, tol=tol, verbosity=SILENT_LEVEL, + krylovdim=5) r = b - A * unwrapvec(x) @test info.converged > 0 @@ -85,7 +86,7 @@ end A = rand(T, (2 * N, N)) .- one(T) / 2 B = rand(T, (2 * N, N)) .- one(T) / 2 f = buildrealmap(A, B) - alg = LSMR(; maxiter=N, tol=tol, verbosity=0, krylovdim=5) + alg = LSMR(; maxiter=N, tol=tol, verbosity=SILENT_LEVEL, krylovdim=5) xr, infor = @constinferred reallssolve(f, b, alg) @test infor.converged > 0 y = (A * xr + B * conj(xr)) diff --git a/test/runtests.jl b/test/runtests.jl index 42bc26d9..51a352b4 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,6 +4,7 @@ Random.seed!(76543210) using Test, TestExtras, Logging using LinearAlgebra, SparseArrays using KrylovKit +using KrylovKit: SILENT_LEVEL, WARN_LEVEL, STARTSTOP_LEVEL, EACHITERATION_LEVEL using VectorInterface include("testsetup.jl") @@ -62,8 +63,8 @@ end @testset "Svdsolve differentiation rules" verbose = true begin include("ad/svdsolve.jl") end -@testset "Block" verbose = true begin - include("Block.jl") +@testset "block" verbose = true begin + include("block.jl") end t = time() - t diff --git a/test/schursolve.jl b/test/schursolve.jl index 57a8e6ae..720c73d4 100644 --- a/test/schursolve.jl +++ b/test/schursolve.jl @@ -14,16 +14,16 @@ @test_logs schursolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n1, :SR, alg) alg = Arnoldi(; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), - verbosity=1) + verbosity=WARN_LEVEL) @test_logs schursolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n1, :SR, alg) alg = Arnoldi(; orth=orth, krylovdim=n1 + 1, maxiter=1, tol=tolerance(T), - verbosity=1) + verbosity=WARN_LEVEL) @test_logs (:warn,) schursolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n1, :SR, alg) alg = Arnoldi(; orth=orth, krylovdim=n, maxiter=1, tol=tolerance(T), - verbosity=2) + verbosity=STARTSTOP_LEVEL) @test_logs (:info,) schursolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n1, :SR, alg) @@ -76,7 +76,7 @@ end A = rand(T, (N, N)) .- one(T) / 2 v = rand(T, (N,)) alg = Arnoldi(; orth=orth, krylovdim=3 * n, maxiter=10, tol=tolerance(T), - verbosity=0) + verbosity=SILENT_LEVEL) T1, V1, D1, info1 = @constinferred schursolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n, :SR, alg) diff --git a/test/svdsolve.jl b/test/svdsolve.jl index d9e3b98f..3ad61a49 100644 --- a/test/svdsolve.jl +++ b/test/svdsolve.jl @@ -16,21 +16,21 @@ @test_logs svdsolve(wrapop(A, Val(mode)), wrapvec(A[:, 1], Val(mode)), n1, :LR, alg) alg = GKL(; orth=orth, krylovdim=2 * n, maxiter=1, tol=tolerance(T), - verbosity=1) + verbosity=WARN_LEVEL) @test_logs svdsolve(wrapop(A, Val(mode)), wrapvec(A[:, 1], Val(mode)), n1, :LR, alg) alg = GKL(; orth=orth, krylovdim=n1 + 1, maxiter=1, tol=tolerance(T), - verbosity=1) + verbosity=WARN_LEVEL) @test_logs (:warn,) svdsolve(wrapop(A, Val(mode)), wrapvec(A[:, 1], Val(mode)), n1, :LR, alg) alg = GKL(; orth=orth, krylovdim=2 * n, maxiter=1, tol=tolerance(T), - verbosity=2) + verbosity=STARTSTOP_LEVEL) @test_logs (:info,) svdsolve(wrapop(A, Val(mode)), wrapvec(A[:, 1], Val(mode)), n1, :LR, alg) alg = GKL(; orth=orth, krylovdim=2 * n, maxiter=1, tol=tolerance(T), - verbosity=4) + verbosity=EACHITERATION_LEVEL+1) @test_logs min_level = Logging.Warn svdsolve(wrapop(A, Val(mode)), wrapvec(A[:, 1], Val(mode)), n1, :LR, @@ -56,7 +56,7 @@ end v = rand(T, (2 * N,)) n₁ = div(n, 2) alg = GKL(; orth=orth, krylovdim=n, maxiter=10, tol=tolerance(T), eager=true, - verbosity=0) + verbosity=SILENT_LEVEL) S, lvecs, rvecs, info = @constinferred svdsolve(wrapop(A, Val(mode)), wrapvec(v, Val(mode)), n₁, :LR, alg) diff --git a/test/testsetup.jl b/test/testsetup.jl index f1402656..0f0f02d0 100644 --- a/test/testsetup.jl +++ b/test/testsetup.jl @@ -2,7 +2,7 @@ module TestSetup export tolerance, ≊, MinimalVec, isinplace, stack export wrapop, wrapvec, unwrapvec, buildrealmap -export relax_tol +export relax_tol, mat_with_eigrepition import VectorInterface as VI using VectorInterface @@ -42,6 +42,20 @@ function buildrealmap(A, B) return f end +"function for generating a matrix with repeated eigenvalues" +function mat_with_eigrepition(T, N, multiplicity) + U = LinearAlgebra.qr(randn(T, (N, N))).Q # Haar random matrix + D = sort(randn(real(T), N)) + i = 0 + while multiplicity >= 2 && (i + multiplicity) <= N + D[i .+ (1:multiplicity)] .= D[i+1] + i += multiplicity + multiplicity -= 1 + end + A = U * LinearAlgebra.Diagonal(D) * U' + return (A + A')/2 +end + # Wrappers # -------- using VectorInterface: MinimalSVec, MinimalMVec, MinimalVec From 73f3df80e8434a270bf635e91d81a5e5ce4476c1 Mon Sep 17 00:00:00 2001 From: syyui Date: Wed, 28 May 2025 19:21:38 +0800 Subject: [PATCH 80/82] format --- test/eigsolve.jl | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/eigsolve.jl b/test/eigsolve.jl index 47213feb..fbbb29c5 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -344,10 +344,13 @@ end v = ones(Float64, size(A, 1)) @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-8, verbosity=SILENT_LEVEL)) @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-8, verbosity=WARN_LEVEL)) - @test_logs (:info,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-8, verbosity=STARTSTOP_LEVEL)) - @test_logs (:warn,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-10, verbosity=WARN_LEVEL)) + @test_logs (:info,) realeigsolve(A, v, 1, :LM, + Arnoldi(; tol=1e-8, verbosity=STARTSTOP_LEVEL)) + @test_logs (:warn,) realeigsolve(A, v, 1, :LM, + Arnoldi(; tol=1e-10, verbosity=WARN_LEVEL)) @test_logs (:warn,) (:info,) realeigsolve(A, v, 1, :LM, - Arnoldi(; tol=1e-10, verbosity=STARTSTOP_LEVEL)) + Arnoldi(; tol=1e-10, + verbosity=STARTSTOP_LEVEL)) # this should not trigger a warning A[1, 2] = A[2, 1] = 0 @@ -357,7 +360,8 @@ end A[2, 3] = -1e-6 @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=SILENT_LEVEL)) @test_logs realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=WARN_LEVEL)) - @test_logs (:info,) realeigsolve(A, v, 1, :LM, Arnoldi(; tol=1e-12, verbosity=STARTSTOP_LEVEL)) + @test_logs (:info,) realeigsolve(A, v, 1, :LM, + Arnoldi(; tol=1e-12, verbosity=STARTSTOP_LEVEL)) end @testset "BlockLanczos - eigsolve for large sparse matrix and map input" begin From e019fb78f2b79d41d70b5ba7369723d56388e99e Mon Sep 17 00:00:00 2001 From: syyui Date: Fri, 30 May 2025 14:29:42 +0800 Subject: [PATCH 81/82] remove in docstring --- src/factorizations/blocklanczos.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/factorizations/blocklanczos.jl b/src/factorizations/blocklanczos.jl index 9a1c3a96..cfe2af68 100644 --- a/src/factorizations/blocklanczos.jl +++ b/src/factorizations/blocklanczos.jl @@ -58,7 +58,7 @@ Base.iterate(b::Block) = iterate(b.vec) Base.iterate(b::Block, state) = iterate(b.vec, state) """ - mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: BlockKrylovFactorization{T,S,SR} + mutable struct BlockLanczosFactorization{T,S<:Number,SR<:Real} <: KrylovFactorization{T,S,SR} Structure to store a BlockLanczos factorization of a real symmetric or complex hermitian linear map `A` of the form From 392ebc6c9bece92ad4598cdfc84274ef9d8f760a Mon Sep 17 00:00:00 2001 From: yuiyuiui <2946723935@qq.com> Date: Sun, 8 Jun 2025 17:27:30 +0800 Subject: [PATCH 82/82] revise for review --- test/eigsolve.jl | 11 +++++++++-- test/testsetup.jl | 3 ++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/test/eigsolve.jl b/test/eigsolve.jl index fbbb29c5..f4ce6b29 100644 --- a/test/eigsolve.jl +++ b/test/eigsolve.jl @@ -500,7 +500,7 @@ end scalartypes = mode === :vector ? (Float32, Float64, ComplexF32, ComplexF64) : (ComplexF64,) @testset for T in scalartypes - block_size = 2 + block_size = 4 A = mat_with_eigrepition(T, N, block_size) x₀ = Block{T}([wrapvec(rand(T, N), Val(mode)) for _ in 1:block_size]) eigvalsA = eigvals(A) @@ -568,6 +568,13 @@ end evals1, _, info1 = eigsolve(wrapop(A, Val(mode)), x₀_lanczos, n, :SR, alg1) evals2, _, info2 = eigsolve(wrapop(A, Val(mode)), x₀_block, n, :SR, alg2) @test info1.converged == info2.converged + @test info1.numiter == info2.numiter + @test info1.numops == info2.numops + @test isapprox(info1.normres, info2.normres, atol=tolerance(T)) + @test isapprox(unwrapvec.(info1.residual[1:info1.converged])[2], + unwrapvec.(info2.residual[1:info2.converged])[2]; atol=tolerance(T)) + @test isapprox(evals1[1:info1.converged], evals2[1:info2.converged]; + atol=tolerance(T)) end @testset for T in scalartypes A = rand(T, (2N, 2N)) .- one(T) / 2 @@ -619,7 +626,7 @@ end eigA = eigvals(A) @test D1[1] ≈ eigA[1] D2, V2, info2 = eigsolve(wrapop(A, Val(mode)), x₀) - D2[1] = max(abs(eigA[1]), abs(eigA[end])) + @test D2[1] ≈ (abs(eigA[1]) > abs(eigA[end]) ? eigA[1] : eigA[end]) @test_throws ErrorException eigsolve(wrapop(A, Val(mode)), x₀, 1, :LI) B = copy(A) B[1, 2] += T(1) # error for non-symmetric/hermitian operator diff --git a/test/testsetup.jl b/test/testsetup.jl index 0f0f02d0..82a7ce50 100644 --- a/test/testsetup.jl +++ b/test/testsetup.jl @@ -47,8 +47,9 @@ function mat_with_eigrepition(T, N, multiplicity) U = LinearAlgebra.qr(randn(T, (N, N))).Q # Haar random matrix D = sort(randn(real(T), N)) i = 0 - while multiplicity >= 2 && (i + multiplicity) <= N + while multiplicity >= 2 && (i + multiplicity) <= N÷2 D[i .+ (1:multiplicity)] .= D[i+1] + D[N+1-i .- (1:multiplicity)] .= D[N-i] i += multiplicity multiplicity -= 1 end